diff options
author | Marco Fortina <marco.fortina@atlantica.it> | 2022-04-08 01:52:58 +0300 |
---|---|---|
committer | Marco Fortina <marco.fortina@atlantica.it> | 2022-04-08 01:52:58 +0300 |
commit | dd2e0fbe6f57acf76ef10344d783cb0472625ed2 (patch) | |
tree | c4edb9b8036eca8c7fb0870645d25878e8f54ed5 /src | |
parent | 3874589f12713ccf72d783d316846499f416d389 (diff) |
Adding SSH X11Forward support
Diffstat (limited to 'src')
-rw-r--r-- | src/remmina_ssh.c | 458 | ||||
-rw-r--r-- | src/remmina_ssh.h | 1 | ||||
-rw-r--r-- | src/remmina_ssh_plugin.c | 1 |
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 }, |