diff options
Diffstat (limited to 'contrib/passwordcheck')
-rw-r--r-- | contrib/passwordcheck/.gitignore | 4 | ||||
-rw-r--r-- | contrib/passwordcheck/Makefile | 24 | ||||
-rw-r--r-- | contrib/passwordcheck/expected/passwordcheck.out | 19 | ||||
-rw-r--r-- | contrib/passwordcheck/passwordcheck.c | 150 | ||||
-rw-r--r-- | contrib/passwordcheck/sql/passwordcheck.sql | 23 |
5 files changed, 220 insertions, 0 deletions
diff --git a/contrib/passwordcheck/.gitignore b/contrib/passwordcheck/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/passwordcheck/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/passwordcheck/Makefile b/contrib/passwordcheck/Makefile new file mode 100644 index 0000000..006735a --- /dev/null +++ b/contrib/passwordcheck/Makefile @@ -0,0 +1,24 @@ +# contrib/passwordcheck/Makefile + +MODULE_big = passwordcheck +OBJS = \ + $(WIN32RES) \ + passwordcheck.o +PGFILEDESC = "passwordcheck - strengthen user password checks" + +# uncomment the following two lines to enable cracklib support +# PG_CPPFLAGS = -DUSE_CRACKLIB '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"' +# SHLIB_LINK = -lcrack + +REGRESS = passwordcheck + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/passwordcheck +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/passwordcheck/expected/passwordcheck.out b/contrib/passwordcheck/expected/passwordcheck.out new file mode 100644 index 0000000..e04cda6 --- /dev/null +++ b/contrib/passwordcheck/expected/passwordcheck.out @@ -0,0 +1,19 @@ +LOAD 'passwordcheck'; +CREATE USER regress_user1; +-- ok +ALTER USER regress_user1 PASSWORD 'a_nice_long_password'; +-- error: too short +ALTER USER regress_user1 PASSWORD 'tooshrt'; +ERROR: password is too short +-- error: contains user name +ALTER USER regress_user1 PASSWORD 'xyzregress_user1'; +ERROR: password must not contain user name +-- error: contains only letters +ALTER USER regress_user1 PASSWORD 'alessnicelongpassword'; +ERROR: password must contain both letters and nonletters +-- encrypted ok (password is "secret") +ALTER USER regress_user1 PASSWORD 'md51a44d829a20a23eac686d9f0d258af13'; +-- error: password is user name +ALTER USER regress_user1 PASSWORD 'md5e589150ae7d28f93333afae92b36ef48'; +ERROR: password must not equal user name +DROP USER regress_user1; diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c new file mode 100644 index 0000000..9d8c58d --- /dev/null +++ b/contrib/passwordcheck/passwordcheck.c @@ -0,0 +1,150 @@ +/*------------------------------------------------------------------------- + * + * passwordcheck.c + * + * + * Copyright (c) 2009-2022, PostgreSQL Global Development Group + * + * Author: Laurenz Albe <laurenz.albe@wien.gv.at> + * + * IDENTIFICATION + * contrib/passwordcheck/passwordcheck.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> + +#ifdef USE_CRACKLIB +#include <crack.h> +#endif + +#include "commands/user.h" +#include "fmgr.h" +#include "libpq/crypt.h" + +PG_MODULE_MAGIC; + +/* Saved hook value in case of unload */ +static check_password_hook_type prev_check_password_hook = NULL; + +/* passwords shorter than this will be rejected */ +#define MIN_PWD_LENGTH 8 + +extern void _PG_init(void); + +/* + * check_password + * + * performs checks on an encrypted or unencrypted password + * ereport's if not acceptable + * + * username: name of role being created or changed + * password: new password (possibly already encrypted) + * password_type: PASSWORD_TYPE_* code, to indicate if the password is + * in plaintext or encrypted form. + * validuntil_time: password expiration time, as a timestamptz Datum + * validuntil_null: true if password expiration time is NULL + * + * This sample implementation doesn't pay any attention to the password + * expiration time, but you might wish to insist that it be non-null and + * not too far in the future. + */ +static void +check_password(const char *username, + const char *shadow_pass, + PasswordType password_type, + Datum validuntil_time, + bool validuntil_null) +{ + if (prev_check_password_hook) + prev_check_password_hook(username, shadow_pass, + password_type, validuntil_time, + validuntil_null); + + if (password_type != PASSWORD_TYPE_PLAINTEXT) + { + /* + * Unfortunately we cannot perform exhaustive checks on encrypted + * passwords - we are restricted to guessing. (Alternatively, we could + * insist on the password being presented non-encrypted, but that has + * its own security disadvantages.) + * + * We only check for username = password. + */ + const char *logdetail = NULL; + + if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must not equal user name"))); + } + else + { + /* + * For unencrypted passwords we can perform better checks + */ + const char *password = shadow_pass; + int pwdlen = strlen(password); + int i; + bool pwd_has_letter, + pwd_has_nonletter; +#ifdef USE_CRACKLIB + const char *reason; +#endif + + /* enforce minimum length */ + if (pwdlen < MIN_PWD_LENGTH) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is too short"))); + + /* check if the password contains the username */ + if (strstr(password, username)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must not contain user name"))); + + /* check if the password contains both letters and non-letters */ + pwd_has_letter = false; + pwd_has_nonletter = false; + for (i = 0; i < pwdlen; i++) + { + /* + * isalpha() does not work for multibyte encodings but let's + * consider non-ASCII characters non-letters + */ + if (isalpha((unsigned char) password[i])) + pwd_has_letter = true; + else + pwd_has_nonletter = true; + } + if (!pwd_has_letter || !pwd_has_nonletter) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must contain both letters and nonletters"))); + +#ifdef USE_CRACKLIB + /* call cracklib to check password */ + if ((reason = FascistCheck(password, CRACKLIB_DICTPATH))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is easily cracked"), + errdetail_log("cracklib diagnostic: %s", reason))); +#endif + } + + /* all checks passed, password is ok */ +} + +/* + * Module initialization function + */ +void +_PG_init(void) +{ + /* activate password checks when the module is loaded */ + prev_check_password_hook = check_password_hook; + check_password_hook = check_password; +} diff --git a/contrib/passwordcheck/sql/passwordcheck.sql b/contrib/passwordcheck/sql/passwordcheck.sql new file mode 100644 index 0000000..d98796a --- /dev/null +++ b/contrib/passwordcheck/sql/passwordcheck.sql @@ -0,0 +1,23 @@ +LOAD 'passwordcheck'; + +CREATE USER regress_user1; + +-- ok +ALTER USER regress_user1 PASSWORD 'a_nice_long_password'; + +-- error: too short +ALTER USER regress_user1 PASSWORD 'tooshrt'; + +-- error: contains user name +ALTER USER regress_user1 PASSWORD 'xyzregress_user1'; + +-- error: contains only letters +ALTER USER regress_user1 PASSWORD 'alessnicelongpassword'; + +-- encrypted ok (password is "secret") +ALTER USER regress_user1 PASSWORD 'md51a44d829a20a23eac686d9f0d258af13'; + +-- error: password is user name +ALTER USER regress_user1 PASSWORD 'md5e589150ae7d28f93333afae92b36ef48'; + +DROP USER regress_user1; |