From 85dea26141fc2f9fd9033a716b05e3ad1c202341 Mon Sep 17 00:00:00 2001
From: "Rob Swindell (on Windows 11)" <rob@synchro.net>
Date: Fri, 11 Apr 2025 18:11:31 -0700
Subject: [PATCH] Make the "high quality" password checking optional (default:
 off/false)

To enable high quality/entropy password checking, set SCFG->System->Security
->Demand High Quality Password to "Yes".

This defaults to false since the "quality" standard is higher than it used to
be and that could be confusing for sysops or users.
---
 src/sbbs3/scfg/scfgsys.c | 18 +++++++++++++++++-
 src/sbbs3/scfgdefs.h     |  1 +
 src/sbbs3/scfglib1.c     |  1 +
 src/sbbs3/scfgsave.c     |  1 +
 src/sbbs3/userdat.c      | 26 +++++++++++++-------------
 5 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/src/sbbs3/scfg/scfgsys.c b/src/sbbs3/scfg/scfgsys.c
index 4b366f4077..1cfdfea268 100644
--- a/src/sbbs3/scfg/scfgsys.c
+++ b/src/sbbs3/scfg/scfgsys.c
@@ -600,7 +600,6 @@ void security_cfg(void)
 		         , (!(cfg.uq & UQ_ALIASES) || cfg.sys_login & LOGIN_REALNAME) ? "Yes" : "No");
 		snprintf(opt[i++], MAX_OPLN, "%-33.33s%s", "Allow Login by User Number"
 		         , (cfg.sys_login & LOGIN_USERNUM) ? "Yes" : "No");
-
 		SAFEPRINTF(str, "%s Password"
 		           , cfg.sys_misc & SM_PWEDIT && cfg.sys_pwdays ? "Users Must Change"
 		    : cfg.sys_pwdays ? "Users Get New Random" : "Users Can Choose");
@@ -613,6 +612,8 @@ void security_cfg(void)
 		if (cfg.sys_misc & SM_PWEDIT)
 			sprintf(tmp + strlen(tmp), ", %u chars minimum", cfg.min_pwlen);
 		snprintf(opt[i++], MAX_OPLN, "%-33.33s%s", str, tmp);
+		snprintf(opt[i++], MAX_OPLN, "%-33.33s%s", "Demand High Quality Password"
+				, cfg.hq_password ? "Yes" : "No");
 		snprintf(opt[i++], MAX_OPLN, "%-33.33s%s", "Always Prompt for Password"
 		         , cfg.sys_login & LOGIN_PWPROMPT ? "Yes":"No");
 		snprintf(opt[i++], MAX_OPLN, "%-33.33s%s", "Display/Log Passwords Locally"
@@ -811,6 +812,21 @@ void security_cfg(void)
 					cfg.sys_pwdays = 0;
 				}
 				break;
+			case __COUNTER__:
+				i = (cfg.hq_password) ? 0:1;
+				uifc.helpbuf =
+					"`Demand High Quality Password:`\n"
+					"\n"
+					"If you want users to be required to have a \"high quality\" password\n"
+					"based on calculated entropy, set this option to `Yes`.\n"
+					"\n"
+					"For elevated security, set this option to `Yes`.\n"
+				;
+				i = uifc.list(WIN_MID | WIN_SAV, 0, 10, 0, &i, 0
+				              , "Require Users to use High Quality/Entropy Passwords", uifcYesNoOpts);
+				if ((i == 0 && !cfg.hq_password) || (i == 1 && cfg.hq_password))
+					cfg.hq_password = !cfg.hq_password;
+				break;
 			case __COUNTER__:
 				i = cfg.sys_login & LOGIN_PWPROMPT ? 0:1;
 				uifc.helpbuf =
diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h
index 59d342cf42..e956d6d637 100644
--- a/src/sbbs3/scfgdefs.h
+++ b/src/sbbs3/scfgdefs.h
@@ -486,6 +486,7 @@ typedef struct
 	fevent_t 		sys_daily;			/* Daily event */
 	fevent_t 		sys_logon;			/* Logon event */
 	fevent_t 		sys_logout;			/* Logout event */
+	bool			hq_password;		/* Require high quality/entropy user passwords */
 	uint8_t			min_pwlen;
 	uint16_t		sys_pwdays; 		/* Max days between password change */
 	uint16_t		sys_deldays;		/* Days to keep deleted users */
diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c
index 159486fac2..d8a2470410 100644
--- a/src/sbbs3/scfglib1.c
+++ b/src/sbbs3/scfglib1.c
@@ -127,6 +127,7 @@ bool read_main_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 	cfg->max_minutes = iniGetInteger(ini, ROOT_SECTION, "max_minutes", 0);
 	cfg->cdt_per_dollar = (uint32_t)iniGetBytes(ini, ROOT_SECTION, "cdt_per_dollar", 1, 0);
 	cfg->guest_msgscan_init = iniGetInteger(ini, ROOT_SECTION, "guest_msgscan_init", 0);
+	cfg->hq_password = iniGetBool(ini, ROOT_SECTION, "hq_password", false);
 	cfg->min_pwlen = iniGetInteger(ini, ROOT_SECTION, "min_password_length", 0);
 	if (cfg->min_pwlen < MIN_PASS_LEN)
 		cfg->min_pwlen = MIN_PASS_LEN;
diff --git a/src/sbbs3/scfgsave.c b/src/sbbs3/scfgsave.c
index 441702bcaf..e5b9335d53 100644
--- a/src/sbbs3/scfgsave.c
+++ b/src/sbbs3/scfgsave.c
@@ -140,6 +140,7 @@ bool write_main_cfg(scfg_t* cfg)
 	iniSetUInteger(&ini, ROOT_SECTION, "cdt_per_dollar", cfg->cdt_per_dollar, NULL);
 	iniSetBool(&ini, ROOT_SECTION, "new_install", cfg->new_install, NULL);
 	iniSetUInteger(&ini, ROOT_SECTION, "guest_msgscan_init", cfg->guest_msgscan_init, NULL);
+	iniSetBool(&ini, ROOT_SECTION, "hq_password", cfg->hq_password, NULL);
 	iniSetUInteger(&ini, ROOT_SECTION, "min_password_length", cfg->min_pwlen, NULL);
 	iniSetBytes(&ini, ROOT_SECTION, "max_log_size", 1, cfg->max_log_size, NULL);
 	iniSetUInteger(&ini, ROOT_SECTION, "max_logs_kept", cfg->max_logs_kept, NULL);
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 33770ce0bf..52d1b55529 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -3990,19 +3990,19 @@ bool check_pass(scfg_t* cfg, const char *pass, user_t* user, bool unique, int* r
 		}
 	}
 
-	// Require a minimum sequence of unique (non-repeating/increment/decrementing) characters
-	int i;
-	int run = 0;
-	for (i = 0; i < (len - 1); ++i) {
-		if (abs(toupper(pass[i]) - toupper(pass[i + 1])) > 1) {
-			if (++run >= cfg->min_pwlen / 2)
-				break;
-		} else
-			run = 0;
-	}
-	if (i >= (len - 1)) {
-		*reason = PasswordInvalid;
-		return false;
+	// Require a minimum number of unique (non-repeating/incrementing/decrementing) characters
+	if (cfg->hq_password) {
+		int i;
+		char good[LEN_PASS + 1] = {0};
+		int g = 0;
+		for (i = 0; i < len; ++i) {
+			if (abs(toupper(pass[i]) - toupper(pass[i + 1])) > 1 && strchr(good, pass[i]) == NULL)
+				good[g++] = pass[i];
+		}
+		if (g < cfg->min_pwlen) {
+			*reason = PasswordInvalid;
+			return false;
+		}
 	}
 
 	// Compare proposed password against user properties
-- 
GitLab