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-2025, 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#include <limits.h>
19
20#ifdef USE_CRACKLIB
21#include <crack.h>
22#endif
23
24#include "commands/user.h"
25#include "fmgr.h"
26#include "libpq/crypt.h"
27
29
30/* Saved hook value */
32
33/* GUC variables */
34static int min_password_length = 8;
35
36/*
37 * check_password
38 *
39 * performs checks on an encrypted or unencrypted password
40 * ereport's if not acceptable
41 *
42 * username: name of role being created or changed
43 * password: new password (possibly already encrypted)
44 * password_type: PASSWORD_TYPE_* code, to indicate if the password is
45 * in plaintext or encrypted form.
46 * validuntil_time: password expiration time, as a timestamptz Datum
47 * validuntil_null: true if password expiration time is NULL
48 *
49 * This sample implementation doesn't pay any attention to the password
50 * expiration time, but you might wish to insist that it be non-null and
51 * not too far in the future.
52 */
53static void
55 const char *shadow_pass,
56 PasswordType password_type,
57 Datum validuntil_time,
58 bool validuntil_null)
59{
62 password_type, validuntil_time,
63 validuntil_null);
64
65 if (password_type != PASSWORD_TYPE_PLAINTEXT)
66 {
67 /*
68 * Unfortunately we cannot perform exhaustive checks on encrypted
69 * passwords - we are restricted to guessing. (Alternatively, we could
70 * insist on the password being presented non-encrypted, but that has
71 * its own security disadvantages.)
72 *
73 * We only check for username = password.
74 */
75 const char *logdetail = NULL;
76
77 if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
79 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
80 errmsg("password must not equal user name")));
81 }
82 else
83 {
84 /*
85 * For unencrypted passwords we can perform better checks
86 */
87 const char *password = shadow_pass;
88 int pwdlen = strlen(password);
89 int i;
90 bool pwd_has_letter,
91 pwd_has_nonletter;
92#ifdef USE_CRACKLIB
93 const char *reason;
94#endif
95
96 /* enforce minimum length */
97 if (pwdlen < min_password_length)
99 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
100 errmsg("password is too short"),
101 errdetail("password must be at least \"passwordcheck.min_password_length\" (%d) bytes long",
103
104 /* check if the password contains the username */
105 if (strstr(password, username))
107 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
108 errmsg("password must not contain user name")));
109
110 /* check if the password contains both letters and non-letters */
111 pwd_has_letter = false;
112 pwd_has_nonletter = false;
113 for (i = 0; i < pwdlen; i++)
114 {
115 /*
116 * isalpha() does not work for multibyte encodings but let's
117 * consider non-ASCII characters non-letters
118 */
119 if (isalpha((unsigned char) password[i]))
120 pwd_has_letter = true;
121 else
122 pwd_has_nonletter = true;
123 }
124 if (!pwd_has_letter || !pwd_has_nonletter)
126 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
127 errmsg("password must contain both letters and nonletters")));
128
129#ifdef USE_CRACKLIB
130 /* call cracklib to check password */
131 if ((reason = FascistCheck(password, CRACKLIB_DICTPATH)))
133 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
134 errmsg("password is easily cracked"),
135 errdetail_log("cracklib diagnostic: %s", reason)));
136#endif
137 }
138
139 /* all checks passed, password is ok */
140}
141
142/*
143 * Module initialization function
144 */
145void
147{
148 /* Define custom GUC variables. */
149 DefineCustomIntVariable("passwordcheck.min_password_length",
150 "Minimum allowed password length.",
151 NULL,
153 8,
154 0, INT_MAX,
155 PGC_SUSET,
157 NULL, NULL, NULL);
158
159 MarkGUCPrefixReserved("passwordcheck");
160
161 /* activate password checks when the module is loaded */
164}
#define STATUS_OK
Definition: c.h:1126
int plain_crypt_verify(const char *role, const char *shadow_pass, const char *client_pass, const char **logdetail)
Definition: crypt.c:256
PasswordType
Definition: crypt.h:41
@ PASSWORD_TYPE_PLAINTEXT
Definition: crypt.h:42
int errdetail(const char *fmt,...)
Definition: elog.c:1203
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
int errdetail_log(const char *fmt,...)
Definition: elog.c:1251
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:5279
void DefineCustomIntVariable(const char *name, const char *short_desc, const char *long_desc, int *valueAddr, int bootValue, int minValue, int maxValue, GucContext context, int flags, GucIntCheckHook check_hook, GucIntAssignHook assign_hook, GucShowHook show_hook)
Definition: guc.c:5158
@ PGC_SUSET
Definition: guc.h:78
#define GUC_UNIT_BYTE
Definition: guc.h:236
static char * username
Definition: initdb.c:153
int i
Definition: isn.c:72
static int min_password_length
Definition: passwordcheck.c:34
static void check_password(const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null)
Definition: passwordcheck.c:54
void _PG_init(void)
PG_MODULE_MAGIC
Definition: passwordcheck.c:28
static check_password_hook_type prev_check_password_hook
Definition: passwordcheck.c:31
uintptr_t Datum
Definition: postgres.h:69
static char * password
Definition: streamutil.c:52
check_password_hook_type check_password_hook
Definition: user.c:91
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