From a104bab10da71f22e5ccda741ff4043feeda4f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net> Date: Wed, 17 Jan 2024 20:24:48 -0500 Subject: [PATCH] Move SSH authentication into answer() This should fix a long-standing issue where someone could connect to the SSH port and do nothing, which would prevent other incoming terminal sessions from being accepted until it times out. Unfortunately, this means that Synchronet can't send any data until authentication is completed, which means useful messages about why you're being disconnected (ie: "Sorry, all terminal nodes are in use or otherwise unavailable.") as well as usless information nobody ever cares about (ie: The IP you're connecting from, that it is resolving your hostname, etc). can no longer be sent to the user. --- src/sbbs3/answer.cpp | 164 ++++++++++++++++++++++++++++--------------- src/sbbs3/main.cpp | 106 ++++++++++------------------ src/sbbs3/sbbs.h | 3 + src/sbbs3/ssl.c | 8 ++- 4 files changed, 150 insertions(+), 131 deletions(-) diff --git a/src/sbbs3/answer.cpp b/src/sbbs3/answer.cpp index 4da4569825..9049256e1e 100644 --- a/src/sbbs3/answer.cpp +++ b/src/sbbs3/answer.cpp @@ -25,6 +25,20 @@ extern "C" void client_on(SOCKET sock, client_t* client, BOOL update); +bool +sbbs_t::set_authresponse(bool activate_ssh) +{ + int status; + + lprintf(LOG_DEBUG, "%04d SSH Setting attribute: SESSINFO_AUTHRESPONSE", client_socket); + status = cryptSetAttribute(ssh_session, CRYPT_SESSINFO_AUTHRESPONSE, activate_ssh); + if(cryptStatusError(status)) { + log_crypt_error_status_sock(status, "setting auth response"); + return false; + } + return true; +} + bool sbbs_t::answer() { char str[MAX_PATH+1],str2[MAX_PATH+1],c; @@ -177,29 +191,47 @@ bool sbbs_t::answer() } #ifdef USE_CRYPTLIB if(sys_status&SS_SSH) { + int ssh_failed=0; + bool activate_ssh = false; + tmp[0]=0; pthread_mutex_lock(&ssh_mutex); - ctmp = get_crypt_attribute(ssh_session, CRYPT_SESSINFO_USERNAME); - if (ctmp) { - SAFECOPY(rlogin_name, parse_login(ctmp)); - free_crypt_attrstr(ctmp); - ctmp = get_crypt_attribute(ssh_session, CRYPT_SESSINFO_PASSWORD); + for(ssh_failed=0; ssh_failed < 3; ssh_failed++) { + lprintf(LOG_DEBUG, "%04d SSH Setting attribute: SESSINFO_ACTIVE", client_socket); + if(cryptStatusError(i=cryptSetAttribute(ssh_session, CRYPT_SESSINFO_ACTIVE, 1))) { + log_crypt_error_status_sock(i, "setting session active"); + activate_ssh = false; + // TODO: Add private key here... + if(i != CRYPT_ENVELOPE_RESOURCE) { + break; + } + } + else { + break; + } + ctmp = get_crypt_attribute(ssh_session, CRYPT_SESSINFO_USERNAME); if (ctmp) { - SAFECOPY(tmp, ctmp); + SAFECOPY(rlogin_name, parse_login(ctmp)); free_crypt_attrstr(ctmp); + ctmp = get_crypt_attribute(ssh_session, CRYPT_SESSINFO_PASSWORD); + if (ctmp) { + SAFECOPY(tmp, ctmp); + free_crypt_attrstr(ctmp); + } + lprintf(LOG_DEBUG,"SSH login: '%s'", rlogin_name); } - pthread_mutex_unlock(&ssh_mutex); - lprintf(LOG_DEBUG,"SSH login: '%s'", rlogin_name); - } - else { - rlogin_name[0] = 0; - pthread_mutex_unlock(&ssh_mutex); - } - useron.number = find_login_id(&cfg, rlogin_name); - if(useron.number) { - getuserdat(&cfg,&useron); - for(i=0;i<3 && online;i++) { - if(stricmp(tmp,useron.pass)) { + else { + rlogin_name[0] = 0; + continue; + } + useron.number = find_login_id(&cfg, rlogin_name); + if(useron.number) { + getuserdat(&cfg,&useron); + if (stricmp(tmp, useron.pass) == 0) { + SAFECOPY(rlogin_pass, tmp); + activate_ssh = set_authresponse(true); + } + else if(ssh_failed) { if(cfg.sys_misc&SM_ECHO_PW) safe_snprintf(str,sizeof(str),"(%04u) %-25s FAILED Password attempt: '%s'" ,useron.number,useron.alias,tmp); @@ -208,53 +240,68 @@ bool sbbs_t::answer() ,useron.number,useron.alias); logline(LOG_NOTICE,"+!",str); badlogin(useron.alias, tmp); - rioctl(IOFI); /* flush input buffer */ - bputs(text[InvalidLogon]); - bputs(text[PasswordPrompt]); - console|=CON_R_ECHOX; - getstr(tmp,LEN_PASS*2,K_UPPER|K_LOWPRIO|K_TAB); - console&=~(CON_R_ECHOX|CON_L_ECHOX); - } - else { - SAFECOPY(rlogin_pass, tmp); - if(REALSYSOP && (cfg.sys_misc&SM_SYSPASSLOGIN) && (cfg.sys_misc&SM_R_SYSOP)) { - rioctl(IOFI); /* flush input buffer */ - if(!chksyspass()) - bputs(text[InvalidLogon]); - else { - i=0; - break; - } - } - else { - i = 0; - break; - } + useron.number=0; } } - if(i) { - if(stricmp(tmp,useron.pass)) { - if(cfg.sys_misc&SM_ECHO_PW) - safe_snprintf(str,sizeof(str),"(%04u) %-25s FAILED Password attempt: '%s'" - ,useron.number,useron.alias,tmp); + else { + if(cfg.sys_misc&SM_ECHO_PW) + lprintf(LOG_NOTICE, "SSH !UNKNOWN USER: '%s' (password: %s)", rlogin_name, truncsp(tmp)); + else + lprintf(LOG_NOTICE, "SSH !UNKNOWN USER: '%s'", rlogin_name); + badlogin(rlogin_name, tmp); + // Enable SSH so we can create a new user... + activate_ssh = set_authresponse(true); + } + } + if (activate_ssh) { + int cid; + char tname[1024]; + int tnamelen; + + ssh_failed=0; + // Check the channel ID and name... + if (cryptStatusOK(i=cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, &cid))) { + if (i == CRYPT_OK) { + tnamelen = 0; + i=cryptGetAttributeString(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TYPE, tname, &tnamelen); + log_crypt_error_status_sock(i, "getting channel type"); + if (tnamelen != 7 || strnicmp(tname, "session", 7)) { + lprintf(LOG_NOTICE, "%04d SSH [%s] active channel '%.*s' is not 'session', disconnecting.", client_socket, client_ipaddr, tnamelen, tname); + badlogin(/* user: */NULL, /* passwd: */NULL, "SSH", &client_addr, /* delay: */false); + // Fail because there's no session. + activate_ssh = false; + } else - safe_snprintf(str,sizeof(str),"(%04u) %-25s FAILED Password attempt" - ,useron.number,useron.alias); - logline(LOG_NOTICE,"+!",str); - badlogin(useron.alias, tmp); - bputs(text[InvalidLogon]); + session_channel = cid; } - useron.number=0; - hangup(); + } + else { + log_crypt_error_status_sock(i, "getting channel id"); + if (i == CRYPT_ERROR_PERMISSION) + lprintf(LOG_CRIT, "!Your cryptlib build is obsolete, please update"); } } - else { - if(cfg.sys_misc&SM_ECHO_PW) - lprintf(LOG_NOTICE, "SSH !UNKNOWN USER: '%s' (password: %s)", rlogin_name, truncsp(tmp)); - else - lprintf(LOG_NOTICE, "SSH !UNKNOWN USER: '%s'", rlogin_name); - badlogin(rlogin_name, tmp); + if (activate_ssh) { + if(cryptStatusError(i=cryptSetAttribute(ssh_session, CRYPT_PROPERTY_OWNER, CRYPT_UNUSED))) { + log_crypt_error_status_sock(i, "clearing owner"); + activate_ssh = false; + } + } + if(!activate_ssh) { + int status; + lprintf(LOG_NOTICE, "%04d SSH [%s] session establishment failed", client_socket, client_ipaddr); + if (cryptStatusError(status = cryptDestroySession(ssh_session))) { + lprintf(LOG_ERR, "%04d SSH ERROR %d destroying Cryptlib Session %d from %s line %d" + , client_socket, status, ssh_session, __FILE__, __LINE__); + } + ssh_mode = false; + pthread_mutex_unlock(&ssh_mutex); + close_socket(client_socket); + useron.number = 0; + return false; } + SetEvent(ssh_active); + if (cryptStatusOK(cryptGetAttribute(ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_WIDTH, &l)) && l > 0) { cols = l; lprintf(LOG_DEBUG, "%04d SSH [%s] height %d", client_socket, client.addr, cols); @@ -271,6 +318,7 @@ bool sbbs_t::answer() terminal[sizeof(terminal)-1] = 0; lprintf(LOG_DEBUG, "%04d SSH [%s] term: %s", client_socket, client.addr, terminal); } + pthread_mutex_unlock(&ssh_mutex); } #endif diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp index c6302f9c1a..d4b698ecd8 100644 --- a/src/sbbs3/main.cpp +++ b/src/sbbs3/main.cpp @@ -274,6 +274,20 @@ int lprintf(int level, const char *fmt, ...) return(lputs(level,sbuf)); } +void +sbbs_t::log_crypt_error_status_sock(int status, const char *action) +{ + char *estr; + int level; + get_crypt_error_string(status, ssh_session, &estr, action, &level); + if (estr) { + if (level < startup->ssh_error_level) + level = startup->ssh_error_level; + lprintf(level, "%04d SSH %s", client_socket, estr); + free_crypt_attrstr(estr); + } +} + /* Picks the right log callback function (event or term) based on the sbbs->cfg.node_num value */ /* Prepends the current node number and user alias (if applicable) */ int sbbs_t::lputs(int level, const char* str) @@ -1929,7 +1943,7 @@ static int crypt_pop_channel_data(sbbs_t *sbbs, char *inbuf, int want, int *got) continue; if (cid != sbbs->session_channel) { if (cryptStatusError(status = cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, cid))) { - GCESS(status, sbbs->client_socket, sbbs->ssh_session, "setting channel"); + sbbs->log_crypt_error_status_sock(status, "setting channel"); return status; } cname = get_crypt_attribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TYPE); @@ -1939,7 +1953,7 @@ static int crypt_pop_channel_data(sbbs_t *sbbs, char *inbuf, int want, int *got) free_crypt_attrstr(cname); closing_channel = cid; if (cryptStatusError(status = cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE, 0))) { - GCESS(status, sbbs->client_socket, sbbs->ssh_session, "closing channel"); + sbbs->log_crypt_error_status_sock(status, "closing channel"); return status; } continue; @@ -1951,7 +1965,7 @@ static int crypt_pop_channel_data(sbbs_t *sbbs, char *inbuf, int want, int *got) * and it was destroyed, so it's no longer possible to get the channel id. */ if (status != CRYPT_ERROR_NOTFOUND) - GCESS(status, sbbs->client_socket, sbbs->ssh_session, "getting channel id"); + sbbs->log_crypt_error_status_sock(status, "getting channel id"); closing_channel = -1; } } @@ -2090,6 +2104,10 @@ void input_thread(void *arg) #ifdef USE_CRYPTLIB if(sbbs->ssh_mode && sock==sbbs->client_socket) { int err; + if (WaitForEvent(sbbs->ssh_active, 1000) == WAIT_TIMEOUT) { + pthread_mutex_unlock(&sbbs->input_thread_mutex); + continue; + } pthread_mutex_lock(&sbbs->ssh_mutex); if(cryptStatusError((err=crypt_pop_channel_data(sbbs, (char*)inbuf, rd, &i)))) { pthread_mutex_unlock(&sbbs->ssh_mutex); @@ -3661,6 +3679,7 @@ bool sbbs_t::init() #ifdef USE_CRYPTLIB pthread_mutex_init(&ssh_mutex,NULL); ssh_mutex_created = true; + ssh_active = CreateEvent(NULL, TRUE, FALSE, (void *)"ssh_active"); #endif pthread_mutex_init(&input_thread_mutex,NULL); input_thread_mutex_created = true; @@ -3779,6 +3798,12 @@ sbbs_t::~sbbs_t() #ifdef USE_CRYPTLIB while(ssh_mutex_created && pthread_mutex_destroy(&ssh_mutex)==EBUSY) mswait(1); + if (ssh_active) { + SetEvent(ssh_active); + while ((!CloseEvent(ssh_active)) && errno == EBUSY) + mswait(1); + ssh_active = nullptr; + } #endif while(input_thread_mutex_created && pthread_mutex_destroy(&input_thread_mutex)==EBUSY) mswait(1); @@ -5389,7 +5414,6 @@ NO_SSH: /* Do SSH stuff here */ #ifdef USE_CRYPTLIB if(ssh) { - int ssh_failed=0; BOOL nodelay = TRUE; ulong nb = 0; @@ -5403,10 +5427,10 @@ NO_SSH: sbbs->ssh_mode = true; if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_CONNECTTIMEOUT, startup->ssh_connect_timeout))) - GCESS(i, client_socket, sbbs->ssh_session, "setting connect timeout"); + sbbs->log_crypt_error_status_sock(i, "setting connect timeout"); if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_PRIVATEKEY, ssh_context))) { - GCESS(i, client_socket, sbbs->ssh_session, "setting private key"); + sbbs->log_crypt_error_status_sock(i, "setting private key"); SSH_END(client_socket); close_socket(client_socket); continue; @@ -5414,81 +5438,22 @@ NO_SSH: setsockopt(client_socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay)); ioctlsocket(client_socket,FIONBIO,&nb); if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_NETWORKSOCKET, client_socket))) { - GCESS(i, client_socket, sbbs->ssh_session, "setting network socket"); - SSH_END(client_socket); - close_socket(client_socket); - continue; - } - for(ssh_failed=0; ssh_failed < 2; ssh_failed++) { - /* Accept any credentials */ - lprintf(LOG_DEBUG, "%04d SSH Setting attribute: SESSINFO_AUTHRESPONSE", client_socket); - if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_AUTHRESPONSE, 1))) { - GCESS(i, client_socket, sbbs->ssh_session, "setting auth response"); - ssh_failed=1; - break; - } - lprintf(LOG_DEBUG, "%04d SSH Setting attribute: SESSINFO_ACTIVE", client_socket); - if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_ACTIVE, 1))) { - GCESS(i, client_socket, sbbs->ssh_session, "setting session active"); - if(i != CRYPT_ENVELOPE_RESOURCE) { - ssh_failed=2; - break; - } - } - else { - int cid; - char tname[1024]; - int tnamelen; - - ssh_failed=0; - // Check the channel ID and name... - if (cryptStatusOK(i=cryptGetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL, &cid))) { - if (i == CRYPT_OK) { - tnamelen = 0; - i=cryptGetAttributeString(sbbs->ssh_session, CRYPT_SESSINFO_SSH_CHANNEL_TYPE, tname, &tnamelen); - GCESS(i, client_socket, sbbs->ssh_session, "getting channel type"); - if (tnamelen != 7 || strnicmp(tname, "session", 7)) { - lprintf(LOG_NOTICE, "%04d SSH [%s] active channel '%.*s' is not 'session', disconnecting.", client_socket, host_ip, tnamelen, tname); - sbbs->badlogin(/* user: */NULL, /* passwd: */NULL, "SSH", &client_addr, /* delay: */false); - // Fail because there's no session. - ssh_failed = 3; - } - else - sbbs->session_channel = cid; - } - } - else { - GCESS(i, client_socket, sbbs->ssh_session, "getting channel id"); - if (i == CRYPT_ERROR_PERMISSION) - lprintf(LOG_CRIT, "!Your cryptlib build is obsolete, please update"); - } - break; - } - } - if (!ssh_failed) { - if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_PROPERTY_OWNER, CRYPT_UNUSED))) { - GCESS(i, client_socket, sbbs->ssh_session, "clearing owner"); - ssh_failed = 2; - } - } - if(ssh_failed) { - lprintf(LOG_NOTICE, "%04d SSH [%s] session establishment failed", client_socket, host_ip); + sbbs->log_crypt_error_status_sock(i, "setting network socket"); SSH_END(client_socket); close_socket(client_socket); continue; } if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_READTIMEOUT, 0))) - GCESS(i, sbbs->client_socket, sbbs->ssh_session, "setting read timeout"); + sbbs->log_crypt_error_status_sock(i, "setting read timeout"); // READ = WRITE TIMEOUT HACK... REMOVE WHEN FIXED if(cryptStatusError(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_OPTION_NET_WRITETIMEOUT, 0))) - GCESS(i, sbbs->client_socket, sbbs->ssh_session, "setting write timeout"); + sbbs->log_crypt_error_status_sock(i, "setting write timeout"); #if 0 if(cryptStatusError(err=crypt_pop_channel_data(sbbs, str, sizeof(str), &i))) { GCES(i, sbbs->cfg.node_num, sbbs->ssh_session, "popping data"); i=0; } #endif - sbbs->online=ON_REMOTE; } #endif @@ -5780,6 +5745,7 @@ NO_SSH: new_node->sys_status|=SS_SSH; new_node->telnet_mode|=TELNET_MODE_OFF; // SSH does not use Telnet commands new_node->ssh_session=sbbs->ssh_session; + new_node->online = ON_REMOTE; } /* Wait for pending data to be sent then turn off ssh_mode for uber-output */ while(sbbs->output_thread_running && RingBufFull(&sbbs->outbuf)) @@ -5791,8 +5757,8 @@ NO_SSH: } protected_uint32_adjust(&node_threads_running, 1); - new_node->input_thread_running = true; - new_node->input_thread=(HANDLE)_beginthread(input_thread,0, new_node); + new_node->input_thread_running = true; + new_node->input_thread=(HANDLE)_beginthread(input_thread, 0, new_node); new_node->output_thread_running = true; new_node->autoterm = sbbs->autoterm; new_node->cols = sbbs->cols; diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h index 58462a8f84..7a848b7237 100644 --- a/src/sbbs3/sbbs.h +++ b/src/sbbs3/sbbs.h @@ -451,6 +451,7 @@ public: bool input_thread_mutex_created = false; pthread_mutex_t ssh_mutex; bool ssh_mutex_created = false; + xpevent_t ssh_active = nullptr; #define OUTCOM_RETRY_DELAY 80 // milliseconds #define OUTCOM_RETRY_ATTEMPTS 1000 // 80 seconds @@ -1020,6 +1021,7 @@ public: const char* parse_login(const char*); /* answer.cpp */ + bool set_authresponse(bool activate_ssh); bool answer(void); /* logon.ccp */ @@ -1066,6 +1068,7 @@ public: int getnodetopage(int all, int telegram); /* main.cpp */ + void log_crypt_error_status_sock(int status, const char *action); int lputs(int level, const char* str); int lprintf(int level, const char *fmt, ...) #if defined(__GNUC__) // Catch printf-format errors diff --git a/src/sbbs3/ssl.c b/src/sbbs3/ssl.c index e6b81825ee..2eb68917a0 100644 --- a/src/sbbs3/ssl.c +++ b/src/sbbs3/ssl.c @@ -15,10 +15,12 @@ void free_crypt_attrstr(char *attr) char* get_crypt_attribute(CRYPT_HANDLE sess, C_IN CRYPT_ATTRIBUTE_TYPE attr) { - int len = 0; - char *estr = NULL; + int len = 0; + char *estr = NULL; + int status; - if (cryptStatusOK(cryptGetAttributeString(sess, attr, NULL, &len))) { + status = cryptGetAttributeString(sess, attr, NULL, &len); + if (cryptStatusOK(status)) { estr = malloc(len + 1); if (estr) { if (cryptStatusError(cryptGetAttributeString(sess, attr, estr, &len))) { -- GitLab