From 88bc364c05d62ea55a2d7156d60f519c86d92f69 Mon Sep 17 00:00:00 2001 From: Marco Fortina Date: Tue, 12 Apr 2022 19:03:22 +0200 Subject: [PATCH] examples: Add 'ssh X11 client' sample Signed-off-by: Marco Fortina Reviewed-by: Jakub Jelen --- .gitlab-ci.yml | 1 + doc/shell.dox | 33 +- examples/CMakeLists.txt | 4 + examples/ssh_X11_client.c | 859 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 891 insertions(+), 6 deletions(-) create mode 100644 examples/ssh_X11_client.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 117435ad..4d332197 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -399,6 +399,7 @@ freebsd/x86_64: - branches@libssh/libssh-mirror - branches@cryptomilk/libssh-mirror - branches@jjelen/libssh-mirror + - branches@marco.fortina/libssh-mirror ############################################################################### diff --git a/doc/shell.dox b/doc/shell.dox index 0693bbcc..d770f27a 100644 --- a/doc/shell.dox +++ b/doc/shell.dox @@ -320,18 +320,36 @@ int interactive_shell_session(ssh_session session, ssh_channel channel) If your remote application is graphical, you can forward the X11 protocol to your local computer. -To do that, you first declare that you accept X11 connections with -ssh_channel_accept_x11(). Then you create the forwarding tunnel for -the X11 protocol with ssh_channel_request_x11(). +To do that, you first declare a callback to manage channel_open_request_x11_function. +Then you create the forwarding tunnel for the X11 protocol with ssh_channel_request_x11(). The following code performs channel initialization and shell session opening, and handles a parallel X11 connection: @code +#include + +ssh_channel x11channel = NULL; + +ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + x11channel = ssh_channel_new(session); + return x11channel; +} + int interactive_shell_session(ssh_channel channel) { int rc; - ssh_channel x11channel; + + struct ssh_callbacks_struct cb = + { + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL + }; + + ssh_callbacks_init(&cb); + rc = ssh_set_callbacks(session, &cb); + if (rc != SSH_OK) return rc; rc = ssh_channel_request_pty(channel); if (rc != SSH_OK) return rc; @@ -350,12 +368,15 @@ int interactive_shell_session(ssh_channel channel) } @endcode -Don't forget to set the $DISPLAY environment variable on the remote +Don't forget to check the $DISPLAY environment variable on the remote side, or the remote applications won't try using the X11 tunnel: @code -$ export DISPLAY=:0 +$ echo $DISPLAY +localhost:10.0 $ xclock & @endcode +See an implementation example at https://gitlab.com/libssh/libssh-mirror/-/tree/master/examples/ssh_X11_client.c for details. + */ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1cfefd8b..c688b6a3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,6 +35,10 @@ if (UNIX AND NOT WIN32) target_compile_options(ssh-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(ssh-client ssh::ssh) + add_executable(ssh-X11-client ssh_X11_client.c ${examples_SRCS}) + target_compile_options(ssh-X11-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-X11-client ssh::ssh) + if (WITH_SERVER AND (ARGP_LIBRARY OR HAVE_ARGP_H)) if (HAVE_LIBUTIL) add_executable(ssh_server_fork ssh_server.c) diff --git a/examples/ssh_X11_client.c b/examples/ssh_X11_client.c new file mode 100644 index 00000000..c1eda75d --- /dev/null +++ b/examples/ssh_X11_client.c @@ -0,0 +1,859 @@ +/* + * ssh.c - Simple example of SSH X11 client using libssh + * + * Copyright (C) 2022 Marco Fortina + * + * 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. + * + * + * + * ssh_X11_client + * ============== + * + * AUTHOR URL + * https://gitlab.com/marco.fortina/libssh-x11-client/ + * + * This is a simple example of SSH X11 client using libssh. + * + * Features: + * + * - support local display (e.g. :0) + * - support remote display (e.g. localhost:10.0) + * - using callbacks and event polling to significantly reduce CPU utilization + * - use X11 forwarding with authentication spoofing (like openssh) + * + * Note: + * + * - part of this code was inspired by openssh's one. + * + * Dependencies: + * + * - gcc >= 7.5.0 + * - libssh >= 0.8.0 + * - libssh-dev >= 0.8.0 + * + * To Build: + * gcc -o ssh_X11_client ssh_X11_client.c -lssh -g + * + * Donations: + * + * If you liked this work and wish to support the developer please donate to: + * Bitcoin: 1N2rQimKbeUQA8N2LU5vGopYQJmZsBM2d6 + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* + * Data Structures and Macros +*/ + +#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; + int fd_in; + int fd_out; + int protected; + struct item *next; +} node_t; + +node_t *node = NULL; + + +/* + * Mutex +*/ + +pthread_mutex_t mutex; + + +/* + * Function declarations +*/ + +/* Linked nodes to manage channel/fd tuples */ +static void insert_item(ssh_channel channel, int fd_in, int fd_out, int protected); +static void delete_item(ssh_channel channel); +static node_t * search_item(ssh_channel channel); + +/* X11 Display */ +const char * ssh_gai_strerror(int gaierr); +static int x11_get_proto(const char *display, char **_proto, char **_data); +static void set_nodelay(int fd); +static int connect_local_xsocket_path(const char *pathname); +static int connect_local_xsocket(int display_number); +static int x11_connect_display(void); + +/* Send data to channel */ +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata); + +/* Read data from channel */ +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *userdata); + +/* EOF&Close channel */ +static void channel_close_callback(ssh_session session, ssh_channel channel, void *userdata); + +/* X11 Request */ +static ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata); + +/* Main loop */ +static int main_loop(ssh_channel channel); + +/* Internals */ +int64_t _current_timestamp(void); + +/* Global variables */ +const char *hostname = NULL; +int enableX11 = 1; + +/* + * Callbacks Data Structures +*/ + +/* SSH Channel Callbacks */ +struct ssh_channel_callbacks_struct channel_cb = +{ + .channel_data_function = copy_channel_to_fd_callback, + .channel_eof_function = channel_close_callback, + .channel_close_function = channel_close_callback, + .userdata = NULL +}; + +/* SSH Callbacks */ +struct ssh_callbacks_struct cb = +{ + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL +}; + + +/* + * SSH Event Context +*/ + +short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; +ssh_event event; + + +/* + * Internal data structures +*/ + +struct termios _saved_tio; + + +/* + * Internal functions +*/ + +int64_t +_current_timestamp(void) { + struct timeval tv; + int64_t milliseconds; + + gettimeofday(&tv, NULL); + milliseconds = (int64_t)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000); + + return milliseconds; +} + +static void +_logging_callback(int priority, const char *function, const char *buffer, void *userdata) +{ + FILE *fp = NULL; + char buf[100]; + int64_t milliseconds; + + time_t now = time (0); + + (void)userdata; + + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime (&now)); + + fp = fopen("debug.log","a"); + if(fp == NULL) + { + printf("Error!"); + exit(-11); + } + + milliseconds = _current_timestamp(); + + fprintf(fp, "[%s.%jd, %d] %s: %s\n", buf, milliseconds, priority, function, buffer); + fclose(fp); +} + +static int +_enter_term_raw_mode(void) +{ + struct termios tio; + int ret = tcgetattr(fileno(stdin), &tio); + if (ret != -1) { + _saved_tio = tio; + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); +#ifdef IUCLC + tio.c_iflag &= ~IUCLC; +#endif + tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + ret = tcsetattr(fileno(stdin), TCSADRAIN, &tio); + } + return ret; +} + +static int +_leave_term_raw_mode(void) +{ + int ret = tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio); + return ret; +} + + +/* + * Functions +*/ + +static void +insert_item(ssh_channel channel, int fd_in, int fd_out, int protected) +{ + node_t *node_iterator = NULL, *new = NULL; + + pthread_mutex_lock(&mutex); + + 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 +delete_item(ssh_channel channel) +{ + node_t *current = NULL, *previous = NULL; + + pthread_mutex_lock(&mutex); + + 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 * +search_item(ssh_channel channel) +{ + node_t *current = node; + + pthread_mutex_lock(&mutex); + + while (current != NULL) { + if (current->channel == channel) { + pthread_mutex_unlock(&mutex); + return current; + } else { + current = current->next; + } + } + + pthread_mutex_unlock(&mutex); + + return NULL; +} + + + +static void +set_nodelay(int fd) +{ + int opt; + socklen_t optlen; + + optlen = sizeof(opt); + if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "getsockopt TCP_NODELAY: %.100s", strerror(errno)); + return; + } + if (opt == 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d is TCP_NODELAY", fd); + return; + } + opt = 1; + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d setting TCP_NODELAY", fd); + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) == -1) + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "setsockopt TCP_NODELAY: %.100s", strerror(errno)); +} + + +const char * +ssh_gai_strerror(int gaierr) +{ + if (gaierr == EAI_SYSTEM && errno != 0) + return strerror(errno); + return gai_strerror(gaierr); +} + + + +static int +x11_get_proto(const char *display, char **_proto, char **_cookie) +{ + char cmd[1024], line[512], xdisplay[512]; + static char proto[512], cookie[512]; + FILE *f = NULL; + int ret = 0; + + *_proto = proto; + *_cookie = cookie; + + proto[0] = cookie[0] = '\0'; + + if (strncmp(display, "localhost:", 10) == 0) { + if ((ret = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10)) < 0 || (size_t)ret >= sizeof(xdisplay)) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display name too long. display: %s", display); + return -1; + } + display = xdisplay; + } + + snprintf(cmd, sizeof(cmd), _XAUTH_CMD, display); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "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); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s - ret: %d", proto, cookie, ret); + + return ret; +} + +static int +connect_local_xsocket_path(const char *pathname) +{ + int sock; + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "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) + return sock; + close(sock); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s: %.100s", addr.sun_path, strerror(errno)); + return -1; +} + + +static int +connect_local_xsocket(int display_number) +{ + char buf[1024]; + snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number); + return connect_local_xsocket_path(buf); +} + + +static int +x11_connect_display() +{ + int display_number; + const char *display = NULL; + char buf[1024], *cp = NULL; + struct addrinfo hints, *ai = NULL, *aitop = NULL; + char strport[NI_MAXSERV]; + int gaierr = 0, sock = 0; + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (!display) { + return -1; + } + + /* 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, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Could not parse display number from DISPLAY: %.100s", display); + return -1; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display_number: %d", display_number); + + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "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) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Could not find ':' in DISPLAY: %.100s", display); + return -1; + } + *cp = 0; + if (sscanf(cp + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "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) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "%.100s: unknown host. (%s)", buf, 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) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", strerror(errno)); + continue; + } + /* Connect it to the display. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); + close(sock); + continue; + } + /* Success */ + break; + } + freeaddrinfo(aitop); + if (!ai) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); + return -1; + } + set_nodelay(sock); + return sock; +} + + + +static int +copy_fd_to_channel_callback(int fd, int revents, void *userdata) +{ + ssh_channel channel = (ssh_channel)userdata; + char buf[2097152]; + int sz = 0, ret = 0; + + node_t *temp_node = search_item(channel); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "event: %d - fd: %d", revents, fd); + + if (!channel) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel does not exist."); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + + if (fcntl(fd, F_GETFD) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fcntl error. fd: %d", fd); + ssh_channel_close(channel); + return -1; + } + + if ((revents & POLLIN) || (revents & POLLPRI)) { + sz = read(fd, buf, sizeof(buf)); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sz: %d", sz); + if (sz > 0) { + ret = ssh_channel_write(channel, buf, sz); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel_write ret: %d", ret); + } else if (sz < 0) { + ssh_channel_close(channel); + return -1; + } else { + /* sz = 0. Why the hell I'm here? */ + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Why the hell am I here?: sz: %d", sz); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + } + + if ((revents & POLLHUP) || (revents & POLLNVAL) || (revents & POLLERR)) { + ssh_channel_close(channel); + return -1; + } + + return sz; +} + + +static int +copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *userdata) +{ + node_t *temp_node = NULL; + int fd, sz; + + (void)session; + (void)is_stderr; + (void)userdata; + + temp_node = search_item(channel); + + fd = temp_node->fd_out; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "len: %d - fd: %d - is_stderr: %d", len, fd, is_stderr); + + sz = write(fd, data, len); + + return sz; +} + + +static void +channel_close_callback(ssh_session session, ssh_channel channel, void *userdata) +{ + node_t *temp_node = NULL; + + (void)session; + (void)userdata; + + temp_node = search_item(channel); + + if (temp_node != NULL) { + int fd = temp_node->fd_in; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd: %d", fd); + + delete_item(channel); + ssh_event_remove_fd(event, fd); + + if (temp_node->protected == 0) { + close(fd); + } + } +} + + +static ssh_channel +x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + ssh_channel channel = NULL; + int sock; + + (void)shost; + (void)sport; + (void)userdata; + + channel = ssh_channel_new(session); + + sock = x11_connect_display(); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sock: %d", sock); + + insert_item(channel, sock, sock, 0); + + ssh_event_add_fd(event, sock, events, copy_fd_to_channel_callback, channel); + ssh_event_add_session(event, session); + + ssh_add_channel_callbacks(channel, &channel_cb); + + return channel; +} + + + +/* + * MAIN LOOP +*/ + +static int +main_loop(ssh_channel channel) +{ + ssh_session session = ssh_channel_get_session(channel); + + insert_item(channel, fileno(stdin), fileno(stdout), 1); + + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(channel, &channel_cb); + + event = ssh_event_new(); + if (event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + + if (ssh_event_add_fd(event, fileno(stdin), events, copy_fd_to_channel_callback, channel) != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + + if(ssh_event_add_session(event, session) != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + ssh_event_dopoll(event, 1000); + } while (!ssh_channel_is_closed(channel)); + + delete_item(channel); + ssh_event_remove_fd(event, fileno(stdin)); + ssh_event_remove_session(event, session); + ssh_event_free(event); + + return 0; +} + + +/* + * USAGE + */ + +static void +usage(void) +{ + fprintf(stderr, + "Usage : ssh-X11-client [options] [login@]hostname\n" + "sample X11 client - libssh-%s\n" + "Options :\n" + " -l user : Specifies the user to log in as on the remote machine.\n" + " -p port : Port to connect to on the remote host.\n" + " -v : Verbose mode. Multiple -v options increase the verbosity. The maximum is 5.\n" + " -C : Requests compression of all data.\n" + " -x : Disables X11 forwarding.\n" + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc,argv,"x")) != -1) { + switch(i) { + case 'x': + enableX11 = 0; + break; + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + + if (optind < argc) { + hostname = argv[optind++]; + } + + if (hostname == NULL) { + return -1; + } + + return 0; +} + +/* + * MAIN +*/ + +int +main(int argc, char **argv) +{ + char *password = NULL; + + ssh_session session = NULL; + ssh_channel channel = NULL; + + int ret; + + const char *display = NULL; + char *proto = NULL, *cookie = NULL; + + ssh_set_log_callback(_logging_callback); + ret = ssh_init(); + if (ret != SSH_OK) return ret; + + session = ssh_new(); + if (session == NULL) exit(-1); + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, "Error parsing command line: %s\n", ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, hostname) < 0) { + return -1; + } + + ret = ssh_connect(session); + if (ret != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + exit(-1); + } + + password = getpass("Password: "); + ret = ssh_userauth_password(session, NULL, password); + if (ret != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Error authenticating with password: %s\n", ssh_get_error(session)); + exit(-1); + } + + channel = ssh_channel_new(session); + if (channel == NULL) return SSH_ERROR; + + ret = ssh_channel_open_session(channel); + if (ret != SSH_OK) return ret; + + ret = ssh_channel_request_pty(channel); + if (ret != SSH_OK) return ret; + + ret = ssh_channel_change_pty_size(channel, 80, 24); + if (ret != SSH_OK) return ret; + + if (enableX11 == 1) { + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display) { + ssh_callbacks_init(&cb); + ret = ssh_set_callbacks(session, &cb); + if (ret != SSH_OK) return ret; + + if (x11_get_proto(display, &proto, &cookie) != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Using fake authentication data for X11 forwarding"); + proto = NULL; + cookie = NULL; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s", proto, cookie); + /* See https://gitlab.com/libssh/libssh-mirror/-/blob/master/src/channels.c#L2062 for details. */ + ret = ssh_channel_request_x11(channel, 0, proto, cookie, 0); + if (ret != SSH_OK) return ret; + } + } + + ret = _enter_term_raw_mode(); + if (ret != 0) exit(-1); + + ret = ssh_channel_request_shell(channel); + if (ret != SSH_OK) return ret; + + ret = main_loop(channel); + if (ret != SSH_OK) return ret; + + _leave_term_raw_mode(); + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); +}