From 513c1a4d0ed9f3e0b84f43b73100242a13d430e7 Mon Sep 17 00:00:00 2001
From: deuce <>
Date: Sat, 9 Sep 2006 06:24:05 +0000
Subject: [PATCH] Experimental SSH server support using Cryptlib.

!!!NOTICE!!!
At least Cryptlib 3.2.3a and 3.2.3 (The latest) crashes Synchronet when you
attempt to connect using OpenSSH (SyncTERM with experiment SSH works though)
DO NOT USE THIS UNLESS YOU WANT YOUR BBS TO CRASH!!!
---
 src/sbbs3/GNUmakefile |   9 +-
 src/sbbs3/answer.cpp  |  65 ++++++++++++
 src/sbbs3/main.cpp    | 233 +++++++++++++++++++++++++++++++++++++++++-
 src/sbbs3/newuser.cpp |   4 +
 src/sbbs3/sbbs.h      |   8 ++
 src/sbbs3/sbbs_ini.c  |  18 +++-
 src/sbbs3/sbbscon.c   |   7 +-
 src/sbbs3/startup.h   |  17 +++
 8 files changed, 355 insertions(+), 6 deletions(-)

diff --git a/src/sbbs3/GNUmakefile b/src/sbbs3/GNUmakefile
index f97a2a5eeb..c1e097f004 100644
--- a/src/sbbs3/GNUmakefile
+++ b/src/sbbs3/GNUmakefile
@@ -35,6 +35,11 @@ ifeq ($(os),qnx)
  LDFLAGS += -lsocket
 endif
 
+ifdef USE_CRYPTLIB
+ CFLAGS	+=	-DUSE_CRYPTLIB
+ SBBS_LIBS	+=	-lcl
+endif
+
 ifdef PREFIX
  CFLAGS += -DPREFIX=$(PREFIX)
 endif
@@ -122,12 +127,12 @@ LDFLAGS +=	$(UIFC-MT_LDFLAGS) $(XPDEV-MT_LDFLAGS) $(SMBLIB_LDFLAGS) $(CIOLIB-MT_
 # Monolithic Synchronet executable Build Rule
 $(SBBSMONO): $(MONO_OBJS) $(OBJS)
 	@echo Linking $@
-	$(QUIET)$(CXX) -o $@ $(LDFLAGS) $(MT_LDFLAGS) $(MONO_OBJS) $(OBJS) $(SMBLIB_LIBS) $(XPDEV-MT_LIBS)
+	$(QUIET)$(CXX) -o $@ $(LDFLAGS) $(MT_LDFLAGS) $(MONO_OBJS) $(OBJS) $(SBBS_LIBS) $(SMBLIB_LIBS) $(XPDEV-MT_LIBS)
 
 # Synchronet BBS library Link Rule
 $(SBBS): $(OBJS) $(LIBS)
 	@echo Linking $@
-	$(QUIET)$(MKSHPPLIB) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(SHLIBOPTS)
+	$(QUIET)$(MKSHPPLIB) $(LDFLAGS) -o $@ $(OBJS) $(SBBS_LIBS) $(LIBS) $(SHLIBOPTS)
 
 # FTP Server Link Rule
 $(FTPSRVR): $(MTOBJODIR)/ftpsrvr.o
diff --git a/src/sbbs3/answer.cpp b/src/sbbs3/answer.cpp
index 89b2b0c7c7..1416f9404f 100644
--- a/src/sbbs3/answer.cpp
+++ b/src/sbbs3/answer.cpp
@@ -178,6 +178,71 @@ bool sbbs_t::answer()
 		/* Retrieve terminal type from telnet client --RS */
 		request_telnet_opt(TELNET_DO,TELNET_TERM_TYPE);
 	}
+#ifdef USE_CRYPTLIB
+	if(sys_status&SS_SSH) {
+		cryptGetAttributeString(ssh_session, CRYPT_SESSINFO_USERNAME, rlogin_name, &i);
+		rlogin_name[i]=0;
+		cryptGetAttributeString(ssh_session, CRYPT_SESSINFO_PASSWORD, rlogin_pass, &i);
+		rlogin_pass[i]=0;
+		lprintf(LOG_DEBUG,"Node %d SSH: '%.*s' / '%.*s'"
+			,cfg.node_num
+			,LEN_ALIAS*2,rlogin_name
+			,LEN_ALIAS*2,rlogin_pass);
+		useron.number=userdatdupe(0, U_ALIAS, LEN_ALIAS, rlogin_name, 0);
+		if(useron.number) {
+			getuserdat(&cfg,&useron);
+			useron.misc&=~(ANSI|COLOR|RIP|WIP);
+			SAFECOPY(tmp
+				,rlogin_pass);
+			for(i=0;i<3;i++) {
+				if(stricmp(tmp,useron.pass)) {
+					rioctl(IOFI);       /* flush input buffer */
+					bputs(text[InvalidLogon]);
+					if(cfg.sys_misc&SM_ECHO_PW)
+						sprintf(str,"(%04u)  %-25s  FAILED Password attempt: '%s'"
+							,0,useron.alias,tmp);
+					else
+						sprintf(str,"(%04u)  %-25s  FAILED Password attempt"
+							,0,useron.alias);
+						logline("+!",str);
+					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 {
+					if(REALSYSOP) {
+						rioctl(IOFI);       /* flush input buffer */
+						if(!chksyspass())
+							bputs(text[InvalidLogon]);
+						else {
+							i=0;
+							break;
+						}
+					}
+					else
+						break;
+				}
+			}
+			if(i) {
+				if(stricmp(tmp,useron.pass)) {
+					bputs(text[InvalidLogon]);
+					if(cfg.sys_misc&SM_ECHO_PW)
+						sprintf(str,"(%04u)  %-25s  FAILED Password attempt: '%s'"
+							,0,useron.alias,tmp);
+					else
+						sprintf(str,"(%04u)  %-25s  FAILED Password attempt"
+							,0,useron.alias);
+						logline("+!",str);
+				}
+				useron.number=0;
+				hangup();
+			}
+		}
+		else
+			lprintf(LOG_DEBUG,"Node %d RLogin: Unknown user: %s",cfg.node_num,rlogin_name);
+	}
+#endif
 
 	/* Detect terminal type */
     mswait(200);
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index 4f91223817..a59ddbeaf1 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -82,6 +82,9 @@ SOCKET	uspy_socket[MAX_NODES];	  /* UNIX domain spy sockets */
 SOCKET	node_socket[MAX_NODES];
 static	SOCKET telnet_socket=INVALID_SOCKET;
 static	SOCKET rlogin_socket=INVALID_SOCKET;
+#ifdef USE_CRYPTLIB
+static	SOCKET ssh_socket=INVALID_SOCKET;
+#endif
 static	sbbs_t*	sbbs=NULL;
 static	scfg_t	scfg;
 static	char *	text[TOTAL_TEXT];
@@ -1341,6 +1344,15 @@ void input_thread(void *arg)
 	    if(rd > (int)sizeof(inbuf))
         	rd=sizeof(inbuf);
 
+#ifdef USE_CRYPTLIB
+		if(sbbs->ssh_mode && sock==sbbs->client_socket) {
+			if(!cryptStatusOK(cryptPopData(sbbs->ssh_session, (char*)inbuf, rd, &i)))
+				rd=-1;
+			else
+				rd=i;
+		}
+		else
+#endif
     	rd = recv(sock, (char*)inbuf, rd, 0);
 
 		if(pthread_mutex_unlock(&sbbs->input_thread_mutex)!=0)
@@ -1566,6 +1578,15 @@ void output_thread(void* arg)
 			continue;
 		}
 
+#if USE_CRYPTLIB
+		if(sbbs->ssh_mode) {
+			if(!cryptStatusOK(cryptPushData(sbbs->ssh_session, (char*)buf+bufbot, buftop-bufbot, &i)))
+				i=-1;
+			else
+				cryptFlushData(sbbs->ssh_session);
+		}
+		else
+#endif
 		i=sendsocket(sbbs->client_socket, (char*)buf+bufbot, buftop-bufbot);
 		if(i==SOCKET_ERROR) {
         	if(ERROR_VALUE == ENOTSOCK)
@@ -2340,6 +2361,10 @@ sbbs_t::sbbs_t(ushort node_num, DWORD addr, char* name, SOCKET sd,
 
 	/* Init some important variables */
 
+#ifdef USE_CRYPTLIB
+	ssh_mode=false;
+#endif
+
 	rio_abortable=false;
 
 	console = 0;
@@ -3711,6 +3736,12 @@ static void cleanup(int code)
 		close_socket(rlogin_socket);
 		rlogin_socket=INVALID_SOCKET;
 	}
+#ifdef USE_CRYPTLIB
+	if(ssh_socket!=INVALID_SOCKET) {
+		close_socket(ssh_socket);
+		ssh_socket=INVALID_SOCKET;
+	}
+#endif
 
 
 #ifdef _WINSOCKAPI_
@@ -3783,6 +3814,9 @@ void DLLCALL bbs_thread(void* arg)
 	struct sockaddr_un uspy_addr;
 	socklen_t		uspy_addr_len;
 #endif
+#ifdef USE_CRYPTLIB
+	CRYPT_CONTEXT	ssh_context;
+#endif
 
     if(startup==NULL) {
     	sbbs_beep(100,500);
@@ -3806,6 +3840,9 @@ void DLLCALL bbs_thread(void* arg)
 	/* Setup intelligent defaults */
 	if(startup->telnet_port==0)				startup->telnet_port=IPPORT_TELNET;
 	if(startup->rlogin_port==0)				startup->rlogin_port=513;
+#ifdef USE_CRYPTLIB
+	if(startup->ssh_port==0)				startup->ssh_port=22;
+#endif
 	if(startup->outbuf_drain_timeout==0)	startup->outbuf_drain_timeout=10;
 	if(startup->sem_chk_freq==0)			startup->sem_chk_freq=2;
 	if(startup->temp_dir[0])				backslash(startup->temp_dir);
@@ -4058,6 +4095,93 @@ void DLLCALL bbs_thread(void* arg)
 		lprintf(LOG_INFO,"RLogin server listening on port %d",startup->rlogin_port);
 	}
 
+#ifdef USE_CRYPTLIB
+	if(startup->options&BBS_OPT_ALLOW_SSH) {
+		bool			loaded_key=false;
+
+		CRYPT_KEYSET	ssh_keyset;
+
+		cryptInit();
+		cryptAddRandom(NULL,CRYPT_RANDOM_SLOWPOLL);
+		/* Get the private key... first try loading it from a file... */
+		sprintf(str,"%s%s",scfg.ctrl_dir,"cryptlib.key");
+		if(cryptStatusOK(cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, str, CRYPT_KEYOPT_NONE))) {
+			if(cryptStatusOK(cryptGetPrivateKey(ssh_keyset, &ssh_context, CRYPT_KEYID_NAME, "ssh_server", scfg.sys_pass)))
+				loaded_key=true;
+			cryptKeysetClose(ssh_keyset);
+			/* Failed to load the key... delete the keyfile and create a new one */
+			if(!loaded_key)
+				remove(str);
+		}
+
+		if(!loaded_key) {
+			/* Couldn't do that... create a new context and use the key from there... */
+
+			if(!cryptStatusOK(i=cryptCreateContext(&ssh_context, CRYPT_UNUSED, CRYPT_ALGO_RSA))) {
+				lprintf(LOG_ERR,"Cryptlib error %d creating context",i);
+				goto NO_SSH;
+			}
+			if(!cryptStatusOK(i=cryptSetAttributeString(ssh_context, CRYPT_CTXINFO_LABEL, "ssh_server", 10))) {
+				lprintf(LOG_ERR,"Cryptlib error %d setting key label",i);
+				goto NO_SSH;
+			}
+			if(!cryptStatusOK(i=cryptGenerateKey(ssh_context))) {
+				lprintf(LOG_ERR,"Cryptlib error %d generating key",i);
+				goto NO_SSH;
+			}
+
+			/* Ok, now try saving this one... use the syspass to enctrpy it. */
+			if(cryptStatusOK(cryptKeysetOpen(&ssh_keyset, CRYPT_UNUSED, CRYPT_KEYSET_FILE, str, CRYPT_KEYOPT_CREATE))) {
+				cryptAddPrivateKey(ssh_keyset, ssh_context, scfg.sys_pass);
+				cryptKeysetClose(ssh_keyset);
+			}
+		}
+
+		/* open a socket and wait for a client */
+
+		ssh_socket = open_socket(SOCK_STREAM, "ssh");
+
+		if(ssh_socket == INVALID_SOCKET) {
+			lprintf(LOG_ERR,"!ERROR %d creating SSH socket", ERROR_VALUE);
+			cleanup(1);
+			return;
+		}
+
+		lprintf(LOG_INFO,"SSH socket %d opened",ssh_socket);
+
+		/*****************************/
+		/* Listen for incoming calls */
+		/*****************************/
+		memset(&server_addr, 0, sizeof(server_addr));
+
+		server_addr.sin_addr.s_addr = htonl(startup->ssh_interface);
+		server_addr.sin_family = AF_INET;
+		server_addr.sin_port   = htons(startup->ssh_port);
+
+		if(startup->seteuid!=NULL)
+			startup->seteuid(FALSE);
+		result = retry_bind(ssh_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
+			,startup->bind_retry_count,startup->bind_retry_delay,"SSH Server",lprintf);
+		if(startup->seteuid!=NULL)
+			startup->seteuid(TRUE);
+		if(result != 0) {
+			lprintf(LOG_NOTICE,"%s",BIND_FAILURE_HELP);
+			cleanup(1);
+			return;
+		}
+
+		result = listen(ssh_socket, 1);
+
+		if(result != 0) {
+			lprintf(LOG_ERR,"!ERROR %d (%d) listening on SSH socket", result, ERROR_VALUE);
+			cleanup(1);
+			return;
+		}
+		lprintf(LOG_INFO,"SSH server listening on port %d",startup->ssh_port);
+	}
+NO_SSH:
+#endif
+
 	sbbs = new sbbs_t(0, server_addr.sin_addr.s_addr
 		,"BBS System", telnet_socket, &scfg, text, NULL);
     sbbs->online = 0;
@@ -4249,6 +4373,14 @@ void DLLCALL bbs_thread(void* arg)
 			if(rlogin_socket+1>high_socket_set)
 				high_socket_set=rlogin_socket+1;
 		}
+#ifdef USE_CRYPTLIB
+		if(startup->options&BBS_OPT_ALLOW_SSH
+			&& ssh_socket!=INVALID_SOCKET) {
+			FD_SET(ssh_socket,&socket_set);
+			if(ssh_socket+1>high_socket_set)
+				high_socket_set=ssh_socket+1;
+		}
+#endif
 #ifdef __unix__
 		for(i=first_node;i<=last_node;i++)  {
 			if(uspy_listen_socket[i-1]!=INVALID_SOCKET)  {
@@ -4286,6 +4418,9 @@ void DLLCALL bbs_thread(void* arg)
 		client_addr_len = sizeof(client_addr);
 
 		bool rlogin = false;
+#ifdef USE_CRYPTLIB
+		bool ssh = false;
+#endif
 
 		is_client=FALSE;
 		if(telnet_socket!=INVALID_SOCKET 
@@ -4299,6 +4434,46 @@ void DLLCALL bbs_thread(void* arg)
 	        	,&client_addr_len);
 			rlogin = true;
 			is_client=TRUE;
+#ifdef USE_CRYPTLIB
+		} else if(ssh_socket!=INVALID_SOCKET 
+			&& FD_ISSET(ssh_socket,&socket_set)) {
+			client_socket = accept_socket(ssh_socket, (struct sockaddr *)&client_addr
+	        	,&client_addr_len);
+			if(!cryptStatusOK(i=cryptCreateSession(&sbbs->ssh_session, CRYPT_UNUSED, CRYPT_SESSION_SSH_SERVER))) {
+				lprintf(LOG_ERR,"Cryptlib error %d creating session",i);
+				close_socket(client_socket);
+				continue;
+			}
+			if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_PRIVATEKEY, ssh_context))) {
+				lprintf(LOG_ERR,"Cryptlib error %d setting private key",i);
+				cryptDestroySession(sbbs->ssh_session);
+				close_socket(client_socket);
+				continue;
+			}
+			/* Accept any credentials */
+			if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_AUTHRESPONSE, 1))) {
+				lprintf(LOG_ERR,"Cryptlib error %d setting AUTHRESPONSE",i);
+				cryptDestroySession(sbbs->ssh_session);
+				close_socket(client_socket);
+				continue;
+			}
+			if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_NETWORKSOCKET, client_socket))) {
+				lprintf(LOG_ERR,"Cryptlib error %d setting socket",i);
+				cryptDestroySession(sbbs->ssh_session);
+				close_socket(client_socket);
+				continue;
+			}
+			if(!cryptStatusOK(i=cryptSetAttribute(sbbs->ssh_session, CRYPT_SESSINFO_ACTIVE, 1))) {
+				lprintf(LOG_ERR,"Cryptlib error %d setting session active",i);
+				cryptDestroySession(sbbs->ssh_session);
+				close_socket(client_socket);
+				continue;
+			}
+			cryptPopData(sbbs->ssh_session, str, sizeof(str), &i);
+			ssh = true;
+			is_client=TRUE;
+			sbbs->ssh_mode=true;
+#endif
 		} else {
 #ifdef __unix__
 			for(i=first_node;i<=last_node;i++)  {
@@ -4364,13 +4539,24 @@ void DLLCALL bbs_thread(void* arg)
 		strcpy(host_ip,inet_ntoa(client_addr.sin_addr));
 
 		if(trashcan(&scfg,host_ip,"ip-silent")) {
+#ifdef USE_CRYPTLIB
+			if(ssh) {
+				cryptDestroySession(sbbs->ssh_session);
+				sbbs->ssh_mode=false;
+			}
+#endif
 			close_socket(client_socket);
 			continue;
 		}
 
 		lprintf(LOG_INFO,"%04d %s connection accepted from: %s port %u"
 			,client_socket
-			,rlogin ? "RLogin" : "Telnet", host_ip, ntohs(client_addr.sin_port));
+#ifdef USE_CRYPTLIB
+			,rlogin ? "RLogin" : (ssh ? "SSH" : "Telnet")
+#else
+			,rlogin ? "RLogin" : "Telnet"
+#endif
+			, host_ip, ntohs(client_addr.sin_port));
 
 #ifdef _WIN32
 		if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
@@ -4381,6 +4567,12 @@ void DLLCALL bbs_thread(void* arg)
         sbbs->online=ON_REMOTE;
 
 		if(sbbs->trashcan(host_ip,"ip")) {
+#ifdef USE_CRYPTLIB
+			if(ssh) {
+				cryptDestroySession(sbbs->ssh_session);
+				sbbs->ssh_mode=false;
+			}
+#endif
 			close_socket(client_socket);
 			lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can"
 				,client_socket);
@@ -4419,6 +4611,12 @@ void DLLCALL bbs_thread(void* arg)
 		}
 
 		if(sbbs->trashcan(host_name,"host")) {
+#ifdef USE_CRYPTLIB
+			if(ssh) {
+				cryptDestroySession(sbbs->ssh_session);
+				sbbs->ssh_mode=false;
+			}
+#endif
 			close_socket(client_socket);
 			lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can",client_socket);
 			sprintf(logstr, "Blocked Hostname: %s",host_name);
@@ -4445,7 +4643,11 @@ void DLLCALL bbs_thread(void* arg)
 		SAFECOPY(client.addr,host_ip);
 		SAFECOPY(client.host,host_name);
 		client.port=ntohs(client_addr.sin_port);
+#ifdef USE_CRYPTLIB
+		client.protocol=rlogin ? "RLogin":(ssh ? "SSH" : "Telnet");
+#else
 		client.protocol=rlogin ? "RLogin":"Telnet";
+#endif
 		client.user="<unknown>";
 		client_on(client_socket,&client,FALSE /* update */);
 
@@ -4473,6 +4675,12 @@ void DLLCALL bbs_thread(void* arg)
 			}
 			mswait(3000);
 			client_off(client_socket);
+#ifdef USE_CRYPTLIB
+			if(ssh) {
+				cryptDestroySession(sbbs->ssh_session);
+				sbbs->ssh_mode=false;
+			}
+#endif
 			close_socket(client_socket);
 			continue;
 		}
@@ -4480,9 +4688,16 @@ void DLLCALL bbs_thread(void* arg)
         node_socket[i-1]=client_socket;
 
 		sbbs_t* new_node = new sbbs_t(i, client_addr.sin_addr.s_addr, host_name
-        	,client_socket, &scfg, text, &client);
+        	,client_socket
+			,&scfg, text, &client);
 
 		new_node->client=client;
+#ifdef USE_CRYPTLIB
+		if(ssh) {
+			new_node->ssh_session=sbbs->ssh_session;
+			new_node->ssh_mode=true;
+		}
+#endif
 
 		/* copy the IDENT response, if any */
 		if(identity!=NULL)
@@ -4503,6 +4718,12 @@ void DLLCALL bbs_thread(void* arg)
 			delete new_node;
 			node_socket[i-1]=INVALID_SOCKET;
 			client_off(client_socket);
+#ifdef USE_CRYPTLIB
+			if(ssh) {
+				cryptDestroySession(sbbs->ssh_session);
+				sbbs->ssh_mode=false;
+			}
+#endif
 			close_socket(client_socket);
 			continue;
 		}
@@ -4512,6 +4733,14 @@ void DLLCALL bbs_thread(void* arg)
 			new_node->sys_status|=SS_RLOGIN;
 			new_node->telnet_mode|=TELNET_MODE_OFF; // RLogin does not use Telnet commands
 		}
+#ifdef USE_CRYPTLIB
+		if(ssh) {
+			new_node->connection="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;
+		}
+#endif
 
 	    node_threads_running++;
 		new_node->input_thread=(HANDLE)_beginthread(input_thread,0, new_node);
diff --git a/src/sbbs3/newuser.cpp b/src/sbbs3/newuser.cpp
index 06e99dd72f..20d5dd7083 100644
--- a/src/sbbs3/newuser.cpp
+++ b/src/sbbs3/newuser.cpp
@@ -186,7 +186,11 @@ BOOL sbbs_t::newuser()
 		else
 			useron.misc&=~NO_EXASCII;
 
+#ifdef USE_CRYPTLIB
+		if((sys_status&SS_RLOGIN || sys_status&SS_SSH) && rlogin_name[0])
+#else
 		if(sys_status&SS_RLOGIN && rlogin_name[0])
+#endif
 			strcpy(useron.alias,rlogin_name);
 		else {
 			while(online) {
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 79be76860f..a56afac354 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -109,6 +109,10 @@ extern int	thread_suid_broken;			/* NPTL is no longer broken */
 
 #endif
 
+#ifdef USE_CRYPTLIB
+#include <cryptlib.h>
+#endif
+
 /***********************/
 /* Synchronet-specific */
 /***********************/
@@ -169,6 +173,10 @@ public:
 	char	client_name[128];
 	char	client_ident[128];
 	DWORD	local_addr;
+#ifdef USE_CRYPTLIB
+	CRYPT_SESSION	ssh_session;
+	bool	ssh_mode;
+#endif
 
 	scfg_t	cfg;
 
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index 037a58683d..7c862b7e65 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -296,6 +296,13 @@ void sbbs_read_ini(
 		bbs->rlogin_port
 			=iniGetShortInt(list,section,"RLoginPort",513);
 
+#ifdef USE_CRYPTLIB
+		bbs->ssh_interface
+			=iniGetIpAddress(list,section,"SSHInterface",global->interface_addr);
+		bbs->ssh_port
+			=iniGetShortInt(list,section,"SSHPort",22);
+#endif
+
 		bbs->first_node
 			=iniGetShortInt(list,section,"FirstNode",1);
 		bbs->last_node
@@ -729,9 +736,18 @@ BOOL sbbs_write_ini(
 			iniRemoveValue(lp,section,"RLoginInterface");
 		else if(!iniSetIpAddress(lp,section,"RLoginInterface",bbs->rlogin_interface,&style))
 			break;
-
 		if(!iniSetShortInt(lp,section,"RLoginPort",bbs->rlogin_port,&style))
 			break;
+
+#ifdef USE_CRYPTLIB
+		if(bbs->ssh_interface==global->interface_addr)
+			iniRemoveValue(lp,section,"SSHInterface");
+		else if(!iniSetIpAddress(lp,section,"SSHInterface",bbs->ssh_interface,&style))
+			break;
+		if(!iniSetShortInt(lp,section,"SSHPort",bbs->ssh_port,&style))
+			break;
+#endif
+
 		if(!iniSetShortInt(lp,section,"FirstNode",bbs->first_node,&style))
 			break;
 		if(!iniSetShortInt(lp,section,"LastNode",bbs->last_node,&style))
diff --git a/src/sbbs3/sbbscon.c b/src/sbbs3/sbbscon.c
index 5d2cb9c046..4c00094b76 100644
--- a/src/sbbs3/sbbscon.c
+++ b/src/sbbs3/sbbscon.c
@@ -1640,7 +1640,12 @@ int main(int argc, char** argv)
 		if(!thread_suid_broken) {
  			if(bbs_startup.telnet_port < IPPORT_RESERVED
 				|| (bbs_startup.options & BBS_OPT_ALLOW_RLOGIN
-					&& bbs_startup.rlogin_port < IPPORT_RESERVED))
+					&& bbs_startup.rlogin_port < IPPORT_RESERVED)
+#ifdef USE_CRYPTLIB
+				|| (bbs_startup.options & BBS_OPT_ALLOW_SSH
+					&& bbs_startup.ssh_port < IPPORT_RESERVED)
+#endif
+				)
 				bbs_startup.options|=BBS_OPT_NO_RECYCLE;
 			if(ftp_startup.port < IPPORT_RESERVED)
 				ftp_startup.options|=FTP_OPT_NO_RECYCLE;
diff --git a/src/sbbs3/startup.h b/src/sbbs3/startup.h
index 8344dd25dd..d02dc89ce8 100644
--- a/src/sbbs3/startup.h
+++ b/src/sbbs3/startup.h
@@ -75,12 +75,18 @@ typedef struct {
     WORD	last_node;
 	WORD	telnet_port;
 	WORD	rlogin_port;
+#ifdef USE_CRYPTLIB
+	WORD	ssh_port;
+#endif
 	WORD	outbuf_highwater_mark;	/* output block size control */
 	WORD	outbuf_drain_timeout;
 	WORD	sem_chk_freq;		/* semaphore file checking frequency (in seconds) */
     DWORD   telnet_interface;
     DWORD	options;			/* See BBS_OPT definitions */
     DWORD	rlogin_interface;
+#ifdef USE_CRYPTLIB
+	DWORD	ssh_interface;
+#endif
     RingBuf** node_spybuf;			/* Spy output buffer (each node)	*/
     RingBuf** node_inbuf;			/* User input buffer (each node)	*/
     sem_t**	node_spysem;			/* Spy output semaphore (each node)	*/
@@ -154,6 +160,9 @@ static struct init_field {
 #define BBS_OPT_NO_EVENTS			(1<<9)	/* Don't run event thread			*/
 #define BBS_OPT_NO_SPY_SOCKETS		(1<<10)	/* Don't create spy sockets			*/
 #define BBS_OPT_NO_HOST_LOOKUP		(1<<11)
+#ifdef USE_CRYPTLIB
+#define BBS_OPT_ALLOW_SSH		(1<<26)	/* Allow logins via BSD SSH		*/
+#endif
 #define BBS_OPT_NO_RECYCLE			(1<<27)	/* Disable recycling of server		*/
 #define BBS_OPT_GET_IDENT			(1<<28)	/* Get Identity (RFC 1413)			*/
 #define BBS_OPT_NO_JAVASCRIPT		(1<<29)	/* JavaScript disabled				*/
@@ -161,8 +170,13 @@ static struct init_field {
 #define BBS_OPT_MUTE				(1<<31)	/* Mute sounds						*/
 
 /* bbs_startup_t.options bits that require re-init/recycle when changed */
+#ifdef USE_CRYPTLIB
+#define BBS_INIT_OPTS	(BBS_OPT_ALLOW_RLOGIN|BBS_OPT_ALLOW_SSH|BBS_OPT_NO_EVENTS|BBS_OPT_NO_SPY_SOCKETS \
+						|BBS_OPT_NO_JAVASCRIPT|BBS_OPT_LOCAL_TIMEZONE)
+#else
 #define BBS_INIT_OPTS	(BBS_OPT_ALLOW_RLOGIN|BBS_OPT_NO_EVENTS|BBS_OPT_NO_SPY_SOCKETS \
 						|BBS_OPT_NO_JAVASCRIPT|BBS_OPT_LOCAL_TIMEZONE)
+#endif
 
 #if defined(STARTUP_INI_BITDESC_TABLES)
 static ini_bitdesc_t bbs_options[] = {
@@ -178,6 +192,9 @@ static ini_bitdesc_t bbs_options[] = {
 	{ BBS_OPT_NO_EVENTS				,"NO_EVENTS"			},
 	{ BBS_OPT_NO_HOST_LOOKUP		,"NO_HOST_LOOKUP"		},
 	{ BBS_OPT_NO_SPY_SOCKETS		,"NO_SPY_SOCKETS"		},
+#ifdef USE_CRYPTLIB
+	{ BBS_OPT_ALLOW_SSH			,"ALLOW_SSH"			},
+#endif
 	{ BBS_OPT_NO_RECYCLE			,"NO_RECYCLE"			},
 	{ BBS_OPT_GET_IDENT				,"GET_IDENT"			},
 	{ BBS_OPT_NO_JAVASCRIPT			,"NO_JAVASCRIPT"		},
-- 
GitLab