Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions contrib/passwordcheck/expected/passwordcheck.out
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 38 additions & 2 deletions contrib/passwordcheck/expected/passwordcheck_1.out
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 78 additions & 2 deletions contrib/passwordcheck/passwordcheck.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)))
Expand Down Expand Up @@ -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 */
Expand Down
40 changes: 38 additions & 2 deletions contrib/passwordcheck/sql/passwordcheck.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,57 @@ 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';

-- 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';

Expand Down
72 changes: 72 additions & 0 deletions doc/src/sgml/passwordcheck.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,76 @@
</note>
</listitem>
</varlistentry>

<varlistentry>
<term>
<varname>passwordcheck.require_uppercase</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>passwordcheck.require_uppercase</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Require at least one uppercase ASCII letter. The default is
<literal>on</literal>. Only superusers can change this setting.
</para>
</listitem>
</varlistentry>

<varlistentry>
<term>
<varname>passwordcheck.require_lowercase</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>passwordcheck.require_lowercase</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Require at least one lowercase ASCII letter. The default is
<literal>on</literal>. Only superusers can change this setting.
</para>
</listitem>
</varlistentry>

<varlistentry>
<term>
<varname>passwordcheck.require_digit</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>passwordcheck.require_digit</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Require at least one ASCII digit. The default is
<literal>on</literal>. Only superusers can change this setting.
</para>
</listitem>
</varlistentry>

<varlistentry>
<term>
<varname>passwordcheck.require_special</varname> (<type>boolean</type>)
<indexterm>
<primary><varname>passwordcheck.require_special</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Require at least one ASCII punctuation character. The default is
<literal>on</literal>. Only superusers can change this setting.
</para>
</listitem>
</varlistentry>
</variablelist>

<note>
<para>
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.
</para>
</note>

<para>
In ordinary usage, this parameter is set in
<filename>postgresql.conf</filename>, but superusers can alter it on-the-fly
Expand All @@ -94,6 +162,10 @@
<programlisting>
# postgresql.conf
passwordcheck.min_password_length = 12
passwordcheck.require_uppercase = on
passwordcheck.require_lowercase = on
passwordcheck.require_digit = on
passwordcheck.require_special = on
</programlisting>
</sect2>
</sect1>