Skip to content
Snippets Groups Projects
Select Git revision
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • new_user_dat
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

ssh.c

Blame
  • ssh.c 29.31 KiB
    /* Copyright (C), 2007 by Stephen Hurd */
    
    /* $Id: ssh.c,v 1.31 2020/05/28 22:58:26 deuce Exp $ */
    
    #include <assert.h>
    #include <stdlib.h>
    
    #include "base64.h"
    #include "bbslist.h"
    #include "ciolib.h"
    #include "conn.h"
    #include "cryptlib.h"
    #include "eventwrap.h"
    #include "gen_defs.h"
    #include "genwrap.h"
    #include "sftp.h"
    #include "ssh.h"
    #include "sockwrap.h"
    #include "syncterm.h"
    #include "threadwrap.h"
    #include "uifcinit.h"
    #include "window.h"
    #include "xpendian.h"
    #include "xpprintf.h"
    
    #ifdef _MSC_VER
    #pragma warning(disable : 4244 4267 4018)
    #endif
    
    SOCKET          ssh_sock = INVALID_SOCKET;
    CRYPT_SESSION   ssh_session;
    int             ssh_channel = -1;
    pthread_mutex_t ssh_mutex;
    pthread_mutex_t ssh_tx_mutex;
    int             sftp_channel = -1;
    sftpc_state_t   sftp_state;
    bool            pubkey_thread_running;
    
    void
    exit_crypt(void)
    {
    	cryptEnd();
    }
    
    void
    init_crypt(void)
    {
    	int status;
    
    	status = cryptInit();
    	if (cryptStatusOK(status)) {
    		atexit(exit_crypt);
    		status = cryptAddRandom(NULL, CRYPT_RANDOM_SLOWPOLL);
    	}
    	if (cryptStatusError(status))
    		cryptlib_error_message(status, "initializing cryptlib");
    }
    
    static int
    FlushData(CRYPT_SESSION sess)
    {
    	int ret = cryptFlushData(sess);
    	if (ret == CRYPT_ERROR_COMPLETE || ret == CRYPT_ERROR_READ) {
    		conn_api.terminate = true;
    		shutdown(ssh_sock, SHUT_RDWR);
    	}
    	return ret;
    }
    
    static int
    PopData(CRYPT_HANDLE e, void *buf, int len, int *copied)
    {
    	cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 0);
    	int ret = cryptPopData(e, buf, len, copied);
    	cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 30);
    	if (ret == CRYPT_ERROR_COMPLETE || ret == CRYPT_ERROR_READ) {
    		conn_api.terminate = true;
    		shutdown(ssh_sock, SHUT_RDWR);
    	}
    	return ret;
    }
    
    static int
    PushData(CRYPT_HANDLE e, void *buf, int len, int *copied)
    {
    	int ret = cryptPushData(e, buf, len, copied);
    	if (ret == CRYPT_ERROR_COMPLETE || ret == CRYPT_ERROR_WRITE) {
    		conn_api.terminate = true;
    		shutdown(ssh_sock, SHUT_RDWR);
    	}
    	return ret;
    }
    
    void
    cryptlib_error_message(int status, const char *msg)
    {
    	char  str[64];
    	char  str2[64];
    	char *errmsg = NULL;
    	int   err_len;
    	bool  err_written = false;
    
    	sprintf(str, "Error %d %s\r\n\r\n", status, msg);
    	pthread_mutex_lock(&ssh_mutex);
    	if (cryptStatusOK(cryptGetAttributeString(ssh_session, CRYPT_ATTRIBUTE_ERRORMESSAGE, NULL, &err_len))) {
    		errmsg = malloc(err_len + strlen(str) + 5);
    		if (errmsg) {
    			strcpy(errmsg, str);
    			if (cryptStatusOK(cryptGetAttributeString(ssh_session, CRYPT_ATTRIBUTE_ERRORMESSAGE, errmsg + strlen(str), &err_len))) {
    				pthread_mutex_unlock(&ssh_mutex);
    				errmsg[strlen(str) + err_len] = 0;
    				strcat(errmsg, "\r\n\r\n");
    				sprintf(str2, "Error %d %s", status, msg);
    				uifcmsg(str2, errmsg);
    				err_written = true;
    				free(errmsg);
    			}
    		}
    	}
    	if (!err_written) {
    		pthread_mutex_unlock(&ssh_mutex);
    		sprintf(str2, "Error %d %s", status, msg);
    		uifcmsg(str2, "Additionally, a failure occured getting the error message");
    		free(errmsg);
    	}
    }
    
    static void
    close_sftp_channel(int chan)
    {
    	pthread_mutex_lock(&ssh_mutex);
    	if (chan != -1) {
    		FlushData(ssh_session);
    		if (cryptStatusOK(cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, chan))) {
    			cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
    		}
    	}
    	if (chan == sftp_channel)
    		sftp_channel = -1;
    	else {
    		FlushData(ssh_session);
    		if (cryptStatusOK(cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sftp_channel))) {
    			cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
    		}
    		sftp_channel = -1;
    	}
    	pthread_mutex_unlock(&ssh_mutex);
    	sftpc_finish(sftp_state);
    }
    
    static void
    close_ssh_channel(void)
    {
    	pthread_mutex_lock(&ssh_mutex);
    	if (ssh_channel != -1) {
    		FlushData(ssh_session);
    		if (cryptStatusOK(cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, ssh_channel))) {
    			cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
    		}
    		ssh_channel = -1;
    	}
    	pthread_mutex_unlock(&ssh_mutex);
    }
    
    static bool
    check_channel_open(int *chan)
    {
    	int open = 0;
    	int status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, *chan);
    	if (cryptStatusError(status)) {
    		open = 0;
    	}
    	else {
    		status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_OPEN, &open);
    		if (cryptStatusError(status)) {
    			open = 0;
    		}
    		if (!open) {
    			cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
    		}
    	}
    	return open;
    }
    
    void
    ssh_input_thread(void *args)
    {
    	int    popstatus, gchstatus, status;
    	int    rd;
    	size_t buffered;
    	size_t buffer;
    	int    chan;
    	bool   both_gone = false;
    	bool   sftp_do_finish;
    	bool   data_avail;
    
    	SetThreadName("SSH Input");
    	conn_api.input_thread_running = 1;
    	while (!conn_api.terminate) {
    		sftp_do_finish = false;
    		pthread_mutex_lock(&ssh_mutex);
    		if (FlushData(ssh_session) == CRYPT_ERROR_COMPLETE) {
    			pthread_mutex_unlock(&ssh_mutex);
    			break;
    		}
    		if (ssh_channel != -1) {
    			if (!check_channel_open(&ssh_channel))
    				ssh_channel = -1;
    		}
    		if (sftp_channel != -1) {
    			if (!check_channel_open(&sftp_channel)) {
    				sftp_do_finish = true;
    				sftp_channel = -1;
    			}
    		}
    		if (ssh_channel == -1 && sftp_channel == -1) {
    			both_gone = true;
    		}
    		pthread_mutex_unlock(&ssh_mutex);
    		if (both_gone) {
    			break;
    		}
    		if (sftp_do_finish) {
    			sftpc_finish(sftp_state);
    			sftp_do_finish = false;
    		}
    		if (!socket_check(ssh_sock, &data_avail, NULL, 100)) {
    			break;
    		}
    		if (!data_avail)
    			continue;
    
    		pthread_mutex_lock(&ssh_mutex);
    		if (FlushData(ssh_session) == CRYPT_ERROR_COMPLETE) {
    			pthread_mutex_unlock(&ssh_mutex);
    			break;
    		}
    
    		// Check channels are active (again)...
    		if (ssh_channel != -1) {
    			if (!check_channel_open(&ssh_channel))
    				ssh_channel = -1;
    		}
    		if (sftp_channel != -1) {
    			if (!check_channel_open(&sftp_channel)) {
    				pthread_mutex_unlock(&ssh_mutex);
    				sftpc_finish(sftp_state);
    				pthread_mutex_lock(&ssh_mutex);
    				sftp_channel = -1;
    			}
    		}
    		if (ssh_channel == -1 && sftp_channel == -1) {
    			pthread_mutex_unlock(&ssh_mutex);
    			break;
    		}
    
    		cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 0);
    		popstatus = PopData(ssh_session, conn_api.rd_buf, conn_api.rd_buf_size, &rd);
    		cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 30);
    		if (cryptStatusOK(popstatus)) {
    			gchstatus = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, &chan);
    		}
    		else {
    			gchstatus = CRYPT_OK;
    			chan = -1;
    		}
    
                    // Handle case where there was socket activity without readable data (ie: rekey)
    		if (popstatus == CRYPT_ERROR_TIMEOUT) {
    			FlushData(ssh_session);
    			pthread_mutex_unlock(&ssh_mutex);
    			continue;
    		}
    
    		// A final read on a channel just occured... figure out which is missing...
    		if (gchstatus == CRYPT_ERROR_NOTFOUND) {
    			if (ssh_channel != -1) {
    				FlushData(ssh_session);
    				status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, ssh_channel);
    				if (status == CRYPT_ERROR_NOTFOUND) {
    					chan = ssh_channel;
    				}
    			}
    			if (chan == -1) {
    				if (sftp_channel != -1) {
    					FlushData(ssh_session);
    					status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sftp_channel);
    					if (status == CRYPT_ERROR_NOTFOUND) {
    						chan = sftp_channel;
    					}
    				}
    			}
    		}
    
    		if (cryptStatusOK(popstatus) && chan != -1) {
    			if (chan == sftp_channel) {
    				if (gchstatus == CRYPT_ERROR_NOTFOUND) {
    					sftp_channel = -1;
    				}
    				/*
    				 * TODO: Can't hold sftp mutex here!
    				 *       It will be held by whatever's waiting for this.
    				 *       and will deadlock.
    				 */
    				if (rd > 0 && !sftpc_recv(sftp_state, conn_api.rd_buf, rd)) {
    					int sc = sftp_channel;
    					pthread_mutex_unlock(&ssh_mutex);
    					close_sftp_channel(sc);
    					pthread_mutex_lock(&ssh_mutex);
    					FlushData(ssh_session);
    					pthread_mutex_unlock(&ssh_mutex);
    					continue;
    				}
    			}
    			else if (chan == ssh_channel) {
    				if (gchstatus == CRYPT_ERROR_NOTFOUND) {
    					ssh_channel = -1;
    				}
    				pthread_mutex_unlock(&ssh_mutex);
    				if (rd > 0) {
    					buffered = 0;
    					while (buffered < rd && !conn_api.terminate) {
    						pthread_mutex_lock(&(conn_inbuf.mutex));
    						buffer = conn_buf_wait_free(&conn_inbuf, rd - buffered, 100);
    						buffered += conn_buf_put(&conn_inbuf, conn_api.rd_buf + buffered, buffer);
    						pthread_mutex_unlock(&(conn_inbuf.mutex));
    					}
    				}
    				pthread_mutex_lock(&ssh_mutex);
    			}
    		}
    		FlushData(ssh_session);
    		pthread_mutex_unlock(&ssh_mutex);
    	}
    	conn_api.terminate = true;
    	shutdown(ssh_sock, SHUT_RDWR);
    	conn_api.input_thread_running = 2;
    }
    
    void
    ssh_output_thread(void *args)
    {
    	int    wr;
    	int    ret;
    	size_t sent;
    	int    status;
    
    	SetThreadName("SSH Output");
    	conn_api.output_thread_running = 1;
    	while (!conn_api.terminate) {
    		pthread_mutex_lock(&(conn_outbuf.mutex));
    		wr = conn_buf_wait_bytes(&conn_outbuf, 1, 100);
    		if (wr) {
    			wr = conn_buf_get(&conn_outbuf, conn_api.wr_buf, conn_api.wr_buf_size);
    			pthread_mutex_unlock(&(conn_outbuf.mutex));
    			sent = 0;
    			pthread_mutex_lock(&ssh_tx_mutex);
    			while (sent < wr && !conn_api.terminate) {
    				ret = 0;
    				pthread_mutex_lock(&ssh_mutex);
    				if (ssh_channel == -1) {
    					pthread_mutex_unlock(&ssh_mutex);
    					conn_api.terminate = true;
    					break;
    				}
    				FlushData(ssh_session);
    				status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, ssh_channel);
    				if (cryptStatusOK(status)) {
    					status = PushData(ssh_session, conn_api.wr_buf + sent, wr - sent, &ret);
    					if (cryptStatusOK(status))
    						FlushData(ssh_session);
    				}
    				if (cryptStatusError(status)) {
    					pthread_mutex_unlock(&ssh_mutex);
    					if (!conn_api.terminate) {
    						conn_api.terminate = true;
    						if ((status != CRYPT_ERROR_COMPLETE) && (status != CRYPT_ERROR_NOTFOUND)) /* connection closed */
    							cryptlib_error_message(status, "sending data");
    					}
    					break;
    				}
    				pthread_mutex_unlock(&ssh_mutex);
    				sent += ret;
    			}
    			pthread_mutex_unlock(&ssh_tx_mutex);
    		}
    		else {
    			pthread_mutex_unlock(&(conn_outbuf.mutex));
    		}
    	}
    	conn_api.terminate = true;
    	shutdown(ssh_sock, SHUT_RDWR);
    	conn_api.output_thread_running = 2;
    }
    
    static bool
    sftp_send(uint8_t *buf, size_t sz, void *cb_data)
    {
    	size_t sent = 0;
    	int active = 1;
    
    	if (sz == 0)
    		return true;
    	pthread_mutex_lock(&ssh_tx_mutex);
    	while (sent < sz && !conn_api.terminate) {
    		int status;
    		int ret = 0;
    		pthread_mutex_lock(&ssh_mutex);
    		FlushData(ssh_session);
    		status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, sftp_channel);
    		if (cryptStatusOK(status)) {
    			active = 0;
    			status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_OPEN, &active);
    			if (cryptStatusOK(status) && active)
    				status = PushData(ssh_session, buf + sent, sz - sent, &ret);
    		}
    		pthread_mutex_unlock(&ssh_mutex);
    		if (cryptStatusError(status)) {
    			if (status != CRYPT_ERROR_COMPLETE && status != CRYPT_ERROR_NOTFOUND) { /* connection closed */
    				if (!conn_api.terminate)
    					cryptlib_error_message(status, "sending sftp data");
    			}
    			break;
    		}
    		sent += ret;
    	}
    	pthread_mutex_unlock(&ssh_tx_mutex);
    	return sent == sz;
    }
    
    static char *
    get_public_key(CRYPT_CONTEXT ctx)
    {
    	int sz;
    	int status;
    	char *raw;
    	char *ret;
    	size_t rsz;
    
    	pthread_mutex_lock(&ssh_mutex);
    	status = cryptGetAttributeString(ctx, CRYPT_CTXINFO_SSH_PUBLIC_KEY, NULL, &sz);
    	pthread_mutex_unlock(&ssh_mutex);
    	if (cryptStatusOK(status)) {
    		raw = malloc(sz);
    		if (raw != NULL) {
    			pthread_mutex_lock(&ssh_mutex);
    			status = cryptGetAttributeString(ctx, CRYPT_CTXINFO_SSH_PUBLIC_KEY, raw, &sz);
    			pthread_mutex_unlock(&ssh_mutex);
    			if (cryptStatusOK(status)) {
    				rsz = (sz - 4) * 4 / 3 + 3;
    				ret = malloc(rsz);
    				if (ret != NULL) {
    					b64_encode(ret, rsz, raw + 4, sz - 4);
    					free(raw);
    					return ret;
    				}
    			}
    			free(raw);
    		}
    	}
    	return NULL;
    }
    
    static bool
    key_not_present(sftp_filehandle_t f, const char *priv)
    {
    	size_t bufsz = 0;
    	size_t old_bufpos = 0;
    	size_t bufpos = 0;
    	size_t off = 0;
    	size_t eol;
    	char *buf = NULL;
    	char *newbuf;
    	char *eolptr;
    	sftp_str_t r = NULL;
    	bool skipread = false;
    
    	while (!conn_api.terminate) {
    		if (skipread) {
    			old_bufpos = 0;
    			skipread = false;
    		}
    		else {
    			if (bufsz - bufpos < 1024) {
    				newbuf = realloc(buf, bufsz + 4096);
    				if (newbuf == NULL) {
    					free(buf);
    					return false;
    				}
    				buf = newbuf;
    				bufsz += 4096;
    			}
    			if (!sftpc_read(sftp_state, f, off, (bufsz - bufpos > 1024) ? 1024 : bufsz - bufpos, &r)) {
    				free(buf);
    				if (sftpc_get_err(sftp_state) == SSH_FX_EOF)
    					return true;
    				return false;
    			}
    			memcpy(&buf[bufpos], r->c_str, r->len);
    			old_bufpos = bufpos;
    			bufpos += r->len;
    			off += r->len;
    			free_sftp_str(r);
    			r = NULL;
    		}
    		for (eol = old_bufpos; eol < bufpos; eol++) {
    			if (buf[eol] == '\r' || buf[eol] == '\n')
    				break;
    		}
    		if (eol < bufpos) {
    			skipread = true;
    			eolptr = &buf[eol];
    			*eolptr = 0;
    			if (strstr(buf, priv) != NULL) {
    				free(buf);
    				return false;
    			}
    			*eolptr = '\n';
    			while (eol < bufpos && (buf[eol] == '\r' || buf[eol] == '\n'))
    				eol++;
    			memmove(buf, &buf[eol], bufpos - eol);
    			bufpos = bufpos - eol;
    		}
    	}
    	free(buf);
    	return false;
    }
    
    static void
    add_public_key(void *vpriv)
    {
    	int status;
    	int active;
    	int new_sftp_channel = -1;
    	char *priv = vpriv;
    
    	// Wait for at most five seconds for channel to be fully active
    	active = 0;
    	for (unsigned sleep_count = 0; sleep_count < 500 && conn_api.terminate == 0; sleep_count++) {
    		pthread_mutex_lock(&ssh_mutex);
    		if (ssh_channel != -1) {
    			status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, ssh_channel);
    			if (cryptStatusOK(status))
    				status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, &active);
    			if (cryptStatusOK(status) && active) {
    				pthread_mutex_unlock(&ssh_mutex);
    				break;
    			}
    		}
    		pthread_mutex_unlock(&ssh_mutex);
    		if (conn_api.terminate)
    			break;
    		SLEEP(10);
    	};
    	if (!active) {
    		pubkey_thread_running = false;
    		free(priv);
    		return;
    	}
    	pthread_mutex_lock(&ssh_tx_mutex);
    	pthread_mutex_lock(&ssh_mutex);
    	FlushData(ssh_session);
    	status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, CRYPT_UNUSED);
    	if (cryptStatusError(status)) {
    		pthread_mutex_unlock(&ssh_mutex);
    		cryptlib_error_message(status, "setting new channel");
    	} else {
    		status = cryptSetAttributeString(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TYPE, "subsystem", 9);
    		if (cryptStatusError(status)) {
    			pthread_mutex_unlock(&ssh_mutex);
    			cryptlib_error_message(status, "setting channel type");
    		} else {
    			status = cryptSetAttributeString(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ARG1, "sftp", 4);
    			if (cryptStatusError(status)) {
    				pthread_mutex_unlock(&ssh_mutex);
    				cryptlib_error_message(status, "setting subsystem");
    			} else {
    				status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, &new_sftp_channel);
    				if (cryptStatusError(status)) {
    					sftp_channel = new_sftp_channel = -1;
    					pthread_mutex_unlock(&ssh_mutex);
    					cryptlib_error_message(status, "getting new channel");
    				}
    				else if (new_sftp_channel == -1) { // Shouldn't be possible...
    					sftp_channel = new_sftp_channel = -1;
    					pthread_mutex_unlock(&ssh_mutex);
    				}
    			}
    		}
    	}
    	if (new_sftp_channel != -1) {
    		status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_OPEN, &active);
    		if (cryptStatusError(status) || !active) {
    			cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0);
    			pthread_mutex_unlock(&ssh_mutex);
    			pthread_mutex_unlock(&ssh_tx_mutex);
    			free(priv);
    			pubkey_thread_running = false;
    			return;
    		}
    		status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 1);
    		if (cryptStatusError(status) && status != CRYPT_ENVELOPE_RESOURCE) {
    			pthread_mutex_unlock(&ssh_mutex);
    			pthread_mutex_unlock(&ssh_tx_mutex);
    			close_sftp_channel(new_sftp_channel);
    			free(priv);
    			pubkey_thread_running = false;
    			return;
    		}
    		int sc = new_sftp_channel;
    		pthread_mutex_unlock(&ssh_mutex);
    		pthread_mutex_unlock(&ssh_tx_mutex);
    		active = 0;
    		for (unsigned sleep_count = 0; sleep_count < 500 && conn_api.terminate == 0; sleep_count++) {
    			pthread_mutex_lock(&ssh_mutex);
    			status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, new_sftp_channel);
    			if (cryptStatusOK(status))
    				status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, &active);
    			pthread_mutex_unlock(&ssh_mutex);
    			if (cryptStatusOK(status) && active)
    				break;
    			SLEEP(10);
    		}
    		if (!active) {
    			close_sftp_channel(sc);
    			free(priv);
    			pubkey_thread_running = false;
    			return;
    		}
    		/*
    		 * Old version of Synchronet will accept the channel, then
    		 * immediately close it.  If we then send data on the channel,
    		 * it will get mixed in with the first channels data because
    		 * it doesn't have the channel patches.
    		 * 
    		 * To avoid that, we'll sleep for a second to allow
    		 * the remote to close the channel if it wants to.
    		 */
    		for (unsigned sleep_count = 0; sleep_count < 100 && !conn_api.terminate; sleep_count++) {
    			SLEEP(10);
    		}
    		pthread_mutex_lock(&ssh_tx_mutex);
    		pthread_mutex_lock(&ssh_mutex);
    		if (conn_api.terminate || !check_channel_open(&new_sftp_channel)) {
    			pthread_mutex_unlock(&ssh_mutex);
    			pthread_mutex_unlock(&ssh_tx_mutex);
    			free(priv);
    			pubkey_thread_running = false;
    			return;
    		}
    		sftp_channel = new_sftp_channel;
    		pthread_mutex_unlock(&ssh_mutex);
    		pthread_mutex_unlock(&ssh_tx_mutex);
    		sftp_state = sftpc_begin(sftp_send, NULL);
    		if (sftp_state == NULL) {
    			close_sftp_channel(new_sftp_channel);
    			free(priv);
    			pubkey_thread_running = false;
    			return;
    		}
    		if (sftpc_init(sftp_state)) {
    			sftp_filehandle_t f = NULL;
    			// TODO: Add permissions?
    
    			if (sftpc_open(sftp_state, ".ssh/authorized_keys", SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND | SSH_FXF_CREAT, NULL, &f)) {
    				/* Read through the file looking for our key */
    				if (key_not_present(f, priv)) {
    					// TODO: Types other than RSA...
    					sftp_str_t ln =  sftp_asprintf("ssh-rsa %s Added by SyncTERM\n", priv);
    					if (ln != NULL) {
    						sftpc_write(sftp_state, f, 0, ln);
    						free_sftp_str(ln);
    					}
    				}
    				sftpc_close(sftp_state, &f);
    			}
    			sftpc_finish(sftp_state);
    		}
    		close_sftp_channel(new_sftp_channel);
    	}
    	else {
    		pthread_mutex_unlock(&ssh_tx_mutex);
    	}
    	free(priv);
    	pubkey_thread_running = false;
    }
    
    static void
    error_popup(struct bbslist *bbs, const char *blurb, int status)
    {
    	if (ssh_sock != INVALID_SOCKET) {
    		closesocket(ssh_sock);
    		ssh_sock = INVALID_SOCKET;
    	}
    	if (!bbs->hidepopups)
    		cryptlib_error_message(status, blurb);
    	conn_api.terminate = true;
    	if (!bbs->hidepopups)
    		uifc.pop(NULL);
    }
    
    #define KEY_PASSWORD "TODO:ThisIsDumb"
    #define KEY_LABEL "ssh_key"
    int
    ssh_connect(struct bbslist *bbs)
    {
    	int           off = 1;
    	int           status;
    	char          password[MAX_PASSWD_LEN + 1];
    	char          username[MAX_USER_LEN + 1];
    	int           rows, cols;
    	const char   *term;
    	int           slen;
    	uint8_t       server_fp[sizeof(bbs->ssh_fingerprint)];
    	char          path[MAX_PATH+1];
    	CRYPT_KEYSET  ssh_keyset;
    	CRYPT_CONTEXT ssh_context;
    	char         *pubkey = NULL;
    
    	if (!bbs->hidepopups)
    		init_uifc(true, true);
    	pthread_mutex_init(&ssh_mutex, NULL);
    	pthread_mutex_lock(&ssh_mutex);
    	ssh_channel = -1;
    	sftp_channel = -1;
    	ssh_socket = INVALID_SOCKET;
    	pthread_mutex_unlock(&ssh_mutex);
    	pthread_mutex_init(&ssh_tx_mutex, NULL);
    
    	get_syncterm_filename(path, sizeof(path), SYNCTERM_PATH_KEYS, false);
    	if(cryptStatusOK(cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, path, CRYPT_KEYOPT_READONLY))) {
    		status = cryptGetPrivateKey(ssh_keyset, &ssh_context, CRYPT_KEYID_NAME, KEY_LABEL, KEY_PASSWORD);
    		if(cryptStatusError(status)) {
    			error_popup(bbs, "creating context", status);
    		}
    		if (cryptStatusError(cryptKeysetClose(ssh_keyset))) {
    			error_popup(bbs, "closing keyset", status);
    		}
    	}
    	else {
    		do {
    			/* Couldn't do that... create a new context and use the key from there... */
    			status = cryptCreateContext(&ssh_context, CRYPT_UNUSED, CRYPT_ALGO_RSA);
    			if (cryptStatusError(status)) {
    				error_popup(bbs, "creating context", status);
    				break;
    			}
    			status = cryptSetAttributeString(ssh_context, CRYPT_CTXINFO_LABEL, KEY_LABEL, strlen(KEY_LABEL));
    			if (cryptStatusError(status)) {
    				error_popup(bbs, "setting label", status);
    				break;
    			}
    			status = cryptGenerateKey(ssh_context);
    			if (cryptStatusError(status)) {
    				error_popup(bbs, "generating key", status);
    				break;
    			}
    
    			/* Ok, now try saving this one... use the syspass to encrypt it. */
    			status = cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, path, CRYPT_KEYOPT_CREATE);
    			if (cryptStatusError(status)) {
    				error_popup(bbs, "creating keyset", status);
    				break;
    			}
    			status = cryptAddPrivateKey(ssh_keyset, ssh_context, KEY_PASSWORD);
    			if (cryptStatusError(status)) {
    				cryptKeysetClose(ssh_keyset);
    				error_popup(bbs, "adding private key", status);
    				break;
    			}
    			status = cryptKeysetClose(ssh_keyset);
    			if (cryptStatusError(status)) {
    				error_popup(bbs, "closing keyset", status);
    				break;
    			}
    		} while(0);
    	}
    	if (cryptStatusError(status)) {
    		cryptDestroyContext(ssh_context);
    		ssh_context = -1;
    	}
    
    	ssh_sock = conn_socket_connect(bbs);
    	if (ssh_sock == INVALID_SOCKET)
    		return -1;
    
    	if (!bbs->hidepopups)
    		uifc.pop("Creating Session");
    	status = cryptCreateSession(&ssh_session, CRYPT_UNUSED, CRYPT_SESSION_SSH);
    	if (cryptStatusError(status)) {
    		error_popup(bbs, "creating session", status);
    		return -1;
    	}
    
    	/* we need to disable Nagle on the socket. */
    	if (setsockopt(ssh_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&off, sizeof(off)))
    		fprintf(stderr, "%s:%d: Error %d calling setsockopt()\n", __FILE__, __LINE__, errno);
    
    	SAFECOPY(password, bbs->password);
    	SAFECOPY(username, bbs->user);
    
    	if (!bbs->hidepopups)
    		uifc.pop(NULL);
    
    	if (!username[0]) {
    		if (bbs->hidepopups)
    			init_uifc(false, false);
    		uifcinput("UserID", MAX_USER_LEN, username, 0, "No stored UserID.");
    		if (bbs->hidepopups)
    			uifcbail();
    	}
    
    	if (!bbs->hidepopups)
    		uifc.pop("Setting Username");
    
    	/* Add username/password */
    	status = cryptSetAttributeString(ssh_session, CRYPT_SESSINFO_USERNAME, username, strlen(username));
    	if (cryptStatusError(status)) {
    		error_popup(bbs, "setting username", status);
    		return -1;
    	}
    
    	if (!bbs->hidepopups)
    		uifc.pop(NULL);
    	if (bbs->conn_type == CONN_TYPE_SSHNA) {
    		status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_OPTIONS, CRYPT_SSHOPTION_NONE_AUTH);
    		if (cryptStatusError(status)) {
    			error_popup(bbs, "disabling password auth", status);
    			return -1;
    		}
    	}
    	else {
    		if (!password[0] && ssh_context == -1) {
    			if (bbs->hidepopups)
    				init_uifc(false, false);
    			uifcinput("Password", MAX_PASSWD_LEN, password, K_PASSWORD, "Incorrect password.  Try again.");
    			if (bbs->hidepopups)
    				uifcbail();
    		}
    
    		if (password[0]) {
    			if (!bbs->hidepopups)
    				uifc.pop("Setting Password");
    			status = cryptSetAttributeString(ssh_session, CRYPT_SESSINFO_PASSWORD, password, strlen(password));
    			if (cryptStatusError(status)) {
    				error_popup(bbs, "setting password", status);
    				return -1;
    			}
    		}
    
    		if (ssh_context != -1) {
    			if (!bbs->hidepopups)
    				uifc.pop("Setting Private Key");
    			pubkey = get_public_key(ssh_context);
    			status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_PRIVATEKEY, ssh_context);
    			cryptDestroyContext(ssh_context);
    			ssh_context = -1;
    			if (cryptStatusError(status)) {
    				free(pubkey);
    				error_popup(bbs, "setting private key", status);
    				return -1;
    			}
    		}
    	}
    
    	if (!bbs->hidepopups) {
    		uifc.pop(NULL);
    		uifc.pop("Setting Username");
    	}
    
    	/* Pass socket to cryptlib */
    	status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_NETWORKSOCKET, ssh_sock);
    	if (cryptStatusError(status)) {
    		free(pubkey);
    		error_popup(bbs, "passing socket", status);
    		return -1;
    	}
    
    	// We need to set the channel so we can set channel attributes.
    	status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, CRYPT_UNUSED);
    
    	if (!bbs->hidepopups) {
    		uifc.pop(NULL);
    		uifc.pop("Setting Terminal Type");
    	}
    	term = get_emulation_str(get_emulation(bbs));
    	status = cryptSetAttributeString(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TERMINAL, term, strlen(term));
    
    	get_term_win_size(&cols, &rows, NULL, NULL, &bbs->nostatus);
    
    	if (!bbs->hidepopups) {
    		uifc.pop(NULL);
    		uifc.pop("Setting Terminal Width");
    	}
    	status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_WIDTH, cols);
    
    	if (!bbs->hidepopups) {
    		uifc.pop(NULL);
    		uifc.pop("Setting Terminal Height");
    	}
    	status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_HEIGHT, rows);
    
    	cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 30);
    	cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_WRITETIMEOUT, 30);
    
    	/* Activate the session */
    	if (!bbs->hidepopups) {
    		uifc.pop(NULL);
    		uifc.pop("Activating Session");
    	}
    
    	do {
    		status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_ACTIVE, 1);
    		if (status == CRYPT_ENVELOPE_RESOURCE) {
    			// This will fail the first time through since there is no password.
    			cryptDeleteAttribute(ssh_session, CRYPT_SESSINFO_PASSWORD);
    			if (bbs->hidepopups)
    				init_uifc(false, false);
    			password[0] = 0;
    			uifcinput("Password", MAX_PASSWD_LEN, password, K_PASSWORD, "Incorrect password.  Try again.");
    			if (bbs->hidepopups)
    				uifcbail();
    			status = cryptSetAttributeString(ssh_session, CRYPT_SESSINFO_PASSWORD, password, strlen(password));
    			if (cryptStatusOK(status))
    				status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_AUTHRESPONSE, 1);
    		}
    	} while (status == CRYPT_ENVELOPE_RESOURCE);
    	if (cryptStatusError(status)) {
    		free(pubkey);
    		error_popup(bbs, "activating session", status);
    		return -1;
    	}
    	FlushData(ssh_session);
    
    	if (!bbs->hidepopups) {
    		uifc.pop(NULL);
    		uifc.pop("Clearing Ownership");
    	}
    	status = cryptSetAttribute(ssh_session, CRYPT_PROPERTY_OWNER, CRYPT_UNUSED);
    	if (cryptStatusError(status)) {
    		free(pubkey);
    		error_popup(bbs, "clearing session ownership", status);
    		return -1;
    	}
    	if (!bbs->hidepopups) {
                    /* Clear ownership */
    		uifc.pop(NULL);
    		uifc.pop("Getting SSH Channel");
    	}
    	// Using ssh_channel outside of ssh_mutex (which doesn't exist yet)
    	/* coverity[missing_lock:SUPPRESS] */
    	status = cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, &ssh_channel);
    	if (cryptStatusError(status) || ssh_channel == -1) {
    		free(pubkey);
    		error_popup(bbs, "getting ssh channel", status);
    		return -1;
    	}
    
    	memset(server_fp, 0, sizeof(server_fp));
    	status = cryptGetAttributeString(ssh_session, CRYPT_SESSINFO_SERVER_FINGERPRINT_SHA1, server_fp, &slen);
    	if (cryptStatusOK(status)) {
    		if (memcmp(bbs->ssh_fingerprint, server_fp, sizeof(server_fp))) {
    			static const char * const opts[4] = {"Disconnect", "Update", "Ignore", ""};
    			FILE *listfile;
    			str_list_t inifile;
    			char fpstr[41];
    			int i;
    
    			slen = 0;
    			for (i = 0; i < sizeof(server_fp); i++) {
    				sprintf(&fpstr[i * 2], "%02x", server_fp[i]);
    			}
    			fpstr[sizeof(fpstr)-1] = 0;
    			if (bbs->has_fingerprint) {
    				char ofpstr[41];
    
    				for (i = 0; i < sizeof(server_fp); i++) {
    					sprintf(&ofpstr[i * 2], "%02x", bbs->ssh_fingerprint[i]);
    				}
    				ofpstr[sizeof(ofpstr)-1] = 0;
    				asprintf(&uifc.helpbuf, "`Fingerprint Changed`\n\n"
    				    "The server fingerprint has changed from the last known good connection.\n"
    				    "This may indicate someone is evesdropping on your connection.\n"
    				    "It is also possible that a host key has just been changed.\n"
    				    "\n"
    				    "Last known fingerprint: %s\n"
    				    "Fingerprint sent now:   %s\n", ofpstr, fpstr);
    				i = uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &slen, NULL, "Fingerprint Changed", (char **)opts);
    				free(uifc.helpbuf);
    			}
    			else
    				i = 1;
    			switch(i) {
    				case 1:
    					if ((listfile = fopen(settings.list_path, "r")) != NULL) {
    						inifile = iniReadFile(listfile);
    						fclose(listfile);
    						iniSetString(&inifile, bbs->name, "SSHFingerprint", fpstr, &ini_style);
    						if ((listfile = fopen(settings.list_path, "w")) != NULL) {
    							iniWriteFile(listfile, inifile);
    							fclose(listfile);
    						}
    						strListFree(&inifile);
    					}
    					break;
    				case 2:
    					break;
    				default:
    					free(pubkey);
    					if (!bbs->hidepopups)
    						uifc.pop(NULL);
    					return -1;
    			}
    		}
    		bbs->has_fingerprint = true;
    	}
    
    	if (!bbs->hidepopups)
    		uifc.pop(NULL);
    
    	create_conn_buf(&conn_inbuf, BUFFER_SIZE);
    	create_conn_buf(&conn_outbuf, BUFFER_SIZE);
    	conn_api.rd_buf = (unsigned char *)malloc(BUFFER_SIZE);
    	conn_api.rd_buf_size = BUFFER_SIZE;
    	conn_api.wr_buf = (unsigned char *)malloc(BUFFER_SIZE);
    	conn_api.wr_buf_size = BUFFER_SIZE;
    
    	_beginthread(ssh_output_thread, 0, NULL);
    	_beginthread(ssh_input_thread, 0, NULL);
    	if (bbs->sftp_public_key) {
    		pubkey_thread_running = true;
    		_beginthread(add_public_key, 0, pubkey);
    	}
    	else {
    		free(pubkey);
    	}
    
    	if (!bbs->hidepopups)
    		uifc.pop(NULL); // TODO: Why is this called twice?
    
    	return 0;
    }
    
    int
    ssh_close(void)
    {
    	char garbage[1024];
    
    	conn_api.terminate = true;
    	cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 0);
    	cryptSetAttribute(ssh_session, CRYPT_OPTION_NET_WRITETIMEOUT, 0);
    	if (sftp_state)
    		sftpc_finish(sftp_state);
    	while (conn_api.input_thread_running == 1 || conn_api.output_thread_running == 1 || pubkey_thread_running) {
    		conn_recv_upto(garbage, sizeof(garbage), 0);
    		SLEEP(1);
    	}
    	pthread_mutex_lock(&ssh_mutex);
    	int sc = sftp_channel;
    	pthread_mutex_unlock(&ssh_mutex);
    	if (sc != -1)
    		close_sftp_channel(sc);
    	if (sftp_state)
    		sftpc_end(sftp_state);
    	close_ssh_channel();
    	cryptDestroySession(ssh_session);
    	if (ssh_sock != INVALID_SOCKET) {
    		closesocket(ssh_sock);
    		ssh_sock = INVALID_SOCKET;
    	}
    	destroy_conn_buf(&conn_inbuf);
    	destroy_conn_buf(&conn_outbuf);
    	FREE_AND_NULL(conn_api.rd_buf);
    	FREE_AND_NULL(conn_api.wr_buf);
    	pthread_mutex_destroy(&ssh_mutex);
    	pthread_mutex_destroy(&ssh_tx_mutex);
    	return 0;
    }