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