Line data Source code
1 : /*------------------------------------------------------------------------- 2 : * 3 : * passwordcheck.c 4 : * 5 : * 6 : * Copyright (c) 2009-2024, PostgreSQL Global Development Group 7 : * 8 : * Author: Laurenz Albe <laurenz.albe@wien.gv.at> 9 : * 10 : * IDENTIFICATION 11 : * contrib/passwordcheck/passwordcheck.c 12 : * 13 : *------------------------------------------------------------------------- 14 : */ 15 : #include "postgres.h" 16 : 17 : #include <ctype.h> 18 : 19 : #ifdef USE_CRACKLIB 20 : #include <crack.h> 21 : #endif 22 : 23 : #include "commands/user.h" 24 : #include "fmgr.h" 25 : #include "libpq/crypt.h" 26 : 27 2 : PG_MODULE_MAGIC; 28 : 29 : /* Saved hook value in case of unload */ 30 : static check_password_hook_type prev_check_password_hook = NULL; 31 : 32 : /* passwords shorter than this will be rejected */ 33 : #define MIN_PWD_LENGTH 8 34 : 35 : /* 36 : * check_password 37 : * 38 : * performs checks on an encrypted or unencrypted password 39 : * ereport's if not acceptable 40 : * 41 : * username: name of role being created or changed 42 : * password: new password (possibly already encrypted) 43 : * password_type: PASSWORD_TYPE_* code, to indicate if the password is 44 : * in plaintext or encrypted form. 45 : * validuntil_time: password expiration time, as a timestamptz Datum 46 : * validuntil_null: true if password expiration time is NULL 47 : * 48 : * This sample implementation doesn't pay any attention to the password 49 : * expiration time, but you might wish to insist that it be non-null and 50 : * not too far in the future. 51 : */ 52 : static void 53 12 : check_password(const char *username, 54 : const char *shadow_pass, 55 : PasswordType password_type, 56 : Datum validuntil_time, 57 : bool validuntil_null) 58 : { 59 12 : if (prev_check_password_hook) 60 0 : prev_check_password_hook(username, shadow_pass, 61 : password_type, validuntil_time, 62 : validuntil_null); 63 : 64 12 : if (password_type != PASSWORD_TYPE_PLAINTEXT) 65 : { 66 : /* 67 : * Unfortunately we cannot perform exhaustive checks on encrypted 68 : * passwords - we are restricted to guessing. (Alternatively, we could 69 : * insist on the password being presented non-encrypted, but that has 70 : * its own security disadvantages.) 71 : * 72 : * We only check for username = password. 73 : */ 74 4 : const char *logdetail = NULL; 75 : 76 4 : if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK) 77 2 : ereport(ERROR, 78 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 79 : errmsg("password must not equal user name"))); 80 : } 81 : else 82 : { 83 : /* 84 : * For unencrypted passwords we can perform better checks 85 : */ 86 8 : const char *password = shadow_pass; 87 8 : int pwdlen = strlen(password); 88 : int i; 89 : bool pwd_has_letter, 90 : pwd_has_nonletter; 91 : #ifdef USE_CRACKLIB 92 : const char *reason; 93 : #endif 94 : 95 : /* enforce minimum length */ 96 8 : if (pwdlen < MIN_PWD_LENGTH) 97 2 : ereport(ERROR, 98 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 99 : errmsg("password is too short"))); 100 : 101 : /* check if the password contains the username */ 102 6 : if (strstr(password, username)) 103 2 : ereport(ERROR, 104 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 105 : errmsg("password must not contain user name"))); 106 : 107 : /* check if the password contains both letters and non-letters */ 108 4 : pwd_has_letter = false; 109 4 : pwd_has_nonletter = false; 110 86 : for (i = 0; i < pwdlen; i++) 111 : { 112 : /* 113 : * isalpha() does not work for multibyte encodings but let's 114 : * consider non-ASCII characters non-letters 115 : */ 116 82 : if (isalpha((unsigned char) password[i])) 117 76 : pwd_has_letter = true; 118 : else 119 6 : pwd_has_nonletter = true; 120 : } 121 4 : if (!pwd_has_letter || !pwd_has_nonletter) 122 2 : ereport(ERROR, 123 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 124 : errmsg("password must contain both letters and nonletters"))); 125 : 126 : #ifdef USE_CRACKLIB 127 : /* call cracklib to check password */ 128 : if ((reason = FascistCheck(password, CRACKLIB_DICTPATH))) 129 : ereport(ERROR, 130 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 131 : errmsg("password is easily cracked"), 132 : errdetail_log("cracklib diagnostic: %s", reason))); 133 : #endif 134 : } 135 : 136 : /* all checks passed, password is ok */ 137 4 : } 138 : 139 : /* 140 : * Module initialization function 141 : */ 142 : void 143 2 : _PG_init(void) 144 : { 145 : /* activate password checks when the module is loaded */ 146 2 : prev_check_password_hook = check_password_hook; 147 2 : check_password_hook = check_password; 148 2 : }