Age Owner Branch data TLA 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 : :
5261 tgl@sss.pgh.pa.us 27 :CBC 1 : 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 : 6 : check_password(const char *username,
54 : : const char *shadow_pass,
55 : : PasswordType password_type,
56 : : Datum validuntil_time,
57 : : bool validuntil_null)
58 : : {
1718 michael@paquier.xyz 59 [ - + ]: 6 : if (prev_check_password_hook)
1718 michael@paquier.xyz 60 :UBC 0 : prev_check_password_hook(username, shadow_pass,
61 : : password_type, validuntil_time,
62 : : validuntil_null);
63 : :
2629 heikki.linnakangas@i 64 [ + + ]:CBC 6 : 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 : : */
824 michael@paquier.xyz 74 : 2 : const char *logdetail = NULL;
75 : :
2629 heikki.linnakangas@i 76 [ + + ]: 2 : if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
77 [ + - ]: 1 : 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 : 4 : const char *password = shadow_pass;
87 : 4 : 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 [ + + ]: 4 : if (pwdlen < MIN_PWD_LENGTH)
97 [ + - ]: 1 : ereport(ERROR,
98 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
99 : : errmsg("password is too short")));
100 : :
101 : : /* check if the password contains the username */
102 [ + + ]: 3 : if (strstr(password, username))
103 [ + - ]: 1 : 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 : 2 : pwd_has_letter = false;
109 : 2 : pwd_has_nonletter = false;
110 [ + + ]: 43 : 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 [ + + ]: 41 : if (isalpha((unsigned char) password[i]))
117 : 38 : pwd_has_letter = true;
118 : : else
119 : 3 : pwd_has_nonletter = true;
120 : : }
121 [ + - + + ]: 2 : if (!pwd_has_letter || !pwd_has_nonletter)
122 [ + - ]: 1 : 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 */
5261 tgl@sss.pgh.pa.us 137 : 2 : }
138 : :
139 : : /*
140 : : * Module initialization function
141 : : */
142 : : void
143 : 1 : _PG_init(void)
144 : : {
145 : : /* activate password checks when the module is loaded */
1718 michael@paquier.xyz 146 : 1 : prev_check_password_hook = check_password_hook;
5261 tgl@sss.pgh.pa.us 147 : 1 : check_password_hook = check_password;
148 : 1 : }
|