diff --git a/README.md b/README.md index a8f15da9..7fb5954b 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,8 @@ A full [Change Log](doc/changes.md) is available and updated for major releases. The following features supported by Kermit 95 v2.1.3 remain unavailable in C-Kermit for Windows at this time: -* SSH X11 forwarding, and a few other features have not been implemented yet - - ticket #44 is tracking these. +* few misc SSH features have not been implemented yet - ticket #44 is tracking + these. * SSH v1 support will not return as this is not supported by libssh anymore. * SSH/SSL/TLS on Windows versions prior to Windows XP SP3 will likely not return as OpenSSL no longer supports these older versions of Windows or the compilers diff --git a/doc/changes.md b/doc/changes.md index ffd9dd93..1c2bbbac 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -14,6 +14,8 @@ Nothing yet establishing a connection with `SSH ADD { local, remote }` and remove all forwards of a given type with `SSH CLEAR { local, remote }`. These commands don't yet have any effect on an already established SSH connection. +* X11 forwarding is back. Turn on with `SET SSH X11 ON`, and set your display + with `SET TELNET ENV DISPLAY` * The SSH backend has been moved into a DLL. On startup, C-Kermit attempts to to load the backend DLL provided the `-#2` command line argument has not been supplied. If no SSH backend gets loaded, you can load one manually with the new diff --git a/doc/ssh-readme.md b/doc/ssh-readme.md index 148af79e..7eb073bd 100644 --- a/doc/ssh-readme.md +++ b/doc/ssh-readme.md @@ -132,7 +132,6 @@ libssh will or will not support. At this time all of these commands are hidden and just return an error when entered. ``` -SSH [OPEN] /X11-FORWARDING: {on,off} SSH AGENT ADD identity-file DELETE identity-file @@ -147,7 +146,6 @@ SET SSH PRIVILEGED-PORT {ON,OFF} QUIET {ON,OFF} V2 AUTO-REKEY {ON,OFF} - X11-FORWARDING {ON, OFF} XAUTH-LOCATION filename ``` diff --git a/kermit/k95/ckctel.c b/kermit/k95/ckctel.c index 4e626b66..cea6c35e 100644 --- a/kermit/k95/ckctel.c +++ b/kermit/k95/ckctel.c @@ -80,6 +80,7 @@ int sstelnet = 0; /* Do server-side Telnet negotiation */ #include "ckocon.h" extern int tt_type, max_tt; extern struct tt_info_rec tt_info[]; +#include "ckossh.h" #endif /* OS2 */ #endif /* NOTERM */ @@ -1051,6 +1052,9 @@ tn_get_display() /* explicitedly requested we try to send one via X-Display Location */ /* But do not send a string at all if FORWARD_X is in use. */ + /* Note that in Kermit 95 this is also used for X11 forwarding */ + /* over SSH */ + /* if (!IS_TELNET()) return(0); */ debug(F110,"tn_get_display() myipaddr",myipaddr,0); @@ -1073,8 +1077,12 @@ tn_get_display() } else #endif /* CK_ENVIRONMENT */ - if (TELOPT_ME(TELOPT_XDISPLOC) || - TELOPT_U(TELOPT_FORWARD_X)) { + if ((TELOPT_ME(TELOPT_XDISPLOC) || + TELOPT_U(TELOPT_FORWARD_X)) +#if OS2 + || (IS_SSH() && ssh_get_iparam(SSH_IPARAM_XFW)) +#endif /* OS2 */ + ) { ckmakmsg(tmploc,256,myipaddr,":0.0",NULL,NULL); disp = tmploc; } diff --git a/kermit/k95/ckolssh.c b/kermit/k95/ckolssh.c index 50444ce7..c4ef2475 100644 --- a/kermit/k95/ckolssh.c +++ b/kermit/k95/ckolssh.c @@ -56,6 +56,9 @@ char *cksshv = "SSH support (LibSSH), 10.0, 18 Apr 2023"; #include "ckolsshs.h" #include "ckossh.h" +/* for FamilyLocal, FamilyInternet, etc */ +#include "ckctel.h" + /* Global Variables: * These used to be all declared in ckuus3.c around like 8040, but since @@ -126,7 +129,7 @@ char *cksshv = "SSH support (LibSSH), 10.0, 18 Apr 2023"; * Just reports an error if version is 1 (SSH-1 not supported) * value is saved in ssh_ver. 0=auto. * /SUBSYSTEM:name - * TODO /X11-FORWARDING: {on,off} + * /X11-FORWARDING: {on,off} * SSH ADD * LOCAL-PORT-FORWARD local-port host port * REMOTE-PORT-FORWARD remote-port host port @@ -189,7 +192,7 @@ char *cksshv = "SSH support (LibSSH), 10.0, 18 Apr 2023"; * Report Errors - Verbosity Level. Range 0-7. Value stored in ssh_vrb * VERSION {2, AUTOMATIC} * value is saved in ssh_ver. 0=auto. - * TODO: X11-FORWARDING {ON, OFF} + * X11-FORWARDING {ON, OFF} * SET TELNET ENV DISPLAY is used to set the DISPLAY value * TODO: XAUTH-LOCATION filename */ @@ -201,8 +204,6 @@ char *cksshv = "SSH support (LibSSH), 10.0, 18 Apr 2023"; /* More TODO: * - TODO: Other Settings * - TODO: How do we know /command: has finished? EOF? - * - TODO: X11 Forwarding - * - TODO: Other forwarding * - TODO: Build libssh with GSSAPI, pthreads and kerberos * https://github.com/jwinarske/pthreads4w * - TODO: Deal with flush @@ -214,6 +215,38 @@ char *cksshv = "SSH support (LibSSH), 10.0, 18 Apr 2023"; * - TODO: deal with changing terminal type after connect ? (K95 doesn't) */ + +/* The TO-DO list from the Kermit 95 2.1.3 SSH module: + . Kerberos 4 and 5 Auto-get/auto-destroy support + + . Improve the debugging info to the debug log + + . listen to SET TCP REVERSE-DNS-LOOKUPS + + . listen to SET TCP DNS-SRV-RECORDS + + . make SET TELNET ENV DISPLAY more portable or SSH equivalent + + . SET SSH V2 AUTO-REKEY functionality + + . Implement service name processing in apply_kermit_settings() + + . add \f...()'s to process the .ssh\... key files (pub, priv, host) + + . document using Kermit file transfer to upload a public key identity + file and append it to the authorized_keys file on the host + + . all strings are to be tranmitted as UTF-8. (username, passphrase, ...) + need to examine what conversions are necessary. + + . do not close the SSH connection when the terminal session stops + leave it open until an SSH CLOSE command is given. This will allow + port forwarding and additional terminal sessions to be created on top + of the SSH connection. + + . Incoming SSH connections SET HOST * + */ + /* ==== LibSSH Settings ==== * * Settings we could use now: @@ -499,6 +532,9 @@ char* (*p_GetHomePath)() = NULL; char* (*p_GetHomeDrive)() = NULL; int (*p_ckstrncpy)(char * dest, const char * src, int len) = NULL; int (*p_debug_logging)() = NULL; +unsigned char* (*p_get_display)() = NULL; +int (*p_parse_displayname)(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp) = NULL; void get_current_terminal_dimensions(int* rows, int* cols) { p_get_current_terminal_dimensions(rows, cols); @@ -598,6 +634,15 @@ int debug_logging() { return p_debug_logging(); } +unsigned char* get_display() { + return p_get_display(); +} + +int parse_displayname(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp) { + return p_parse_displayname(displayname, familyp, hostp, + dpynump, scrnump, restp); +} #undef malloc #undef realloc @@ -718,6 +763,10 @@ int ssh_dll_init(ssh_init_parameters_t *params) { CHECK_FP(p_ckstrncpy) p_debug_logging = params->p_debug_logging; CHECK_FP(p_debug_logging) + p_get_display = params->p_get_display; + CHECK_FP(p_get_display) + p_parse_displayname = params->p_parse_displayname; + CHECK_FP(p_get_display) /* And then supply pointers to all our functions to K95 */ params->p_install_funcs("ssh_set_iparam", ssh_set_iparam); @@ -765,6 +814,11 @@ int ssh_dll_init(ssh_init_parameters_t *params) { return 0; } +#else +/* These live in ckossh.c */ +unsigned char* get_display(); +int parse_displayname(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp); #endif /* SSH_DLL */ int ssh_set_iparam(int param, int value) { @@ -1108,6 +1162,10 @@ int ssh_open() { int rc; const char* uidbuf; + /* X11 forwarding details */ + int display_number = 0, screen_number = 0; + char *x11_host = NULL; + uidbuf = ssh_get_uid(); debug(F100, "ssh_open()", "", 0); @@ -1185,6 +1243,43 @@ int ssh_open() { debug(F111, "ssh_open() - get terminal dimensions", "height", pty_height); debug(F111, "ssh_open() - get terminal dimensions", "width", pty_width); + if (ssh_xfw) { + unsigned char* display = get_display(); + char *rest = NULL; + int family; + + debug(F110, "ssh_open() DISPLAY", display, 0); + + if (parse_displayname(display, &family, &x11_host, &display_number, &screen_number, &rest)) { + /* We don't support unix domain sockets. Change it to the IP + * loopback interface and hope it works. */ + if (family == FamilyLocal) { + debug(F100, "ssh_open() - display proto is local, converting to IP", NULL, 0); + family = FamilyInternet; + if (x11_host) { + free(x11_host); + x11_host = NULL; + } + + x11_host = malloc(strlen("localhost") + 1); + + if (x11_host) { + strcpy(x11_host, "localhost"); + } else { + ssh_xfw = FALSE; /* Can't connect to the specified display */ + } + } + + debug(F110, "ssh_open() Host & display number", x11_host, display_number); + + } else { + debug(F100, "ssh_open() failed to parse DISPLAY, disabling forwarding", NULL, 0); + ssh_xfw = FALSE; + } + + if (rest) free(rest); + } + /* The SSH Subsystem will take ownership of this and handle cleaning it up * on disconnect */ debug(F100, "ssh_open() - construct parameters", "", 0); @@ -1213,10 +1308,18 @@ int ssh_open() { ssh2_kex, /* Key exchange methods */ ssh_get_nodelay_enabled(),/* Enable/disable Nagle's algorithm */ ssh_pxc, /* Proxy Command */ - port_forwards /* Direct and Reverse port forward config */ + port_forwards, /* Direct and Reverse port forward config */ + ssh_xfw, /* Forward X11 */ + x11_host, /* Host to forward X11 too */ + display_number, /* X11 display number */ + ssh_xal /* Xauth location */ ); if (user) free(user); + if (x11_host) { + free(x11_host); + x11_host = NULL; + } if (parameters == NULL) { debug(F100, "ssh_open() - failed to construct parameters struct", "", 0); @@ -1226,6 +1329,7 @@ int ssh_open() { /* This will be used to communicate with the SSH subsystem. It has * ring buffers, mutexes, semaphores, et. *WE* own this and must free it * on disconnect. */ + debug(F100, "ssh_open() - create client", NULL, 0); ssh_client = ssh_client_new(); if (ssh_client == NULL) { debug(F100, "ssh_open() - failed to construct client struct", "", 0); @@ -2555,6 +2659,7 @@ int ssh_feature_supported(int feature_id) { case SSH_FEAT_OPENSSH_CONF: /* Configuration via openssh config file */ case SSH_FEAT_KEY_MGMT: /* SSH key creation, etc */ case SSH_FEAT_PORT_FWD: /* Local and remote port forwarding */ + case SSH_FEAT_X11_FWD: /* X11 forwarding */ case SSH_FEAT_REKEY_AUTO: /* TODO: do we implement this? */ return TRUE; @@ -2566,7 +2671,7 @@ int ssh_feature_supported(int feature_id) { case SSH_FEAT_FROM_PRIV_PRT: /* Not supported by libssh */ case SSH_FEAT_GSSAPI_KEYEX: /* Not supported by libssh */ case SSH_FEAT_DYN_PORT_FWD: /* Requires a SOCKS server implementation */ - case SSH_FEAT_X11_FWD: /* TODO - not implemented here yet */ + case SSH_FEAT_X11_XAUTH: /* TODO - not implemented here yet */ case SSH_FEAT_AGENT_FWD: /* TODO - not implemented here yet */ case SSH_FEAT_GSSAPI_DELEGAT: /* TODO: can we support this ? I think so */ case SSH_FEAT_AGENT_MGMT: /* TODO: can we support this ? */ diff --git a/kermit/k95/ckolsshs.c b/kermit/k95/ckolsshs.c index 1c40ce7e..9973e754 100644 --- a/kermit/k95/ckolsshs.c +++ b/kermit/k95/ckolsshs.c @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include "ckcdeb.h" #include "ckcker.h" @@ -145,7 +147,9 @@ ssh_parameters_t* ssh_parameters_new( int pty_width, int pty_height, const char* auth_methods, const char* ciphers, int heartbeat, const char* hostkey_algorithms, const char* macs, const char* key_exchange_methods, int nodelay, - const char* proxy_command, const ssh_port_forward_t *port_forwards) { + const char* proxy_command, const ssh_port_forward_t *port_forwards, + BOOL forward_x, const char* display_host, int display_number, + const char* xauth_location) { ssh_parameters_t* params; params = (ssh_parameters_t*)malloc(sizeof(ssh_parameters_t)); @@ -270,6 +274,13 @@ ssh_parameters_t* ssh_parameters_new( params->allow_password_auth = TRUE; } + if (forward_x && display_host != NULL) { + params->forward_x = forward_x; + params->x11_host = _strdup(display_host); + params->x11_display = display_number; + params->xauth_location = _strdup(xauth_location); + } + return params; } @@ -285,8 +296,10 @@ void ssh_parameters_free(ssh_parameters_t* parameters) { free(parameters->global_known_hosts_file); if (parameters->username) free(parameters->username); - if (parameters->password) + if (parameters->password) { + SecureZeroMemory(parameters->password, sizeof(parameters->password)); free(parameters->password); + } if (parameters->terminal_type) free(parameters->terminal_type); if (parameters->allowed_ciphers) @@ -299,6 +312,10 @@ void ssh_parameters_free(ssh_parameters_t* parameters) { free(parameters->key_exchange_methods); if (parameters->proxy_command) free(parameters->proxy_command); + if (parameters->x11_host) + free(parameters->x11_host); + if (parameters->xauth_location) + free(parameters->xauth_location); free(parameters); } @@ -685,9 +702,11 @@ static int verify_known_host(ssh_client_state_t * state) { * Agent forwarding is disabled to avoid trojan horses. */ - /* TODO: If X11 forwarding, error: - * X11 forwarding is disabled to avoid trojan horses. - */ + if (state->parameters->forward_x) { + strncat(msg, "X11 forwarding is disabled to avoid trojan horses.\n", + sizeof(msg) - strlen(msg) - 1); + state->parameters->forward_x = FALSE; + } /* Check if we were asked to forward any ports */ if (state->parameters->port_forwards != NULL) { @@ -849,6 +868,9 @@ static int password_authenticate(ssh_client_state_t * state, BOOL *canceled) { /* Password has already been supplied. Try that */ debug(F100, "sshsubsys - Using pre-entered password", "", 0); ckstrncpy(password,state->parameters->password,sizeof(password)); + SecureZeroMemory(state->parameters->password, sizeof(state->parameters->password)); + free(state->parameters->password); + state->parameters->password = NULL; ok = TRUE; } else { snprintf(prompt, sizeof(prompt), "%s%.30s@%.128s's password: ", @@ -868,7 +890,7 @@ static int password_authenticate(ssh_client_state_t * state, BOOL *canceled) { } rc = ssh_userauth_password(state->session, NULL, password); - memset(password, 0, strlen(password)); + SecureZeroMemory(password, sizeof(password)); if (rc == SSH_AUTH_SUCCESS) { debug(F100, "sshsubsys - Password login succeeded", "", 0); ssh_string_free_char(user); @@ -1251,6 +1273,10 @@ static int authenticate(ssh_client_state_t * state, BOOL *canceled) { static int open_tty_channel(ssh_client_state_t * state) { int rc = 0; + if (state->ttyChannel != NULL) { + return SSH_OK; /* already open */ + } + debug(F100, "sshsubsys - Opening SSH tty channel", "", 0); state->ttyChannel = ssh_channel_new(state->session); @@ -1402,6 +1428,11 @@ static int configure_session(ssh_client_state_t * state) { int rc = SSH_ERR_OK; int verbosity = SSH_LOG_PROTOCOL; + /* TODO: ssh_channel_callbacks_struct + * Can we make use of the channel_data_function and channel_eof_function + * callbacks to handle initiating copies to the various ring buffers? + * And channel_close_function to handle tidying up forwardings? + * */ static struct ssh_callbacks_struct cb = { .auth_function = auth_prompt }; @@ -1993,6 +2024,39 @@ static int start_forward_server(ssh_forward_t *fwd, return rc; } + + +/** Starts X11 forwarding on the tty channel. + * + * @param state Client state + * @return 0 on success. + */ +static int start_X11_forwarding(ssh_client_state_t *state) { + char *proto = NULL, *cookie = NULL; + int rc; + + rc = open_tty_channel(state); + if (rc != SSH_OK) { + printf("Failed to open tty channel for X11 forwarding!\n"); + } + + /* TODO: Call xauth (if specified) to get the proto and cookie. + * (K95 2.1.3 never actually did this, despite having an xauth + * location setting) + */ + + rc = ssh_channel_request_x11( + state->ttyChannel, + 0, /* Only one X11 app will be redirected? No. */ + proto, /* X11 authentication protocol. NULL to use MIT-MAGIC-COOKIE-1. */ + cookie, /* X11 authentication cookie. NULL to generate a random one. */ + 0); /* Screen number */ + if (rc != SSH_OK) { + printf("X11 forwarding request failed: %d\n", rc); + } + return rc; +} + /** Reads any available data from a port forwarding Channel and writes it to * the output ring buffer to be consumed by the thread handling that * connection. @@ -2437,6 +2501,139 @@ static void accept_direct_forwarding_connections( } +/** Creates a new connection to something on the client network using + * the details specified in the supplied fwd (localHost, localPort). + * If successful, the connection is added to the fwds list of connections + * and a new thread is launched to service it. + * + * This is called when setting up new incoming remote forward connections, and + * new X11 connections. + * + * @param channel SSH channel to create a new TCP/IP connection for + * @param fwd ssh_forward_t to create a new TCP/IP connection for + * @param clientState Client state. + */ +static void create_local_connection(ssh_channel channel, + ssh_forward_t *fwd, + ssh_client_state_t *clientState) { + struct addrinfo *result = NULL, + *ptr = NULL, + hints; + char* peer_address = NULL; + int port = 0, peer_port = 0, iResult=0; + char localPort[32]; + ssh_forward_connection_t *con, *conNode; + ssh_fwd_thread_params_t *threadParams; + SOCKET sock = INVALID_SOCKET; + + /* Setup hints */ + ZeroMemory(&hints, sizeof(hints)); + hints.ai_family = AF_INET; /* IPv4 address family */ + /* Use AF_INET6 for IPv6, or PF_UNSPEC for either */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* Resolve server address & port */ + snprintf(localPort, 32, "%d", fwd->localPort); + iResult = getaddrinfo(fwd->localHost, localPort, &hints, &result); + + if ( iResult != 0 ) { + /* Fail! */ + + debug(F101, "sshsubsys - getaddrinfo failed with error", NULL, iResult); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return; + } + + /* Walk through the list of addresses until we find one we can use + * to connect to the local host and port */ + for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) { + + /* Create socket */ + sock = socket(ptr->ai_family, + ptr->ai_socktype, + ptr->ai_protocol); + + if (sock == INVALID_SOCKET) { + debug(F101, "sshsubsys - failed to create socket", + NULL, WSAGetLastError()); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + break; + } + + // Connect to server. + iResult = connect( sock, ptr->ai_addr, (int)ptr->ai_addrlen); + if (iResult == SOCKET_ERROR) { + /* Failed to connect with this socket - try the next */ + closesocket(sock); + sock = INVALID_SOCKET; + return; + } + break; + } + + freeaddrinfo(result); + + if (sock == INVALID_SOCKET) { + debug(F100, "sshsubsys - reverse/x11 fwd connect failed", NULL, 0); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + return; + } + + debug(F101, "sshsubsys - new reverse/x11 forward connected on socket", + NULL, sock); + + /* Create a new connection instance */ + /* New connection! */ + con = malloc(sizeof(ssh_forward_connection_t)); + con->next = NULL; + con->channel = channel; + con->outputBuffer = NULL; + con->inputBuffer = NULL; + con->state = SSH_FWD_STATE_OPEN; + + con->disconnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + /* Add the connection to the list */ + if (fwd->connections == NULL) { + fwd->connections = con; + } else { + conNode = fwd->connections; + while (conNode->next != NULL) { + conNode = conNode->next; + } + conNode->next = con; + } + + /* Set up the I/O buffers */ + con->outputBuffer = ring_buffer_new(1024*1024); + con->inputBuffer = ring_buffer_new(1024*1024); + + /* The socket */ + con->socket = sock; + + /* Then launch a thread to handle the socket */ + debug(F100, "sshsubsys - new remote/x11 fwd connection, launch thread", NULL, 0); + threadParams = malloc(sizeof(ssh_fwd_thread_params_t)); + threadParams->socket = con->socket; + threadParams->outputBuffer = con->inputBuffer; + threadParams->inputBuffer = con->outputBuffer; + threadParams->readyRead = clientState->forwardingReadyRead; + threadParams->disconnectEvent = con->disconnectEvent; + con->thread = (HANDLE)_beginthreadex( + NULL, /* Security info */ + 65536, /* Stack size */ + ssh_forwarding_connection_thread, /* Start address */ + (void *)threadParams, /* Arg list */ + 0, /* init flags - start immediately */ + NULL /* Thread identifier */ + ); +} + + /** Accepts any pending reverse forwarding connections * * @param clientState Client state we're accepting connections for @@ -2451,15 +2648,8 @@ static void accept_reverse_forwarding_connections( do { char* peer_address = NULL; - int port = 0, peer_port = 0, iResult=0; - ssh_forward_connection_t *con, *conNode; - ssh_fwd_thread_params_t *threadParams; - SOCKET sock = INVALID_SOCKET; + int port = 0, peer_port = 0; ssh_forward_t *fwd = client->forwards; - struct addrinfo *result = NULL, - *ptr = NULL, - hints; - char localPort[32]; channel = ssh_channel_open_forward_port( clientState->session, @@ -2489,113 +2679,55 @@ static void accept_reverse_forwarding_connections( fwd++; } - /* TODO: Try to open a connection to the local host and port */ - /* Setup hints */ - ZeroMemory(&hints, sizeof(hints)); - hints.ai_family = AF_INET; /* IPv4 address family */ - /* Use AF_INET6 for IPv6, or PF_UNSPEC for either */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - /* Resolve server address & port */ - snprintf(localPort, 32, "%d", fwd->localPort); - iResult = getaddrinfo(fwd->localHost, localPort, &hints, &result); - - if ( iResult != 0 ) { - /* Fail! */ - - debug(F101, "sshsubsys - getaddrinfo failed with error", NULL, iResult); + if (fwd == NULL) { + debug(F100, "sshsubsys - warning! Could not find forwarding " + "connection for received direct forwarding request. " + "Rejecting request.", NULL, 0); ssh_channel_send_eof(channel); ssh_channel_free(channel); continue; } - /* Walk through the list of addresses until we find one we can use - * to connect to the local host and port */ - for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) { - - /* Create socket */ - sock = socket(ptr->ai_family, - ptr->ai_socktype, - ptr->ai_protocol); - - if (sock == INVALID_SOCKET) { - debug(F101, "sshsubsys - failed to create socket", - NULL, WSAGetLastError()); - ssh_channel_send_eof(channel); - ssh_channel_free(channel); - break; - } + create_local_connection(channel, fwd, clientState); - // Connect to server. - iResult = connect( sock, ptr->ai_addr, (int)ptr->ai_addrlen); - if (iResult == SOCKET_ERROR) { - /* Failed to connect with this socket - try the next */ - closesocket(sock); - sock = INVALID_SOCKET; - continue; - } - break; - } - - freeaddrinfo(result); + } while (channel != NULL); +} - if (sock == INVALID_SOCKET) { - debug(F100, "sshsubsys - reverse forward connect failed", NULL, 0); - ssh_channel_send_eof(channel); - ssh_channel_free(channel); - continue; - } - debug(F101, "sshsubsys - new reverse forward connected on socket", - NULL, sock); +/** Accepts any waiting inbound X11 forwarding connections + * + * @param clientState Clientstate + * @param client Client + * @param fwd Forwarding with X server connection details (LocalHost, LocalPort) + * to which the new connection will be added + */ +static void accept_x11_forwarding_connections( + ssh_client_state_t *clientState, + ssh_client_t *client, + ssh_forward_t *fwd) { + ssh_channel channel = NULL; + debug(F100, "sshsubsys - checking for incoming X11 connections", NULL, 0); - /* Create a new connection instance */ - /* New connection! */ - con = malloc(sizeof(ssh_forward_connection_t)); - con->next = NULL; - con->channel = channel; - con->outputBuffer = NULL; - con->inputBuffer = NULL; - con->state = SSH_FWD_STATE_OPEN; + do { + int port = 0, iResult=0; + ssh_forward_connection_t *con, *conNode; + ssh_fwd_thread_params_t *threadParams; + SOCKET sock = INVALID_SOCKET; + ssh_forward_t *fwd = client->forwards; + struct addrinfo *result = NULL, + *ptr = NULL, + hints; + char localPort[32]; - con->disconnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + channel = ssh_channel_accept_x11(clientState->ttyChannel, 0); - /* Add the connection to the list */ - if (fwd->connections == NULL) { - fwd->connections = con; - } else { - conNode = fwd->connections; - while (conNode->next != NULL) { - conNode = conNode->next; - } - conNode->next = con; + if (channel == NULL) { + return; /* no pending connections */ } - /* Set up the I/O buffers */ - con->outputBuffer = ring_buffer_new(1024*1024); - con->inputBuffer = ring_buffer_new(1024*1024); - - /* The socket */ - con->socket = sock; - - /* Then launch a thread to handle the socket */ - debug(F100, "sshsubsys - new remote fwd connection, launch thread", NULL, 0); - threadParams = malloc(sizeof(ssh_fwd_thread_params_t)); - threadParams->socket = con->socket; - threadParams->outputBuffer = con->inputBuffer; - threadParams->inputBuffer = con->outputBuffer; - threadParams->readyRead = clientState->forwardingReadyRead; - threadParams->disconnectEvent = con->disconnectEvent; - con->thread = (HANDLE)_beginthreadex( - NULL, /* Security info */ - 65536, /* Stack size */ - ssh_forwarding_connection_thread, /* Start address */ - (void *)threadParams, /* Arg list */ - 0, /* init flags - start immediately */ - NULL /* Thread identifier */ - ); + debug(F100, "sshsubsys - got X11 forwarding request", NULL, 0); + create_local_connection(channel, fwd, clientState); } while (channel != NULL); } @@ -2644,71 +2776,8 @@ static void free_fwd_connection(ssh_forward_connection_t *conn) { } -/** Configures libssh and connects to the server - * - * @param state SSH Client State - * @return An error code on failure - */ -static int connect_ssh(ssh_client_state_t* state, ssh_client_t *client) { - int rc; - BOOL user_canceled; - char* banner = NULL; - BOOL forwarding_ok = TRUE; - - /* Connect! */ - debug(F100, "sshsubsys - SSH Connect...", "", 0); - - /* Apply configuration to the SSH session */ - rc = configure_session(state); - if (rc != SSH_ERR_OK) { - return rc; - } - - rc = ssh_connect(state->session); - if (rc != SSH_OK) { - debug(F111,"sshsubsys - Error connecting to host", - ssh_get_error(state->session), rc); - return rc; - } - - /* Check the hosts key is valid */ - rc = verify_known_host(state); - if (rc != SSH_ERR_NO_ERROR) { - debug(F111, "sshsubsys - Host verification failed", "rc", rc); - printf("Host verification failed.\n"); - return rc; - } - - /* This is apparently required for some reason in order for - * get_issue_banner to work */ - rc = ssh_userauth_none(state->session, NULL); - if (rc == SSH_AUTH_ERROR) { - return rc; - } - if (rc != SSH_AUTH_SUCCESS) { - banner = ssh_get_issue_banner(state->session); - if (banner) { - printf(banner); - ssh_string_free_char(banner); - banner = NULL; - } - - /* Authenticate! */ - rc = authenticate(state, &user_canceled); - if (rc != SSH_AUTH_SUCCESS ) { - debug(F111, "sshsubsys - Authentication failed - disconnecting", "rc", rc); - printf("Authentication failed - disconnecting.\n"); - - if (rc == SSH_AUTH_ERROR) rc = SSH_ERR_AUTH_ERROR; - if (rc == SSH_AUTH_PARTIAL) rc = SSH_ERR_AUTH_ERROR; - if (rc == SSH_AUTH_DENIED) rc = SSH_ERR_ACCESS_DENIED; - - return rc; - } - } - - debug(F100, "sshsubsys - Authentication succeeded", "", 0); - +static void configure_forwarding(ssh_client_state_t *state, + ssh_client_t *client) { /* This is set to false if host key verification fails and strict host key * checking is set to NO. */ @@ -2758,7 +2827,7 @@ static int connect_ssh(ssh_client_state_t* state, ssh_client_t *client) { fwd->hostname, /* Local host to forward to */ fwd->host_port, /* Local port to forward to */ INFINITE - ); + ); if (fwdNode == NULL) { continue; /* Failed to configure port forward */ @@ -2780,12 +2849,125 @@ static int connect_ssh(ssh_client_state_t* state, ssh_client_t *client) { } } - /* Set up X11 tunnel */ - /* TODO */ - /* Set up Agent forwarding */ /* TODO */ } +} + +static void setup_x11_forwarding(ssh_client_state_t *state, + ssh_client_t *client) { + /* Set up X11 tunnel */ + if (state->forwarding_ok && + state->parameters->forward_x && + state->parameters->x11_host != NULL) { + + + /* Add an entry to the forwards list so we have a way of + * tracking connections to the local X server */ + ssh_forward_t *fwd = malloc(sizeof(ssh_forward_t)); + fwd->next = NULL; + fwd->state = SSH_FWD_STATE_PENDING; + fwd->connections = NULL; + + fwd->type = SSH_FWD_TYPE_X11; + fwd->remoteHost = NULL; + fwd->remotePort = 0; + fwd->localHost = _strdup(state->parameters->x11_host); + fwd->localPort = 6000 + state->parameters->x11_display; + + if (client->forwards == NULL) { + client->forwards = fwd; + } else { + /* Find the last entry in the list */ + ssh_forward_t * node = client->forwards; + while (node->next != NULL) { + node = node->next; + } + node->next = fwd; + } + + if (start_X11_forwarding(state) == SSH_OK) { + fwd->state = SSH_FWD_STATE_OPEN; + } else { + fwd->state = SSH_FWD_STATE_ERROR; + state->parameters->forward_x = FALSE; + } + } +} + + +/** Configures libssh and connects to the server + * + * @param state SSH Client State + * @return An error code on failure + */ +static int connect_ssh(ssh_client_state_t* state, ssh_client_t *client) { + int rc; + BOOL user_canceled; + char* banner = NULL; + BOOL forwarding_ok = TRUE; + + /* Connect! */ + debug(F100, "sshsubsys - SSH Connect...", "", 0); + + /* Apply configuration to the SSH session */ + rc = configure_session(state); + if (rc != SSH_ERR_OK) { + return rc; + } + + rc = ssh_connect(state->session); + if (rc != SSH_OK) { + debug(F111,"sshsubsys - Error connecting to host", + ssh_get_error(state->session), rc); + return rc; + } + + /* Check the hosts key is valid */ + rc = verify_known_host(state); + if (rc != SSH_ERR_NO_ERROR) { + debug(F111, "sshsubsys - Host verification failed", "rc", rc); + printf("Host verification failed.\n"); + return rc; + } + + /* This is apparently required for some reason in order for + * get_issue_banner to work */ + rc = ssh_userauth_none(state->session, NULL); + if (rc == SSH_AUTH_ERROR) { + return rc; + } + if (rc != SSH_AUTH_SUCCESS) { + banner = ssh_get_issue_banner(state->session); + if (banner) { + printf(banner); + ssh_string_free_char(banner); + banner = NULL; + } + + /* Authenticate! */ + rc = authenticate(state, &user_canceled); + if (rc != SSH_AUTH_SUCCESS ) { + debug(F111, "sshsubsys - Authentication failed - disconnecting", "rc", rc); + printf("Authentication failed - disconnecting.\n"); + + if (rc == SSH_AUTH_ERROR) rc = SSH_ERR_AUTH_ERROR; + if (rc == SSH_AUTH_PARTIAL) rc = SSH_ERR_AUTH_ERROR; + if (rc == SSH_AUTH_DENIED) rc = SSH_ERR_ACCESS_DENIED; + + return rc; + } + } + + debug(F100, "sshsubsys - Authentication succeeded", "", 0); + + /* Setup direct and reverse forwarding if its been requested */ + configure_forwarding(state, client); + + + /* Setup X11 forwarding. What the libssh documentation fails to mention + * is that this must be done *before* the session is started */ + setup_x11_forwarding(state, client); debug(F100, "sshsubsys - Starting session...", "", 0); if (state->parameters->session_type == SESSION_TYPE_SUBSYSTEM) { @@ -3086,7 +3268,8 @@ unsigned int __stdcall ssh_thread(ssh_thread_params_t *parameters) { while (fwdNode != NULL) { debug(F101, "sshsubsys - process fwd", NULL, xx); - if (fwdNode->state == SSH_FWD_STATE_PENDING) { + if (fwdNode->state == SSH_FWD_STATE_PENDING + && fwdNode->type == SSH_FWD_TYPE_DIRECT) { int rc; /* This forward is not setup yet - it was probably added @@ -3126,6 +3309,9 @@ unsigned int __stdcall ssh_thread(ssh_thread_params_t *parameters) { if (fwdNode->type == SSH_FWD_TYPE_DIRECT) { debug(F101, "sshsubsys - fwd state open, accept connections", NULL, xx); accept_direct_forwarding_connections(state, client, fwdNode); + } else if (fwdNode->type == SSH_FWD_TYPE_X11) { + debug(F101, "sshsubsys - X11 forwarding open, accept connections", NULL, xx); + accept_x11_forwarding_connections(state, client, fwdNode); } /* Then handle communications for each active connection */ @@ -3220,6 +3406,11 @@ unsigned int __stdcall ssh_thread(ssh_thread_params_t *parameters) { fwdNode->remotePort ); /* TODO: Do we need to check for SSH_AGAIN and retry? */ + } else if (fwdNode->type == SSH_FWD_TYPE_X11) { + /* There doesn't appear to be a way to stop X11 forwarding + * short of perhaps closing the associated channel (the tty + * channel) + */ } /* Then close all existing connections */ diff --git a/kermit/k95/ckolsshs.h b/kermit/k95/ckolsshs.h index 804bbff9..b952a6f3 100644 --- a/kermit/k95/ckolsshs.h +++ b/kermit/k95/ckolsshs.h @@ -169,9 +169,10 @@ typedef struct { const ssh_port_forward_t *port_forwards; - /* TODO: When agent, X11, and other port forwarding is added - * all forwarding should be forced off/cleared when host key - * verification fails and strict host key checking is set to no. */ + BOOL forward_x; /* Forward X11 ? */ + char* x11_host; /* Host where the X server is running */ + int x11_display; /* X11 display number */ + char* xauth_location; /* Xauth location (filename) */ } ssh_parameters_t; @@ -249,6 +250,10 @@ void get_current_terminal_dimensions(int* rows, int* cols); * @param key_exchange_methods Comma-separated list of key exchange methods * @param nodelay Set to disable Nagle's algorithm * @param proxy_command Set the command to be executed in order to connect to server + * @param forward_x Forward X11 + * @param display_host Host running the X11 server + * @param display_number X11 display number + * @param xauth_location Xauth location * @return A new ssh_parameters_t instance. */ ssh_parameters_t* ssh_parameters_new( @@ -260,7 +265,9 @@ ssh_parameters_t* ssh_parameters_new( int pty_width, int pty_height, const char* auth_methods, const char* ciphers, int heartbeat, const char* hostkey_algorithms, const char* macs, const char* key_exchange_methods, int nodelay, - const char* proxy_command, const ssh_port_forward_t *port_forwards); + const char* proxy_command, const ssh_port_forward_t *port_forwards, + BOOL forward_x, const char* display_host, int display_number, + const char* xauth_location); /** Frees the ssh_parameters_t struct and all its members. * diff --git a/kermit/k95/ckonssh.c b/kermit/k95/ckonssh.c index 69851a93..ee4131e5 100644 --- a/kermit/k95/ckonssh.c +++ b/kermit/k95/ckonssh.c @@ -264,6 +264,9 @@ char* (*p_GetHomePath)() = NULL; char* (*p_GetHomeDrive)() = NULL; int (*p_ckstrncpy)(char * dest, const char * src, int len) = NULL; int (*p_debug_logging)() = NULL; +unsigned char* (*p_get_display)() = NULL; +int (*p_parse_displayname)(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp) = NULL; void get_current_terminal_dimensions(int* rows, int* cols) { p_get_current_terminal_dimensions(rows, cols); @@ -362,6 +365,16 @@ int debug_logging() { return p_debug_logging(); } +unsigned char* get_display() { + return p_get_display(); +} + +int parse_displayname(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp) { + return p_parse_displayname(displayname, familyp, hostp, + dpynump, scrnump, restp); +} + /** Called by Kermit 95 when the DLL is loaded. This should make * the DLL ready for use by storing copies of all the needed * utility functions supplied by Kermit 95, and supplying to @@ -388,6 +401,8 @@ int ssh_dll_init(ssh_init_parameters_t *params) { p_GetHomeDrive = params->p_GetHomeDrive; p_ckstrncpy = params->p_ckstrncpy; p_debug_logging = params->p_debug_logging; + p_get_display = params->p_get_display; + p_parse_displayname = params->p_parse_displayname; params->p_install_funcs("ssh_set_iparam", ssh_set_iparam); params->p_install_funcs("ssh_get_iparam", ssh_get_iparam); @@ -506,7 +521,11 @@ kstrdup(const char *str) memcpy(cp, str, len); return cp; } - +#else +/* These live in ckossh.c */ +unsigned char* get_display(); +int parse_displayname(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp); #endif /* SSH_DLL */ /** Sets an integer parameter diff --git a/kermit/k95/ckossh.c b/kermit/k95/ckossh.c index 977f28a1..73197c36 100644 --- a/kermit/k95/ckossh.c +++ b/kermit/k95/ckossh.c @@ -45,6 +45,7 @@ char *cksshv = "SSH-DLL support, 10.0, 28 July 2024"; #include "ckcker.h" #include "ckocon.h" #include "ckoreg.h" +#include "ckctel.h" /* Various global variables owned by the rest of C-Kermit */ extern char uidbuf[]; /* User ID set via /user: */ @@ -140,6 +141,16 @@ int debug_logging() { return deblog; } +unsigned char* get_display() { + return tn_get_display(); +} + +int parse_displayname(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp) { + return fwdx_parse_displayname(displayname, familyp, hostp, + dpynump, scrnump, restp); +} + #ifdef SSH_DLL /* SSH_DLL: @@ -417,6 +428,8 @@ int ssh_load(char* dllname) { init_params.p_GetHomeDrive = GetHomeDrive; init_params.p_ckstrncpy = ckstrncpy; init_params.p_debug_logging = debug_logging; + init_params.p_get_display = tn_get_display; /* ckctel.c */ + init_params.p_parse_displayname = fwdx_parse_displayname; /* ckctel.c */ /* Initialise! */ rc = init(&init_params); diff --git a/kermit/k95/ckossh.h b/kermit/k95/ckossh.h index 5c79ff57..596a25b0 100644 --- a/kermit/k95/ckossh.h +++ b/kermit/k95/ckossh.h @@ -184,6 +184,7 @@ _PROTOTYP(ktab_ret ssh_get_keytab,(int keytab_id)); #define SSH_FEAT_REKEY_AUTO 14 #define SSH_FEAT_FROM_PRIV_PRT 15 #define SSH_FEAT_DYN_PORT_FWD 16 +#define SSH_FEAT_X11_XAUTH 17 _PROTOTYP(int ssh_feature_supported,(int feature_id)); @@ -214,6 +215,22 @@ typedef struct { char* (*p_GetHomeDrive)(); int (*p_ckstrncpy)(char * dest, const char * src, int len); int (*p_debug_logging)(); + + /* Returns a statically allocated string containing the currently + * configured X11 display + */ + unsigned char* (*p_get_display)(); + + /* Utility function for parsing the display name. Result is returned + * via: + * *familyp - protocol family (FamilyInternet, FamilyLocal, FamilyDECnet) + * **hostp - host string + * *dpynump - Display number + * *scrnump - Screen number + * **restp - Anything else at the end + */ + int (*p_parse_displayname)(char *displayname, int *familyp, char **hostp, + int *dpynump, int *scrnump, char **restp); } ssh_init_parameters_t; /* diff --git a/kermit/k95/ckuus2.c b/kermit/k95/ckuus2.c index 462d990b..f632e9e5 100644 --- a/kermit/k95/ckuus2.c +++ b/kermit/k95/ckuus2.c @@ -1117,12 +1117,15 @@ static char *hmxyssh[] = { "SET SSH X11-FORWARDING { ON, OFF }", " Specifies whether X Windows System Data is to be forwarded across the", " established SSH connection. The default is OFF. When ON, the DISPLAY", -" value is either set using the SET TELNET ENV DISPLAY command or read", -" from the DISPLAY environment variable.", +" value is set using the SET TELNET ENV DISPLAY command.", " ", +#ifdef COMMENT +/* While this command existed in K95 2.1.3, it was never actually implemented + * there. */ "SET SSH XAUTH-LOCATION filename", " Specifies the location of the xauth executable (if provided with the", " X11 Server software.)", +#endif /* COMMENT */ #else /* SSHBUILTIN */ "Syntax: SET SSH COMMAND command", " Specifies the external command to be used to make an SSH connection.", diff --git a/kermit/k95/ckuus3.c b/kermit/k95/ckuus3.c index 6818ed9a..49ad0f98 100644 --- a/kermit/k95/ckuus3.c +++ b/kermit/k95/ckuus3.c @@ -8015,7 +8015,7 @@ static struct keytab sshtab[] = { /* SET SSH command table */ { "verbose", SSH_VRB, 0 }, { "version", SSH_VER, 0 }, /* SSH_FEAT_SSH_V1 */ { "x11-forwarding", SSH_XFW, 0 }, /* SSH_FEAT_X11_FWD */ - { "xauth-location", SSH_XAL, 0 }, /* SSH_FEAT_X11_FWD */ + { "xauth-location", SSH_XAL, 0 }, /* SSH_FEAT_X11_XAUTH */ #else #ifdef SSHCMD { "command", SSH_CMD, 0 }, @@ -8246,8 +8246,13 @@ shossh() { ); } if (ssh_feature_supported(SSH_FEAT_X11_FWD)) { + extern char* tn_env_disp; /* ckctel.c */ printf(" ssh x11-forwarding: %s\n",showooa(ssh_get_iparam(SSH_IPARAM_XFW))); - printf(" ssh xauth-location: %s\n",showstring((char*)ssh_get_sparam(SSH_SPARAM_XAL))); + printf(" ssh x11-forwarding display: %s\n",showstring(tn_env_disp)); + /* TODO: Show the display - tn_env_disp */ + if (ssh_feature_supported(SSH_FEAT_X11_XAUTH)) { + printf(" ssh xauth-location: %s\n",showstring((char*)ssh_get_sparam(SSH_SPARAM_XAL))); + } } if (ssh_feature_supported(SSH_FEAT_SSH_V1)) { @@ -8325,13 +8330,13 @@ dosetssh() { */ sshtab[z].flgs = CM_INV; } - else if ((sshtab[z].kwval == SSH_XFW || sshtab[z].kwval == SSH_XAL) - && !ssh_feature_supported(SSH_FEAT_X11_FWD)) { + else if ((sshtab[z].kwval == SSH_XFW && !ssh_feature_supported(SSH_FEAT_X11_FWD)) + || (sshtab[z].flgs = CM_INV && !ssh_feature_supported(SSH_FEAT_X11_XAUTH))) { /* * "set ssh x11-forwarding" and "set ssh xauth-location" commands. */ - sshtab[z].flgs = CM_INV; } + // sshtab[z].kwval == SSH_XAL else if ((sshtab[z].kwval == SSH_DYF || sshtab[z].kwval == SSH_GWP) && !ssh_feature_supported(SSH_FEAT_DYN_PORT_FWD)) { /* Dynamic Port Forwarding