2022-04-12 20:03:22 +03:00
|
|
|
/*
|
|
|
|
* 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 <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
#include <libssh/libssh.h>
|
|
|
|
#include <libssh/callbacks.h>
|
|
|
|
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/tcp.h>
|
|
|
|
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
2022-05-23 16:26:52 +03:00
|
|
|
if (sock == -1) {
|
2022-04-12 20:03:22 +03:00
|
|
|
_ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", strerror(errno));
|
2022-05-23 16:26:52 +03:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-04-12 20:03:22 +03:00
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
|
|
addr.sun_path[0] = '\0';
|
2022-05-23 16:26:52 +03:00
|
|
|
/* pathname is guaranteed to be initialized and larger than addr.sun_path[108] */
|
|
|
|
memcpy(addr.sun_path + 1, pathname, sizeof(addr.sun_path) - 1);
|
2022-04-12 20:03:22 +03:00
|
|
|
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)
|
|
|
|
{
|
2022-05-23 16:26:52 +03:00
|
|
|
char buf[1024] = {0};
|
2022-04-12 20:03:22 +03:00
|
|
|
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 {
|
2022-05-23 16:26:52 +03:00
|
|
|
if (ssh_event_dopoll(event, 1000) == SSH_ERROR) {
|
|
|
|
printf("Error : %s\n", ssh_get_error(session));
|
|
|
|
/* fall through */
|
|
|
|
}
|
2022-04-12 20:03:22 +03:00
|
|
|
} 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();
|
|
|
|
}
|