Update code to v1.0.14 (10)

This commit is contained in:
Caten
2024-02-29 19:35:00 +08:00
parent c2ee3b694c
commit a956d26f6d
3188 changed files with 2317293 additions and 146 deletions

View File

@@ -0,0 +1,445 @@
/**
* @example sshtunnel.c
* An example of an RFB client tunneled through SSH by using libssh2.
* This is based on https://www.libssh2.org/examples/direct_tcpip.html
* with the following changes:
* - the listening is split out into a separate thread function
* - the listener gets closed immediately once a connection was accepted
* - the listening port is chosen by the OS, SO_REUSEADDR removed
* - global variables moved into SshData helper structure
* - added name resolution for the ssh host
*/
#include <rfb/rfbclient.h>
#include <libssh2.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#ifdef LIBVNCSERVER_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef LIBVNCSERVER_HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef LIBVNCSERVER_HAVE_UNISTD_H
#include <unistd.h>
#endif
/* The one global bool that's global so we can set it via
a signal handler... */
int maintain_connection = 1;
typedef struct
{
rfbClient *client;
LIBSSH2_SESSION *session;
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
pthread_t thread;
#elif defined(LIBVNCSERVER_HAVE_WIN32THREADS)
uintptr_t thread;
#endif
int ssh_sock;
int local_listensock;
int local_listenport;
const char *remote_desthost;
int remote_destport;
} SshData;
THREAD_ROUTINE_RETURN_TYPE ssh_proxy_loop(void *arg)
{
SshData *data = arg;
int rc, i;
struct sockaddr_in sin;
socklen_t sinlen;
LIBSSH2_CHANNEL *channel = NULL;
const char *shost;
int sport;
fd_set fds;
struct timeval tv;
ssize_t len, wr;
char buf[16384];
int proxy_sock = RFB_INVALID_SOCKET;
proxy_sock = accept(data->local_listensock, (struct sockaddr *)&sin, &sinlen);
if(proxy_sock == RFB_INVALID_SOCKET) {
fprintf(stderr, "ssh_proxy_loop: accept: %s\n", strerror(errno));
goto shutdown;
}
/* Close listener once a connection got accepted */
rfbCloseSocket(data->local_listensock);
shost = inet_ntoa(sin.sin_addr);
sport = ntohs(sin.sin_port);
printf("ssh_proxy_loop: forwarding connection from %s:%d here to remote %s:%d\n",
shost, sport, data->remote_desthost, data->remote_destport);
channel = libssh2_channel_direct_tcpip_ex(data->session, data->remote_desthost,
data->remote_destport, shost, sport);
if(!channel) {
fprintf(stderr, "ssh_proxy_loop: Could not open the direct-tcpip channel!\n"
"(Note that this can be a problem at the server!"
" Please review the server logs.)\n");
goto shutdown;
}
/* Must use non-blocking IO hereafter due to the current libssh2 API */
libssh2_session_set_blocking(data->session, 0);
while(1) {
FD_ZERO(&fds);
FD_SET(proxy_sock, &fds);
tv.tv_sec = 0;
tv.tv_usec = 100000;
rc = select(proxy_sock + 1, &fds, NULL, NULL, &tv);
if(-1 == rc) {
fprintf(stderr, "ssh_proxy_loop: select: %s\n", strerror(errno));
goto shutdown;
}
if(rc && FD_ISSET(proxy_sock, &fds)) {
len = recv(proxy_sock, buf, sizeof(buf), 0);
if(len < 0) {
fprintf(stderr, "read: %s\n", strerror(errno));
goto shutdown;
}
else if(0 == len) {
fprintf(stderr, "ssh_proxy_loop: the client at %s:%d disconnected!\n", shost,
sport);
goto shutdown;
}
wr = 0;
while(wr < len) {
i = libssh2_channel_write(channel, buf + wr, len - wr);
if(LIBSSH2_ERROR_EAGAIN == i) {
continue;
}
if(i < 0) {
fprintf(stderr, "ssh_proxy_loop: libssh2_channel_write: %d\n", i);
goto shutdown;
}
wr += i;
}
}
while(1) {
len = libssh2_channel_read(channel, buf, sizeof(buf));
if(LIBSSH2_ERROR_EAGAIN == len)
break;
else if(len < 0) {
fprintf(stderr, "ssh_proxy_loop: libssh2_channel_read: %d\n", (int)len);
goto shutdown;
}
wr = 0;
while(wr < len) {
i = send(proxy_sock, buf + wr, len - wr, 0);
if(i <= 0) {
fprintf(stderr, "ssh_proxy_loop: write: %s\n", strerror(errno));
goto shutdown;
}
wr += i;
}
if(libssh2_channel_eof(channel)) {
fprintf(stderr, "ssh_proxy_loop: the server at %s:%d disconnected!\n",
data->remote_desthost, data->remote_destport);
goto shutdown;
}
}
}
shutdown:
printf("ssh_proxy_loop: shutting down\n");
rfbCloseSocket(proxy_sock);
if(channel)
libssh2_channel_free(channel);
libssh2_session_disconnect(data->session, "Client disconnecting normally");
libssh2_session_free(data->session);
rfbCloseSocket(data->ssh_sock);
return THREAD_ROUTINE_RETURN_VALUE;
}
/**
Decide whether or not the SSH tunnel setup should continue
based on the current host and its fingerprint.
Business logic is up to the implementer in a real app, i.e.
compare keys, ask user etc...
@return -1 if tunnel setup should be aborted
0 if tunnel setup should continue
*/
int ssh_fingerprint_check(const char *fingerprint, size_t fingerprint_len,
const char *host, rfbClient *client)
{
size_t i;
fprintf(stderr, "ssh_fingerprint_check: host %s has ", host);
for(i = 0; i < fingerprint_len; i++)
printf("%02X ", (unsigned char)fingerprint[i]);
printf("\n");
return 0;
}
/**
Creates an SSH tunnel and a local proxy and returns the port the proxy is listening on.
@return A pointer to an SshData structure or NULL on error.
*/
SshData* ssh_tunnel_open(const char *ssh_host,
const char *ssh_user,
const char *ssh_password,
const char *ssh_pub_key_path,
const char *ssh_priv_key_path,
const char *ssh_priv_key_password,
const char *rfb_host,
int rfb_port,
rfbClient *client)
{
int rc, i;
struct sockaddr_in sin;
socklen_t sinlen;
const char *fingerprint;
char *userauthlist;
struct addrinfo hints, *res;
SshData *data;
/* Sanity checks */
if(!ssh_host || !ssh_user || !rfb_host) /* these must be set */
return NULL;
data = calloc(1, sizeof(SshData));
data->client = client;
data->remote_desthost = rfb_host; /* resolved by the server */
data->remote_destport = rfb_port;
/* Connect to SSH server */
data->ssh_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(data->ssh_sock == RFB_INVALID_SOCKET) {
fprintf(stderr, "ssh_tunnel_open: socket: %s\n", strerror(errno));
goto error;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((rc = getaddrinfo(ssh_host, NULL, &hints, &res)) == 0) {
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = (((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr);
freeaddrinfo(res);
} else {
fprintf(stderr, "ssh_tunnel_open: getaddrinfo: %s\n", gai_strerror(rc));
goto error;
}
sin.sin_port = htons(22);
if(connect(data->ssh_sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) {
fprintf(stderr, "ssh_tunnel_open: failed to connect to SSH server!\n");
goto error;
}
/* Create a session instance */
data->session = libssh2_session_init();
if(!data->session) {
fprintf(stderr, "ssh_tunnel_open: could not initialize SSH session!\n");
goto error;
}
/* ... start it up. This will trade welcome banners, exchange keys,
* and setup crypto, compression, and MAC layers
*/
rc = libssh2_session_handshake(data->session, data->ssh_sock);
if(rc) {
fprintf(stderr, "ssh_tunnel_open: error when starting up SSH session: %d\n", rc);
goto error;
}
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
fingerprint = libssh2_hostkey_hash(data->session, LIBSSH2_HOSTKEY_HASH_SHA256);
if(ssh_fingerprint_check(fingerprint, 32, ssh_host, data->client) == -1) {
fprintf(stderr, "ssh_tunnel_open: fingerprint check indicated tunnel setup stop\n");
goto error;
}
/* check what authentication methods are available */
userauthlist = libssh2_userauth_list(data->session, ssh_user, strlen(ssh_user));
printf("ssh_tunnel_open: authentication methods: %s\n", userauthlist);
if(ssh_password && strstr(userauthlist, "password")) {
if(libssh2_userauth_password(data->session, ssh_user, ssh_password)) {
fprintf(stderr, "ssh_tunnel_open: authentication by password failed.\n");
goto error;
}
}
else if(ssh_priv_key_path && ssh_priv_key_password && strstr(userauthlist, "publickey")) {
if(libssh2_userauth_publickey_fromfile(data->session, ssh_user, ssh_pub_key_path,
ssh_priv_key_path, ssh_priv_key_password)) {
fprintf(stderr, "ssh_tunnel_open: authentication by public key failed!\n");
goto error;
}
}
else {
fprintf(stderr, "ssh_tunnel_open: no supported authentication methods found!\n");
goto error;
}
/* Create and bind the local listening socket */
data->local_listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(data->local_listensock == RFB_INVALID_SOCKET) {
fprintf(stderr, "ssh_tunnel_open: socket: %s\n", strerror(errno));
return NULL;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(0); /* let the OS choose the port */
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
if(INADDR_NONE == sin.sin_addr.s_addr) {
fprintf(stderr, "ssh_tunnel_open: inet_addr: %s\n", strerror(errno));
goto error;
}
sinlen = sizeof(sin);
if(-1 == bind(data->local_listensock, (struct sockaddr *)&sin, sinlen)) {
fprintf(stderr, "bind: %s\n", strerror(errno));
goto error;
}
if(-1 == listen(data->local_listensock, 1)) {
fprintf(stderr, "listen: %s\n", strerror(errno));
goto error;
}
/* get info back from OS */
if (getsockname(data->local_listensock, (struct sockaddr *)&sin, &sinlen ) == -1){
fprintf(stderr, "ssh_tunnel_open: getsockname: %s\n", strerror(errno));
goto error;
}
data->local_listenport = ntohs(sin.sin_port);
printf("ssh_tunnel_open: waiting for TCP connection on %s:%d...\n",
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
/* Create the proxy thread */
#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD)
if (pthread_create(&data->thread, NULL, ssh_proxy_loop, data) != 0) {
#elif defined(LIBVNCSERVER_HAVE_WIN32THREADS)
if(data->thread = _beginthread(proxy_loop, 0, data) == 0);
#endif
fprintf(stderr, "ssh_tunnel_open: proxy thread creation failed\n");
goto error;
}
return data;
error:
if (data->session) {
libssh2_session_disconnect(data->session, "Error in SSH tunnel setup");
libssh2_session_free(data->session);
}
rfbCloseSocket(data->local_listensock);
rfbCloseSocket(data->ssh_sock);
free(data);
return NULL;
}
void ssh_tunnel_close(SshData *data) {
if(!data)
return;
/* the proxy thread does the internal cleanup as it can be
ended due to external reasons */
THREAD_JOIN(data->thread);
free(data);
printf("ssh_tunnel_close: done\n");
}
void intHandler(int dummy) {
maintain_connection = 0;
}
int main(int argc, char *argv[])
{
rfbClient *client = rfbGetClient(8,3,4);
/*
Get args and create SSH tunnel
*/
int rc = libssh2_init(0);
if(rc) {
fprintf(stderr, "libssh2 initialization failed (%d)\n", rc);
return EXIT_FAILURE;
}
SshData *data;
if (argc == 6) {
/* SSH tunnel w/ password */
data = ssh_tunnel_open(argv[1], argv[2], argv[3], NULL, NULL, NULL, argv[4], atoi(argv[5]), client);
} else if (argc == 8) {
/* SSH tunnel w/ privkey */
data = ssh_tunnel_open(argv[1], argv[2], NULL, argv[3], argv[4], argv[5], argv[6], atoi(argv[7]), client);
} else {
fprintf(stderr,
"Usage (w/ password): %s <ssh-server-IP> <ssh-server-username> <ssh-server-password> <rfb-host> <rfb-port>\n"
"Usage (w/ privkey): %s <ssh-server-IP> <ssh-server-username> <pubkey_filename> <privkey_filename> <privkey_password> <rfb-host> <rfb-port>\n",
argv[0], argv[0]);
return(EXIT_FAILURE);
}
/*
The actual VNC connection setup.
*/
client->serverHost = strdup("127.0.0.1");
if(data) // might be NULL if ssh setup failed
client->serverPort = data->local_listenport;
rfbClientSetClientData(client, (void*)42, data);
if (!data || !rfbInitClient(client,NULL,NULL))
return EXIT_FAILURE;
printf("Successfully connected to %s:%d - hit Ctrl-C to disconnect\n", client->serverHost, client->serverPort);
signal(SIGINT, intHandler);
while (maintain_connection) {
int n = WaitForMessage(client,50);
if(n < 0)
break;
if(n)
if(!HandleRFBServerMessage(client))
break;
}
/* Disconnect client inside tunnel */
if(client && client->sock != RFB_INVALID_SOCKET)
rfbCloseSocket(client->sock);
/* Close the tunnel and clean up */
ssh_tunnel_close(rfbClientGetClientData(client, (void*)42));
/* free client */
rfbClientCleanup(client);
/* Teardown libssh2 */
libssh2_exit();
return EXIT_SUCCESS;
}