#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>
#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;
pthread_mutex_t mutex;
static int 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);
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);
static int copy_fd_to_channel_callback(int fd, int revents, void *userdata);
static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel,
void *data, uint32_t len, int is_stderr,
void *userdata);
static void channel_close_callback(ssh_session session, ssh_channel channel,
void *userdata);
static ssh_channel x11_open_request_callback(ssh_session session,
const char *shost, int sport,
void *userdata);
static int main_loop(ssh_channel channel);
int64_t _current_timestamp(void);
const char *hostname = NULL;
int enableX11 = 1;
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
};
struct ssh_callbacks_struct cb =
{
.channel_open_request_x11_function = x11_open_request_callback,
.userdata = NULL
};
short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL;
ssh_event event;
struct termios _saved_tio;
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;
}
static int 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) {
node = (node_t *) calloc(1, sizeof(node_t));
if (node == NULL) {
pthread_mutex_unlock(&mutex);
return -1;
}
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;
}
new = (node_t *) malloc(sizeof(node_t));
if (new == NULL) {
pthread_mutex_unlock(&mutex);
return -1;
}
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);
return 0;
}
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 = NULL;
pthread_mutex_lock(&mutex);
current = node;
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, rc;
socklen_t optlen;
optlen = sizeof(opt);
rc = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen);
if (rc == -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);
rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
if (rc == -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) {
ret = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10);
if (ret < 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, rc;
struct sockaddr_un addr;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) {
_ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s",
strerror(errno));
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0';
memcpy(addr.sun_path + 1, pathname, sizeof(addr.sun_path) - 1);
rc = connect(sock, (struct sockaddr *)&addr,
offsetof(struct sockaddr_un, sun_path) + 1 + strlen(pathname));
if (rc == 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] = {0};
snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number);
return connect_local_xsocket_path(buf);
}
static int x11_connect_display(void)
{
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;
display = getenv("DISPLAY");
_ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display);
if (display == 0) {
return -1;
}
if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') {
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);
sock = connect_local_xsocket(display_number);
_ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %d", sock);
if (sock < 0) {
return -1;
}
return sock;
}
strncpy(buf, display, sizeof(buf) - 1);
cp = strchr(buf, ':');
if (cp == 0) {
_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;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
snprintf(strport, sizeof(strport), "%u", 6000 + display_number);
gaierr = getaddrinfo(buf, strport, &hints, &aitop);
if (gaierr != 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) {
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;
}
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;
}
break;
}
freeaddrinfo(aitop);
if (ai == 0) {
_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 == NULL) {
_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 {
_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, rv;
(void)shost;
(void)sport;
(void)userdata;
channel = ssh_channel_new(session);
sock = x11_connect_display();
_ssh_log(SSH_LOG_FUNCTIONS, __func__, "sock: %d", sock);
rv = insert_item(channel, sock, sock, 0);
if (rv != 0) {
ssh_channel_free(channel);
return NULL;
}
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;
}
static int main_loop(ssh_channel channel)
{
ssh_session session = ssh_channel_get_session(channel);
int rv;
rv = insert_item(channel, fileno(stdin), fileno(stdout), 1);
if (rv != 0) {
return -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;
}
rv = ssh_event_add_fd(event, fileno(stdin), events,
copy_fd_to_channel_callback, channel);
if (rv != SSH_OK) {
printf("Couldn't add an fd to the event\n");
return -1;
}
rv = ssh_event_add_session(event, session);
if (rv != SSH_OK) {
printf("Couldn't add the session to the event\n");
return -1;
}
do {
if (ssh_event_dopoll(event, 1000) == SSH_ERROR) {
printf("Error : %s\n", ssh_get_error(session));
}
} 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;
}
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;
}
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;
}
ret = x11_get_proto(display, &proto, &cookie);
if (ret != 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);
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();
}