From 7679dcf8616e87cbdc587a15b9f7a0eded167313 Mon Sep 17 00:00:00 2001
From: Rob Swindell <rob@synchro.net>
Date: Fri, 30 Dec 2022 02:40:13 -0800
Subject: [PATCH] Common login control (e.g. via real name) using new
 find_login_id() function

Previously, many servers and services didn't support login by real name
(e.g. issue #469) even if the sysop had that option enabled in SCFG.

Move login control settings from node.ini to system (main.ini -> login)

The 3 node toggle options:
- Allow Login by User Number
- Allow Login by Real Name
- Always Prompt for Password

... have been now moved from SCFG->Nodes->Node x->Toggle Options to
SCFG-System->Toggle Options.

If you upgraded to v3.20a before now, you'll want to double-check these
settings to make sure they're how you want them set. New upgraders that run
upgrade_to_v320.js (e.g. via 'jsexec update') will get these settings migrated
automatically.

Added some error detection/logging to upgrade_to_v320.js when failing to open
.cnf files.

Constified some more user/login related function args and return types.
---
 exec/load/sbbsdefs.js     |  5 +++
 exec/upgrade_to_v320.js   | 19 +++++++++
 src/sbbs3/answer.cpp      |  8 ++--
 src/sbbs3/ftpsrvr.c       |  4 +-
 src/sbbs3/js_system.c     |  8 ++++
 src/sbbs3/login.cpp       | 26 +++--------
 src/sbbs3/mailsrvr.c      |  6 +--
 src/sbbs3/sbbs.h          |  6 +--
 src/sbbs3/sbbsdefs.h      | 89 ++++++++++++++++++++------------------
 src/sbbs3/scfg/scfgnode.c | 67 +----------------------------
 src/sbbs3/scfg/scfgsys.c  | 90 ++++++++++++++++++++++++++++++++-------
 src/sbbs3/scfgdefs.h      |  3 +-
 src/sbbs3/scfglib1.c      |  1 +
 src/sbbs3/scfgsave.c      |  1 +
 src/sbbs3/services.c      |  6 +--
 src/sbbs3/userdat.c       | 15 +++++++
 src/sbbs3/userdat.h       |  2 +
 src/sbbs3/websrvr.c       |  7 +--
 18 files changed, 198 insertions(+), 165 deletions(-)

diff --git a/exec/load/sbbsdefs.js b/exec/load/sbbsdefs.js
index 647de21927..66b42e767b 100644
--- a/exec/load/sbbsdefs.js
+++ b/exec/load/sbbsdefs.js
@@ -60,6 +60,11 @@ var   SYS_USRVDELM	=(1<<30);	/* Users can see deleted msgs				*/
 var   SYS_SYSVDELM	=(1<<31);	/* Sysops can see deleted msgs				*/
 					    		/********************************************/
 
+								// system.login_settings
+var	LOGIN_USERNUM	=(1<<0);	// Allow logins by user number
+var	LOGIN_REALNAME	=(1<<1);	// Allow logins by user's real name
+var	LOGIN_PWPROMPT	=(1<<2);	// Always display password prompt, even for bad login-id
+
 						    	/********************************************/
     							/* bbs.sys_status							*/
 							    /********************************************/
diff --git a/exec/upgrade_to_v320.js b/exec/upgrade_to_v320.js
index b20f001773..69d3bd509e 100755
--- a/exec/upgrade_to_v320.js
+++ b/exec/upgrade_to_v320.js
@@ -1,13 +1,19 @@
 // Convert SBBS v3.1x ctrl/*.cnf to SBBS v3.2 ctrl/*.ini
 
 print("Upgrading Synchronet v3.1x config files to v3.20");
+load('sbbsdefs.js');
 var cnflib = load({}, 'cnflib.js');
+var node_settings;
 
 function upgrade_node(dir)
 {
 	var path = dir + "node.cnf";
 	print(path + " -> node.ini");
 	var cnf = cnflib.read(path);
+	if(!cnf) {
+		alert("Error reading " + path);
+		exit(1);
+	}
 	var ini = new File(dir + "node.ini");
 	ini.ini_section_separator = "";
 	if(!ini.open("w+")) {
@@ -16,11 +22,16 @@ function upgrade_node(dir)
 	}
 	ini.iniSetObject(null, cnf);
 	ini.close();
+	node_settings = cnf.settings;
 }
 
 //---------------------------------------------------------------------------
 print("main.cnf -> main.ini");
 var cnf = cnflib.read("main.cnf");
+if(!cnf) {
+	alert("Error reading main.cnf");
+	exit(1);
+}
 var ini = new File("main.ini");
 ini.ini_section_separator = "";
 if(!ini.open("w+")) {
@@ -89,6 +100,14 @@ for(var i in cnf.node_dir) {
 	upgrade_node(path);
 }
 delete cnf.node_dir;
+cnf.login = 0;
+if(node_settings & NM_LOGON_R)
+	cnf.login |= LOGIN_REALNAME;
+if(node_settings & NM_LOGON_P)
+	cnf.login |= LOGIN_PWPROMPT;
+if(!(node_settings & NM_NO_NUM))
+	cnf.login |= LOGIN_USERNUM;
+
 for(var i in cnf.validation_set)
 	ini.iniSetObject("valset:" + i, cnf.validation_set[i]);
 delete cnf.validation_set;
diff --git a/src/sbbs3/answer.cpp b/src/sbbs3/answer.cpp
index 62486e3147..f6883e5edf 100644
--- a/src/sbbs3/answer.cpp
+++ b/src/sbbs3/answer.cpp
@@ -91,7 +91,7 @@ bool sbbs_t::answer()
 			truncstr(terminal,"/");
 			useron.number = 0;
 			if(rlogin_name[0])
-				useron.number=matchuser(&cfg, rlogin_name, /* sysop_alias: */FALSE);
+				useron.number = find_login_id(&cfg, rlogin_name);
 			if(useron.number) {
 				getuserdat(&cfg,&useron);
 				SAFEPRINTF(path,"%srlogin.cfg",cfg.ctrl_dir);
@@ -194,9 +194,7 @@ bool sbbs_t::answer()
 			rlogin_name[0] = 0;
 			pthread_mutex_unlock(&ssh_mutex);
 		}
-		useron.number=matchuser(&cfg, rlogin_name, /* sysop_alias: */FALSE);
-		if(!useron.number && check_realname(&cfg, rlogin_name) && cfg.node_misc & NM_LOGON_R)
-			useron.number = finduserstr(0, USER_NAME, rlogin_name);
+		useron.number = find_login_id(&cfg, rlogin_name);
 		if(useron.number) {
 			getuserdat(&cfg,&useron);
 			for(i=0;i<3 && online;i++) {
@@ -464,7 +462,7 @@ bool sbbs_t::answer()
 	if(!useron.number 
 		&& rlogin_name[0]!=0 
 		&& !(cfg.sys_misc&SM_CLOSED) 
-		&& !matchuser(&cfg, rlogin_name, /* Sysop alias: */FALSE)
+		&& !find_login_id(&cfg, rlogin_name)
 		&& !::trashcan(&cfg, rlogin_name, "name")) {
 		lprintf(LOG_INFO, "%s !UNKNOWN specified username: '%s', starting new user signup", client.protocol,rlogin_name);
 		bprintf("%s: %s\r\n", text[UNKNOWN_USER], rlogin_name);
diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c
index dfce5608ec..11716d9a8c 100644
--- a/src/sbbs3/ftpsrvr.c
+++ b/src/sbbs3/ftpsrvr.c
@@ -2423,7 +2423,7 @@ static void ctrl_thread(void* arg)
 			SKIP_WHITESPACE(p);
 			truncsp(p);
 			SAFECOPY(user.alias,p);
-			user.number=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/);
+			user.number = find_login_id(&scfg, user.alias);
 			if(!user.number && (stricmp(user.alias,"anonymous") == 0 || stricmp(user.alias, "ftp") == 0))
 				user.number=matchuser(&scfg,"guest",FALSE);
 			if(user.number && getuserdat(&scfg, &user)==0 && user.pass[0]==0) 
@@ -2439,7 +2439,7 @@ static void ctrl_thread(void* arg)
 			SKIP_WHITESPACE(p);
 
 			SAFECOPY(password,p);
-			user.number=matchuser(&scfg,user.alias,FALSE /*sysop_alias*/);
+			user.number = find_login_id(&scfg, user.alias);
 			if(!user.number) {
 				if(scfg.sys_misc&SM_ECHO_PW)
 					lprintf(LOG_WARNING,"%04d !UNKNOWN USER: '%s' (password: %s)",sock,user.alias,p);
diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c
index 7a61ccc7f9..19488ef4ab 100644
--- a/src/sbbs3/js_system.c
+++ b/src/sbbs3/js_system.c
@@ -41,6 +41,7 @@ enum {
 	,SYS_PROP_OP_AVAIL
 	,SYS_PROP_ID
 	,SYS_PROP_MISC
+	,SYS_PROP_LOGIN
 	,SYS_PROP_INETADDR
 	,SYS_PROP_LOCATION
 	,SYS_PROP_TIMEZONE
@@ -148,6 +149,9 @@ static JSBool js_system_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 		case SYS_PROP_MISC:
 			*vp=UINT_TO_JSVAL(cfg->sys_misc);
 			break;
+		case SYS_PROP_LOGIN:
+			*vp=UINT_TO_JSVAL(cfg->sys_login);
+			break;
 		case SYS_PROP_INETADDR:
 			p=cfg->sys_inetaddr;
 			break;
@@ -372,6 +376,9 @@ static JSBool js_system_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict
 		case SYS_PROP_MISC:
 			JS_ValueToECMAUint32(cx, *vp, (uint32_t*)&sys->cfg->sys_misc);
 			break;
+		case SYS_PROP_LOGIN:
+			JS_ValueToECMAUint32(cx, *vp, (uint32_t*)&sys->cfg->sys_login);
+			break;
 		case SYS_PROP_OP_AVAIL:
 			if(!set_sysop_availability(sys->cfg, JSVAL_TO_BOOLEAN(*vp))) {
 				JS_ReportError(cx, "%s: Failed to set sysop availability", __FUNCTION__);
@@ -396,6 +403,7 @@ static jsSyncPropertySpec js_system_properties[] = {
 	{	"operator_available",		SYS_PROP_OP_AVAIL,	JSPROP_ENUMERATE,	31801  },
 	{	"qwk_id",					SYS_PROP_ID,		SYSOBJ_FLAGS,		310  },
 	{	"settings",					SYS_PROP_MISC,		JSPROP_ENUMERATE,	310  },
+	{	"login_settings",			SYS_PROP_LOGIN,		JSPROP_ENUMERATE,	32000  },
 	{	"inetaddr",					SYS_PROP_INETADDR,	JSPROP_READONLY,	310  },	/* alias */
 	{	"inet_addr",				SYS_PROP_INETADDR,	SYSOBJ_FLAGS,		311  },
 	{	"location",					SYS_PROP_LOCATION,	SYSOBJ_FLAGS,		310  },
diff --git a/src/sbbs3/login.cpp b/src/sbbs3/login.cpp
index 9d3c7d4777..e2c7bd5d1e 100644
--- a/src/sbbs3/login.cpp
+++ b/src/sbbs3/login.cpp
@@ -22,7 +22,7 @@
 #include "sbbs.h"
 #include "cmdshell.h"
 
-char* sbbs_t::parse_login(char* str)
+const char* sbbs_t::parse_login(const char* str)
 {
 	sys_status &= ~(SS_QWKLOGON|SS_FASTLOGON);
 
@@ -38,35 +38,23 @@ char* sbbs_t::parse_login(char* str)
 	return str;
 }
 
-int sbbs_t::login(char *username, char *pw_prompt, const char* user_pw, const char* sys_pw)
+int sbbs_t::login(const char *username, char *pw_prompt, const char* user_pw, const char* sys_pw)
 {
 	char	str[128];
 	char 	tmp[512];
 	long	useron_misc=useron.misc;
 
-	useron.number=0;
 	username = parse_login(username);
 
-	if(!(cfg.node_misc&NM_NO_NUM) && IS_DIGIT(username[0])) {
-		useron.number=atoi(username);
+	useron.number = find_login_id(&cfg, username);
+	if(useron.number) {
 		getuserdat(&cfg,&useron);
 		if(useron.number && useron.misc&(DELETED|INACTIVE))
-			useron.number=0; 
-	}
-
-	if(!useron.number) {
-		useron.number=matchuser(&cfg,username,FALSE);
-		if(!useron.number && check_realname(&cfg, username) && cfg.node_misc & NM_LOGON_R)
-			useron.number = finduserstr(0, USER_NAME, username);
-		if(useron.number) {
-			getuserdat(&cfg,&useron);
-			if(useron.number && useron.misc&(DELETED|INACTIVE))
-				useron.number=0;
-		} 
+			useron.number=0;
 	}
 
 	if(!useron.number) {
-		if((cfg.node_misc&NM_LOGON_P) && pw_prompt != NULL) {
+		if((cfg.sys_login & LOGIN_PWPROMPT) && pw_prompt != NULL) {
 			SAFECOPY(useron.alias,username);
 			bputs(pw_prompt);
 			console|=CON_R_ECHOX;
@@ -140,7 +128,7 @@ int sbbs_t::login(char *username, char *pw_prompt, const char* user_pw, const ch
 	return(LOGIC_TRUE);
 }
 
-void sbbs_t::badlogin(char* user, char* passwd, const char* protocol, xp_sockaddr* addr, bool delay)
+void sbbs_t::badlogin(const char* user, const char* passwd, const char* protocol, xp_sockaddr* addr, bool delay)
 {
 	char reason[128];
 	char host_name[128];
diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c
index deea7c25c3..6330bc92ac 100644
--- a/src/sbbs3/mailsrvr.c
+++ b/src/sbbs3/mailsrvr.c
@@ -1295,7 +1295,7 @@ static void pop3_thread(void* arg)
 			SKIP_WHITESPACE(p);
 			SAFECOPY(password,p);
 		}
-		user.number=matchuser(&scfg,username,FALSE /*sysop_alias*/);
+		user.number = find_login_id(&scfg, username);
 		if(!user.number) {
 			if(scfg.sys_misc&SM_ECHO_PW)
 				lprintf(LOG_NOTICE,"%04d %s [%s] !UNKNOWN USER: '%s' (password: %s)"
@@ -4190,7 +4190,7 @@ static void smtp_thread(void* arg)
 				SAFECOPY(user_pass,p);
 			}
 
-			if((relay_user.number=matchuser(&scfg,user_name,FALSE))==0) {
+			if((relay_user.number = find_login_id(&scfg, user_name))==0) {
 				if(scfg.sys_misc&SM_ECHO_PW)
 					lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN USER: '%s' (password: %s)"
 						,socket, client.protocol, client_id, user_name, user_pass);
@@ -4277,7 +4277,7 @@ static void smtp_thread(void* arg)
 			else
 				p=response;
 			SAFECOPY(user_name,response);
-			if((relay_user.number=matchuser(&scfg,user_name,FALSE))==0) {
+			if((relay_user.number = find_login_id(&scfg, user_name))==0) {
 				lprintf(LOG_WARNING,"%04d %s %s !UNKNOWN USER: '%s'"
 					,socket, client.protocol, client_id, user_name);
 				badlogin(socket, session, client.protocol, badauth_rsp, user_name, NULL, host_name, &smtp.client_addr);
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 554e11cafb..875501cdff 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -965,9 +965,9 @@ public:
 	int		putnodeext(uint number, char * str);
 
 	/* login.ccp */
-	int		login(char *user_name, char *pw_prompt, const char* user_pw = NULL, const char* sys_pw = NULL);
-	void	badlogin(char* user, char* passwd, const char* protocol=NULL, xp_sockaddr* addr=NULL, bool delay=true);
-	char*	parse_login(char*);
+	int		login(const char *user_name, char *pw_prompt, const char* user_pw = NULL, const char* sys_pw = NULL);
+	void	badlogin(const char* user, const char* passwd, const char* protocol=NULL, xp_sockaddr* addr=NULL, bool delay=true);
+	const char*	parse_login(const char*);
 
 	/* answer.cpp */
 	bool	answer(void);
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index 9375801d72..abefc131f9 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -130,48 +130,53 @@
 #define UQ_COLORTERM	(1L<<20)	/* Ask if new user has color terminal	*/
 #define UQ_DUPNETMAIL	(1L<<21)	/* Don't allow duplicate e-mail address */
 
-									/* Different bits in sys_misc				*/
-#define SM_CLOSED		(1L<<0) 	/* System is clsoed to New Users			*/
-#define SM_SYSSTAT		(1L<<1) 	/* Sysops activity included in statistics	*/
-#define SM_NOSYSINFO	(1L<<2) 	/* Suppress system info display at logon	*/
-#define SM_PWEDIT		(1L<<3) 	/* Allow users to change their passwords	*/
-#define SM_RA_EMU		(1L<<4) 	/* Reverse R/A commands at msg read prompt	*/
-#define SM_ANON_EM		(1L<<5) 	/* Allow anonymous e-mail					*/
-#define SM_LISTLOC		(1L<<6) 	/* Use location of caller in user lists 	*/
-#define SM_WILDCAT		(1L<<7) 	/* Expand Wildcat color codes in messages	*/
-#define SM_PCBOARD		(1L<<8) 	/* Expand PCBoard color codes in messages	*/
-#define SM_WWIV 		(1L<<9) 	/* Expand WWIV color codes in messages		*/
-#define SM_CELERITY		(1L<<10)	/* Expand Celerity color codes in messages	*/
-#define SM_RENEGADE		(1L<<11)	/* Expand Renegade color codes in messages	*/
-#define SM_ECHO_PW		(1L<<12)	/* Echo passwords locally					*/
-#define SM_SYSPASSLOGIN	(1L<<13)	/* Require system password for sysop login	*/
-#define SM_AUTO_DST		(1L<<14)	/* Automatic Daylight Savings Toggle (US)   */
-#define SM_R_SYSOP		(1L<<15)	/* Allow remote sysop logon/commands		*/
-#define SM_QUOTE_EM		(1L<<16)	/* Allow quoting of e-mail					*/
-#define SM_EURODATE		(1L<<17)	/* Europian date format (DD/MM/YY)			*/
-#define SM_MILITARY		(1L<<18)	/* Military time format 					*/
-#define SM_TIMEBANK		(1L<<19)	/* Allow time bank functions				*/
-#define SM_FILE_EM		(1L<<20)	/* Allow file attachments in E-mail 		*/
-#define SM_SHRTPAGE		(1L<<21)	/* Short sysop page 						*/
-#define SM_TIME_EXP		(1L<<22)	/* Set to expired values if out-of-time 	*/
-#define SM_FASTMAIL		(1L<<23)	/* Fast e-mail storage mode 				*/
-#define SM_NONODELIST	(1L<<24)	/* Suppress active node list during logon	*/
-#define SM_UNUSED2		(1L<<25)	/*											*/
-#define SM_FWDTONET		(1L<<26)	/* Allow forwarding of e-mail to netmail	*/
-#define SM_DELREADM		(1L<<27)	/* Delete read mail automatically			*/
-#define SM_NOCDTCVT		(1L<<28)	/* No credit to minute conversions allowed	*/
-#define SM_DELEMAIL		(1L<<29)	/* Physically remove deleted e-mail immed.	*/
-#define SM_USRVDELM		(1L<<30)	/* Users can see deleted msgs				*/
-#define SM_SYSVDELM		(1L<<31)	/* Sysops can see deleted msgs				*/
-
-									/* Different bits in node_misc				*/
-#define NM_NO_NUM		(1<<8)		/* Don't allow logons by user number        */
-#define NM_LOGON_R		(1<<9)		/* Allow logons by user real name			*/
-#define NM_LOGON_P		(1<<10)		/* Secure logons (always ask for password)	*/
-#define NM_LOWPRIO		(1<<15)		/* Always use low priority input			*/
-#define NM_7BITONLY		(1L<<16)	/* Except 7-bit input only (E71 terminals)	*/
-#define NM_NOPAUSESPIN	(1L<<18)	/* No spinning cursor at pause prompt		*/
-#define NM_CLOSENODEDAB	(1L<<19)	/* Keep node.dab file closed (for Samba)	*/
+								// Different bits in sys_misc				
+#define SM_CLOSED		(1<<0) 	// System is clsoed to New Users			
+#define SM_SYSSTAT		(1<<1) 	// Sysops activity included in statistics	
+#define SM_NOSYSINFO	(1<<2) 	// Suppress system info display at logon	
+#define SM_PWEDIT		(1<<3) 	// Allow users to change their passwords	
+#define SM_RA_EMU		(1<<4) 	// Reverse R/A commands at msg read prompt	
+#define SM_ANON_EM		(1<<5) 	// Allow anonymous e-mail					
+#define SM_LISTLOC		(1<<6) 	// Use location of caller in user lists 	
+#define SM_WILDCAT		(1<<7) 	// Expand Wildcat color codes in messages	
+#define SM_PCBOARD		(1<<8) 	// Expand PCBoard color codes in messages	
+#define SM_WWIV 		(1<<9) 	// Expand WWIV color codes in messages		
+#define SM_CELERITY		(1<<10)	// Expand Celerity color codes in messages	
+#define SM_RENEGADE		(1<<11)	// Expand Renegade color codes in messages	
+#define SM_ECHO_PW		(1<<12)	// Echo passwords locally					
+#define SM_SYSPASSLOGIN	(1<<13)	// Require system password for sysop login	
+#define SM_AUTO_DST		(1<<14)	// Automatic Daylight Savings Toggle (US)   
+#define SM_R_SYSOP		(1<<15)	// Allow remote sysop login/commands		
+#define SM_QUOTE_EM		(1<<16)	// Allow quoting of e-mail					
+#define SM_EURODATE		(1<<17)	// European date format (DD/MM/YY)			
+#define SM_MILITARY		(1<<18)	// Military (24hr) time format 				
+#define SM_TIMEBANK		(1<<19)	// Allow time bank functions				
+#define SM_FILE_EM		(1<<20)	// Allow file attachments in E-mail 		
+#define SM_SHRTPAGE		(1<<21)	// Short sysop page 						
+#define SM_TIME_EXP		(1<<22)	// Set to expired values if out-of-time 	
+#define SM_FASTMAIL		(1<<23)	// Fast e-mail storage mode 				
+#define SM_NONODELIST	(1<<24)	// Suppress active node list during logon	
+#define SM_UNUSED2		(1<<25)
+#define SM_FWDTONET		(1<<26)	// Allow forwarding of e-mail to netmail	
+#define SM_DELREADM		(1<<27)	// Delete read mail automatically			
+#define SM_NOCDTCVT		(1<<28)	// No credit to minute conversions allowed	
+#define SM_DELEMAIL		(1<<29)	// Physically remove deleted e-mail immed.	
+#define SM_USRVDELM		(1<<30)	// Users can see deleted msgs				
+#define SM_SYSVDELM		(1<<31)	// Sysops can see deleted msgs				
+
+								// Bit flags for scfg_t.login
+#define LOGIN_USERNUM	(1<<0)	// Allow logins by user number
+#define LOGIN_REALNAME	(1<<1)	// Allow logins by user's real name
+#define LOGIN_PWPROMPT	(1<<2)	// Always display password prompt, even for bad login-id
+
+								// Different bits in node_misc
+#define NM_NO_NUM		(1<<8)	// Don't allow logins by user number (deprecated)       
+#define NM_LOGON_R		(1<<9)	// Allow logins by user real name (deprecated) 
+#define NM_LOGON_P		(1<<10)	// Secure logins (always ask for password) (deprecated) 	
+#define NM_LOWPRIO		(1<<15)	// Always use low priority input			
+#define NM_7BITONLY		(1<<16)	// Except 7-bit input only (E71 terminals)	
+#define NM_NOPAUSESPIN	(1<<18)	// No spinning cursor at pause prompt		
+#define NM_CLOSENODEDAB	(1<<19)	// Keep node.dab file closed (for Samba)	
 
 									/* Bit values for level_misc[x] 	*/
 #define LEVEL_EXPTOLVL	(1<<0)		/* Expire to level_expireto[x]		*/
diff --git a/src/sbbs3/scfg/scfgnode.c b/src/sbbs3/scfg/scfgnode.c
index 526d8a0ccf..d7a5ea6cce 100644
--- a/src/sbbs3/scfg/scfgnode.c
+++ b/src/sbbs3/scfg/scfgnode.c
@@ -215,12 +215,6 @@ void node_cfg()
 				done=0;
 				while(!done) {
 					i=0;
-					sprintf(opt[i++],"%-27.27s%s","Allow Login by User Number"
-						,cfg.node_misc&NM_NO_NUM ? "No":"Yes");
-					sprintf(opt[i++],"%-27.27s%s","Allow Login by Real Name"
-						,cfg.node_misc&NM_LOGON_R ? "Yes":"No");
-					sprintf(opt[i++],"%-27.27s%s","Always Prompt for Password"
-						,cfg.node_misc&NM_LOGON_P ? "Yes":"No");
 					sprintf(opt[i++],"%-27.27s%s","Allow 8-bit Remote Logins"
 						,cfg.node_misc&NM_7BITONLY ? "No":"Yes");
 					sprintf(opt[i++],"%-27.27s%s","Spinning Pause Prompt"
@@ -243,63 +237,6 @@ void node_cfg()
 							done=1;
 							break;
 						case 0:
-							i=cfg.node_misc&NM_NO_NUM ? 1:0;
-							uifc.helpbuf=
-								"`Allow Login by User Number:`\n"
-								"\n"
-								"If you want users to be able login using their user number at the\n"
-								"login prompt, set this option to `Yes`.\n"
-							;
-							i=uifc.list(WIN_MID|WIN_SAV,0,10,0,&i,0
-								,"Allow Login by User Number",uifcYesNoOpts);
-							if(i==0 && cfg.node_misc&NM_NO_NUM) {
-								cfg.node_misc&=~NM_NO_NUM;
-								uifc.changes=1; 
-							}
-							else if(i==1 && !(cfg.node_misc&NM_NO_NUM)) {
-								cfg.node_misc|=NM_NO_NUM;
-								uifc.changes=1; 
-							}
-							break;
-						case 1:
-							i=cfg.node_misc&NM_LOGON_R ? 0:1;
-							uifc.helpbuf=
-								"`Allow Login by Real Name:`\n"
-								"\n"
-								"If you want users to be able login using their real name as well as\n"
-								"their alias, set this option to `Yes`.\n"
-							;
-							i=uifc.list(WIN_MID|WIN_SAV,0,10,0,&i,0
-								,"Allow Login by Real Name",uifcYesNoOpts);
-							if(i==0 && !(cfg.node_misc&NM_LOGON_R)) {
-								cfg.node_misc|=NM_LOGON_R;
-								uifc.changes=1; 
-							}
-							else if(i==1 && (cfg.node_misc&NM_LOGON_R)) {
-								cfg.node_misc&=~NM_LOGON_R;
-								uifc.changes=1; 
-							}
-							break;
-						case 2:
-							i=cfg.node_misc&NM_LOGON_P ? 0:1;
-							uifc.helpbuf=
-								"`Always Prompt for Password:`\n"
-								"\n"
-								"If you want to have attempted logins using an unknown user name still\n"
-								"prompt for a password (i.e. for enhanced security), set this option to `Yes`.\n"
-							;
-							i=uifc.list(WIN_MID|WIN_SAV,0,10,0,&i,0
-								,"Always Prompt for Password",uifcYesNoOpts);
-							if(i==0 && !(cfg.node_misc&NM_LOGON_P)) {
-								cfg.node_misc|=NM_LOGON_P;
-								uifc.changes=1; 
-							}
-							else if(i==1 && (cfg.node_misc&NM_LOGON_P)) {
-								cfg.node_misc&=~NM_LOGON_P;
-								uifc.changes=1; 
-							}
-							break;
-						case 3:
 							i=cfg.node_misc&NM_7BITONLY ? 0:1;
 							uifc.helpbuf=
 								"`Allow 8-bit Remote Input During Login:`\n"
@@ -319,7 +256,7 @@ void node_cfg()
 								uifc.changes=1; 
 							}
 							break;
-						case 4:
+						case 1:
 							i=cfg.node_misc&NM_NOPAUSESPIN ? 1:0;
 							uifc.helpbuf=
 								"`Spinning Pause Prompt:`\n"
@@ -338,7 +275,7 @@ void node_cfg()
 								uifc.changes=1; 
 							}
 							break;
-						case 5:
+						case 2:
 							i=cfg.node_misc&NM_CLOSENODEDAB ? 1:0;
 							uifc.helpbuf=
 								"`Keep Node File Open:`\n"
diff --git a/src/sbbs3/scfg/scfgsys.c b/src/sbbs3/scfg/scfgsys.c
index 5cbd32a266..10f8482d4c 100644
--- a/src/sbbs3/scfg/scfgsys.c
+++ b/src/sbbs3/scfg/scfgsys.c
@@ -63,6 +63,7 @@ static void configure_dst(void)
 void sys_cfg(void)
 {
 	static int sys_dflt,adv_dflt,tog_dflt,new_dflt;
+	static int tog_bar;
 	static int adv_bar;
 	char str[81],done=0;
 	int i,j,k,dflt,bar;
@@ -532,6 +533,10 @@ void sys_cfg(void)
 					i=0;
 					sprintf(opt[i++],"%-33.33s%s","Allow User Aliases"
 						,cfg.uq&UQ_ALIASES ? "Yes" : "No");
+					sprintf(opt[i++],"%-33.33s%s","Allow Login by Real Name"
+						,(!(cfg.uq&UQ_ALIASES) || cfg.sys_login & LOGIN_REALNAME) ? "Yes" : "No");
+					sprintf(opt[i++],"%-33.33s%s","Allow Login by User Number"
+						,(cfg.sys_login & LOGIN_USERNUM) ? "Yes" : "No");
 					sprintf(opt[i++],"%-33.33s%s","Allow Time Banking"
 						,cfg.sys_misc&SM_TIMEBANK ? "Yes" : "No");
 					sprintf(opt[i++],"%-33.33s%s","Allow Credit Conversions"
@@ -540,6 +545,8 @@ void sys_cfg(void)
 						,cfg.sys_misc&SM_R_SYSOP ? "Yes" : "No");
 					sprintf(opt[i++],"%-33.33s%s","Display/Log Passwords Locally"
 						,cfg.sys_misc&SM_ECHO_PW ? "Yes" : "No");
+					sprintf(opt[i++],"%-33.33s%s","Always Prompt for Password"
+						,cfg.sys_login & LOGIN_PWPROMPT ? "Yes":"No");
 					sprintf(opt[i++],"%-33.33s%s","Short Sysop Page"
 						,cfg.sys_misc&SM_SHRTPAGE ? "Yes" : "No");
 					sprintf(opt[i++],"%-33.33s%s","Include Sysop in Statistics"
@@ -567,12 +574,12 @@ void sys_cfg(void)
 						"This is a menu of system related options that can be toggled between\n"
 						"two or more states, such as `Yes` and `No`.\n"
 					;
-					switch(uifc.list(WIN_ACT|WIN_BOT|WIN_RHT,0,0,41,&tog_dflt,0
+					switch(uifc.list(WIN_ACT|WIN_BOT|WIN_RHT,0,0,41,&tog_dflt,&tog_bar
 						,"Toggle Options",opt)) {
 						case -1:
 							done=1;
 							break;
-						case 0:
+						case __COUNTER__:
 							i=cfg.uq&UQ_ALIASES ? 0:1;
 							uifc.helpbuf=
 								"`Allow Users to Use Aliases:`\n"
@@ -592,7 +599,41 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 1:
+						case __COUNTER__:
+							if(!(cfg.uq&UQ_ALIASES))
+								break;
+							i = (cfg.sys_login & LOGIN_REALNAME) ? 0:1;
+							uifc.helpbuf=
+								"`Allow Login by Real Name:`\n"
+								"\n"
+								"If you want users to be able login using their real name as well as\n"
+								"their alias, set this option to `Yes`.\n"
+							;
+							i=uifc.list(WIN_MID|WIN_SAV,0,10,0,&i,0
+								,"Allow Login by Real Name",uifcYesNoOpts);
+							if((i==0 && !(cfg.sys_login & LOGIN_REALNAME))
+								|| (i==1 && (cfg.sys_login & LOGIN_REALNAME))) {
+								cfg.sys_login ^= LOGIN_REALNAME;
+								uifc.changes=1; 
+							}
+							break;
+						case __COUNTER__:
+							i = (cfg.sys_login & LOGIN_USERNUM) ? 0:1;
+							uifc.helpbuf=
+								"`Allow Login by User Number:`\n"
+								"\n"
+								"If you want users to be able login using their user number at the\n"
+								"login prompt, set this option to `Yes`.\n"
+							;
+							i=uifc.list(WIN_MID|WIN_SAV,0,10,0,&i,0
+								,"Allow Login by User Number",uifcYesNoOpts);
+							if((i==0 && !(cfg.sys_login & LOGIN_USERNUM))
+								|| (i==1 && (cfg.sys_login & LOGIN_USERNUM))) {
+								cfg.sys_login ^= LOGIN_USERNUM;
+								uifc.changes=1; 
+							}
+							break;
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_TIMEBANK ? 0:1;
 							uifc.helpbuf=
 								"`Allow Time Banking:`\n"
@@ -614,7 +655,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 2:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_NOCDTCVT ? 1:0;
 							uifc.helpbuf=
 								"`Allow Credits to be Converted into Minutes:`\n"
@@ -635,7 +676,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 3:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_R_SYSOP ? 0:1;
 							uifc.helpbuf=
 								"`Allow Sysop Access:`\n"
@@ -654,7 +695,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 4:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_ECHO_PW ? 0:1;
 							uifc.helpbuf=
 								"`Display/Log Passwords Locally:`\n"
@@ -675,7 +716,24 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 5:
+						case __COUNTER__:
+							i=cfg.sys_login & LOGIN_PWPROMPT ? 0:1;
+							uifc.helpbuf=
+								"`Always Prompt for Password:`\n"
+								"\n"
+								"If you want to have attempted logins using an unknown user name still\n"
+								"prompt for a password (i.e. for enhanced security), set this option to\n"
+								"`Yes`.\n"
+							;
+							i=uifc.list(WIN_MID|WIN_SAV,0,10,0,&i,0
+								,"Always Prompt for Password",uifcYesNoOpts);
+							if((i==0 && !(cfg.sys_login & LOGIN_PWPROMPT))
+								|| (i==1 && (cfg.sys_login & LOGIN_PWPROMPT))) {
+								cfg.sys_login ^= LOGIN_PWPROMPT;
+								uifc.changes=1; 
+							}
+							break;
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_SHRTPAGE ? 0:1;
 							uifc.helpbuf=
 								"`Short Sysop Page:`\n"
@@ -694,7 +752,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 6:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_SYSSTAT ? 0:1;
 							uifc.helpbuf=
 								"`Include Sysop Activity in System Statistics:`\n"
@@ -716,7 +774,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 7:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_CLOSED ? 0:1;
 							uifc.helpbuf=
 								"`Closed to New Users:`\n"
@@ -734,7 +792,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 8:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_LISTLOC ? 0:1;
 							uifc.helpbuf=
 								"`User Location in User Lists:`\n"
@@ -755,7 +813,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 9:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_MILITARY ? 0:1;
 							uifc.helpbuf=
 								"`Military:`\n"
@@ -774,7 +832,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 10:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_EURODATE ? 0:1;
 							uifc.helpbuf=
 								"`European Date Format:`\n"
@@ -793,7 +851,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 11:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_TIME_EXP ? 0:1;
 							uifc.helpbuf=
 								"`User Expires When Out-of-time:`\n"
@@ -812,7 +870,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 12:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_SYSPASSLOGIN ? 0:1;
 							uifc.helpbuf=
 								"`Require System Password for Sysop Login:`\n"
@@ -832,7 +890,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 13:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_NOSYSINFO ? 1:0;
 							uifc.helpbuf=
 								"`Display System Information During Logon:`\n"
@@ -851,7 +909,7 @@ void sys_cfg(void)
 								uifc.changes=1; 
 							}
 							break;
-						case 14:
+						case __COUNTER__:
 							i=cfg.sys_misc&SM_NONODELIST ? 1:0;
 							uifc.helpbuf=
 								"`Display Active Node List During Logon:`\n"
diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h
index 5893b94d32..dbc37f5802 100644
--- a/src/sbbs3/scfgdefs.h
+++ b/src/sbbs3/scfgdefs.h
@@ -470,7 +470,8 @@ typedef struct
 	uint16_t		mdm_ansdelay;		/* Modem seconds to delay after answer */
 	uchar			mdm_rings;			/* Rings to wait till answer */
 
-	int32_t 		sys_misc;			/* System Misc Settings */
+	uint32_t		sys_login;			// Login Settings (Bit-flags)
+	uint32_t 		sys_misc;			/* System Misc Settings */
 	char 			sys_pass[41];		/* System Pass Word */
 	char 			sys_name[41];		/* System Name */
 	char 			sys_id[LEN_QWKID+1];/* System ID for QWK Packets */
diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c
index 6cfc349f50..4884d0ec03 100644
--- a/src/sbbs3/scfglib1.c
+++ b/src/sbbs3/scfglib1.c
@@ -110,6 +110,7 @@ BOOL read_main_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 
 	cfg->sys_timezone = iniGetShortInt(ini, ROOT_SECTION, "timezone", 0);
 	cfg->sys_misc = iniGetUInteger(ini, ROOT_SECTION, "settings", 0);
+	cfg->sys_login = iniGetUInteger(ini, ROOT_SECTION, "login", 0);
 	cfg->sys_pwdays = iniGetInteger(ini, ROOT_SECTION, "pwdays", 0);
 	cfg->sys_deldays = iniGetInteger(ini, ROOT_SECTION, "deldays", 0);
 	cfg->sys_exp_warn = iniGetInteger(ini, ROOT_SECTION, "exp_warn", 0);
diff --git a/src/sbbs3/scfgsave.c b/src/sbbs3/scfgsave.c
index ae5d28656c..a7cc2b79db 100644
--- a/src/sbbs3/scfgsave.c
+++ b/src/sbbs3/scfgsave.c
@@ -128,6 +128,7 @@ BOOL write_main_cfg(scfg_t* cfg, int backup_level)
 	iniSetString(&ini, ROOT_SECTION, "password", cfg->sys_pass, NULL);
 	iniSetShortInt(&ini, ROOT_SECTION, "timezone", cfg->sys_timezone, NULL);
 	iniSetHexInt(&ini, ROOT_SECTION, "settings", cfg->sys_misc, NULL);
+	iniSetHexInt(&ini, ROOT_SECTION, "login", cfg->sys_login, NULL);
 	iniSetShortInt(&ini, ROOT_SECTION, "lastnode", cfg->sys_lastnode, NULL);
 	iniSetShortInt(&ini, ROOT_SECTION, "pwdays", cfg->sys_pwdays, NULL);
 	iniSetShortInt(&ini, ROOT_SECTION, "deldays", cfg->sys_deldays, NULL);
diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c
index f70d035001..817077a148 100644
--- a/src/sbbs3/services.c
+++ b/src/sbbs3/services.c
@@ -385,10 +385,8 @@ js_login(JSContext *cx, uintN argc, jsval *arglist)
 			putmsgptrs(&scfg, &client->user, client->subscan);
 	}
 
-	if(IS_DIGIT(*user))
-		client->user.number=atoi(user);
-	else if(*user)
-		client->user.number=matchuser(&scfg,user,FALSE);
+	if(*user)
+		client->user.number = find_login_id(&scfg, user);
 
 	if(getuserdat(&scfg,&client->user)!=0) {
 		lprintf(LOG_NOTICE,"%04d %s !USER NOT FOUND: '%s'"
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index afb7a974ea..6d09c0a39c 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -146,6 +146,21 @@ BOOL matchusername(scfg_t* cfg, const char* name, const char* comp)
 	return *np == '\0' && (*cp == '\0' || *cp == '@');
 }
 
+uint find_login_id(scfg_t* cfg, const char* user_id)
+{
+	int usernum;
+
+	if((cfg->sys_login & LOGIN_USERNUM) && IS_DIGIT(user_id[0]))
+		return atoi(user_id);
+
+	usernum = matchuser(cfg, user_id, /* sysop_alias: */FALSE);
+	if(usernum < 1 && check_realname(cfg, user_id) && (cfg->sys_login & LOGIN_REALNAME))
+		usernum = finduserstr(cfg, 0, USER_NAME, user_id
+			,/* del: */FALSE, /* next: */FALSE
+			,/* Progress_cb: */NULL, /* cbdata: */NULL);
+	return usernum;
+}
+
 /****************************************************************************/
 uint total_users(scfg_t* cfg)
 {
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index 5894890fde..d41980ad78 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -89,6 +89,8 @@ DLLEXPORT int	getnodeclient(scfg_t*, uint number, client_t*, time_t*);
 DLLEXPORT uint	finduserstr(scfg_t*, uint usernumber, enum user_field, const char *str
 					,BOOL del, BOOL next, void (*progress)(void*, int, int), void* cbdata);
 
+DLLEXPORT uint	find_login_id(scfg_t*, const char* user_id);
+
 DLLEXPORT BOOL	chk_ar(scfg_t*, uchar* str, user_t*, client_t*); /* checks access requirements */
 
 DLLEXPORT uint32_t getusermisc(scfg_t*, int usernumber);
diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c
index c2e066e713..5b943ba099 100644
--- a/src/sbbs3/websrvr.c
+++ b/src/sbbs3/websrvr.c
@@ -1932,7 +1932,7 @@ static BOOL check_ars(http_session_t * session)
 	}
 
 	/* Require a password */
-	i=matchuser(&scfg, session->req.auth.username, FALSE);
+	i = find_login_id(&scfg, session->req.auth.username);
 	if(i==0) {
 		if(session->last_user_num!=0) {
 			if(session->last_user_num>0)
@@ -5510,10 +5510,7 @@ js_login(JSContext *cx, uintN argc, jsval *arglist)
 
 	memset(&user,0,sizeof(user));
 
-	if(IS_DIGIT(*username))
-		user.number=atoi(username);
-	else if(*username)
-		user.number=matchuser(&scfg,username,FALSE);
+	user.number = find_login_id(&scfg, username);
 
 	if(getuserdat(&scfg,&user)!=0) {
 		lprintf(LOG_NOTICE,"%04d !USER NOT FOUND: '%s'"
-- 
GitLab