TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * passwordcheck.c
4 : *
5 : *
6 : * Copyright (c) 2009-2023, 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 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 ECB : */
52 : static void
53 GIC 6 : check_password(const char *username,
54 : const char *shadow_pass,
55 : PasswordType password_type,
56 : Datum validuntil_time,
57 ECB : bool validuntil_null)
58 EUB : {
59 GIC 6 : if (prev_check_password_hook)
60 UIC 0 : prev_check_password_hook(username, shadow_pass,
61 : password_type, validuntil_time,
62 ECB : validuntil_null);
63 :
64 GIC 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 ECB : * We only check for username = password.
73 : */
74 CBC 2 : const char *logdetail = NULL;
75 ECB :
76 GIC 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 ECB : * For unencrypted passwords we can perform better checks
85 : */
86 GIC 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 ECB :
95 : /* enforce minimum length */
96 GIC 4 : if (pwdlen < MIN_PWD_LENGTH)
97 1 : ereport(ERROR,
98 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
99 : errmsg("password is too short")));
100 ECB :
101 : /* check if the password contains the username */
102 GIC 3 : if (strstr(password, username))
103 1 : ereport(ERROR,
104 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
105 : errmsg("password must not contain user name")));
106 ECB :
107 : /* check if the password contains both letters and non-letters */
108 CBC 2 : pwd_has_letter = false;
109 GIC 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 ECB : * consider non-ASCII characters non-letters
115 : */
116 GIC 41 : if (isalpha((unsigned char) password[i]))
117 CBC 38 : pwd_has_letter = true;
118 : else
119 3 : pwd_has_nonletter = true;
120 ECB : }
121 GIC 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 ECB :
136 : /* all checks passed, password is ok */
137 GIC 2 : }
138 :
139 : /*
140 : * Module initialization function
141 ECB : */
142 : void
143 GIC 1 : _PG_init(void)
144 ECB : {
145 : /* activate password checks when the module is loaded */
146 CBC 1 : prev_check_password_hook = check_password_hook;
147 GIC 1 : check_password_hook = check_password;
148 1 : }
|