PostgreSQL Source Code  git master
passwordcheck.c
Go to the documentation of this file.
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 
28 
29 /* Saved hook value in case of unload */
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
54  const char *shadow_pass,
55  PasswordType password_type,
56  Datum validuntil_time,
57  bool validuntil_null)
58 {
61  password_type, validuntil_time,
62  validuntil_null);
63 
64  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  const char *logdetail = NULL;
75 
76  if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
77  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  const char *password = shadow_pass;
87  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  if (pwdlen < MIN_PWD_LENGTH)
97  ereport(ERROR,
98  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
99  errmsg("password is too short")));
100 
101  /* check if the password contains the username */
102  if (strstr(password, username))
103  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  pwd_has_letter = false;
109  pwd_has_nonletter = false;
110  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  if (isalpha((unsigned char) password[i]))
117  pwd_has_letter = true;
118  else
119  pwd_has_nonletter = true;
120  }
121  if (!pwd_has_letter || !pwd_has_nonletter)
122  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 }
138 
139 /*
140  * Module initialization function
141  */
142 void
143 _PG_init(void)
144 {
145  /* activate password checks when the module is loaded */
148 }
check_password_hook_type check_password_hook
Definition: user.c:91
#define STATUS_OK
Definition: c.h:1169
int plain_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char **logdetail)
Definition: crypt.c:222
PasswordType
Definition: crypt.h:28
@ PASSWORD_TYPE_PLAINTEXT
Definition: crypt.h:29
int errcode(int sqlerrcode)
Definition: elog.c:859
int errmsg(const char *fmt,...)
Definition: elog.c:1072
int errdetail_log(const char *fmt,...)
Definition: elog.c:1253
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
int i
Definition: isn.c:73
static void check_password(const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null)
Definition: passwordcheck.c:53
#define MIN_PWD_LENGTH
Definition: passwordcheck.c:33
void _PG_init(void)
PG_MODULE_MAGIC
Definition: passwordcheck.c:27
static check_password_hook_type prev_check_password_hook
Definition: passwordcheck.c:30
const char * username
Definition: pgbench.c:296
uintptr_t Datum
Definition: postgres.h:64
static char * password
Definition: streamutil.c:54
void(* check_password_hook_type)(const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null)
Definition: user.h:25