Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/Remmina/Remmina.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarco Fortina <marco.fortina@atlantica.it>2022-04-08 01:52:58 +0300
committerMarco Fortina <marco.fortina@atlantica.it>2022-04-08 01:52:58 +0300
commitdd2e0fbe6f57acf76ef10344d783cb0472625ed2 (patch)
treec4edb9b8036eca8c7fb0870645d25878e8f54ed5 /src
parent3874589f12713ccf72d783d316846499f416d389 (diff)
Adding SSH X11Forward support
Diffstat (limited to 'src')
-rw-r--r--src/remmina_ssh.c458
-rw-r--r--src/remmina_ssh.h1
-rw-r--r--src/remmina_ssh_plugin.c1
3 files changed, 431 insertions, 29 deletions
diff --git a/src/remmina_ssh.c b/src/remmina_ssh.c
index 57db40a10..2e214650d 100644
--- a/src/remmina_ssh.c
+++ b/src/remmina_ssh.c
@@ -38,6 +38,9 @@
#ifdef HAVE_LIBSSH
+/* To get definitions of NI_MAXHOST and NI_MAXSERV from <netdb.h> */
+#define _DEFAULT_SOURCE
+
/* Define this before stdlib.h to have posix_openpt */
#define _XOPEN_SOURCE 600
@@ -79,6 +82,9 @@
#ifdef HAVE_PTY_H
#include <pty.h>
#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
#include "remmina_public.h"
#include "remmina/types.h"
#include "remmina_file.h"
@@ -117,6 +123,38 @@ static const gchar *common_identities[] =
NULL
};
+/*-----------------------------------------------------------------------------*
+* X11 Channels *
+*-----------------------------------------------------------------------------*/
+#define _PATH_UNIX_X "/tmp/.X11-unix/X%d"
+#define _XAUTH_CMD "/usr/bin/xauth list %s 2>/dev/null"
+
+typedef struct item {
+ ssh_channel channel;
+ gint fd_in;
+ gint fd_out;
+ gboolean protected;
+ struct item *next;
+} node_t;
+
+node_t *node = NULL;
+
+// Mutex
+pthread_mutex_t mutex;
+
+// Linked nodes to manage channel/fd tuples
+static void remmina_ssh_insert_item(ssh_channel channel, gint fd_in, gint fd_out, gboolean protected);
+static void remmina_ssh_delete_item(ssh_channel channel);
+static node_t * remmina_ssh_search_item(ssh_channel channel);
+
+// X11 Display
+const char * remmina_ssh_ssh_gai_strerror(int gaierr);
+static int remmina_ssh_x11_get_proto(const char *display, char **_proto, char **_data);
+static void remmina_ssh_set_nodelay(int fd);
+static int remmina_ssh_connect_local_xsocket_path(const char *pathname);
+static int remmina_ssh_connect_local_xsocket(int display_number);
+static int remmina_ssh_x11_connect_display();
+
// Send data to channel
static int remimna_ssh_cp_to_ch_cb(int fd, int revents, void *userdata);
@@ -126,15 +164,309 @@ static int remmina_ssh_cp_to_fd_cb(ssh_session session, ssh_channel channel, voi
// EOF&Close channel
static void remmina_ssh_ch_close_cb(ssh_session session, ssh_channel channel, void *userdata);
-/*
- * SSH Event Context
-*/
+// X11 Request
+static ssh_channel remmina_ssh_x11_open_request_cb(ssh_session session, const char *shost, int sport, void *userdata);
+// SSH Channel Callbacks
+struct ssh_channel_callbacks_struct channel_cb =
+{
+ .channel_data_function = remmina_ssh_cp_to_fd_cb,
+ .channel_eof_function = remmina_ssh_ch_close_cb,
+ .channel_close_function = remmina_ssh_ch_close_cb,
+ .userdata = NULL
+};
+
+// SSH Event Context
short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL;
-/*
- * Functions
-*/
+// Functions
+static void
+remmina_ssh_insert_item(ssh_channel channel, gint fd_in, gint fd_out, gboolean protected)
+{
+ TRACE_CALL(__func__);
+
+ pthread_mutex_lock(&mutex);
+
+ REMMINA_DEBUG("insert node - fd_in: %d - fd_out: %d - protected %d", fd_in, fd_out, protected);
+
+ node_t *node_iterator, *new;
+ if (node == NULL) {
+ /* Calloc ensure that node is full of 0 */
+ node = (node_t *) calloc(1, sizeof(node_t));
+ node->channel = channel;
+ node->fd_in = fd_in;
+ node->fd_out = fd_out;
+ node->protected = protected;
+ node->next = NULL;
+ } else {
+ node_iterator = node;
+ while (node_iterator->next != NULL)
+ node_iterator = node_iterator->next;
+ /* Create the new node */
+ new = (node_t *) malloc(sizeof(node_t));
+ new->channel = channel;
+ new->fd_in = fd_in;
+ new->fd_out = fd_out;
+ new->protected = protected;
+ new->next = NULL;
+ node_iterator->next = new;
+ }
+
+ pthread_mutex_unlock(&mutex);
+}
+
+static void
+remmina_ssh_delete_item(ssh_channel channel)
+{
+ TRACE_CALL(__func__);
+
+ REMMINA_DEBUG("delete node");
+
+ pthread_mutex_lock(&mutex);
+
+ node_t *current, *previous = NULL;
+ for (current = node; current; previous = current, current = current->next) {
+ if (current->channel != channel)
+ continue;
+
+ if (previous == NULL)
+ node = current->next;
+ else
+ previous->next = current->next;
+
+ free(current);
+ pthread_mutex_unlock(&mutex);
+ return;
+ }
+
+ pthread_mutex_unlock(&mutex);
+}
+
+static node_t *
+remmina_ssh_search_item(ssh_channel channel)
+{
+ TRACE_CALL(__func__);
+
+ REMMINA_DEBUG("search node");
+
+ pthread_mutex_lock(&mutex);
+
+ node_t *current = node;
+ while (current != NULL) {
+ if (current->channel == channel) {
+ pthread_mutex_unlock(&mutex);
+ REMMINA_DEBUG("found node - fd_in: %d - fd_out: %d - protected: %d", current->fd_in, current->fd_out, current->protected);
+ return current;
+ } else {
+ current = current->next;
+ }
+ }
+
+ pthread_mutex_unlock(&mutex);
+
+ return NULL;
+}
+
+static void
+remmina_ssh_set_nodelay(int fd)
+{
+ TRACE_CALL(__func__);
+ int opt;
+ socklen_t optlen;
+
+ optlen = sizeof(opt);
+ if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) {
+ REMMINA_ERROR("getsockopt TCP_NODELAY: %.100s", strerror(errno));
+ return;
+ }
+ if (opt == 1) {
+ REMMINA_DEBUG("fd %d is TCP_NODELAY", fd);
+ return;
+ }
+ opt = 1;
+ REMMINA_DEBUG("fd %d setting TCP_NODELAY", fd);
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) == -1)
+ REMMINA_ERROR("setsockopt TCP_NODELAY: %.100s", strerror(errno));
+}
+
+const char *
+remmina_ssh_ssh_gai_strerror(int gaierr)
+{
+ TRACE_CALL(__func__);
+
+ if (gaierr == EAI_SYSTEM && errno != 0)
+ return strerror(errno);
+ return gai_strerror(gaierr);
+}
+
+static int
+remmina_ssh_x11_get_proto(const char *display, char **_proto, char **_cookie)
+{
+ TRACE_CALL(__func__);
+
+ char cmd[1024], line[512], xdisplay[512];
+ static char proto[512], cookie[512];
+ FILE *f;
+ int ret = 0, r;
+
+ *_proto = proto;
+ *_cookie = cookie;
+
+ proto[0] = cookie[0] = '\0';
+
+ if (strncmp(display, "localhost:", 10) == 0) {
+ if ((r = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10)) < 0 || (size_t)r >= sizeof(xdisplay)) {
+ REMMINA_ERROR("display name too long. display: %s", display);
+ return -1;
+ }
+ display = xdisplay;
+ }
+
+ snprintf(cmd, sizeof(cmd), _XAUTH_CMD, display);
+ REMMINA_DEBUG("xauth cmd: %s", cmd);
+
+ f = popen(cmd, "r");
+ if (f && fgets(line, sizeof(line), f) && sscanf(line, "%*s %511s %511s", proto, cookie) == 2) {
+ ret = 0;
+ } else {
+ ret = 1;
+ }
+
+ if (f) pclose(f);
+
+ REMMINA_DEBUG("proto: %s - cookie: %s - ret: %d", proto, cookie, ret);
+
+ return ret;
+}
+
+static int
+remmina_ssh_connect_local_xsocket_path(const char *pathname)
+{
+ TRACE_CALL(__func__);
+
+ int sock;
+ struct sockaddr_un addr;
+
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock == -1)
+ REMMINA_ERROR("socket: %.100s", strerror(errno));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ addr.sun_path[0] = '\0';
+ memcpy(addr.sun_path + 1, pathname, strlen(pathname));
+ if (connect(sock, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(pathname)) == 0) {
+ REMMINA_DEBUG("sock: %d", sock);
+ return sock;
+ }
+
+ REMMINA_ERROR("connect %.100s: %.100s", addr.sun_path, strerror(errno));
+ close(sock);
+
+ return -1;
+}
+
+static int
+remmina_ssh_connect_local_xsocket(int display_number)
+{
+ TRACE_CALL(__func__);
+
+ char buf[1024];
+ snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number);
+ return remmina_ssh_connect_local_xsocket_path(buf);
+}
+
+static int
+remmina_ssh_x11_connect_display()
+{
+ TRACE_CALL(__func__);
+
+ unsigned int display_number;
+ const char *display;
+ char buf[1024], *cp;
+ struct addrinfo hints, *ai, *aitop;
+ char strport[NI_MAXSERV];
+ int gaierr, sock = 0;
+
+ /* Try to open a socket for the local X server. */
+ display = getenv("DISPLAY");
+ if (!display) {
+ return -1;
+ }
+
+ REMMINA_DEBUG("display: %s", display);
+
+ /* Check if it is a unix domain socket. */
+ if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') {
+ /* Connect to the unix domain socket. */
+ if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) {
+ REMMINA_ERROR("Could not parse display number from DISPLAY: %.100s", display);
+ return -1;
+ }
+
+ REMMINA_DEBUG("display_number: %d", display_number);
+
+ /* Create a socket. */
+ sock = remmina_ssh_connect_local_xsocket(display_number);
+
+ REMMINA_DEBUG("socket: %d", sock);
+
+ if (sock < 0)
+ return -1;
+
+ /* OK, we now have a connection to the display. */
+ return sock;
+ }
+
+ /* Connect to an inet socket. */
+ strncpy(buf, display, sizeof(buf) - 1);
+ cp = strchr(buf, ':');
+ if (!cp) {
+ REMMINA_ERROR("Could not find ':' in DISPLAY: %.100s", display);
+ return -1;
+ }
+ *cp = 0;
+ if (sscanf(cp + 1, "%u", &display_number) != 1) {
+ REMMINA_ERROR("Could not parse display number from DISPLAY: %.100s", display);
+ return -1;
+ }
+
+ /* Look up the host address */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ snprintf(strport, sizeof(strport), "%u", 6000 + display_number);
+ if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) {
+ REMMINA_ERROR("%.100s: unknown host. (%s)", buf, remmina_ssh_ssh_gai_strerror(gaierr));
+ return -1;
+ }
+ for (ai = aitop; ai; ai = ai->ai_next) {
+ /* Create a socket. */
+ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sock == -1) {
+ REMMINA_ERROR("socket: %.100s", strerror(errno));
+ continue;
+ }
+ /* Connect it to the display. */
+ if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
+ REMMINA_ERROR("connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno));
+ close(sock);
+ continue;
+ }
+ /* Success */
+ break;
+ }
+ freeaddrinfo(aitop);
+ if (!ai) {
+ REMMINA_ERROR("connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno));
+ return -1;
+ }
+ remmina_ssh_set_nodelay(sock);
+
+ REMMINA_DEBUG("sock: %d", sock);
+
+ return sock;
+}
static int
remimna_ssh_cp_to_ch_cb(int fd, int revents, void *userdata)
@@ -144,7 +476,13 @@ remimna_ssh_cp_to_ch_cb(int fd, int revents, void *userdata)
gchar buf[2097152];
gint sz = 0, ret = 0;
+ node_t *temp_node = remmina_ssh_search_item(channel);
+
if (!channel) {
+ if (!temp_node->protected) {
+ close(fd);
+ REMMINA_DEBUG("fd %d closed.", fd);
+ }
REMMINA_ERROR("channel does not exist.");
return -1;
}
@@ -156,8 +494,14 @@ remimna_ssh_cp_to_ch_cb(int fd, int revents, void *userdata)
REMMINA_DEBUG("ssh_channel_write ret: %d sz: %d", ret, sz);
} else if (sz < 0) {
REMMINA_ERROR("fd bytes read: %d", sz);
+ return -1;
} else {
REMMINA_CRITICAL("Why the hell am I here?");
+ if (!temp_node->protected) {
+ close(fd);
+ REMMINA_DEBUG("fd %d closed.", fd);
+ }
+ return -1;
}
}
@@ -174,11 +518,15 @@ static int
remmina_ssh_cp_to_fd_cb(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *userdata)
{
TRACE_CALL(__func__);
- gint fd = *(int*)userdata;
- gint sz = 0;
(void)session;
- (void)channel;
(void)is_stderr;
+ (void)userdata;
+
+// RemminaSSHShell *shell = (RemminaSSHShell *)userdata;
+
+ node_t *temp_node = remmina_ssh_search_item(channel);
+ gint fd = temp_node->fd_out;
+ gint sz = 0;
sz = write(fd, data, len);
REMMINA_DEBUG("fd bytes written: %d", sz);
@@ -190,13 +538,49 @@ static void
remmina_ssh_ch_close_cb(ssh_session session, ssh_channel channel, void *userdata)
{
TRACE_CALL(__func__);
-// gint fd = *(int*)userdata;
(void)session;
- (void)userdata;
+ RemminaSSHShell *shell = (RemminaSSHShell *)userdata;
+
+ node_t *temp_node = remmina_ssh_search_item(channel);
+
+ if (temp_node != NULL) {
+ int fd = temp_node->fd_in;
+
+ if (!temp_node->protected) {
+ remmina_ssh_delete_item(channel);
+ ssh_event_remove_fd(shell->event, fd);
+ close(fd);
+ REMMINA_DEBUG("fd %d closed.", fd);
+ }
+ }
REMMINA_DEBUG("Channel closed.");
}
+static ssh_channel
+remmina_ssh_x11_open_request_cb(ssh_session session, const char *shost, int sport, void *userdata)
+{
+ TRACE_CALL(__func__);
+
+ (void)shost;
+ (void)sport;
+
+ RemminaSSHShell *shell = (RemminaSSHShell *)userdata;
+
+ ssh_channel channel = ssh_channel_new(session);
+
+ int sock = remmina_ssh_x11_connect_display();
+
+ remmina_ssh_insert_item(channel, sock, sock, FALSE);
+
+ ssh_event_add_fd(shell->event, sock, events, remimna_ssh_cp_to_ch_cb, channel);
+ ssh_event_add_session(shell->event, session);
+
+ ssh_add_channel_callbacks(channel, &channel_cb);
+
+ return channel;
+}
+
gchar *
remmina_ssh_identity_path(const gchar *id)
{
@@ -2463,6 +2847,30 @@ remmina_ssh_shell_thread(gpointer data)
ssh_channel_request_pty(channel);
+ // SSH Callbacks
+ struct ssh_callbacks_struct cb =
+ {
+ .channel_open_request_x11_function = remmina_ssh_x11_open_request_cb,
+ .userdata = shell,
+ };
+
+ if (remmina_file_get_int(remminafile, "ssh_forward_x11", FALSE)) {
+ ssh_callbacks_init(&cb);
+ ssh_set_callbacks(REMMINA_SSH(shell)->session, &cb);
+
+ const char *display = getenv("DISPLAY");
+ char *proto = NULL, *cookie = NULL;
+ if (remmina_ssh_x11_get_proto(display, &proto, &cookie) != 0) {
+ REMMINA_DEBUG("Using fake authentication data for X11 forwarding");
+ proto = NULL;
+ cookie = NULL;
+ }
+
+ REMMINA_DEBUG("proto: %s - cookie: %s", proto, cookie);
+ ret = ssh_channel_request_x11(channel, 0, proto, cookie, 0);
+ if (ret != SSH_OK) return NULL;
+ }
+
if (shell->exec && shell->exec[0]) {
REMMINA_DEBUG ("Requesting an SSH exec channel");
ret = ssh_channel_request_exec(channel, shell->exec);
@@ -2520,8 +2928,8 @@ remmina_ssh_shell_thread(gpointer data)
LOCK_SSH(shell)
// Create new event context.
- ssh_event event = ssh_event_new();
- if (event == NULL) {
+ shell->event = ssh_event_new();
+ if (shell->event == NULL) {
REMMINA_CRITICAL("Internal error in %s: Couldn't get a event.", __func__);
return NULL;
}
@@ -2529,36 +2937,28 @@ remmina_ssh_shell_thread(gpointer data)
REMMINA_DEBUG("shell->slave: %d", shell->slave);
// Add the fd to the event and assign it the callback.
- if (ssh_event_add_fd(event, shell->slave, events, remimna_ssh_cp_to_ch_cb, channel) != SSH_OK) {
+ if (ssh_event_add_fd(shell->event, shell->slave, events, remimna_ssh_cp_to_ch_cb, channel) != SSH_OK) {
REMMINA_CRITICAL("Internal error in %s: Couldn't add an fd to the event.", __func__);
return NULL;
}
// Remove the poll handle from session and assign them to the event.
- if (ssh_event_add_session(event, REMMINA_SSH(shell)->session) != SSH_OK) {
+ if (ssh_event_add_session(shell->event, REMMINA_SSH(shell)->session) != SSH_OK) {
REMMINA_CRITICAL("Internal error in %s: Couldn't add the session to the event.", __func__);
return NULL;
}
- // SSH Channel Callbacks
- struct ssh_channel_callbacks_struct channel_cb =
- {
- .channel_data_function = remmina_ssh_cp_to_fd_cb,
- .channel_eof_function = remmina_ssh_ch_close_cb,
- .channel_close_function = remmina_ssh_ch_close_cb,
- .userdata = NULL
- };
+ remmina_ssh_insert_item(shell->channel, shell->slave, shell->slave, TRUE);
- // Set the callback userdata.
- channel_cb.userdata = &shell->slave;
// Initializes the ssh_callbacks_struct.
+ channel_cb.userdata = &shell;
ssh_callbacks_init(&channel_cb);
// Set the channel callback functions.
ssh_set_channel_callbacks(shell->channel, &channel_cb);
UNLOCK_SSH(shell)
do {
- ssh_event_dopoll(event, 1000);
+ ssh_event_dopoll(shell->event, 1000);
} while(!ssh_channel_is_closed(shell->channel));
shell->closed = TRUE;
@@ -2566,15 +2966,15 @@ remmina_ssh_shell_thread(gpointer data)
LOCK_SSH(shell)
// Remove socket fd from event context.
- ret = ssh_event_remove_fd(event, shell->slave);
+ ret = ssh_event_remove_fd(shell->event, shell->slave);
REMMINA_DEBUG("Remove socket fd from event context: %d", ret);
// Remove session object from event context.
- ret = ssh_event_remove_session(event, REMMINA_SSH(shell)->session);
+ ret = ssh_event_remove_session(shell->event, REMMINA_SSH(shell)->session);
REMMINA_DEBUG("Remove session object from event context: %d", ret);
// Free event context.
- ssh_event_free(event);
+ ssh_event_free(shell->event);
REMMINA_DEBUG("Free event context");
// Remove channel callback.
diff --git a/src/remmina_ssh.h b/src/remmina_ssh.h
index 4a7082096..0c543643d 100644
--- a/src/remmina_ssh.h
+++ b/src/remmina_ssh.h
@@ -252,6 +252,7 @@ typedef struct _RemminaSSHShell {
gboolean closed;
RemminaSSHExitFunc exit_callback;
gpointer user_data;
+ ssh_event event;
} RemminaSSHShell;
/* Create a new SSH Shell session object from RemminaFile */
diff --git a/src/remmina_ssh_plugin.c b/src/remmina_ssh_plugin.c
index dfc67044f..13f3d7d99 100644
--- a/src/remmina_ssh_plugin.c
+++ b/src/remmina_ssh_plugin.c
@@ -1542,6 +1542,7 @@ static const RemminaProtocolSetting remmina_ssh_advanced_settings[] =
{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sshlogenabled", N_("Log SSH session when exiting Remmina"), FALSE, NULL, NULL },
{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "sshsavesession", N_("Log SSH session asynchronously"), FALSE, NULL, N_("Saving the session asynchronously may have a notable performance impact") },
{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "audiblebell", N_("Audible terminal bell"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_forward_x11", N_("SSH ForwardX11"), TRUE, NULL, NULL },
{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_compression", N_("SSH compression"), FALSE, NULL, NULL },
{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Don't remember passwords"), TRUE, NULL, NULL },
{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "ssh_stricthostkeycheck", N_("Strict host key checking"), TRUE, NULL, NULL },