Select Git revision
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;
}