diff options
Diffstat (limited to 'plugins/rdp/rdp_plugin.c')
-rw-r--r-- | plugins/rdp/rdp_plugin.c | 3363 |
1 files changed, 3363 insertions, 0 deletions
diff --git a/plugins/rdp/rdp_plugin.c b/plugins/rdp/rdp_plugin.c new file mode 100644 index 0000000..68ea505 --- /dev/null +++ b/plugins/rdp/rdp_plugin.c @@ -0,0 +1,3363 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2022 Antenore Gatta, Giovanni Panozzo + * Copyright (C) 2022-2023 Antenore Gatta, Giovanni Panozzo, Hiroyuki Tanaka + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + + +#define _GNU_SOURCE + +#include "remmina/plugin.h" +#include "rdp_plugin.h" +#include "rdp_event.h" +#include "rdp_graphics.h" +#include "rdp_file.h" +#include "rdp_settings.h" +#include "rdp_cliprdr.h" +#include "rdp_monitor.h" +#include "rdp_channels.h" + +#include <errno.h> +#include <pthread.h> +#include <time.h> +#include <sys/time.h> +#ifdef GDK_WINDOWING_X11 +#include <cairo/cairo-xlib.h> +#else +#include <cairo/cairo.h> +#endif +#include <ctype.h> +#include <freerdp/addin.h> +#include <freerdp/assistance.h> +#if FREERDP_VERSION_MAJOR >= 3 +#include <freerdp/channels/rdp2tcp.h> +#else +#define RDP2TCP_DVC_CHANNEL_NAME "rdp2tcp" +#endif +#include <freerdp/client/channels.h> +#include <freerdp/client/cliprdr.h> +#include <freerdp/client/cmdline.h> +#include <freerdp/constants.h> +#include <freerdp/error.h> +#include <freerdp/event.h> +#include <freerdp/freerdp.h> +#include <freerdp/settings.h> +#include <winpr/cmdline.h> +#include <winpr/memory.h> + +#ifdef HAVE_CUPS +#include <cups/cups.h> +#endif + +#include <unistd.h> +#include <string.h> + +#ifdef GDK_WINDOWING_X11 +#include <X11/Xlib.h> +#include <X11/XKBlib.h> +#include <gdk/gdkx.h> +#elif defined(GDK_WINDOWING_WAYLAND) +#include <gdk/gdkwayland.h> +#endif + +#if defined(__FreeBSD__) +#include <pthread_np.h> +#endif + +#include <freerdp/locale/keyboard.h> + +#define REMMINA_RDP_FEATURE_TOOL_REFRESH 1 +#define REMMINA_RDP_FEATURE_SCALE 2 +#define REMMINA_RDP_FEATURE_UNFOCUS 3 +#define REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL 4 +#define REMMINA_RDP_FEATURE_DYNRESUPDATE 5 +#define REMMINA_RDP_FEATURE_MULTIMON 6 +#define REMMINA_RDP_FEATURE_VIEWONLY 7 + +#define REMMINA_CONNECTION_TYPE_NONE 0 + +#if FREERDP_VERSION_MAJOR >= 3 + #define CLPARAM const char +#else + #define CLPARAM char +#endif + +#if FREERDP_VERSION_MAJOR < 3 +static HANDLE freerdp_abort_event(rdpContext* context) { + WINPR_ASSERT(context); + return context->abortEvent; +} + +static BOOL freerdp_settings_set_pointer_len(rdpSettings* settings, size_t id, const void* data, size_t len) +{ + switch(id) { + case FreeRDP_LoadBalanceInfo: + free(settings->LoadBalanceInfo); + settings->LoadBalanceInfo = _strdup(data); + settings->LoadBalanceInfoLength = len; + return TRUE; + default: + return FALSE; + } +} + +static void freerdp_abort_connect_context(rdpContext* context) { + WINPR_ASSERT(context); + freerdp_abort_connect(context->instance); +} +#endif + +RemminaPluginService *remmina_plugin_service = NULL; + +static BOOL gfx_h264_available = FALSE; +// keep track of last interaction time for keep alive +static time_t last_time; + +/* Compatibility: these functions have been introduced with https://github.com/FreeRDP/FreeRDP/commit/8c5d96784d + * and are missing on older FreeRDP, so we add them here. + * They should be removed from here after all distributed versions of FreeRDP (libwinpr) will have + * CommandLineParseCommaSeparatedValuesEx() onboard. + * + * (C) Copyright goes to the FreeRDP authors. + */ +static CLPARAM **remmina_rdp_CommandLineParseCommaSeparatedValuesEx(const char *name, const char *list, size_t *count) +{ + TRACE_CALL(__func__); +#if FREERDP_CHECK_VERSION(2, 0, 0) + return (CLPARAM **)CommandLineParseCommaSeparatedValuesEx(name, list, count); +#else + char **p; + char *str; + size_t nArgs; + size_t index; + size_t nCommas; + size_t prefix, len; + + nCommas = 0; + + if (count == NULL) + return NULL; + + *count = 0; + + if (!list) { + if (name) { + size_t len = strlen(name); + p = (char **)calloc(2UL + len, sizeof(char *)); + + if (p) { + char *dst = (char *)&p[1]; + p[0] = dst; + sprintf_s(dst, len + 1, "%s", name); + *count = 1; + return p; + } + } + + return NULL; + } + + { + const char *it = list; + + while ((it = strchr(it, ',')) != NULL) { + it++; + nCommas++; + } + } + + nArgs = nCommas + 1; + + if (name) + nArgs++; + + prefix = (nArgs + 1UL) * sizeof(char *); + len = strlen(list); + p = (char **)calloc(len + prefix + 1, sizeof(char *)); + + if (!p) + return NULL; + + str = &((char *)p)[prefix]; + memcpy(str, list, len); + + if (name) + p[0] = (char *)name; + + for (index = name ? 1 : 0; index < nArgs; index++) { + char *comma = strchr(str, ','); + p[index] = str; + + if (comma) { + str = comma + 1; + *comma = '\0'; + } + } + + *count = nArgs; + return p; +#endif +} + +static CLPARAM **remmina_rdp_CommandLineParseCommaSeparatedValues(const char *list, size_t *count) +{ + TRACE_CALL(__func__); + return remmina_rdp_CommandLineParseCommaSeparatedValuesEx(NULL, list, count); +} + +/* + * End of CommandLineParseCommaSeparatedValuesEx() compatibility and copyright + */ +static BOOL rf_process_event_queue(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + UINT16 flags; + rdpInput *input; + rfContext *rfi = GET_PLUGIN_DATA(gp); + RemminaPluginRdpEvent *event; + DISPLAY_CONTROL_MONITOR_LAYOUT *dcml; + CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 }; + RemminaFile *remminafile; + + if (rfi->event_queue == NULL) + return true; + + input = rfi->clientContext.context.input; + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + while ((event = (RemminaPluginRdpEvent *)g_async_queue_try_pop(rfi->event_queue)) != NULL) { + time(&last_time); //update last user interaction time + switch (event->type) { + case REMMINA_RDP_EVENT_TYPE_SCANCODE: + + if (event->key_event.extended1){ + flags = KBD_FLAGS_EXTENDED1; + } + else{ + flags = event->key_event.extended ? KBD_FLAGS_EXTENDED : 0; + } + flags |= event->key_event.up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN; + input->KeyboardEvent(input, flags, event->key_event.key_code); + break; + + case REMMINA_RDP_EVENT_TYPE_SCANCODE_UNICODE: + /* + * TS_UNICODE_KEYBOARD_EVENT RDP message, see https://msdn.microsoft.com/en-us/library/cc240585.aspx + */ + flags = event->key_event.up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN; + input->UnicodeKeyboardEvent(input, flags, event->key_event.unicode_code); + break; + + case REMMINA_RDP_EVENT_TYPE_MOUSE: + if (event->mouse_event.extended) + input->ExtendedMouseEvent(input, event->mouse_event.flags, + event->mouse_event.x, event->mouse_event.y); + else + input->MouseEvent(input, event->mouse_event.flags, + event->mouse_event.x, event->mouse_event.y); + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST: + rfi->clipboard.context->ClientFormatList(rfi->clipboard.context, event->clipboard_formatlist.pFormatList); + free(event->clipboard_formatlist.pFormatList); + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE: + { + UINT32 msgFlags = (event->clipboard_formatdataresponse.data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; +#if FREERDP_VERSION_MAJOR >= 3 + response.common.msgFlags = msgFlags; + response.common.dataLen = event->clipboard_formatdataresponse.size; +#else + response.msgFlags = msgFlags; + response.dataLen = event->clipboard_formatdataresponse.size; +#endif + response.requestedFormatData = event->clipboard_formatdataresponse.data; + rfi->clipboard.context->ClientFormatDataResponse(rfi->clipboard.context, &response); + } + break; + + case REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST: + REMMINA_PLUGIN_DEBUG("Sending client FormatDataRequest to server"); + gettimeofday(&(rfi->clipboard.clientformatdatarequest_tv), NULL); + rfi->clipboard.context->ClientFormatDataRequest(rfi->clipboard.context, event->clipboard_formatdatarequest.pFormatDataRequest); + free(event->clipboard_formatdatarequest.pFormatDataRequest); + break; + + case REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT: + if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE); + /* TODO Add an option for this */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ForceMultimon, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE); + /* got some crashes with g_malloc0, to be investigated */ + dcml = calloc(freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount), sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + REMMINA_PLUGIN_DEBUG("REMMINA_RDP_EVENT_TYPE_SEND_MONITOR_LAYOUT:"); + if (!dcml) + break; + + const rdpMonitor *base = freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorDefArray); + for (gint i = 0; i < freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount); ++i) { + const rdpMonitor *current = &base[i]; + REMMINA_PLUGIN_DEBUG("Sending display layout for monitor n° %d", i); + dcml[i].Flags = (current->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + REMMINA_PLUGIN_DEBUG("Monitor %d is primary: %d", i, dcml[i].Flags); + dcml[i].Left = current->x; + REMMINA_PLUGIN_DEBUG("Monitor %d x: %d", i, dcml[i].Left); + dcml[i].Top = current->y; + REMMINA_PLUGIN_DEBUG("Monitor %d y: %d", i, dcml[i].Top); + dcml[i].Width = current->width; + REMMINA_PLUGIN_DEBUG("Monitor %d width: %d", i, dcml[i].Width); + dcml[i].Height = current->height; + REMMINA_PLUGIN_DEBUG("Monitor %d height: %d", i, dcml[i].Height); + dcml[i].PhysicalWidth = current->attributes.physicalWidth; + REMMINA_PLUGIN_DEBUG("Monitor %d physical width: %d", i, dcml[i].PhysicalWidth); + dcml[i].PhysicalHeight = current->attributes.physicalHeight; + REMMINA_PLUGIN_DEBUG("Monitor %d physical height: %d", i, dcml[i].PhysicalHeight); + if (current->attributes.orientation) + dcml[i].Orientation = current->attributes.orientation; + else + dcml[i].Orientation = event->monitor_layout.desktopOrientation; + REMMINA_PLUGIN_DEBUG("Monitor %d orientation: %d", i, dcml[i].Orientation); + dcml[i].DesktopScaleFactor = event->monitor_layout.desktopScaleFactor; + dcml[i].DeviceScaleFactor = event->monitor_layout.deviceScaleFactor; + } + rfi->dispcontext->SendMonitorLayout(rfi->dispcontext, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_MonitorCount), dcml); + g_free(dcml); + } else { + dcml = g_malloc0(sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + if (dcml) { + dcml->Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + dcml->Width = event->monitor_layout.width; + dcml->Height = event->monitor_layout.height; + dcml->Orientation = event->monitor_layout.desktopOrientation; + dcml->DesktopScaleFactor = event->monitor_layout.desktopScaleFactor; + dcml->DeviceScaleFactor = event->monitor_layout.deviceScaleFactor; + rfi->dispcontext->SendMonitorLayout(rfi->dispcontext, 1, dcml); + g_free(dcml); \ + } + } + break; + case REMMINA_RDP_EVENT_DISCONNECT: + /* Disconnect requested via GUI (i.e: tab destroy/close) */ + freerdp_abort_connect_context(&rfi->clientContext.context); + break; + } + + g_free(event); + } + + return true; +} + +static gboolean remmina_rdp_tunnel_init(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + + /* Opens the optional SSH tunnel if needed. + * Used also when reopening the same tunnel for a FreeRDP reconnect. + * Returns TRUE if all OK, and setups correct rfi->Settings values + * with connection and certificate parameters */ + + gchar *hostport; + gchar *s; + gchar *host; + gchar *cert_host; + gint cert_port; + gint port; + + rfContext *rfi = GET_PLUGIN_DATA(gp); + + REMMINA_PLUGIN_DEBUG("Tunnel init"); + hostport = remmina_plugin_service->protocol_plugin_start_direct_tunnel(gp, 3389, FALSE); + if (hostport == NULL) + return FALSE; + + remmina_plugin_service->get_server_port(hostport, 3389, &host, &port); + + if (host[0] == 0) + return FALSE; + + REMMINA_PLUGIN_DEBUG("protocol_plugin_start_direct_tunnel() returned %s", hostport); + + cert_host = host; + cert_port = port; + + if (!rfi->is_reconnecting) { + /* settings->CertificateName and settings->ServerHostname is created + * only on 1st connect, not on reconnections */ + + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname, host); + + if (cert_port == 3389) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_CertificateName, cert_host); + } else { + s = g_strdup_printf("%s:%d", cert_host, cert_port); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_CertificateName, s); + g_free(s); + } + } + + REMMINA_PLUGIN_DEBUG("Tunnel has been optionally initialized. Now connecting to %s:%d", host, port); + + if (cert_host != host) g_free(cert_host); + g_free(host); + g_free(hostport); + + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ServerPort, port); + + return TRUE; +} + +static BOOL rf_auto_reconnect(rfContext *rfi) +{ + TRACE_CALL(__func__); + rdpSettings *settings = rfi->clientContext.context.settings; + RemminaPluginRdpUiObject *ui; + time_t treconn; + gchar *cval; + gint maxattempts; + + RemminaProtocolWidget *gp = rfi->protocol_widget; + RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + rfi->is_reconnecting = TRUE; + rfi->stop_reconnecting_requested = FALSE; + + /* Get the value set in FreeRDP_AutoReconnectMaxRetries (20) */ + maxattempts = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries); + REMMINA_PLUGIN_DEBUG("maxattempts from default: %d", maxattempts); + /* Get the value from the global preferences if any */ + if ((cval = remmina_plugin_service->pref_get_value("rdp_reconnect_attempts")) != NULL) + maxattempts = atoi(cval); + REMMINA_PLUGIN_DEBUG("maxattempts from general preferences: %d", maxattempts); + /* Get the value from the profile if any, otherwise uses the value of maxattempts */ + maxattempts = remmina_plugin_service->file_get_int(remminafile, "rdp_reconnect_attempts", maxattempts); + REMMINA_PLUGIN_DEBUG("maxattempts from general plugin: %d", maxattempts); + /* If maxattemps is <= 0, we get the value from FreeRDP_AutoReconnectMaxRetries (20) */ + if (maxattempts <= 0) + maxattempts = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries); + freerdp_settings_set_uint32(settings, FreeRDP_AutoReconnectMaxRetries, maxattempts); + REMMINA_PLUGIN_DEBUG("maxattempts set to: %d", maxattempts); + + rfi->reconnect_maxattempts = maxattempts; + rfi->reconnect_nattempt = 0; + + /* Only auto reconnect on network disconnects. */ + switch (freerdp_error_info(rfi->clientContext.context.instance)) { + case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED: + /* Disconnected by server hitting a bug or resource limit */ + break; + case ERRINFO_SUCCESS: + /* A network disconnect was detected */ + break; + default: + rfi->is_reconnecting = FALSE; + return FALSE; + } + + if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled)) { + /* No auto-reconnect - just quit */ + rfi->is_reconnecting = FALSE; + return FALSE; + } + + /* A network disconnect was detected and we should try to reconnect */ + REMMINA_PLUGIN_DEBUG("[%s] network disconnection detected, initiating reconnection attempt", + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_RECONNECT_PROGRESS; + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + /* Sleep half a second to allow: + * - processing of the UI event we just pushed on the queue + * - better network conditions + * Remember: We hare on a thread, so the main gui won’t lock */ + usleep(500000); + + /* Perform an auto-reconnect. */ + while (TRUE) { + /* Quit retrying if max retries has been exceeded */ + if (rfi->reconnect_nattempt++ >= rfi->reconnect_maxattempts) { + REMMINA_PLUGIN_DEBUG("[%s] maximum number of reconnection attempts exceeded.", + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + } + + if (rfi->stop_reconnecting_requested) { + REMMINA_PLUGIN_DEBUG("[%s] reconnect request loop interrupted by user.", + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + } + + /* Attempt the next reconnect */ + REMMINA_PLUGIN_DEBUG("[%s] reconnection, attempt #%d of %d", + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname), rfi->reconnect_nattempt, rfi->reconnect_maxattempts); + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_RECONNECT_PROGRESS; + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + treconn = time(NULL); + + /* Reconnect the SSH tunnel, if needed */ + if (!remmina_rdp_tunnel_init(rfi->protocol_widget)) { + REMMINA_PLUGIN_DEBUG("[%s] unable to recreate tunnel with remmina_rdp_tunnel_init.", + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + } else { + if (freerdp_reconnect(rfi->clientContext.context.instance)) { + /* Reconnection is successful */ + REMMINA_PLUGIN_DEBUG("[%s] reconnected.", freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + rfi->is_reconnecting = FALSE; + return TRUE; + } + } + + /* Wait until 5 secs have elapsed from last reconnect attempt, while checking for rfi->stop_reconnecting_requested */ + while (time(NULL) - treconn < 5) { + if (rfi->stop_reconnecting_requested) + break; + usleep(200000); // 200ms sleep + } + } + + rfi->is_reconnecting = FALSE; + return FALSE; +} + +static BOOL rf_begin_paint(rdpContext *context) +{ + TRACE_CALL(__func__); + rdpGdi *gdi; + + if (!context) + return FALSE; + + gdi = context->gdi; + if (!gdi || !gdi->primary || !gdi->primary->hdc || !gdi->primary->hdc->hwnd) + return FALSE; + + return TRUE; +} + +static BOOL rf_end_paint(rdpContext *context) +{ + TRACE_CALL(__func__); + rdpGdi *gdi; + rfContext *rfi; + RemminaPluginRdpUiObject *ui; + int i, ninvalid; + region *reg; + HGDI_RGN cinvalid; + + gdi = context->gdi; + rfi = (rfContext *)context; + + if (gdi == NULL || gdi->primary == NULL || gdi->primary->hdc == NULL || gdi->primary->hdc->hwnd == NULL) + return TRUE; + + if (gdi->primary->hdc->hwnd->invalid->null) + return TRUE; + + if (gdi->primary->hdc->hwnd->ninvalid < 1) + return TRUE; + + ninvalid = gdi->primary->hdc->hwnd->ninvalid; + cinvalid = gdi->primary->hdc->hwnd->cinvalid; + reg = (region *)g_malloc(sizeof(region) * ninvalid); + for (i = 0; i < ninvalid; i++) { + reg[i].x = cinvalid[i].x; + reg[i].y = cinvalid[i].y; + reg[i].w = cinvalid[i].w; + reg[i].h = cinvalid[i].h; + } + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_UPDATE_REGIONS; + ui->reg.ninvalid = ninvalid; + ui->reg.ureg = reg; + + remmina_rdp_event_queue_ui_async(rfi->protocol_widget, ui); + + + gdi->primary->hdc->hwnd->invalid->null = TRUE; + gdi->primary->hdc->hwnd->ninvalid = 0; + + + return TRUE; +} + +static BOOL rf_desktop_resize(rdpContext *context) +{ + TRACE_CALL(__func__); + rfContext *rfi; + RemminaProtocolWidget *gp; + RemminaPluginRdpUiObject *ui; + UINT32 w, h; + + rfi = (rfContext *)context; + gp = rfi->protocol_widget; + + w = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth); + h = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight); + remmina_plugin_service->protocol_plugin_set_width(gp, w); + remmina_plugin_service->protocol_plugin_set_height(gp, h); + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_EVENT; + ui->event.type = REMMINA_RDP_UI_EVENT_DESTROY_CAIRO_SURFACE; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + /* Tell libfreerdp to change its internal GDI bitmap width and heigt, + * this will also destroy gdi->primary_buffer, making our rfi->surface invalid */ + gdi_resize(((rdpContext *)rfi)->gdi, w, h); + + /* Call to remmina_rdp_event_update_scale(gp) on the main UI thread, + * this will recreate rfi->surface from gdi->primary_buffer */ + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_EVENT; + ui->event.type = REMMINA_RDP_UI_EVENT_UPDATE_SCALE; + remmina_rdp_event_queue_ui_sync_retint(gp, ui); + + remmina_plugin_service->protocol_plugin_desktop_resize(gp); + + return TRUE; +} + +static BOOL rf_play_sound(rdpContext *context, const PLAY_SOUND_UPDATE *play_sound) +{ + TRACE_CALL(__func__); + rfContext *rfi; + RemminaProtocolWidget *gp; + GdkDisplay *disp; + + rfi = (rfContext *)context; + gp = rfi->protocol_widget; + + disp = gtk_widget_get_display(GTK_WIDGET(gp)); + gdk_display_beep(disp); + + return TRUE; +} + +static BOOL rf_keyboard_set_indicators(rdpContext *context, UINT16 led_flags) +{ + TRACE_CALL(__func__); + rfContext *rfi; + RemminaProtocolWidget *gp; + GdkDisplay *disp; + + rfi = (rfContext *)context; + gp = rfi->protocol_widget; + disp = gtk_widget_get_display(GTK_WIDGET(gp)); + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY(disp)) { + /* TODO: We are not on the main thread. Will X.Org complain? */ + Display *x11_display; + x11_display = gdk_x11_display_get_xdisplay(disp); + XkbLockModifiers(x11_display, XkbUseCoreKbd, + LockMask | Mod2Mask, + ((led_flags & KBD_SYNC_CAPS_LOCK) ? LockMask : 0) | + ((led_flags & KBD_SYNC_NUM_LOCK) ? Mod2Mask : 0) + ); + + /* TODO: Add support to KANA_LOCK and SCROLL_LOCK */ + } +#endif + + return TRUE; +} + +static BOOL rf_keyboard_set_ime_status(rdpContext *context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + TRACE_CALL(__func__); + if (!context) + return FALSE; + + /* Unimplemented, we ignore it */ + + return TRUE; +} + +static BOOL remmina_rdp_pre_connect(freerdp *instance) +{ + TRACE_CALL(__func__); + rdpChannels *channels; + rdpSettings *settings; + rdpContext *context = instance->context; + + settings = context->settings; + channels = context->channels; + freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX); + freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_UNSPECIFIED); + freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, TRUE); + freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel, 1); + + PubSub_SubscribeChannelConnected(instance->context->pubSub, + remmina_rdp_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + remmina_rdp_OnChannelDisconnectedEventHandler); + + if (!freerdp_client_load_addins(channels, settings)) + return FALSE; + + return true; +} + +static BOOL remmina_rdp_post_connect(freerdp *instance) +{ + TRACE_CALL(__func__); + rfContext *rfi; + RemminaProtocolWidget *gp; + RemminaPluginRdpUiObject *ui; + UINT32 freerdp_local_color_format; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_OK; + + rfi->attempt_interactive_authentication = FALSE; // We authenticated! + + rfi->srcBpp = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth); + + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec) == FALSE) + rfi->sw_gdi = TRUE; + + rf_register_graphics(instance->context->graphics); + + REMMINA_PLUGIN_DEBUG("bpp: %d", rfi->bpp); + switch (rfi->bpp) { + case 24: + REMMINA_PLUGIN_DEBUG("CAIRO_FORMAT_RGB24"); + freerdp_local_color_format = PIXEL_FORMAT_BGRX32; + rfi->cairo_format = CAIRO_FORMAT_RGB24; + break; + case 32: + /** Do not use alpha as it's not used with the desktop + * CAIRO_FORMAT_ARGB32 + * See https://gitlab.com/Remmina/Remmina/-/issues/2456 + */ + REMMINA_PLUGIN_DEBUG("CAIRO_FORMAT_RGB24"); + freerdp_local_color_format = PIXEL_FORMAT_BGRA32; + rfi->cairo_format = CAIRO_FORMAT_RGB24; + break; + default: + REMMINA_PLUGIN_DEBUG("CAIRO_FORMAT_RGB16_565"); + freerdp_local_color_format = PIXEL_FORMAT_RGB16; + rfi->cairo_format = CAIRO_FORMAT_RGB16_565; + break; + } + + if (!gdi_init(instance, freerdp_local_color_format)) { + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_GDI_INIT; + return FALSE; + } + + if (instance->context->codecs->h264 == NULL && freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264)) { + gdi_free(instance); + rfi->postconnect_error = REMMINA_POSTCONNECT_ERROR_NO_H264; + return FALSE; + } + + // pointer_cache_register_callbacks(instance->update); + rdpUpdate *update = instance->context->update; + update->BeginPaint = rf_begin_paint; + update->EndPaint = rf_end_paint; + update->DesktopResize = rf_desktop_resize; + + update->PlaySound = rf_play_sound; + update->SetKeyboardIndicators = rf_keyboard_set_indicators; + update->SetKeyboardImeStatus = rf_keyboard_set_ime_status; + + remmina_rdp_clipboard_init(rfi); + rfi->connected = true; + + ui = g_new0(RemminaPluginRdpUiObject, 1); + ui->type = REMMINA_RDP_UI_CONNECTED; + remmina_rdp_event_queue_ui_async(gp, ui); + + return TRUE; +} + +#if !defined(FREERDP_VERSION_MAJOR) || (FREERDP_VERSION_MAJOR < 3) +static BOOL remmina_rdp_authenticate(freerdp *instance, char **username, char **password, char **domain) +{ + TRACE_CALL(__func__); + gchar *s_username, *s_password, *s_domain; + gint ret; + rfContext *rfi; + RemminaProtocolWidget *gp; + gboolean save; + gboolean disablepasswordstoring; + RemminaFile *remminafile; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE); + + ret = remmina_plugin_service->protocol_plugin_init_auth(gp, + (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN, + _("Enter RDP authentication credentials"), + remmina_plugin_service->file_get_string(remminafile, "username"), + remmina_plugin_service->file_get_string(remminafile, "password"), + remmina_plugin_service->file_get_string(remminafile, "domain"), + NULL); + if (ret == GTK_RESPONSE_OK) { + s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp); + if (s_username) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Username, s_username); + + s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp); + if (s_password) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Password, s_password); + + s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp); + if (s_domain) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Domain, s_domain); + + remmina_plugin_service->file_set_string(remminafile, "username", s_username); + remmina_plugin_service->file_set_string(remminafile, "domain", s_domain); + + save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp); + if (save) { + // User has requested to save credentials. We put the password + // into remminafile->settings. It will be saved later, on successful connection, by + // rcw.c + remmina_plugin_service->file_set_string(remminafile, "password", s_password); + } else { + remmina_plugin_service->file_set_string(remminafile, "password", NULL); + } + + + if (s_username) g_free(s_username); + if (s_password) g_free(s_password); + if (s_domain) g_free(s_domain); + + return TRUE; + } else { + return FALSE; + } + + return TRUE; +} + +static BOOL remmina_rdp_gw_authenticate(freerdp *instance, char **username, char **password, char **domain) +{ + TRACE_CALL(__func__); + gchar *s_username, *s_password, *s_domain; + gint ret; + rfContext *rfi; + RemminaProtocolWidget *gp; + gboolean save; + gboolean disablepasswordstoring; + gboolean basecredforgw; + RemminaFile *remminafile; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + if (!remmina_plugin_service->file_get_string(remminafile, "gateway_server")) + return false; + disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE); + basecredforgw = remmina_plugin_service->file_get_int(remminafile, "base-cred-for-gw", FALSE); + + if (basecredforgw) { + ret = remmina_plugin_service->protocol_plugin_init_auth(gp, + (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN, + _("Enter RDP authentication credentials"), + remmina_plugin_service->file_get_string(remminafile, "username"), + remmina_plugin_service->file_get_string(remminafile, "password"), + remmina_plugin_service->file_get_string(remminafile, "domain"), + NULL); + } else { + ret = remmina_plugin_service->protocol_plugin_init_auth(gp, + (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN, + _("Enter RDP gateway authentication credentials"), + remmina_plugin_service->file_get_string(remminafile, "gateway_username"), + remmina_plugin_service->file_get_string(remminafile, "gateway_password"), + remmina_plugin_service->file_get_string(remminafile, "gateway_domain"), + NULL); + } + + + if (ret == GTK_RESPONSE_OK) { + s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp); + if (s_username) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, s_username); + + s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp); + if (s_password) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, s_password); + + s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp); + if (s_domain) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, s_domain); + + save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp); + + if (basecredforgw) { + remmina_plugin_service->file_set_string(remminafile, "username", s_username); + remmina_plugin_service->file_set_string(remminafile, "domain", s_domain); + if (save) + remmina_plugin_service->file_set_string(remminafile, "password", s_password); + else + remmina_plugin_service->file_set_string(remminafile, "password", NULL); + } else { + remmina_plugin_service->file_set_string(remminafile, "gateway_username", s_username); + remmina_plugin_service->file_set_string(remminafile, "gateway_domain", s_domain); + if (save) + remmina_plugin_service->file_set_string(remminafile, "gateway_password", s_password); + else + remmina_plugin_service->file_set_string(remminafile, "gateway_password", NULL); + } + + if (s_username) g_free(s_username); + if (s_password) g_free(s_password); + if (s_domain) g_free(s_domain); + + return true; + } else { + return false; + } + + return true; +} +#else +static BOOL remmina_rdp_authenticate_ex(freerdp* instance, char** username, char** password, + char** domain, rdp_auth_reason reason) +{ + TRACE_CALL(__func__); + gchar *s_username = NULL, *s_password = NULL, *s_domain = NULL; + const gchar* key_user = NULL; + const gchar* key_domain = NULL; + const gchar* key_password = NULL; + const gchar* key_title = NULL; + gint ret; + rfContext *rfi; + RemminaProtocolWidget *gp; + gboolean save; + gboolean disablepasswordstoring; + RemminaFile *remminafile; + RemminaMessagePanelFlags flags = REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD | REMMINA_MESSAGE_PANEL_FLAG_USERNAME | REMMINA_MESSAGE_PANEL_FLAG_DOMAIN; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + disablepasswordstoring = remmina_plugin_service->file_get_int(remminafile, "disablepasswordstoring", FALSE); + + FreeRDP_Settings_Keys_String cfg_key_user = FreeRDP_STRING_UNUSED; + FreeRDP_Settings_Keys_String cfg_key_domain = FreeRDP_STRING_UNUSED; + FreeRDP_Settings_Keys_String cfg_key_password = FreeRDP_STRING_UNUSED; + switch(reason) { + case AUTH_NLA: + case AUTH_TLS: + case AUTH_RDP: + key_title = _("Enter RDP authentication credentials"); + key_user = "username"; + key_domain = "domain"; + key_password = "password"; + cfg_key_user = FreeRDP_Username; + cfg_key_domain = FreeRDP_Domain; + cfg_key_password = FreeRDP_Password; + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + key_title = _("Enter RDP gateway authentication credentials"); + key_user = "gateway_username"; + key_domain = "gateway_domain"; + key_password = "gateway_password"; + cfg_key_user = FreeRDP_GatewayUsername; + cfg_key_domain = FreeRDP_GatewayDomain; + cfg_key_password = FreeRDP_GatewayPassword; + break; + case AUTH_SMARTCARD_PIN: + key_title = _("Enter RDP SmartCard PIN"); + key_password = "smartcard_pin"; + flags = 0; + break; + default: + // TODO: Display an error dialog informing the user that the remote requires some mechanism FreeRDP or Remmina currently do not support + g_fprintf(stderr, "[authentication] unsupported type %d, access denied", reason); + return FALSE; + } + + if (!disablepasswordstoring) + flags |= REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD; + + ret = remmina_plugin_service->protocol_plugin_init_auth(gp, flags, + key_title, + remmina_plugin_service->file_get_string(remminafile, key_user), + remmina_plugin_service->file_get_string(remminafile, key_password), + remmina_plugin_service->file_get_string(remminafile, disablepasswordstoring ? NULL : key_domain), + NULL); + if (ret == GTK_RESPONSE_OK) { + if (cfg_key_user != FreeRDP_STRING_UNUSED) + { + s_username = remmina_plugin_service->protocol_plugin_init_get_username(gp); + if (s_username) + freerdp_settings_set_string(rfi->clientContext.context.settings, cfg_key_user, s_username); + remmina_plugin_service->file_set_string(remminafile, key_user, s_username); + } + + if (cfg_key_password != FreeRDP_STRING_UNUSED) + { + s_password = remmina_plugin_service->protocol_plugin_init_get_password(gp); + if (s_password) + freerdp_settings_set_string(rfi->clientContext.context.settings, cfg_key_password, s_password); + } + + if (cfg_key_domain != FreeRDP_STRING_UNUSED) { + s_domain = remmina_plugin_service->protocol_plugin_init_get_domain(gp); + if (s_domain) + freerdp_settings_set_string(rfi->clientContext.context.settings, cfg_key_domain, s_domain); + remmina_plugin_service->file_set_string(remminafile, key_domain, s_domain); + } + + save = remmina_plugin_service->protocol_plugin_init_get_savepassword(gp); + if (save) { + // User has requested to save credentials. We put the password + // into remminafile->settings. It will be saved later, on successful connection, by + // rcw.c + remmina_plugin_service->file_set_string(remminafile, key_password, s_password); + } else { + remmina_plugin_service->file_set_string(remminafile, key_password, NULL); + } + + + if (s_username) g_free(s_username); + if (s_password) g_free(s_password); + if (s_domain) g_free(s_domain); + + return TRUE; + } else { + return FALSE; + } + + return TRUE; +} + +static BOOL remmina_rdp_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count, + DWORD* choice, BOOL gateway) +{ + // TODO: Display a simple list of smartcards/certs to choose from. + // See client_cli_choose_smartcard for a sample + return client_cli_choose_smartcard(instance, cert_list, count, choice, gateway); +} + +static BOOL remmina_rdp_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token, + size_t count, ...) +{ + // TODO: Open a (currently hard coded) URL, authenticate in a webview/browser, return the access token. + // See client_cli_get_access_token or sdl_webview_get_access_token for implementations + return client_cli_get_access_token(instance, tokenType, token, count); +} + +static BOOL remmina_rdp_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, + BOOL isConsentMandatory, size_t length, + const WCHAR* message) +{ + // TODO: Present a message to the user, usually terms of service or similar + // See client_cli_present_gateway_message or sdl_present_gateway_message + return client_cli_present_gateway_message(instance, type, isDisplayMandatory, isConsentMandatory, length, message); +} + +static int remmina_rdp_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + // TODO: Display the reason for connection termination + // See client_cli_logon_error_info or sdl_logon_error_info + return client_cli_logon_error_info(instance, data, type); +} + +static SSIZE_T remmina_rdp_retry_dialog(freerdp* instance, const char* what, size_t current, + void* userarg) +{ + // TODO: + // See client_common_retry_dialog or + return client_common_retry_dialog(instance, what, current, userarg); +} + +static void remmina_rdp_post_final_disconnect(freerdp* instance) +{ + // Clean up resources allocated in PreConnect +} +#endif + +static DWORD remmina_rdp_verify_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, DWORD flags) +{ + TRACE_CALL(__func__); + gint status; + rfContext *rfi; + RemminaProtocolWidget *gp; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + + status = remmina_plugin_service->protocol_plugin_init_certificate(gp, subject, issuer, fingerprint); + + if (status == GTK_RESPONSE_OK) + return 1; + + return 0; +} + +static DWORD +remmina_rdp_verify_certificate(freerdp *instance, const char *common_name, const char *subject, const char *issuer, const char *fingerprint, BOOL host_mismatch) __attribute__ ((unused)); +static DWORD +remmina_rdp_verify_certificate(freerdp *instance, const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, BOOL host_mismatch) +{ + TRACE_CALL(__func__); + gint status; + rfContext *rfi; + RemminaProtocolWidget *gp; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + + status = remmina_plugin_service->protocol_plugin_init_certificate(gp, subject, issuer, fingerprint); + + if (status == GTK_RESPONSE_OK) + return 1; + + return 0; +} + +static DWORD remmina_rdp_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, + const char *old_subject, const char *old_issuer, + const char *old_fingerprint, DWORD flags) +{ + TRACE_CALL(__func__); + gint status; + rfContext *rfi; + RemminaProtocolWidget *gp; + + rfi = (rfContext *)instance->context; + gp = rfi->protocol_widget; + + status = remmina_plugin_service->protocol_plugin_changed_certificate(gp, subject, issuer, fingerprint, old_fingerprint); + + if (status == GTK_RESPONSE_OK) + return 1; + + return 0; +} + +static void remmina_rdp_post_disconnect(freerdp *instance) +{ + TRACE_CALL(__func__); + + if (!instance || !instance->context) + return; + + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + remmina_rdp_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + remmina_rdp_OnChannelDisconnectedEventHandler); + + /* The remaining cleanup will be continued on main thread by complete_cleanup_on_main_thread() */ + + // With FreeRDP3 only resources allocated in PostConnect and later are cleaned up here. +} + +static void remmina_rdp_main_loop(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + DWORD status; + gchar buf[100]; + rfContext *rfi = GET_PLUGIN_DATA(gp); + RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + time_t cur_time, time_diff; + + int jitter_time = remmina_plugin_service->file_get_int(remminafile, "rdp_mouse_jitter", 0); + time(&last_time); +#if FREERDP_VERSION_MAJOR >= 3 + while (!freerdp_shall_disconnect_context(&rfi->clientContext.context)) { +#else + while (!freerdp_shall_disconnect(rfi->clientContext.context.instance)) { +#endif + // move mouse if we've been idle and option is selected + time(&cur_time); + time_diff = cur_time - last_time; + if (jitter_time > 0 && time_diff > jitter_time){ + last_time = cur_time; + remmina_rdp_mouse_jitter(gp); + } + + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {0}; + DWORD nCount = freerdp_get_event_handles(&rfi->clientContext.context, &handles[0], ARRAYSIZE(handles)); + if (rfi->event_handle) + handles[nCount++] = rfi->event_handle; + + handles[nCount++] = freerdp_abort_event(&rfi->clientContext.context); + + if (nCount == 0) { + fprintf(stderr, "freerdp_get_event_handles failed\n"); + break; + } + + status = WaitForMultipleObjects(nCount, handles, FALSE, 100); + + if (status == WAIT_FAILED) { + fprintf(stderr, "WaitForMultipleObjects failed with %lu\n", (unsigned long)status); + break; + } + + if (rfi->event_handle && WaitForSingleObject(rfi->event_handle, 0) == WAIT_OBJECT_0) { + if (!rf_process_event_queue(gp)) { + fprintf(stderr, "Could not process local keyboard/mouse event queue\n"); + break; + } + if (read(rfi->event_pipe[0], buf, sizeof(buf))) { + } + } + + /* Check if a processed event called freerdp_abort_connect() and exit if true */ + if (WaitForSingleObject(freerdp_abort_event(&rfi->clientContext.context), 0) == WAIT_OBJECT_0) + /* Session disconnected by local user action */ + break; + + if (!freerdp_check_event_handles(&rfi->clientContext.context)) { + if (rf_auto_reconnect(rfi)) { + /* Reset the possible reason/error which made us doing many reconnection reattempts and continue */ + remmina_plugin_service->protocol_plugin_set_error(gp, NULL); + continue; + } + if (freerdp_get_last_error(&rfi->clientContext.context) == FREERDP_ERROR_SUCCESS) + fprintf(stderr, "Could not check FreeRDP file descriptor\n"); + break; + } + } + const gchar *host = freerdp_settings_get_string (rfi->clientContext.context.settings, FreeRDP_ServerHostname); + // TRANSLATORS: the placeholder may be either an IP/FQDN or a server hostname + REMMINA_PLUGIN_AUDIT(_("Disconnected from %s via RDP"), host); + freerdp_disconnect(rfi->clientContext.context.instance); + REMMINA_PLUGIN_DEBUG("RDP client disconnected"); +} + +static int remmina_rdp_load_static_channel_addin(rdpChannels *channels, rdpSettings *settings, char *name, void *data) +{ + TRACE_CALL(__func__); + PVIRTUALCHANNELENTRY entry = NULL; + PVIRTUALCHANNELENTRYEX entryEx = NULL; + + entryEx = (PVIRTUALCHANNELENTRYEX)(void *)freerdp_load_channel_addin_entry( + name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + + if (!entryEx) + entry = freerdp_load_channel_addin_entry(name, NULL, NULL, FREERDP_ADDIN_CHANNEL_STATIC); + + if (entryEx) { + if (freerdp_channels_client_load_ex(channels, settings, entryEx, data) == 0) { + fprintf(stderr, "loading channel %s\n", name); + return TRUE; + } + } else if (entry) { + if (freerdp_channels_client_load(channels, settings, entry, data) == 0) { + fprintf(stderr, "loading channel %s\n", name); + return TRUE; + } + } + + return FALSE; +} + +static gchar *remmina_rdp_find_prdriver(char *smap, char *prn) +{ + char c, *p, *dr; + int matching; + size_t sz; + + enum { S_WAITPR, + S_INPRINTER, + S_WAITCOLON, + S_WAITDRIVER, + S_INDRIVER, + S_WAITSEMICOLON } state = S_WAITPR; + + matching = 0; + while ((c = *smap++) != 0) { + switch (state) { + case S_WAITPR: + if (c != '\"') return NULL; + state = S_INPRINTER; + p = prn; + matching = 1; + break; + case S_INPRINTER: + if (matching && c == *p && *p != 0) { + p++; + } else if (c == '\"') { + if (*p != 0) + matching = 0; + state = S_WAITCOLON; + } else { + matching = 0; + } + break; + case S_WAITCOLON: + if (c != ':') + return NULL; + state = S_WAITDRIVER; + break; + case S_WAITDRIVER: + if (c != '\"') + return NULL; + state = S_INDRIVER; + dr = smap; + break; + case S_INDRIVER: + if (c == '\"') { + if (matching) + goto found; + else + state = S_WAITSEMICOLON; + } + break; + case S_WAITSEMICOLON: + if (c != ';') + return NULL; + state = S_WAITPR; + break; + } + } + return NULL; + +found: + sz = smap - dr; + p = (char *)malloc(sz); + memcpy(p, dr, sz); + p[sz - 1] = 0; + return p; +} + +#ifdef HAVE_CUPS +/** + * Callback function used by cupsEnumDests + * - For each enumerated local printer tries to set the Printer Name and Driver. + * @return 1 if there are other printers to scan or 0 when it's done. + */ +static int remmina_rdp_set_printers(void *user_data, unsigned flags, cups_dest_t *dest) +{ + rfContext *rfi = (rfContext *)user_data; + RemminaProtocolWidget *gp = rfi->protocol_widget; + + /** @warning printer-make-and-model is not always the same as on the Windows, + * therefore it fails finding to right one and it fails to add + * the printer. + * + * We pass NULL and we do not check for errors. The following code is + * how it is supposed to work. @todo Ask CUPS mailing list for help. + * + * @code + * const char *model = cupsGetOption("printer-make-and-model", + * dest->num_options, + * dest->options); + * @endcode + */ + + RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + const gchar *s = remmina_plugin_service->file_get_string(remminafile, "printer_overrides"); + + RDPDR_PRINTER *printer; + printer = (RDPDR_PRINTER *)calloc(1, sizeof(RDPDR_PRINTER)); + +#if FREERDP_VERSION_MAJOR >= 3 + RDPDR_DEVICE *pdev; + pdev = &(printer->device); +#else + RDPDR_PRINTER *pdev; + pdev = printer; +#endif + + pdev->Type = RDPDR_DTYP_PRINT; + REMMINA_PLUGIN_DEBUG("Printer Type: %d", pdev->Type); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectPrinters, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE); + + REMMINA_PLUGIN_DEBUG("Destination: %s", dest->name); + if (!(pdev->Name = _strdup(dest->name))) { + free(printer); + return 1; + } + + REMMINA_PLUGIN_DEBUG("Printer Name: %s", pdev->Name); + + if (s) { + gchar *d = remmina_rdp_find_prdriver(strdup(s), pdev->Name); + if (d) { + printer->DriverName = strdup(d); + REMMINA_PLUGIN_DEBUG("Printer DriverName set to: %s", printer->DriverName); + g_free(d); + } else { + /** + * When remmina_rdp_find_prdriver doesn't return a DriverName + * it means that we don't want to share that printer + * + */ + free(pdev->Name); + free(printer); + return 1; + } + } else { + /* We set to a default driver*/ + printer->DriverName = _strdup("MS Publisher Imagesetter"); + } + + REMMINA_PLUGIN_DEBUG("Printer Driver: %s", printer->DriverName); + if (!freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)printer)) { + free(printer->DriverName); + free(pdev->Name); + free(printer); + return 1; + } + + return 1; +} +#endif /* HAVE_CUPS */ + +/* Send Ctrl+Alt+Del keystrokes to the plugin drawing_area widget */ +static void remmina_rdp_send_ctrlaltdel(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete }; + rfContext *rfi = GET_PLUGIN_DATA(gp); + + remmina_plugin_service->protocol_plugin_send_keys_signals(rfi->drawing_area, + keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE); +} + +static gboolean remmina_rdp_set_connection_type(rdpSettings *settings, guint32 type) +{ + freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, type); + + if (type == CONNECTION_TYPE_MODEM) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, TRUE); + } else if (type == CONNECTION_TYPE_BROADBAND_LOW) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE); + } else if (type == CONNECTION_TYPE_SATELLITE) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE); + } else if (type == CONNECTION_TYPE_BROADBAND_HIGH) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE); + } else if (type == CONNECTION_TYPE_WAN) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE); + } else if (type == CONNECTION_TYPE_LAN) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE); + } else if (type == CONNECTION_TYPE_AUTODETECT) { + freerdp_settings_set_bool(settings, FreeRDP_DisableWallpaper, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AllowFontSmoothing, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_AllowDesktopComposition, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DisableFullWindowDrag, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableMenuAnims, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_DisableThemes, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, TRUE); + + /* Automatically activate GFX and RFX codec support */ +#ifdef WITH_GFX_H264 + freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, gfx_h264_available); + freerdp_settings_set_bool(settings, FreeRDP_GfxH264, gfx_h264_available); +#endif + freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE); + } else if (type == REMMINA_CONNECTION_TYPE_NONE) { + return FALSE; + } else { + return FALSE; + } + + return TRUE; +} + +#ifdef GDK_WINDOWING_X11 +#if FREERDP_CHECK_VERSION(2, 3, 0) +static gchar *remmina_get_rdp_kbd_remap(const gchar *keymap) +{ + TRACE_CALL(__func__); + guint *table; + gchar keys[20]; + gchar *rdp_kbd_remap = NULL; + gint i; + Display *display; + + table = remmina_plugin_service->pref_keymap_get_table(keymap); + if (!table) + return rdp_kbd_remap; + rdp_kbd_remap = g_malloc0(512); + display = XOpenDisplay(0); + for (i = 0; table[i] > 0; i += 2) { + g_snprintf(keys, sizeof(keys), "0x%02x=0x%02x", freerdp_keyboard_get_rdp_scancode_from_x11_keycode(XKeysymToKeycode(display, table[i])), + freerdp_keyboard_get_rdp_scancode_from_x11_keycode(XKeysymToKeycode(display, table[i + 1]))); + if (i > 0) + g_strlcat(rdp_kbd_remap, ",", 512); + g_strlcat(rdp_kbd_remap, keys, 512); + } + XCloseDisplay(display); + + return rdp_kbd_remap; +} +#endif +#endif + +static gboolean remmina_rdp_main(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + const gchar *s; + gchar *sm; + gchar *value; + const gchar *cs; + RemminaFile *remminafile; + rfContext *rfi = GET_PLUGIN_DATA(gp); + rdpChannels *channels; + gchar *gateway_host; + gint gateway_port; + gchar *datapath = NULL; + gboolean status = TRUE; +#ifdef GDK_WINDOWING_X11 + gchar *rdp_kbd_remap; +#endif + gint i; + + gint desktopOrientation, desktopScaleFactor, deviceScaleFactor; + + channels = rfi->clientContext.context.channels; + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + datapath = g_build_path("/", + remmina_plugin_service->file_get_user_datadir(), + "RDP", + NULL); + REMMINA_PLUGIN_DEBUG("RDP data path is %s", datapath); + + if ((datapath != NULL) && (datapath[0] != '\0')) + if (access(datapath, W_OK) == 0) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ConfigPath, datapath); + g_free(datapath); + + if (remmina_plugin_service->file_get_int(remminafile, "assistance_mode", 0)){ + rdpAssistanceFile* file = freerdp_assistance_file_new(); + if (!file){ + REMMINA_PLUGIN_DEBUG("Could not allocate assistance file structure"); + return FALSE; + } + + if (remmina_plugin_service->file_get_string(remminafile, "assistance_file") == NULL || + remmina_plugin_service->file_get_string(remminafile, "assistance_pass") == NULL ){ + + REMMINA_PLUGIN_DEBUG("Assistance file and password are not set while assistance mode is on"); + return FALSE; + } + + status = freerdp_assistance_parse_file(file, + remmina_plugin_service->file_get_string(remminafile, "assistance_file"), + remmina_plugin_service->file_get_string(remminafile, "assistance_pass")); + + if (status < 0){ + REMMINA_PLUGIN_DEBUG("Could not parse assistance file"); + return FALSE; + } + + + if (!freerdp_assistance_populate_settings_from_assistance_file(file, rfi->clientContext.context.settings)){ + REMMINA_PLUGIN_DEBUG("Could not populate settings from assistance file"); + return FALSE; + } + } + + +#if defined(PROXY_TYPE_IGNORE) + if (!remmina_plugin_service->file_get_int(remminafile, "useproxyenv", FALSE) ? TRUE : FALSE) { + REMMINA_PLUGIN_DEBUG("Not using system proxy settings"); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE); + } +#endif + + if (!remmina_rdp_tunnel_init(gp)) + return FALSE; + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AutoReconnectionEnabled, (remmina_plugin_service->file_get_int(remminafile, "disableautoreconnect", FALSE) ? FALSE : TRUE)); + /* Disable RDP auto reconnection when SSH tunnel is enabled */ + if (remmina_plugin_service->file_get_int(remminafile, "ssh_tunnel_enabled", FALSE)) + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AutoReconnectionEnabled, FALSE); + + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, remmina_plugin_service->file_get_int(remminafile, "colordepth", 99)); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SoftwareGdi, TRUE); + REMMINA_PLUGIN_DEBUG("gfx_h264_available: %d", gfx_h264_available); + + /* Avoid using H.264 modes if they are not available on libfreerdp */ + if (!gfx_h264_available && (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 65 || freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_ColorDepth == 66))) + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 64); // Fallback to GFX RFX + + if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 0) { + /* RFX (Win7)*/ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, FALSE); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 63) { + /* /gfx (RFX Progressive) (Win8) */ + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, FALSE); + } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 64) { + /* /gfx:rfx (Win8) */ + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, FALSE); + } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 65) { + /* /gfx:avc420 (Win8.1) */ + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, gfx_h264_available); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, FALSE); + } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 66) { + /* /gfx:avc444 (Win10) */ + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, gfx_h264_available); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, gfx_h264_available); + } else if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth) == 99) { + /* Automatic (Let the server choose its best format) */ + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264, gfx_h264_available); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GfxAVC444, gfx_h264_available); + } + + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_RemoteFxCodec) || + freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_NSCodec) || + freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathOutput, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FrameMarkerCommandEnabled, TRUE); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ColorDepth, 32); + rfi->bpp = 32; + } + + gint w = remmina_plugin_service->get_profile_remote_width(gp); + gint h = remmina_plugin_service->get_profile_remote_height(gp); + /* multiple of 4 */ + w = (w + 3) & ~0x3; + h = (h + 3) & ~0x3; + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, w); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight, h); + REMMINA_PLUGIN_DEBUG("Resolution set by the user: %dx%d", w, h); + + /* Workaround for FreeRDP issue #5417: in GFX AVC modes we can't go under + * AVC_MIN_DESKTOP_WIDTH x AVC_MIN_DESKTOP_HEIGHT */ + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline) && + freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GfxH264)) { + if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth) < + AVC_MIN_DESKTOP_WIDTH) + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, + AVC_MIN_DESKTOP_WIDTH); + if (freerdp_settings_get_uint32(rfi->clientContext.context.settings, + FreeRDP_DesktopHeight) < + AVC_MIN_DESKTOP_HEIGHT) + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight, + AVC_MIN_DESKTOP_HEIGHT); + } + + /* Workaround for FreeRDP issue #5119. This will make our horizontal resolution + * an even value, but it will add a vertical black 1 pixel line on the + * right of the desktop */ + if ((freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth) & 1) != 0) { + UINT32 tmp = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, tmp - 1); + } + + remmina_plugin_service->protocol_plugin_set_width(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth)); + remmina_plugin_service->protocol_plugin_set_height(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight)); + + w = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth); + h = freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight); + REMMINA_PLUGIN_DEBUG("Resolution set after workarounds: %dx%d", w, h); + + + if (remmina_plugin_service->file_get_string(remminafile, "username")) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Username, remmina_plugin_service->file_get_string(remminafile, "username")); + + if (remmina_plugin_service->file_get_string(remminafile, "domain")) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Domain, remmina_plugin_service->file_get_string(remminafile, "domain")); + + s = remmina_plugin_service->file_get_string(remminafile, "password"); + if (s) freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Password, s); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AutoLogonEnabled, TRUE); + + /** + * Proxy support + * Proxy settings are hidden at the moment as an advanced feature + */ + gchar *proxy_type = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_type")); + gchar *proxy_username = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_username")); + gchar *proxy_password = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_password")); + gchar *proxy_hostname = g_strdup(remmina_plugin_service->file_get_string(remminafile, "proxy_hostname")); + gint proxy_port = remmina_plugin_service->file_get_int(remminafile, "proxy_port", 80); + REMMINA_PLUGIN_DEBUG("proxy_type: %s", proxy_type); + REMMINA_PLUGIN_DEBUG("proxy_username: %s", proxy_username); + REMMINA_PLUGIN_DEBUG("proxy_password: %s", proxy_password); + REMMINA_PLUGIN_DEBUG("proxy_hostname: %s", proxy_hostname); + REMMINA_PLUGIN_DEBUG("proxy_port: %d", proxy_port); + if (proxy_type && proxy_hostname) { + if (g_strcmp0(proxy_type, "no_proxy") == 0) + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_IGNORE); + else if (g_strcmp0(proxy_type, "http") == 0) + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP); + else if (g_strcmp0(proxy_type, "socks5") == 0) + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType, PROXY_TYPE_SOCKS); + else + g_warning("Invalid proxy protocol, at the moment only no_proxy, HTTP and SOCKS5 are supported"); + REMMINA_PLUGIN_DEBUG("ProxyType set to: %" PRIu32, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_ProxyType)); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ProxyHostname, proxy_hostname); + if (proxy_username) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ProxyUsername, proxy_username); + if (proxy_password) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ProxyPassword, proxy_password); + if (proxy_port) + freerdp_settings_set_uint16(rfi->clientContext.context.settings, FreeRDP_ProxyPort, proxy_port); + } + g_free(proxy_hostname); + g_free(proxy_username); + g_free(proxy_password); + + if (remmina_plugin_service->file_get_int(remminafile, "base-cred-for-gw", FALSE)) { + // Reset gateway credentials + remmina_plugin_service->file_set_string(remminafile, "gateway_username", NULL); + remmina_plugin_service->file_set_string(remminafile, "gateway_domain", NULL); + remmina_plugin_service->file_set_string(remminafile, "gateway_password", NULL); + } + + /* Remote Desktop Gateway server address */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled, FALSE); + s = remmina_plugin_service->file_get_string(remminafile, "gateway_server"); + if (s) { + cs = remmina_plugin_service->file_get_string(remminafile, "gwtransp"); +#if FREERDP_CHECK_VERSION(2, 3, 1) + if (remmina_plugin_service->file_get_int(remminafile, "websockets", FALSE)) + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpUseWebsockets, TRUE); + else + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpUseWebsockets, FALSE); +#endif + if (g_strcmp0(cs, "http") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayRpcTransport, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpTransport, TRUE); + } else if (g_strcmp0(cs, "rpc") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayRpcTransport, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpTransport, FALSE); + } else if (g_strcmp0(cs, "auto") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayRpcTransport, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayHttpTransport, TRUE); + } + remmina_plugin_service->get_server_port(s, 443, &gateway_host, &gateway_port); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayHostname, gateway_host); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_GatewayPort, gateway_port); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, TRUE); + } + /* Remote Desktop Gateway domain */ + if (remmina_plugin_service->file_get_string(remminafile, "gateway_domain")) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, remmina_plugin_service->file_get_string(remminafile, "gateway_domain")); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE); + } + /* Remote Desktop Gateway username */ + if (remmina_plugin_service->file_get_string(remminafile, "gateway_username")) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, remmina_plugin_service->file_get_string(remminafile, "gateway_username")); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE); + } + /* Remote Desktop Gateway password */ + s = remmina_plugin_service->file_get_string(remminafile, "gateway_password"); + if (s) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, s); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE); + } + /* If no different credentials were provided for the Remote Desktop Gateway + * use the same authentication credentials for the host */ + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled) && freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials)) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_Domain)); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_Username)); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_Password)); + } + /* Remote Desktop Gateway usage */ + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_GatewayEnabled)) + freerdp_set_gateway_usage_method(rfi->clientContext.context.settings, + remmina_plugin_service->file_get_int(remminafile, "gateway_usage", FALSE) ? TSC_PROXY_MODE_DETECT : TSC_PROXY_MODE_DIRECT); + + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayAccessToken, + remmina_plugin_service->file_get_string(remminafile, "gatewayaccesstoken")); + + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_AuthenticationLevel, remmina_plugin_service->file_get_int( + remminafile, "authentication level", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_AuthenticationLevel))); + + /* Certificate ignore */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_IgnoreCertificate, remmina_plugin_service->file_get_int(remminafile, "cert_ignore", 0)); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_OldLicenseBehaviour, remmina_plugin_service->file_get_int(remminafile, "old-license", 0)); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AllowUnanouncedOrdersFromServer, remmina_plugin_service->file_get_int(remminafile, "relax-order-checks", 0)); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_GlyphSupportLevel, (remmina_plugin_service->file_get_int(remminafile, "glyph-cache", 0) ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE)); + + if ((cs = remmina_plugin_service->file_get_string(remminafile, "clientname"))) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ClientHostname, cs); + else + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ClientHostname, g_get_host_name()); + + /* Client Build number is optional, if not specified defaults to 0, allow for comments to appear after number */ + if ((cs = remmina_plugin_service->file_get_string(remminafile, "clientbuild"))) { + if (*cs) { + UINT32 val = strtoul(cs, NULL, 0); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_ClientBuild, val); + } + } + + + if (remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")) { + const gchar *tmp = strdup(remmina_plugin_service->file_get_string(remminafile, "loadbalanceinfo")); + freerdp_settings_set_pointer_len(rfi->clientContext.context.settings, FreeRDP_LoadBalanceInfo, tmp, strlen(tmp) + 1); + } + + if (remmina_plugin_service->file_get_string(remminafile, "exec")) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_AlternateShell, remmina_plugin_service->file_get_string(remminafile, "exec")); + + if (remmina_plugin_service->file_get_string(remminafile, "execpath")) + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_ShellWorkingDirectory, remmina_plugin_service->file_get_string(remminafile, "execpath")); + + sm = g_strdup_printf("rdp_quality_%i", remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0)); + value = remmina_plugin_service->pref_get_value(sm); + g_free(sm); + + if (value && value[0]) { + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, strtoul(value, NULL, 16)); + } else { + switch (remmina_plugin_service->file_get_int(remminafile, "quality", DEFAULT_QUALITY_0)) { + case 9: + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_9); + break; + + case 2: + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_2); + break; + + case 1: + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_1); + break; + + case 0: + default: + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_PerformanceFlags, DEFAULT_QUALITY_0); + break; + } + } + g_free(value); + + if ((cs = remmina_plugin_service->file_get_string(remminafile, "network"))) { + guint32 type = 0; + + if (g_strcmp0(cs, "modem") == 0) + type = CONNECTION_TYPE_MODEM; + else if (g_strcmp0(cs, "broadband") == 0) + type = CONNECTION_TYPE_BROADBAND_HIGH; + else if (g_strcmp0(cs, "broadband-low") == 0) + type = CONNECTION_TYPE_BROADBAND_LOW; + else if (g_strcmp0(cs, "broadband-high") == 0) + type = CONNECTION_TYPE_BROADBAND_HIGH; + else if (g_strcmp0(cs, "wan") == 0) + type = CONNECTION_TYPE_WAN; + else if (g_strcmp0(cs, "lan") == 0) + type = CONNECTION_TYPE_LAN; + else if ((g_strcmp0(cs, "autodetect") == 0)) + type = CONNECTION_TYPE_AUTODETECT; + else if ((g_strcmp0(cs, "none") == 0)) + type = REMMINA_CONNECTION_TYPE_NONE; + else + type = REMMINA_CONNECTION_TYPE_NONE; + + if (!remmina_rdp_set_connection_type(rfi->clientContext.context.settings, type)) + REMMINA_PLUGIN_DEBUG("Network settings not set"); + } + + /* PerformanceFlags bitmask need also to be splitted into BOOL variables + * like freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DisableWallpaper, freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AllowFontSmoothing… + * or freerdp_get_param_bool() function will return the wrong value + */ + freerdp_performance_flags_split(rfi->clientContext.context.settings); + +#ifdef GDK_WINDOWING_X11 +#if FREERDP_CHECK_VERSION(2, 3, 0) + rdp_kbd_remap = remmina_get_rdp_kbd_remap(remmina_plugin_service->file_get_string(remminafile, "keymap")); + if (rdp_kbd_remap != NULL) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_KeyboardRemappingList, rdp_kbd_remap); + REMMINA_PLUGIN_DEBUG( + "rdp_keyboard_remapping_list: %s", + freerdp_settings_get_string(rfi->clientContext.context.settings, + FreeRDP_KeyboardRemappingList)); + g_free(rdp_kbd_remap); + } + else { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_KeyboardRemappingList, remmina_plugin_service->pref_get_value("rdp_kbd_remap")); + REMMINA_PLUGIN_DEBUG( + "rdp_keyboard_remapping_list: %s", + freerdp_settings_get_string(rfi->clientContext.context.settings, + FreeRDP_KeyboardRemappingList)); + } +#endif +#endif + + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_KeyboardLayout, remmina_rdp_settings_get_keyboard_layout()); + + if (remmina_plugin_service->file_get_int(remminafile, "console", FALSE)) + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ConsoleSession, TRUE); + + if (remmina_plugin_service->file_get_int(remminafile, "restricted-admin", FALSE)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ConsoleSession, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RestrictedAdminModeRequired, TRUE); + } + + if (remmina_plugin_service->file_get_string(remminafile, "pth")) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ConsoleSession, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RestrictedAdminModeRequired, TRUE); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_PasswordHash, remmina_plugin_service->file_get_string(remminafile, "pth")); + remmina_plugin_service->file_set_int(remminafile, "restricted-admin", TRUE); + } + + cs = remmina_plugin_service->file_get_string(remminafile, "security"); + if (g_strcmp0(cs, "rdp") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseRdpSecurityLayer, TRUE); + } else if (g_strcmp0(cs, "tls") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, FALSE); + } else if (g_strcmp0(cs, "nla") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, FALSE); + } else if (g_strcmp0(cs, "ext") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RdpSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_TlsSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NlaSecurity, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ExtSecurity, TRUE); + } else { + /* This is "-nego" switch of xfreerdp */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_NegotiateSecurityLayer, TRUE); + } + + cs = remmina_plugin_service->file_get_string(remminafile, "tls-seclevel"); + if (cs && g_strcmp0(cs,"")!=0) { + i = atoi(cs); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_TlsSecLevel, i); + } + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_CompressionEnabled, TRUE); + if (remmina_plugin_service->file_get_int(remminafile, "disable_fastpath", FALSE)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathInput, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathOutput, FALSE); + } else { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathInput, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_FastPathOutput, TRUE); + } + + /* Orientation and scaling settings */ + remmina_rdp_settings_get_orientation_scale_prefs(&desktopOrientation, &desktopScaleFactor, &deviceScaleFactor); + + freerdp_settings_set_uint16(rfi->clientContext.context.settings, FreeRDP_DesktopOrientation, desktopOrientation); + if (desktopScaleFactor != 0 && deviceScaleFactor != 0) { + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopScaleFactor, desktopScaleFactor); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DeviceScaleFactor, deviceScaleFactor); + } + + /* Try to enable "Display Control Virtual Channel Extension", needed to + * dynamically resize remote desktop. This will automatically open + * the "disp" dynamic channel, if available */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportDisplayControl, TRUE); + + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportDisplayControl)) { + CLPARAM *d[1]; + int dcount; + + dcount = 1; + d[0] = "disp"; + freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, dcount, d); + } + + if (freerdp_settings_get_bool(rfi->clientContext.context.settings, FreeRDP_SupportGraphicsPipeline)) { + CLPARAM *d[1]; + + int dcount; + + dcount = 1; + d[0] = "rdpgfx"; + freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, dcount, d); + } + + /* Sound settings */ + cs = remmina_plugin_service->file_get_string(remminafile, "sound"); + if (g_strcmp0(cs, "remote") == 0) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteConsoleAudio, TRUE); + } else if ((cs != NULL && cs[0] != '\0') && g_str_has_prefix(cs, "local")) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioPlayback, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioCapture, TRUE); + } else { + /* Disable sound */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioPlayback, FALSE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RemoteConsoleAudio, FALSE); + } + + cs = remmina_plugin_service->file_get_string(remminafile, "microphone"); + if (cs != NULL && cs[0] != '\0') { + if (g_strcmp0(cs, "0") == 0) { + REMMINA_PLUGIN_DEBUG("“microphone” was set to 0, setting to \"\""); + remmina_plugin_service->file_set_string(remminafile, "microphone", ""); + } else { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_AudioCapture, TRUE); + REMMINA_PLUGIN_DEBUG("“microphone” set to “%s”", cs); + CLPARAM **p; + size_t count; + + p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("audin", g_strdup(cs), &count); + + freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p); + g_free(p); + } + } + + cs = remmina_plugin_service->file_get_string(remminafile, "audio-output"); + if (cs != NULL && cs[0] != '\0') { + REMMINA_PLUGIN_DEBUG("audio output set to %s", cs); + CLPARAM **p; + size_t count; + + p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("rdpsnd", g_strdup(cs), &count); + status = freerdp_client_add_static_channel(rfi->clientContext.context.settings, count, p); + if (status) + status = freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p); + g_free(p); + } + + + cs = remmina_plugin_service->file_get_string(remminafile, "freerdp_log_level"); + if (cs != NULL && cs[0] != '\0') + REMMINA_PLUGIN_DEBUG("Log level set to to %s", cs); + else + cs = g_strdup("INFO"); + wLog *root = WLog_GetRoot(); + WLog_SetStringLogLevel(root, cs); + + cs = remmina_plugin_service->file_get_string(remminafile, "freerdp_log_filters"); + if (cs != NULL && cs[0] != '\0') { + REMMINA_PLUGIN_DEBUG("Log filters set to to %s", cs); + WLog_AddStringLogFilters(cs); + } else { + WLog_AddStringLogFilters(NULL); + } + + + cs = remmina_plugin_service->file_get_string(remminafile, "usb"); + if (cs != NULL && cs[0] != '\0') { + CLPARAM **p; + size_t count; + p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("urbdrc", g_strdup(cs), &count); + freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p); + g_free(p); + } + + cs = remmina_plugin_service->file_get_string(remminafile, "vc"); + if (cs != NULL && cs[0] != '\0') { + CLPARAM **p; + size_t count; + p = remmina_rdp_CommandLineParseCommaSeparatedValues(g_strdup(cs), &count); + freerdp_client_add_static_channel(rfi->clientContext.context.settings, count, p); + g_free(p); + } + + cs = remmina_plugin_service->file_get_string(remminafile, "dvc"); + if (cs != NULL && cs[0] != '\0') { + CLPARAM **p; + size_t count; + p = remmina_rdp_CommandLineParseCommaSeparatedValues(g_strdup(cs), &count); + freerdp_client_add_dynamic_channel(rfi->clientContext.context.settings, count, p); + g_free(p); + } + + cs = remmina_plugin_service->file_get_string(remminafile, RDP2TCP_DVC_CHANNEL_NAME); + if (cs != NULL && cs[0] != '\0') { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_RDP2TCPArgs, cs); + +#if FREERDP_VERSION_MAJOR >= 3 + char* args = freerdp_settings_get_string_writable(rfi->clientContext.context.settings, FreeRDP_RDP2TCPArgs); +#else + char* args = freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_RDP2TCPArgs); +#endif + REMMINA_PLUGIN_DEBUG(RDP2TCP_DVC_CHANNEL_NAME " set to %s",args); + remmina_rdp_load_static_channel_addin(channels, rfi->clientContext.context.settings, RDP2TCP_DVC_CHANNEL_NAME, args); + } + + int vermaj, vermin, verrev; + freerdp_get_version(&vermaj, &vermin, &verrev); + +#if FREERDP_CHECK_VERSION(2, 1, 0) + cs = remmina_plugin_service->file_get_string(remminafile, "timeout"); + if (cs != NULL && cs[0] != '\0') { + const gchar *endptr = NULL; + guint64 val = g_ascii_strtoull(cs, (gchar **)&endptr, 10); + if (val > 600000 || val <= 0) + val = 600000; + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_TcpAckTimeout, (UINT32)val); + } +#endif + + if (remmina_plugin_service->file_get_int(remminafile, "preferipv6", FALSE) ? TRUE : FALSE) + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_PreferIPv6OverIPv4, TRUE); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectClipboard, remmina_plugin_service->file_get_int(remminafile, "disableclipboard", FALSE) ? FALSE : TRUE); + + cs = remmina_plugin_service->file_get_string(remminafile, "sharefolder"); + if (cs != NULL && cs[0] != '\0') { + gchar *ccs = g_strdup(cs); + REMMINA_PLUGIN_DEBUG("[Deprecated->migrating] - Old sharefolder %s to \"drive \"", ccs); + if (!remmina_plugin_service->file_get_string(remminafile, "drive")) { + remmina_plugin_service->file_set_string(remminafile, "drive", g_strdup(ccs)); + remmina_plugin_service->file_set_string(remminafile, "sharefolder", NULL); + REMMINA_PLUGIN_DEBUG("[Deprecated->migrated] - drive set to %s", g_strdup(ccs)); + } + g_free(ccs); + //CLPARAM **p; + //size_t count; + //p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("drive", g_strdup(cs), &count); + //status = freerdp_client_add_device_channel(rfi->clientContext.context.settings, count, p); + //g_free(p); + } + cs = remmina_plugin_service->file_get_string(remminafile, "drive"); + if (cs != NULL && cs[0] != '\0') { + REMMINA_PLUGIN_DEBUG("Redirect directory set to %s", cs); + CLPARAM **p; + size_t count; + + gchar **folders = g_strsplit(cs, ";", -1); + for (i = 0; folders[i] != NULL; i++) { + REMMINA_PLUGIN_DEBUG("Parsing folder %s", folders[i]); + p = remmina_rdp_CommandLineParseCommaSeparatedValuesEx("drive", g_strdup(folders[i]), &count); + status = freerdp_client_add_device_channel(rfi->clientContext.context.settings, count, p); + g_free(p); + } + g_strfreev(folders); + } + + if (remmina_plugin_service->file_get_int(remminafile, "shareprinter", FALSE)) { +#ifdef HAVE_CUPS + REMMINA_PLUGIN_DEBUG("Sharing printers"); + const gchar *po = remmina_plugin_service->file_get_string(remminafile, "printer_overrides"); + if (po && po[0] != 0) { + /* Fallback to remmina code to override print drivers */ + if (cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, NULL, 0, 0, remmina_rdp_set_printers, rfi)) + REMMINA_PLUGIN_DEBUG("All printers have been shared"); + else + REMMINA_PLUGIN_DEBUG("Cannot share printers, are there any available?"); + } else { + /* Use libfreerdp code to map all printers */ + CLPARAM *d[1]; + int dcount; + dcount = 1; + d[0] = "printer"; + freerdp_client_add_device_channel(rfi->clientContext.context.settings, dcount, d); + } +#endif /* HAVE_CUPS */ + } + + if (remmina_plugin_service->file_get_int(remminafile, "span", FALSE)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SpanMonitors, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE); + remmina_plugin_service->file_set_int(remminafile, "multimon", 1); + } + + if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) { + guint32 maxwidth = 0; + guint32 maxheight = 0; + gchar *monitorids; + guint32 i; + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE); + /* TODO Add an option for this */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ForceMultimon, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE); + + gchar *monitorids_string = g_strdup(remmina_plugin_service->file_get_string(remminafile, "monitorids")); + /* Otherwise we get all the attached monitors + * monitorids may contains desktop orientation values. + * But before we check if there are orientation attributes + */ + if (monitorids_string != NULL && monitorids_string[0] != '\0') { + if (g_strstr_len(monitorids_string, -1, ",") != NULL) { + if (g_strstr_len(monitorids_string, -1, ":") != NULL) { + rdpMonitor *base = (rdpMonitor *)freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorDefArray); + /* We have an ID and an orientation degree */ + gchar **temp_items; + gchar **rot_items; + temp_items = g_strsplit(monitorids_string, ",", -1); + for (i = 0; i < g_strv_length(temp_items); i++) { + rot_items = g_strsplit(temp_items[i], ":", -1); + rdpMonitor *current = &base[atoi(rot_items[0])]; + if (i == 0) + monitorids = g_strdup(rot_items[0]); + else + monitorids = g_strdup_printf("%s,%s", monitorids, rot_items[0]); + current->attributes.orientation = atoi(rot_items[1]); + REMMINA_PLUGIN_DEBUG("Monitor n %d orientation: %d", i, current->attributes.orientation); + } + } else { + monitorids = g_strdup(monitorids_string); + } + } else { + monitorids = g_strdup(monitorids_string); + } + } else { + monitorids = g_strdup(monitorids_string); + } + remmina_rdp_monitor_get(rfi, &monitorids, &maxwidth, &maxheight); + if (monitorids != NULL && monitorids[0] != '\0') { + UINT32 *base = (UINT32 *)freerdp_settings_get_pointer(rfi->clientContext.context.settings, FreeRDP_MonitorIds); + gchar **items; + items = g_strsplit(monitorids, ",", -1); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_NumMonitorIds, g_strv_length(items)); + REMMINA_PLUGIN_DEBUG("NumMonitorIds: %d", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_NumMonitorIds)); + for (i = 0; i < g_strv_length(items); i++) { + if (base != NULL){ + UINT32 *current = &base[i]; + *current = atoi(items[i]); + REMMINA_PLUGIN_DEBUG("Added monitor with ID %" PRIu32, *current); + } + } + g_free(monitorids); + g_strfreev(items); + } + if (maxwidth && maxheight) { + REMMINA_PLUGIN_DEBUG("Setting DesktopWidth and DesktopHeight to: %dx%d", maxwidth, maxheight); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth, maxwidth); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight, maxheight); + REMMINA_PLUGIN_DEBUG("DesktopWidth and DesktopHeight set to: %dx%d", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth), freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight)); + } else { + REMMINA_PLUGIN_DEBUG("Cannot set Desktop Size, we are using the previously set values: %dx%d", freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth), freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight)); + } + remmina_plugin_service->protocol_plugin_set_width(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopWidth)); + remmina_plugin_service->protocol_plugin_set_height(gp, freerdp_settings_get_uint32(rfi->clientContext.context.settings, FreeRDP_DesktopHeight)); + } + + const gchar *sn = remmina_plugin_service->file_get_string(remminafile, "smartcardname"); + if (remmina_plugin_service->file_get_int(remminafile, "sharesmartcard", FALSE) || + (sn != NULL && sn[0] != '\0')) { + RDPDR_SMARTCARD *smartcard; + smartcard = (RDPDR_SMARTCARD *)calloc(1, sizeof(RDPDR_SMARTCARD)); + +#if FREERDP_VERSION_MAJOR >= 3 + RDPDR_DEVICE *sdev; + sdev = &(smartcard->device); +#else + RDPDR_SMARTCARD *sdev; + sdev = smartcard; +#endif + + sdev->Type = RDPDR_DTYP_SMARTCARD; + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE); + + if (sn != NULL && sn[0] != '\0') + sdev->Name = _strdup(sn); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectSmartCards, TRUE); + + freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)smartcard); + } + + if (remmina_plugin_service->file_get_int(remminafile, "passwordispin", FALSE)) + /* Option works only combined with Username and Domain, because FreeRDP + * doesn’t know anything about info on smart card */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_PasswordIsSmartcardPin, TRUE); + + /* /serial[:<name>[,<path>[,<driver>[,permissive]]]] */ + if (remmina_plugin_service->file_get_int(remminafile, "shareserial", FALSE)) { + RDPDR_SERIAL *serial; + serial = (RDPDR_SERIAL *)calloc(1, sizeof(RDPDR_SERIAL)); + +#if FREERDP_VERSION_MAJOR >= 3 + RDPDR_DEVICE *sdev; + sdev = &(serial->device); +#else + RDPDR_SERIAL *sdev; + sdev = serial; +#endif + + sdev->Type = RDPDR_DTYP_SERIAL; + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE); + + const gchar *sn = remmina_plugin_service->file_get_string(remminafile, "serialname"); + if (sn != NULL && sn[0] != '\0') + sdev->Name = _strdup(sn); + + const gchar *sd = remmina_plugin_service->file_get_string(remminafile, "serialdriver"); + if (sd != NULL && sd[0] != '\0') + serial->Driver = _strdup(sd); + + const gchar *sp = remmina_plugin_service->file_get_string(remminafile, "serialpath"); + if (sp != NULL && sp[0] != '\0') + serial->Path = _strdup(sp); + + if (remmina_plugin_service->file_get_int(remminafile, "serialpermissive", FALSE)) + serial->Permissive = _strdup("permissive"); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectSerialPorts, TRUE); + + freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)serial); + } + + if (remmina_plugin_service->file_get_int(remminafile, "shareparallel", FALSE)) { + RDPDR_PARALLEL *parallel; + parallel = (RDPDR_PARALLEL *)calloc(1, sizeof(RDPDR_PARALLEL)); + +#if FREERDP_VERSION_MAJOR >= 3 + RDPDR_DEVICE *pdev; + pdev = &(parallel->device); +#else + RDPDR_PARALLEL *pdev; + pdev = parallel; +#endif + + pdev->Type = RDPDR_DTYP_PARALLEL; + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_RedirectParallelPorts, TRUE); + + const gchar *pn = remmina_plugin_service->file_get_string(remminafile, "parallelname"); + if (pn != NULL && pn[0] != '\0') + pdev->Name = _strdup(pn); + const gchar *dp = remmina_plugin_service->file_get_string(remminafile, "parallelpath"); + if (dp != NULL && dp[0] != '\0') + parallel->Path = _strdup(dp); + + freerdp_device_collection_add(rfi->clientContext.context.settings, (RDPDR_DEVICE *)parallel); + } + + /** + * multitransport enables RDP8 UDP support + */ + if (remmina_plugin_service->file_get_int(remminafile, "multitransport", FALSE)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_DeviceRedirection, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_SupportMultitransport, TRUE); + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_MultitransportFlags, + (TRANSPORT_TYPE_UDP_FECR | TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED)); + } else { + freerdp_settings_set_uint32(rfi->clientContext.context.settings, FreeRDP_MultitransportFlags, 0); + } + + /* If needed, force interactive authentication by deleting all authentication fields, + * forcing libfreerdp to call our callbacks for authentication. + * This usually happens from a second attempt of connection, never on the 1st one. */ + if (rfi->attempt_interactive_authentication) { + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Username, NULL); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Password, NULL); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_Domain, NULL); + + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain, NULL); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername, NULL); + freerdp_settings_set_string(rfi->clientContext.context.settings, FreeRDP_GatewayPassword, NULL); + + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_GatewayUseSameCredentials, FALSE); + } + + gboolean orphaned; + + if (!freerdp_connect(rfi->clientContext.context.instance)) { + orphaned = (GET_PLUGIN_DATA(rfi->protocol_widget) == NULL); + if (!orphaned) { + UINT32 e; + + e = freerdp_get_last_error(&rfi->clientContext.context); + + switch (e) { + case FREERDP_ERROR_AUTHENTICATION_FAILED: + case STATUS_LOGON_FAILURE: // wrong return code from FreeRDP introduced at the end of July 2016? (fixed with b86c0ba) +#ifdef FREERDP_ERROR_CONNECT_LOGON_FAILURE + case FREERDP_ERROR_CONNECT_LOGON_FAILURE: +#endif + /* Logon failure, will retry with interactive authentication */ + rfi->attempt_interactive_authentication = TRUE; + break; + case STATUS_ACCOUNT_LOCKED_OUT: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT + case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount locked out."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case STATUS_ACCOUNT_EXPIRED: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED + case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount expired."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case STATUS_PASSWORD_EXPIRED: +#ifdef FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED + case FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nPassword expired."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case STATUS_ACCOUNT_DISABLED: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED + case FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount disabled."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; +#ifdef FREERDP_ERROR_SERVER_INSUFFICIENT_PRIVILEGES + /* https://msdn.microsoft.com/en-us/library/ee392247.aspx */ + case FREERDP_ERROR_SERVER_INSUFFICIENT_PRIVILEGES: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nInsufficient user privileges."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; +#endif + case STATUS_ACCOUNT_RESTRICTION: +#ifdef FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION + case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nAccount restricted."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + + case STATUS_PASSWORD_MUST_CHANGE: +#ifdef FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE + case FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE: +#endif + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not access the RDP server “%s”.\nChange user password before connecting."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + + case FREERDP_ERROR_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Lost connection to the RDP server “%s”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case FREERDP_ERROR_DNS_NAME_NOT_FOUND: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not find the address for the RDP server “%s”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case FREERDP_ERROR_TLS_CONNECT_FAILED: + remmina_plugin_service->protocol_plugin_set_error(gp, + _("Could not connect to the RDP server “%s” via TLS. See the DEBUG traces from a terminal for more information."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED: + // TRANSLATORS: the placeholder may be either an IP/FQDN or a server hostname + remmina_plugin_service->protocol_plugin_set_error(gp, _("Unable to establish a connection to the RDP server “%s”. Check “Security protocol negotiation”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; +#ifdef FREERDP_ERROR_POST_CONNECT_FAILED + case FREERDP_ERROR_POST_CONNECT_FAILED: + /* remmina_rdp_post_connect() returned FALSE to libfreerdp. We saved the error on rfi->postconnect_error */ + switch (rfi->postconnect_error) { + case REMMINA_POSTCONNECT_ERROR_OK: + /* We should never come here */ + remmina_plugin_service->protocol_plugin_set_error(gp, _("Cannot connect to the RDP server “%s”."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + case REMMINA_POSTCONNECT_ERROR_GDI_INIT: + remmina_plugin_service->protocol_plugin_set_error(gp, _("Could not start libfreerdp-gdi.")); + break; + case REMMINA_POSTCONNECT_ERROR_NO_H264: + remmina_plugin_service->protocol_plugin_set_error(gp, _("You requested a H.264 GFX mode for the server “%s”, but your libfreerdp does not support H.264. Please use a non-AVC colour depth setting."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + } + break; +#endif +#ifdef FREERDP_ERROR_SERVER_DENIED_CONNECTION + case FREERDP_ERROR_SERVER_DENIED_CONNECTION: + remmina_plugin_service->protocol_plugin_set_error(gp, _("The “%s” server refused the connection."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; +#endif + case 0x800759DB: + // E_PROXY_NAP_ACCESSDENIED https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tsgu/84cd92e4-592c-4219-95d8-18021ac654b0 + remmina_plugin_service->protocol_plugin_set_error(gp, _("The Remote Desktop Gateway “%s” denied the user “%s\\%s” access due to policy."), + freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_GatewayHostname), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_GatewayDomain), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_GatewayUsername)); + break; + + case FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS: + rfi->user_cancelled = TRUE; + break; + + default: + g_printf("libfreerdp returned code is %08X\n", e); + remmina_plugin_service->protocol_plugin_set_error(gp, _("Cannot connect to the “%s” RDP server."), freerdp_settings_get_string(rfi->clientContext.context.settings, FreeRDP_ServerHostname)); + break; + } + } + + return FALSE; + } + + if (GET_PLUGIN_DATA(rfi->protocol_widget) == NULL) orphaned = true; else orphaned = false; + if (!orphaned && freerdp_get_last_error(&rfi->clientContext.context) == FREERDP_ERROR_SUCCESS && !rfi->user_cancelled) + remmina_rdp_main_loop(gp); + + return TRUE; +} + +static void rfi_uninit(rfContext *rfi) +{ + freerdp *instance; + + instance = rfi->clientContext.context.instance; + + if (rfi->remmina_plugin_thread) { + rfi->thread_cancelled = TRUE; // Avoid all rf_queue function to run + pthread_cancel(rfi->remmina_plugin_thread); + if (rfi->remmina_plugin_thread) + pthread_join(rfi->remmina_plugin_thread, NULL); + } + + if (instance) { + if (rfi->connected) { + freerdp_abort_connect_context(&rfi->clientContext.context); + rfi->connected = false; + } + } + + if (instance) { + RDP_CLIENT_ENTRY_POINTS *pEntryPoints = instance->pClientEntryPoints; + if (pEntryPoints) + IFCALL(pEntryPoints->GlobalUninit); + free(instance->pClientEntryPoints); + freerdp_context_free(instance); /* context is rfContext* rfi */ + freerdp_free(instance); /* This implicitly frees instance->context and rfi is no longer valid */ + } +} + +static gboolean complete_cleanup_on_main_thread(gpointer data) +{ + TRACE_CALL(__func__); + + gboolean orphaned; + rfContext *rfi = (rfContext *)data; + RemminaProtocolWidget *gp; + + remmina_rdp_clipboard_free(rfi); + + gdi_free(rfi->clientContext.context.instance); + + gp = rfi->protocol_widget; + if (GET_PLUGIN_DATA(gp) == NULL) orphaned = true; else orphaned = false; + + remmina_rdp_cliprdr_detach_owner(gp); + if (!orphaned) remmina_rdp_event_uninit(gp); + + if (!orphaned) g_object_steal_data(G_OBJECT(gp), "plugin-data"); + + rfi_uninit(rfi); + + /* Notify the RemminaProtocolWidget that we closed our connection, and the GUI interface + * can be removed */ + if (!orphaned) + remmina_plugin_service->protocol_plugin_signal_connection_closed(gp); + + return G_SOURCE_REMOVE; +} + +static gpointer remmina_rdp_main_thread(gpointer data) +{ + TRACE_CALL(__func__); + RemminaProtocolWidget *gp; + rfContext *rfi; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + CANCEL_ASYNC + + gp = (RemminaProtocolWidget *)data; + + rfi = GET_PLUGIN_DATA(gp); + + rfi->attempt_interactive_authentication = FALSE; + do + remmina_rdp_main(gp); + while (!remmina_plugin_service->protocol_plugin_has_error(gp) && rfi->attempt_interactive_authentication == TRUE && !rfi->user_cancelled); + + rfi->remmina_plugin_thread = 0; + + /* cleanup */ + g_idle_add(complete_cleanup_on_main_thread, (gpointer)rfi); + + + return NULL; +} + +static void remmina_rdp_init(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + freerdp *instance; + rfContext *rfi; + + instance = freerdp_new(); + instance->PreConnect = remmina_rdp_pre_connect; + instance->PostConnect = remmina_rdp_post_connect; + instance->PostDisconnect = remmina_rdp_post_disconnect; + //instance->VerifyCertificate = remmina_rdp_verify_certificate; + instance->VerifyCertificateEx = remmina_rdp_verify_certificate_ex; + //instance->VerifyChangedCertificate = remmina_rdp_verify_changed_certificate; + instance->VerifyChangedCertificateEx = remmina_rdp_verify_changed_certificate_ex; +#if FREERDP_VERSION_MAJOR >= 3 + instance->AuthenticateEx = remmina_rdp_authenticate_ex; + instance->ChooseSmartcard = remmina_rdp_choose_smartcard; + instance->GetAccessToken = remmina_rdp_get_access_token; + instance->LoadChannels = freerdp_client_load_channels; + instance->PresentGatewayMessage = remmina_rdp_present_gateway_message; + instance->LogonErrorInfo = remmina_rdp_logon_error_info; + instance->RetryDialog = remmina_rdp_retry_dialog; + instance->PostFinalDisconnect = remmina_rdp_post_final_disconnect; +#else + instance->Authenticate = remmina_rdp_authenticate; + instance->GatewayAuthenticate = remmina_rdp_gw_authenticate; +#endif + + instance->ContextSize = sizeof(rfContext); + freerdp_context_new(instance); + rfi = (rfContext *)instance->context; + + g_object_set_data_full(G_OBJECT(gp), "plugin-data", rfi, free); + + rfi->protocol_widget = gp; + rfi->clientContext.context.settings = instance->context->settings; + rfi->connected = false; + rfi->is_reconnecting = false; + rfi->stop_reconnecting_requested = false; + rfi->user_cancelled = FALSE; + rfi->last_x = 0; + rfi->last_y = 0; + + freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); + + remmina_rdp_event_init(gp); +} + +static gboolean remmina_rdp_open_connection(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + rfContext *rfi = GET_PLUGIN_DATA(gp); + RemminaFile *remminafile; + const gchar *profile_name, *p; + gchar thname[16], c; + gint nthname = 0; + + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + + if (pthread_create(&rfi->remmina_plugin_thread, NULL, remmina_rdp_main_thread, gp)) { + remmina_plugin_service->protocol_plugin_set_error(gp, "%s", + "Could not start pthread."); + + rfi->remmina_plugin_thread = 0; + + return FALSE; + } + + /* Generate a thread name to be used with pthread_setname_np() for debugging */ + profile_name = remmina_plugin_service->file_get_string(remminafile, "name"); + p = profile_name; + strcpy(thname, "RemmRDP:"); + if (p) { + nthname = strlen(thname); + while ((c = *p) != 0 && nthname < sizeof(thname) - 1) { + if (isalnum(c)) + thname[nthname++] = c; + p++; + } + } else { + strcat(thname, "<NONAM>"); + nthname = strlen(thname); + } + thname[nthname] = 0; +#if defined(__linux__) + pthread_setname_np(rfi->remmina_plugin_thread, thname); +#elif defined(__FreeBSD__) + pthread_set_name_np(rfi->remmina_plugin_thread, thname); +#endif + + return TRUE; +} + +static gboolean remmina_rdp_close_connection(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + + REMMINA_PLUGIN_DEBUG("Requesting to close the connection"); + RemminaPluginRdpEvent rdp_event = { 0 }; + rfContext *rfi = GET_PLUGIN_DATA(gp); + + if (!remmina_plugin_service->is_main_thread()) + g_warning("WARNING: %s called on a subthread, which may not work or crash Remmina.", __func__); + + if (rfi && !rfi->connected) { + /* libfreerdp is attempting to connect, we cannot interrupt our main thread + * in the connect phase. + * So we remove "plugin-data" from gp, so our rfi remains "orphan" + */ + remmina_rdp_event_uninit(gp); + g_object_steal_data(G_OBJECT(gp), "plugin-data"); + remmina_plugin_service->protocol_plugin_signal_connection_closed(gp); + return FALSE; + } + + + if (rfi && rfi->clipboard.srv_clip_data_wait == SCDW_BUSY_WAIT) { + REMMINA_PLUGIN_DEBUG("[RDP] requesting clipboard transfer to abort"); + /* Allow clipboard transfer from server to terminate */ + rfi->clipboard.srv_clip_data_wait = SCDW_ABORTING; + usleep(100000); + } + + if (rfi->is_reconnecting) { + /* Special case: window closed when attempting to reconnect */ + rfi->stop_reconnecting_requested = TRUE; + return FALSE; + } + + rdp_event.type = REMMINA_RDP_EVENT_DISCONNECT; + remmina_rdp_event_event_push(gp, &rdp_event); + + return FALSE; +} + +static gboolean remmina_rdp_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature) +{ + TRACE_CALL(__func__); + return TRUE; +} + +static void remmina_rdp_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature) +{ + TRACE_CALL(__func__); + rfContext *rfi = GET_PLUGIN_DATA(gp); + + switch (feature->id) { + case REMMINA_RDP_FEATURE_UNFOCUS: + remmina_rdp_event_unfocus(gp); + break; + + case REMMINA_RDP_FEATURE_SCALE: + if (rfi) { + rfi->scale = remmina_plugin_service->remmina_protocol_widget_get_current_scale_mode(gp); + remmina_rdp_event_update_scale(gp); + } else { + REMMINA_PLUGIN_DEBUG("Remmina RDP plugin warning: Null value for rfi by REMMINA_RDP_FEATURE_SCALE"); + } + break; + + case REMMINA_RDP_FEATURE_MULTIMON: + if (rfi) { + RemminaFile *remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + if (remmina_plugin_service->file_get_int(remminafile, "multimon", FALSE)) { + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_UseMultimon, TRUE); + /* TODO Add an option for this */ + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_ForceMultimon, TRUE); + freerdp_settings_set_bool(rfi->clientContext.context.settings, FreeRDP_Fullscreen, TRUE); + remmina_rdp_event_send_delayed_monitor_layout(gp); + } + } else { + REMMINA_PLUGIN_DEBUG("Remmina RDP plugin warning: Null value for rfi by REMMINA_RDP_FEATURE_MULTIMON"); + } + break; + + case REMMINA_RDP_FEATURE_DYNRESUPDATE: + break; + + case REMMINA_RDP_FEATURE_TOOL_REFRESH: + if (rfi) + gtk_widget_queue_draw_area(rfi->drawing_area, 0, 0, + remmina_plugin_service->protocol_plugin_get_width(gp), + remmina_plugin_service->protocol_plugin_get_height(gp)); + else + REMMINA_PLUGIN_DEBUG("Remmina RDP plugin warning: Null value for rfi by REMMINA_RDP_FEATURE_TOOL_REFRESH"); + break; + + case REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL: + remmina_rdp_send_ctrlaltdel(gp); + break; + + default: + break; + } +} + +/* Send a keystroke to the plugin window */ +static void remmina_rdp_keystroke(RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen) +{ + TRACE_CALL(__func__); + rfContext *rfi = GET_PLUGIN_DATA(gp); + + remmina_plugin_service->protocol_plugin_send_keys_signals(rfi->drawing_area, + keystrokes, keylen, GDK_KEY_PRESS | GDK_KEY_RELEASE); + return; +} + +static gboolean remmina_rdp_get_screenshot(RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd) +{ + rfContext *rfi = GET_PLUGIN_DATA(gp); + rdpGdi *gdi; + size_t szmem; + + UINT32 bytesPerPixel; + UINT32 bitsPerPixel; + + if (!rfi) + return FALSE; + + gdi = ((rdpContext *)rfi)->gdi; + +#if FREERDP_VERSION_MAJOR >= 3 + bytesPerPixel = FreeRDPGetBytesPerPixel(gdi->hdc->format); + bitsPerPixel = FreeRDPGetBitsPerPixel(gdi->hdc->format); +#else + bytesPerPixel = GetBytesPerPixel(gdi->hdc->format); + bitsPerPixel = GetBitsPerPixel(gdi->hdc->format); +#endif + + /** @todo we should lock FreeRDP subthread to update rfi->primary_buffer, rfi->gdi and w/h, + * from here to memcpy, but… how ? */ + + szmem = gdi->width * gdi->height * bytesPerPixel; + + REMMINA_PLUGIN_DEBUG("allocating %zu bytes for a full screenshot", szmem); + rpsd->buffer = malloc(szmem); + if (!rpsd->buffer) { + REMMINA_PLUGIN_DEBUG("could not set aside %zu bytes for a full screenshot", szmem); + return FALSE; + } + rpsd->width = gdi->width; + rpsd->height = gdi->height; + rpsd->bitsPerPixel = bitsPerPixel; + rpsd->bytesPerPixel = bytesPerPixel; + + memcpy(rpsd->buffer, gdi->primary_buffer, szmem); + + /* Returning TRUE instruct also the caller to deallocate rpsd->buffer */ + return TRUE; +} + +/* Array of key/value pairs for colour depths */ +static gpointer colordepth_list[] = +{ + /* 1st one is the default in a new install */ + "99", N_("Automatic (32 bpp) (Server chooses its best format)"), + "66", N_("GFX AVC444 (32 bpp)"), + "65", N_("GFX AVC420 (32 bpp)"), + "64", N_("GFX RFX (32 bpp)"), + "63", N_("GFX RFX Progressive (32 bpp)"), + "0", N_("RemoteFX (32 bpp)"), + "32", N_("True colour (32 bpp)"), + "24", N_("True colour (24 bpp)"), + "16", N_("High colour (16 bpp)"), + "15", N_("High colour (15 bpp)"), + "8", N_("256 colours (8 bpp)"), + NULL +}; + +/* Array of key/value pairs for the FreeRDP logging level */ +static gpointer log_level[] = +{ + "INFO", "INFO", + "FATAL", "FATAL", + "ERROR", "ERROR", + "WARN", "WARN", + "DEBUG", "DEBUG", + "TRACE", "TRACE", + "OFF", "OFF", + NULL +}; + + +/* Array of key/value pairs for quality selection */ +static gpointer quality_list[] = +{ + "0", N_("Poor (fastest)"), + "1", N_("Medium"), + "2", N_("Good"), + "9", N_("Best (slowest)"), + NULL +}; + +/* Array of key/value pairs for quality selection */ +static gpointer network_list[] = +{ + "none", N_("None"), + "autodetect", N_("Auto-detect"), + "modem", N_("Modem"), + "broadband-low", N_("Low performance broadband"), + "satellite", N_("Satellite"), + "broadband-high", N_("High performance broadband"), + "wan", N_("WAN"), + "lan", N_("LAN"), + NULL +}; + +/* Array of key/value pairs for sound options */ +static gpointer sound_list[] = +{ + "off", N_("Off"), + "local", N_("Local"), + "remote", N_("Remote"), + NULL +}; + +/* Array of key/value pairs for security */ +static gpointer security_list[] = +{ + "", N_("Automatic negotiation"), + "nla", N_("NLA protocol security"), + "tls", N_("TLS protocol security"), + "rdp", N_("RDP protocol security"), + "ext", N_("NLA extended protocol security"), + NULL +}; + +/* Array of key/value pairs for mouse movement */ +static gpointer mouse_jitter_list[] = +{ + "No", N_("No"), + "60", N_("Every 1 min"), + "180", N_("Every 3 min"), + "300", N_("Every 5 min"), + "600", N_("Every 10 min"), + NULL +}; + +static gpointer gwtransp_list[] = +{ + "http", "HTTP", + "rpc", "RPC", + "auto", "Auto", + NULL +}; + +static gpointer tls_seclevel[] = +{ + "", N_("Default"), + "0", N_("0 — Windows 7 compatible"), + "1", N_("1"), + "2", N_("2"), + "3", N_("3"), + "4", N_("4"), + "5", N_("5"), + NULL +}; + +static gchar clientbuild_list[] = + N_("2600 (Windows XP), 7601 (Windows Vista/7), 9600 (Windows 8 and newer)"); + +static gchar clientbuild_tooltip[] = + N_("Used i.a. by terminal services in a smart card channel to distinguish client capabilities:\n" + " • < 4034: Windows XP base smart card functions\n" + " • 4034-7064: Windows Vista/7: SCardReadCache(),\n" + " SCardWriteCache(), SCardGetTransmitCount()\n" + " • >= 7065: Windows 8 and newer: SCardGetReaderIcon(),\n" + " SCardGetDeviceTypeId()"); + +static gchar microphone_tooltip[] = + N_("Options for redirection of audio input:\n" + " • [sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,]\n" + " [channel:<channel>] Audio input (microphone)\n" + " • sys:pulse\n" + " • format:1\n" + " • sys:oss,dev:1,format:1\n" + " • sys:alsa"); + +static gchar audio_tooltip[] = + N_("Options for redirection of audio output:\n" + " • [sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,]\n" + " [channel:<channel>] Audio output\n" + " • sys:pulse\n" + " • format:1\n" + " • sys:oss,dev:1,format:1\n" + " • sys:alsa"); + + +static gchar usb_tooltip[] = + N_("Options for redirection of USB device:\n" + " • [dbg,][id:<vid>:<pid>#…,][addr:<bus>:<addr>#…,][auto]\n" + " • auto\n" + " • id:054c:0268#4669:6e6b,addr:04:0c"); + +static gchar timeout_tooltip[] = + N_("Advanced setting for high latency links:\n" + "Adjusts the connection timeout. Use if your connection times out.\n" + "The highest possible value is 600000 ms (10 minutes).\n"); + +static gchar network_tooltip[] = + N_("Performance optimisations based on the network connection type:\n" + "Using auto-detection is advised.\n" + "If “Auto-detect” fails, choose the most appropriate option in the list.\n"); + +static gchar monitorids_tooltip[] = + N_("Comma-separated list of monitor IDs and desktop orientations:\n" + " • [<id>:<orientation-in-degrees>,]\n" + " • 0,1,2,3\n" + " • 0:270,1:90\n" + "Orientations are specified in degrees, valid values are:\n" + " • 0 (landscape)\n" + " • 90 (portrait)\n" + " • 180 (landscape flipped)\n" + " • 270 (portrait flipped)\n" + "\n"); + +static gchar drive_tooltip[] = + N_("Redirect directory <path> as named share <name>.\n" + " • <name>,<fullpath>[;<name>,<fullpath>[;…]]\n" + " • MyHome,/home/remminer\n" + " • /home/remminer\n" + " • MyHome,/home/remminer;SomePath,/path/to/somepath\n" + "Hotplug support is enabled with:\n" + " • hotplug,*\n" + "\n"); + +/* Array of RemminaProtocolSetting for basic settings. + * Each item is composed by: + * a) RemminaProtocolSettingType for setting type + * b) Setting name + * c) Setting description + * d) Compact disposition + * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO + * f) Setting tooltip + * g) Validation data pointer, will be passed to the validation callback method. + * h) Validation callback method (Can be NULL. Every entry will be valid then.) + * use following prototype: + * gboolean mysetting_validator_method(gpointer key, gpointer value, + * gpointer validator_data); + * gpointer key is a gchar* containing the setting's name, + * gpointer value contains the value which should be validated, + * gpointer validator_data contains your passed data. + */ +static const RemminaProtocolSetting remmina_rdp_basic_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("Username"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("Password"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "domain", N_("Domain"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "drive", N_("Share folder"), FALSE, NULL, drive_tooltip, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "restricted-admin", N_("Restricted admin mode"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "pth", N_("Password hash"), FALSE, NULL, N_("Restricted admin mode password hash"), NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "left-handed", N_("Left-handed mouse support"), TRUE, NULL, N_("Swap left and right mouse buttons for left-handed mouse support"), NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disable-smooth-scrolling", N_("Disable smooth scrolling"), TRUE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "multimon", N_("Enable multi monitor"), TRUE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "span", N_("Span screen over multiple monitors"), TRUE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "monitorids", N_("List monitor IDs"), FALSE, NULL, monitorids_tooltip, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, "resolution", NULL, FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "colordepth", N_("Colour depth"), FALSE, colordepth_list, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "network", N_("Network connection type"), FALSE, network_list, network_tooltip, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP, "keymap", NULL, FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL } +}; + +/* Array of RemminaProtocolSetting for advanced settings. + * Each item is composed by: + * a) RemminaProtocolSettingType for setting type + * b) Setting name + * c) Setting description + * d) Compact disposition + * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO + * f) Setting Tooltip + */ +static const RemminaProtocolSetting remmina_rdp_advanced_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "security", N_("Security protocol negotiation"), FALSE, security_list, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "gwtransp", N_("Gateway transport type"), FALSE, gwtransp_list, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "tls-seclevel", N_("TLS Security Level"), FALSE, tls_seclevel, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "freerdp_log_level", N_("FreeRDP log level"), FALSE, log_level, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "freerdp_log_filters", N_("FreeRDP log filters"), FALSE, NULL, N_("tag:level[,tag:level[,…]]") }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "sound", N_("Audio output mode"), FALSE, sound_list, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "audio-output", N_("Redirect local audio output"), TRUE, NULL, audio_tooltip }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "microphone", N_("Redirect local microphone"), TRUE, NULL, microphone_tooltip }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "timeout", N_("Connection timeout in ms"), TRUE, NULL, timeout_tooltip }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_server", N_("Remote Desktop Gateway server"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_username", N_("Remote Desktop Gateway username"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "gateway_password", N_("Remote Desktop Gateway password"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "gateway_domain", N_("Remote Desktop Gateway domain"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "clientname", N_("Client name"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_COMBO, "clientbuild", N_("Client build"), FALSE, clientbuild_list, clientbuild_tooltip }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "exec", N_("Start-up program"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execpath", N_("Start-up path"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "loadbalanceinfo", N_("Load balance info"), FALSE, NULL, NULL }, + // TRANSLATORS: Do not use typographic quotation marks, these must stay as "double quote", also know as “Typewriter ("programmer's") quote, ambidextrous.” + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "printer_overrides", N_("Override printer drivers"), FALSE, NULL, N_("\"Samsung_CLX-3300_Series\":\"Samsung CLX-3300 Series PS\";\"Canon MF410\":\"Canon MF410 Series UFR II\"") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "usb", N_("USB device redirection"), TRUE, NULL, usb_tooltip }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "serialname", N_("Local serial name"), FALSE, NULL, N_("COM1, COM2, etc.") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "serialdriver", N_("Local serial driver"), FALSE, NULL, N_("Serial") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "serialpath", N_("Local serial path"), FALSE, NULL, N_("/dev/ttyS0, /dev/ttyS1, etc.") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "parallelname", N_("Local parallel name"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "parallelpath", N_("Local parallel device"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "smartcardname", N_("Name of smart card"), FALSE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "dvc", N_("Dynamic virtual channel"), FALSE, NULL, N_("<channel>[,<options>]") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "vc", N_("Static virtual channel"), FALSE, NULL, N_("<channel>[,<options>]") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "rdp2tcp", N_("TCP redirection"), FALSE, NULL, N_("/PATH/TO/rdp2tcp") }, + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "rdp_reconnect_attempts", N_("Reconnect attempts number"), FALSE, NULL, N_("The maximum number of reconnect attempts upon an RDP disconnect (default: 20)") }, + { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "rdp_mouse_jitter", N_("Move mouse when connection is idle"), FALSE, mouse_jitter_list, NULL }, + + { REMMINA_PROTOCOL_SETTING_TYPE_ASSISTANCE, "assistance_mode", N_("Attempt to connect in assistance mode"), TRUE, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "preferipv6", N_("Prefer IPv6 AAAA record over IPv4 A record"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareprinter", N_("Share printers"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareserial", N_("Share serial ports"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "serialpermissive", N_("(SELinux) permissive mode for serial ports"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "shareparallel", N_("Share parallel ports"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sharesmartcard", N_("Share a smart card"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Turn off clipboard sync"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "cert_ignore", N_("Ignore certificate"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "old-license", N_("Use the old license workflow"), TRUE, NULL, N_("It disables CAL and hwId is set to 0") }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "console", N_("Attach to console (2003/2003 R2)"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disable_fastpath", N_("Turn off fast-path"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "gateway_usage", N_("Server detection using Remote Desktop Gateway"), TRUE, NULL, NULL }, +#if defined(PROXY_TYPE_IGNORE) + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "useproxyenv", N_("Use system proxy settings"), TRUE, NULL, NULL }, +#endif + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableautoreconnect", N_("Turn off automatic reconnection"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "relax-order-checks", N_("Relax order checks"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "glyph-cache", N_("Glyph cache"), TRUE, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "multitransport", N_("Enable multitransport protocol (UDP)"), TRUE, NULL, N_("Using the UDP protocol may improve performance") }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "base-cred-for-gw", N_("Use base credentials for gateway too"), TRUE, NULL, NULL }, +#if FREERDP_CHECK_VERSION(2, 3, 1) + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "websockets", N_("Enable Gateway websockets support"), TRUE, NULL, NULL }, +#endif + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "no-suppress", N_("Update framebuffer even when not visible"), TRUE, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL } +}; + +/* Array for available features. + * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */ +static const RemminaProtocolFeature remmina_rdp_features[] = +{ + { REMMINA_PROTOCOL_FEATURE_TYPE_PREF, REMMINA_RDP_FEATURE_VIEWONLY, GINT_TO_POINTER(REMMINA_PROTOCOL_FEATURE_PREF_CHECK), "viewonly", + N_("View only") }, + { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_RDP_FEATURE_TOOL_REFRESH, N_("Refresh"), NULL, NULL }, + { REMMINA_PROTOCOL_FEATURE_TYPE_SCALE, REMMINA_RDP_FEATURE_SCALE, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE, REMMINA_RDP_FEATURE_DYNRESUPDATE, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON, REMMINA_RDP_FEATURE_MULTIMON, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_RDP_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Delete"), NULL, NULL }, + { REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS, REMMINA_RDP_FEATURE_UNFOCUS, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL } +}; + +/* This will be filled with version info string */ +static char remmina_plugin_rdp_version[256]; + +/* Protocol plugin definition and features */ +static RemminaProtocolPlugin remmina_rdp = +{ + REMMINA_PLUGIN_TYPE_PROTOCOL, // Type + "RDP", // Name + N_("RDP - Remote Desktop Protocol"), // Description + GETTEXT_PACKAGE, // Translation domain + remmina_plugin_rdp_version, // Version number + "org.remmina.Remmina-rdp-symbolic", // Icon for normal connection + "org.remmina.Remmina-rdp-ssh-symbolic", // Icon for SSH connection + remmina_rdp_basic_settings, // Array for basic settings + remmina_rdp_advanced_settings, // Array for advanced settings + REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type + remmina_rdp_features, // Array for available features + remmina_rdp_init, // Plugin initialization + remmina_rdp_open_connection, // Plugin open connection + remmina_rdp_close_connection, // Plugin close connection + remmina_rdp_query_feature, // Query for available features + remmina_rdp_call_feature, // Call a feature + remmina_rdp_keystroke, // Send a keystroke + remmina_rdp_get_screenshot, // Screenshot + remmina_rdp_event_on_map, // RCW map event + remmina_rdp_event_on_unmap // RCW unmap event +}; + +/* File plugin definition and features */ +static RemminaFilePlugin remmina_rdpf = +{ + REMMINA_PLUGIN_TYPE_FILE, // Type + "RDPF", // Name + N_("RDP - RDP File Handler"), // Description + GETTEXT_PACKAGE, // Translation domain + remmina_plugin_rdp_version, // Version number + remmina_rdp_file_import_test, // Test import function + remmina_rdp_file_import, // Import function + remmina_rdp_file_export_test, // Test export function + remmina_rdp_file_export, // Export function + NULL +}; + +/* Preferences plugin definition and features */ +static RemminaPrefPlugin remmina_rdps = +{ + REMMINA_PLUGIN_TYPE_PREF, // Type + "RDPS", // Name + N_("RDP - Preferences"), // Description + GETTEXT_PACKAGE, // Translation domain + remmina_plugin_rdp_version, // Version number + "RDP", // Label + remmina_rdp_settings_new // Preferences body function +}; + +static char *buildconfig_strstr(const char *bc, const char *option) +{ + TRACE_CALL(__func__); + + char *p, *n; + + p = strcasestr(bc, option); + if (p == NULL) + return NULL; + + if (p > bc && *(p - 1) > ' ') + return NULL; + + n = p + strlen(option); + if (*n > ' ') + return NULL; + + return p; +} + +G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service) +{ + int vermaj, vermin, verrev; + + TRACE_CALL(__func__); + remmina_plugin_service = service; + + /* Check that we are linked to the correct version of libfreerdp */ + + freerdp_get_version(&vermaj, &vermin, &verrev); + if ((vermaj < FREERDP_REQUIRED_MAJOR) || + ((vermaj == FREERDP_REQUIRED_MAJOR) && ((vermin < FREERDP_REQUIRED_MINOR) || + ((vermin == FREERDP_REQUIRED_MINOR) && (verrev < FREERDP_REQUIRED_REVISION))))) { + g_printf("Upgrade your FreeRDP library version from %d.%d.%d to at least libfreerdp %d.%d.%d " + "to run the RDP plugin.\n", + vermaj, vermin, verrev, + FREERDP_REQUIRED_MAJOR, FREERDP_REQUIRED_MINOR, FREERDP_REQUIRED_REVISION); + return FALSE; + } + + bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + if (!service->register_plugin((RemminaPlugin *)&remmina_rdp)) + return FALSE; + + remmina_rdpf.export_hints = _("Export connection in Windows .rdp file format"); + + if (!service->register_plugin((RemminaPlugin *)&remmina_rdpf)) + return FALSE; + + if (!service->register_plugin((RemminaPlugin *)&remmina_rdps)) + return FALSE; + + if (buildconfig_strstr(freerdp_get_build_config(), "WITH_GFX_H264=ON")) { + gfx_h264_available = TRUE; + REMMINA_PLUGIN_DEBUG("gfx_h264_available: %d", gfx_h264_available); + } else { + gfx_h264_available = FALSE; + REMMINA_PLUGIN_DEBUG("gfx_h264_available: %d", gfx_h264_available); + /* Remove values 65 and 66 from colordepth_list array by shifting it */ + gpointer *src, *dst; + dst = src = colordepth_list; + while (*src) { + if (strcmp(*src, "66") != 0 && strcmp(*src, "65") != 0) { + if (dst != src) { + *dst = *src; + *(dst + 1) = *(src + 1); + } + dst += 2; + } + src += 2; + } + *dst = NULL; + } + + snprintf(remmina_plugin_rdp_version, sizeof(remmina_plugin_rdp_version), + "RDP plugin: %s (Git %s), Compiled with libfreerdp %s (%s), Running with libfreerdp %s (rev %s), H.264 %s", + VERSION, REMMINA_GIT_REVISION, +#if FREERDP_VERSION_MAJOR >= 3 + FREERDP_VERSION_FULL, FREERDP_GIT_REVISION, +#else + FREERDP_VERSION_FULL, GIT_REVISION, +#endif + freerdp_get_version_string(), + freerdp_get_build_revision(), + gfx_h264_available ? "Yes" : "No" + ); + + remmina_rdp_settings_init(); + + return TRUE; +} |