diff --git a/exec/chksetup.js b/exec/chksetup.js index cf0359e48e2262755190db1064d60d5941d2f99c..f01567027bf80847ef8720b24a1d47dc16ff7b58 100644 --- a/exec/chksetup.js +++ b/exec/chksetup.js @@ -154,6 +154,10 @@ var tests = { , usr.number , password.length , system.max_password_length)); + else if(!system.check_password(password)) + output.push(format("User #%-4u has a low quality password%s" + , usr.number + , options.verbose ? (': ' + password) : '')); if(!system.trashcan("password", password)) continue; output.push(format("User #%-4u has a disallowed password%s" diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp index cece14de9115baf02c7d8d1ca2feddb44c6932ba..a2e6692754e1ab302df3591b52ba73ebce60acaa 100644 --- a/src/sbbs3/js_bbs.cpp +++ b/src/sbbs3/js_bbs.cpp @@ -5014,7 +5014,7 @@ static jsSyncMethodSpec js_bbs_functions[] = { {"good_password", js_chkpass, 1, JSTYPE_ALIAS }, {"check_password", js_chkpass, 1, JSTYPE_BOOLEAN, JSDOCSTR("password, [forced_unique=false]") , JSDOCSTR("Check if requested user password meets minimum password requirements " - "(length, uniqueness, etc.) (AKA good_password).<br>" + "(length, uniqueness, etc.) (AKA good_password). Also checks the <tt>password.can</tt> filter file.<br>" "When <i>forced_unique</i> is <tt>true</tt>, the password must be substantially different from the user's current password.") , 310 }, diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c index fe72db1320f1b6b802399fb98101c79f75e4eac4..24defb3645b1d46c3e675b4538c8a7f1b718a34c 100644 --- a/src/sbbs3/js_system.c +++ b/src/sbbs3/js_system.c @@ -2086,8 +2086,7 @@ js_chkpassword(JSContext *cx, uintN argc, jsval *arglist) return JS_FALSE; rc = JS_SUSPENDREQUEST(cx); - bool result = check_pass(sys->cfg, str, /* user: */NULL, /* unique: */false, /* reason: */NULL) - && !trashcan(sys->cfg, str, "password"); + bool result = check_pass(sys->cfg, str, /* user: */NULL, /* unique: */false, /* reason: */NULL); JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(result)); JS_RESUMEREQUEST(cx, rc); @@ -2401,7 +2400,8 @@ static jsSyncMethodSpec js_system_functions[] = { , 321}, {"check_password", js_chkpassword, 1, JSTYPE_BOOLEAN, JSDOCSTR("password") , JSDOCSTR("Check that the provided string is suitable for a new user password, " - "returns <tt>true</tt> if it meets the system criteria for a user password") + "returns <tt>true</tt> if it meets the system criteria for a user password.<br>" + "Does <b>not</b> check the <tt>password.can</tt> file.") , 321}, {"check_filename", js_chkfname, 1, JSTYPE_BOOLEAN, JSDOCSTR("filename") , JSDOCSTR("Verify that the specified <i>filename</i> string is legal and allowed for upload by users " diff --git a/src/sbbs3/newuser.cpp b/src/sbbs3/newuser.cpp index 57607765a80391e2faf063f40bd7a9f44ce6f88e..ce5d56cf5555640bcfd5bd8646d62b2042092745 100644 --- a/src/sbbs3/newuser.cpp +++ b/src/sbbs3/newuser.cpp @@ -363,13 +363,16 @@ bool sbbs_t::newuser() else lprintf(LOG_NOTICE, "Rejected RLogin password for new user"); } - c = 0; - while (c < MAX(RAND_PASS_LEN, cfg.min_pwlen)) { /* Create random password */ - useron.pass[c] = sbbs_random(43) + '0'; - if (IS_ALPHANUMERIC(useron.pass[c])) - c++; - } - useron.pass[c] = 0; + lprintf(LOG_INFO, "Generating a random password for new user"); + do { + c = 0; + while (c < MAX(RAND_PASS_LEN, cfg.min_pwlen)) { /* Create random password */ + useron.pass[c] = sbbs_random(43) + '0'; + if (IS_ALPHANUMERIC(useron.pass[c])) + c++; + } + useron.pass[c] = 0; + } while (!check_pass(&cfg, useron.pass, &useron, /* unique: */false, /* reason: */NULL)); bprintf(text[YourPasswordIs], useron.pass); diff --git a/src/sbbs3/scfg/scfgsys.c b/src/sbbs3/scfg/scfgsys.c index 4b366f407709a35fec7c9f8e419dfe463392f985..1cfdfea2686c482fecfb2416ebc6d0bcb4e8c9ad 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 59d342cf422b0cb577887f072538b08b16f17038..e956d6d6375e8658c0de43c6de90ba8cce769dd8 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 159486fac2a26cddd8e1933e5ea6a1a4ab6d0ea2..d8a247041057b308fb4c736dbfba1c0c77cbe439 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 441702bcaf424d68508259b53d1732528c8b03b2..e5b9335d534aa7c17de5cd073fb07688b8565acf 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 9b2b0e095512f0909692698f21a43ddfc1be3c0b..52d1b555298c76bb681109e8bd6b670a79c6c518 100644 --- a/src/sbbs3/userdat.c +++ b/src/sbbs3/userdat.c @@ -3965,6 +3965,91 @@ bool check_realname(scfg_t* cfg, const char* name) return (uchar)name[0] < 0x7f && name[1] && IS_ALPHA(name[0]) && strchr(name, ' '); } +/****************************************************************************/ +/* Check a password for uniqueness and validity */ +/* Does *not* check the password.can file! */ +/****************************************************************************/ +bool check_pass(scfg_t* cfg, const char *pass, user_t* user, bool unique, int* reason) +{ + int reason_; + + if (reason == NULL) + reason = &reason_; + + int len = strlen(pass); + if (len < cfg->min_pwlen || len < MIN_PASS_LEN) { + *reason = PasswordTooShort; + return false; + } + if (unique) { + if (user == NULL) + return false; + if (stricmp(pass, user->pass) == 0) { + *reason = PasswordNotChanged; + 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 + if (user != NULL) { + char first[128], last[128], *p; + + SAFECOPY(first, user->alias); + p = strchr(first, ' '); + if (p) { + *p = 0; + SAFECOPY(last, p + 1); + } + else + last[0] = 0; + if ((unique && user->pass[0] + && (strcasestr(pass, user->pass) || strcasestr(user->pass, pass))) + || (user->name[0] + && (strcasestr(pass, user->name) || strcasestr(user->name, pass))) + || strcasestr(pass, user->alias) || strcasestr(user->alias, pass) + || strcasestr(pass, first) || strcasestr(first, pass) + || (last[0] + && (strcasestr(pass, last) || strcasestr(last, pass))) + || strcasestr(pass, user->handle) || strcasestr(user->handle, pass) + || (user->zipcode[0] + && (strcasestr(pass, user->zipcode) || strcasestr(user->zipcode, pass))) + || (user->phone[0] && strcasestr(user->phone, pass)) + ) { + *reason = PasswordObvious; + return false; + } + } + + // Compare proposed password against system properties + if ((cfg->sys_name[0] + && (strcasestr(pass, cfg->sys_name) || strcasestr(cfg->sys_name, pass))) + || (cfg->sys_op[0] + && (strcasestr(pass, cfg->sys_op) || strcasestr(cfg->sys_op, pass))) + || (cfg->sys_id[0] + && (strcasestr(pass, cfg->sys_id) || strcasestr(cfg->sys_id, pass))) + || (cfg->node_phone[0] && strcasestr(pass, cfg->node_phone)) + ) { + *reason = PasswordObvious; + return false; + } + return true; +} + /****************************************************************************/ /* Login attempt/hack tracking */ /****************************************************************************/ @@ -4588,88 +4673,3 @@ enum parsed_vpath parse_vpath(scfg_t* cfg, const char* vpath, int* lib, int* dir return *filename == NULL ? PARSED_VPATH_DIR : PARSED_VPATH_FULL; } - -/****************************************************************************/ -/* Check a password for uniqueness and validity */ -/* Does *not* check the password.can file! */ -/****************************************************************************/ -bool check_pass(scfg_t* cfg, const char *pass, user_t* user, bool unique, int* reason) -{ - int reason_; - - if (reason == NULL) - reason = &reason_; - - int len = strlen(pass); - if (len < cfg->min_pwlen || len < MIN_PASS_LEN) { - *reason = PasswordTooShort; - return false; - } - if (unique) { - if (user == NULL) - return false; - if (stricmp(pass, user->pass) == 0) { - *reason = PasswordNotChanged; - return false; - } - } - - // 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; - } - - // Compare proposed password against user properties - if (user != NULL) { - char first[128], last[128], *p; - - SAFECOPY(first, user->alias); - p = strchr(first, ' '); - if (p) { - *p = 0; - SAFECOPY(last, p + 1); - } - else - last[0] = 0; - if ((unique && user->pass[0] - && (strcasestr(pass, user->pass) || strcasestr(user->pass, pass))) - || (user->name[0] - && (strcasestr(pass, user->name) || strcasestr(user->name, pass))) - || strcasestr(pass, user->alias) || strcasestr(user->alias, pass) - || strcasestr(pass, first) || strcasestr(first, pass) - || (last[0] - && (strcasestr(pass, last) || strcasestr(last, pass))) - || strcasestr(pass, user->handle) || strcasestr(user->handle, pass) - || (user->zipcode[0] - && (strcasestr(pass, user->zipcode) || strcasestr(user->zipcode, pass))) - || (user->phone[0] && strcasestr(user->phone, pass)) - ) { - *reason = PasswordObvious; - return false; - } - } - - // Compare proposed password against system properties - if ((cfg->sys_name[0] - && (strcasestr(pass, cfg->sys_name) || strcasestr(cfg->sys_name, pass))) - || (cfg->sys_op[0] - && (strcasestr(pass, cfg->sys_op) || strcasestr(cfg->sys_op, pass))) - || (cfg->sys_id[0] - && (strcasestr(pass, cfg->sys_id) || strcasestr(cfg->sys_id, pass))) - || (cfg->node_phone[0] && strcasestr(pass, cfg->node_phone)) - ) { - *reason = PasswordObvious; - return false; - } - return true; -}