diff options
Diffstat (limited to 'contrib/passwordcheck/passwordcheck.c')
-rw-r--r-- | contrib/passwordcheck/passwordcheck.c | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c new file mode 100644 index 0000000..d5f9d14 --- /dev/null +++ b/contrib/passwordcheck/passwordcheck.c @@ -0,0 +1,157 @@ +/*------------------------------------------------------------------------- + * + * passwordcheck.c + * + * + * Copyright (c) 2009-2020, 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); +extern void _PG_fini(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. + */ + char *logdetail; + + 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; + + /* 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 (FascistCheck(password, CRACKLIB_DICTPATH)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is easily cracked"))); +#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; +} + +/* + * Module unload function + */ +void +_PG_fini(void) +{ + /* uninstall hook */ + check_password_hook = prev_check_password_hook; +} |