diff --git a/contrib/passwordcheck/expected/passwordcheck.out b/contrib/passwordcheck/expected/passwordcheck.out index 83472c76d27..fc7562fe414 100644 --- a/contrib/passwordcheck/expected/passwordcheck.out +++ b/contrib/passwordcheck/expected/passwordcheck.out @@ -2,20 +2,56 @@ SET md5_password_warnings = off; LOAD 'passwordcheck'; CREATE USER regress_passwordcheck_user1; -- ok -ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password'; +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_nice_long_password1'; -- error: too short ALTER USER regress_passwordcheck_user1 PASSWORD 'tooshrt'; ERROR: password is too short DETAIL: password must be at least "passwordcheck.min_password_length" (8) bytes long -- ok SET passwordcheck.min_password_length = 6; -ALTER USER regress_passwordcheck_user1 PASSWORD 'v_shrt'; +ALTER USER regress_passwordcheck_user1 PASSWORD 'V_shrt1'; -- error: contains user name ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1'; ERROR: password must not contain user name -- error: contains only letters ALTER USER regress_passwordcheck_user1 PASSWORD 'alessnicelongpassword'; ERROR: password must contain both letters and nonletters +-- error: no uppercase letter +ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password1'; +ERROR: password must contain an uppercase letter +-- error: no lowercase letter +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_NICE_LONG_PASSWORD1'; +ERROR: password must contain a lowercase letter +-- error: no digit +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_nice_long_password'; +ERROR: password must contain a digit +-- error: no special character +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anicelongpassword1'; +ERROR: password must contain a special character +-- ok: uppercase check can be configured +SET passwordcheck.require_uppercase = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'another_long_password1'; +SET passwordcheck.require_uppercase = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'another_long_password1'; +ERROR: password must contain an uppercase letter +-- ok: lowercase check can be configured +SET passwordcheck.require_lowercase = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'ANOTHER_LONG_PASSWORD1'; +SET passwordcheck.require_lowercase = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'ANOTHER_LONG_PASSWORD1'; +ERROR: password must contain a lowercase letter +-- ok: digit check can be configured +SET passwordcheck.require_digit = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Another_long_password'; +SET passwordcheck.require_digit = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Another_long_password'; +ERROR: password must contain a digit +-- ok: special-character check can be configured +SET passwordcheck.require_special = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anotherlongpassword1'; +SET passwordcheck.require_special = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anotherlongpassword1'; +ERROR: password must contain a special character -- encrypted ok (password is "secret") ALTER USER regress_passwordcheck_user1 PASSWORD 'md592350e12ac34e52dd598f90893bb3ae7'; -- error: password is user name diff --git a/contrib/passwordcheck/expected/passwordcheck_1.out b/contrib/passwordcheck/expected/passwordcheck_1.out index fb12ec45cc4..0fb55f30d47 100644 --- a/contrib/passwordcheck/expected/passwordcheck_1.out +++ b/contrib/passwordcheck/expected/passwordcheck_1.out @@ -2,20 +2,56 @@ SET md5_password_warnings = off; LOAD 'passwordcheck'; CREATE USER regress_passwordcheck_user1; -- ok -ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password'; +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_nice_long_password1'; -- error: too short ALTER USER regress_passwordcheck_user1 PASSWORD 'tooshrt'; ERROR: password is too short DETAIL: password must be at least "passwordcheck.min_password_length" (8) bytes long -- ok SET passwordcheck.min_password_length = 6; -ALTER USER regress_passwordcheck_user1 PASSWORD 'v_shrt'; +ALTER USER regress_passwordcheck_user1 PASSWORD 'V_shrt1'; -- error: contains user name ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1'; ERROR: password must not contain user name -- error: contains only letters ALTER USER regress_passwordcheck_user1 PASSWORD 'alessnicelongpassword'; ERROR: password must contain both letters and nonletters +-- error: no uppercase letter +ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password1'; +ERROR: password must contain an uppercase letter +-- error: no lowercase letter +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_NICE_LONG_PASSWORD1'; +ERROR: password must contain a lowercase letter +-- error: no digit +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_nice_long_password'; +ERROR: password must contain a digit +-- error: no special character +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anicelongpassword1'; +ERROR: password must contain a special character +-- ok: uppercase check can be configured +SET passwordcheck.require_uppercase = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'another_long_password1'; +SET passwordcheck.require_uppercase = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'another_long_password1'; +ERROR: password must contain an uppercase letter +-- ok: lowercase check can be configured +SET passwordcheck.require_lowercase = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'ANOTHER_LONG_PASSWORD1'; +SET passwordcheck.require_lowercase = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'ANOTHER_LONG_PASSWORD1'; +ERROR: password must contain a lowercase letter +-- ok: digit check can be configured +SET passwordcheck.require_digit = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Another_long_password'; +SET passwordcheck.require_digit = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Another_long_password'; +ERROR: password must contain a digit +-- ok: special-character check can be configured +SET passwordcheck.require_special = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anotherlongpassword1'; +SET passwordcheck.require_special = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anotherlongpassword1'; +ERROR: password must contain a special character -- encrypted ok (password is "secret") ALTER USER regress_passwordcheck_user1 PASSWORD 'md592350e12ac34e52dd598f90893bb3ae7'; -- error: password is user name diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c index 13fd5c976a0..543fcff7353 100644 --- a/contrib/passwordcheck/passwordcheck.c +++ b/contrib/passwordcheck/passwordcheck.c @@ -35,6 +35,10 @@ static check_password_hook_type prev_check_password_hook = NULL; /* GUC variables */ static int min_password_length = 8; +static bool require_uppercase = true; +static bool require_lowercase = true; +static bool require_digit = true; +static bool require_special = true; /* * check_password @@ -91,7 +95,11 @@ check_password(const char *username, int pwdlen = strlen(password); int i; bool pwd_has_letter, - pwd_has_nonletter; + pwd_has_nonletter, + pwd_has_uppercase, + pwd_has_lowercase, + pwd_has_digit, + pwd_has_special; #ifdef USE_CRACKLIB const char *reason; #endif @@ -113,22 +121,54 @@ check_password(const char *username, /* check if the password contains both letters and non-letters */ pwd_has_letter = false; pwd_has_nonletter = false; + pwd_has_uppercase = false; + pwd_has_lowercase = false; + pwd_has_digit = false; + pwd_has_special = false; for (i = 0; i < pwdlen; i++) { + unsigned char ch = (unsigned char) password[i]; + /* * isalpha() does not work for multibyte encodings but let's * consider non-ASCII characters non-letters */ - if (isalpha((unsigned char) password[i])) + if (isalpha(ch)) pwd_has_letter = true; else pwd_has_nonletter = true; + + if (isupper(ch)) + pwd_has_uppercase = true; + if (islower(ch)) + pwd_has_lowercase = true; + if (isdigit(ch)) + pwd_has_digit = true; + if (ispunct(ch)) + pwd_has_special = true; } if (!pwd_has_letter || !pwd_has_nonletter) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("password must contain both letters and nonletters"))); + if (require_uppercase && !pwd_has_uppercase) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must contain an uppercase letter"))); + if (require_lowercase && !pwd_has_lowercase) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must contain a lowercase letter"))); + if (require_digit && !pwd_has_digit) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must contain a digit"))); + if (require_special && !pwd_has_special) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must contain a special character"))); + #ifdef USE_CRACKLIB /* call cracklib to check password */ if ((reason = FascistCheck(password, CRACKLIB_DICTPATH))) @@ -159,6 +199,42 @@ _PG_init(void) GUC_UNIT_BYTE, NULL, NULL, NULL); + DefineCustomBoolVariable("passwordcheck.require_uppercase", + "Require at least one uppercase ASCII letter.", + NULL, + &require_uppercase, + true, + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomBoolVariable("passwordcheck.require_lowercase", + "Require at least one lowercase ASCII letter.", + NULL, + &require_lowercase, + true, + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomBoolVariable("passwordcheck.require_digit", + "Require at least one ASCII digit.", + NULL, + &require_digit, + true, + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomBoolVariable("passwordcheck.require_special", + "Require at least one ASCII punctuation character.", + NULL, + &require_special, + true, + PGC_SUSET, + 0, + NULL, NULL, NULL); + MarkGUCPrefixReserved("passwordcheck"); /* activate password checks when the module is loaded */ diff --git a/contrib/passwordcheck/sql/passwordcheck.sql b/contrib/passwordcheck/sql/passwordcheck.sql index 21ad8d452b5..f524a63cd7d 100644 --- a/contrib/passwordcheck/sql/passwordcheck.sql +++ b/contrib/passwordcheck/sql/passwordcheck.sql @@ -4,14 +4,14 @@ LOAD 'passwordcheck'; CREATE USER regress_passwordcheck_user1; -- ok -ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password'; +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_nice_long_password1'; -- error: too short ALTER USER regress_passwordcheck_user1 PASSWORD 'tooshrt'; -- ok SET passwordcheck.min_password_length = 6; -ALTER USER regress_passwordcheck_user1 PASSWORD 'v_shrt'; +ALTER USER regress_passwordcheck_user1 PASSWORD 'V_shrt1'; -- error: contains user name ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1'; @@ -19,6 +19,42 @@ ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1' -- error: contains only letters ALTER USER regress_passwordcheck_user1 PASSWORD 'alessnicelongpassword'; +-- error: no uppercase letter +ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password1'; + +-- error: no lowercase letter +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_NICE_LONG_PASSWORD1'; + +-- error: no digit +ALTER USER regress_passwordcheck_user1 PASSWORD 'A_nice_long_password'; + +-- error: no special character +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anicelongpassword1'; + +-- ok: uppercase check can be configured +SET passwordcheck.require_uppercase = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'another_long_password1'; +SET passwordcheck.require_uppercase = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'another_long_password1'; + +-- ok: lowercase check can be configured +SET passwordcheck.require_lowercase = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'ANOTHER_LONG_PASSWORD1'; +SET passwordcheck.require_lowercase = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'ANOTHER_LONG_PASSWORD1'; + +-- ok: digit check can be configured +SET passwordcheck.require_digit = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Another_long_password'; +SET passwordcheck.require_digit = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Another_long_password'; + +-- ok: special-character check can be configured +SET passwordcheck.require_special = off; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anotherlongpassword1'; +SET passwordcheck.require_special = on; +ALTER USER regress_passwordcheck_user1 PASSWORD 'Anotherlongpassword1'; + -- encrypted ok (password is "secret") ALTER USER regress_passwordcheck_user1 PASSWORD 'md592350e12ac34e52dd598f90893bb3ae7'; diff --git a/doc/src/sgml/passwordcheck.sgml b/doc/src/sgml/passwordcheck.sgml index 7ea32410463..e0295137ad2 100644 --- a/doc/src/sgml/passwordcheck.sgml +++ b/doc/src/sgml/passwordcheck.sgml @@ -83,8 +83,76 @@ + + + + passwordcheck.require_uppercase (boolean) + + passwordcheck.require_uppercase configuration parameter + + + + + Require at least one uppercase ASCII letter. The default is + on. Only superusers can change this setting. + + + + + + + passwordcheck.require_lowercase (boolean) + + passwordcheck.require_lowercase configuration parameter + + + + + Require at least one lowercase ASCII letter. The default is + on. Only superusers can change this setting. + + + + + + + passwordcheck.require_digit (boolean) + + passwordcheck.require_digit configuration parameter + + + + + Require at least one ASCII digit. The default is + on. Only superusers can change this setting. + + + + + + + passwordcheck.require_special (boolean) + + passwordcheck.require_special configuration parameter + + + + + Require at least one ASCII punctuation character. The default is + on. Only superusers can change this setting. + + + + + + Password complexity parameters only apply when a password is supplied in + plain text. For a pre-encrypted password, the module can only check + whether it equals the user name. + + + In ordinary usage, this parameter is set in postgresql.conf, but superusers can alter it on-the-fly @@ -94,6 +162,10 @@ # postgresql.conf passwordcheck.min_password_length = 12 +passwordcheck.require_uppercase = on +passwordcheck.require_lowercase = on +passwordcheck.require_digit = on +passwordcheck.require_special = on