mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Replace isMD5() with a more future-proof way to check if pw is encrypted.
The rule is that if pg_authid.rolpassword begins with "md5" and has the right length, it's an MD5 hash, otherwise it's a plaintext password. The idiom has been to use isMD5() to check for that, but that gets awkward, when we add new kinds of verifiers, like the verifiers for SCRAM authentication in the pending SCRAM patch set. Replace isMD5() with a new get_password_type() function, so that when new verifier types are added, we don't need to remember to modify every place that currently calls isMD5(), to also recognize the new kinds of verifiers. Also, use the new plain_crypt_verify function in passwordcheck, so that it doesn't need to know about MD5, or in the future, about other kinds of hashes or password verifiers. Reviewed by Michael Paquier and Peter Eisentraut. Discussion: https://www.postgresql.org/message-id/2d07165c-1793-e243-a2a9-e45b624c7580@iki.fi
This commit is contained in:
@ -21,7 +21,7 @@
|
||||
#endif
|
||||
|
||||
#include "commands/user.h"
|
||||
#include "common/md5.h"
|
||||
#include "libpq/crypt.h"
|
||||
#include "fmgr.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
@ -50,87 +50,77 @@ extern void _PG_init(void);
|
||||
*/
|
||||
static void
|
||||
check_password(const char *username,
|
||||
const char *password,
|
||||
int password_type,
|
||||
const char *shadow_pass,
|
||||
PasswordType password_type,
|
||||
Datum validuntil_time,
|
||||
bool validuntil_null)
|
||||
{
|
||||
int namelen = strlen(username);
|
||||
int pwdlen = strlen(password);
|
||||
char encrypted[MD5_PASSWD_LEN + 1];
|
||||
int i;
|
||||
bool pwd_has_letter,
|
||||
pwd_has_nonletter;
|
||||
|
||||
switch (password_type)
|
||||
if (password_type != PASSWORD_TYPE_PLAINTEXT)
|
||||
{
|
||||
case PASSWORD_TYPE_MD5:
|
||||
/*
|
||||
* Unfortunately we cannot perform exhaustive checks on encrypted
|
||||
* passwords - we are restricted to guessing. (Alternatively, we could
|
||||
* insist on the password being presented non-encrypted, but that has
|
||||
* its own security disadvantages.)
|
||||
*
|
||||
* We only check for username = password.
|
||||
*/
|
||||
char *logdetail;
|
||||
|
||||
if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password must not contain user name")));
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* For unencrypted passwords we can perform better checks
|
||||
*/
|
||||
const char *password = shadow_pass;
|
||||
int pwdlen = strlen(password);
|
||||
int i;
|
||||
bool pwd_has_letter,
|
||||
pwd_has_nonletter;
|
||||
|
||||
/* enforce minimum length */
|
||||
if (pwdlen < MIN_PWD_LENGTH)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password is too short")));
|
||||
|
||||
/* check if the password contains the username */
|
||||
if (strstr(password, username))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password must not contain user name")));
|
||||
|
||||
/* check if the password contains both letters and non-letters */
|
||||
pwd_has_letter = false;
|
||||
pwd_has_nonletter = false;
|
||||
for (i = 0; i < pwdlen; i++)
|
||||
{
|
||||
/*
|
||||
* Unfortunately we cannot perform exhaustive checks on encrypted
|
||||
* passwords - we are restricted to guessing. (Alternatively, we
|
||||
* could insist on the password being presented non-encrypted, but
|
||||
* that has its own security disadvantages.)
|
||||
*
|
||||
* We only check for username = password.
|
||||
* isalpha() does not work for multibyte encodings but let's
|
||||
* consider non-ASCII characters non-letters
|
||||
*/
|
||||
if (!pg_md5_encrypt(username, username, namelen, encrypted))
|
||||
elog(ERROR, "password encryption failed");
|
||||
if (strcmp(password, encrypted) == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password must not contain user name")));
|
||||
break;
|
||||
|
||||
case PASSWORD_TYPE_PLAINTEXT:
|
||||
|
||||
/*
|
||||
* For unencrypted passwords we can perform better checks
|
||||
*/
|
||||
|
||||
/* enforce minimum length */
|
||||
if (pwdlen < MIN_PWD_LENGTH)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password is too short")));
|
||||
|
||||
/* check if the password contains the username */
|
||||
if (strstr(password, username))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password must not contain user name")));
|
||||
|
||||
/* check if the password contains both letters and non-letters */
|
||||
pwd_has_letter = false;
|
||||
pwd_has_nonletter = false;
|
||||
for (i = 0; i < pwdlen; i++)
|
||||
{
|
||||
/*
|
||||
* isalpha() does not work for multibyte encodings but let's
|
||||
* consider non-ASCII characters non-letters
|
||||
*/
|
||||
if (isalpha((unsigned char) password[i]))
|
||||
pwd_has_letter = true;
|
||||
else
|
||||
pwd_has_nonletter = true;
|
||||
}
|
||||
if (!pwd_has_letter || !pwd_has_nonletter)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password must contain both letters and nonletters")));
|
||||
if (isalpha((unsigned char) password[i]))
|
||||
pwd_has_letter = true;
|
||||
else
|
||||
pwd_has_nonletter = true;
|
||||
}
|
||||
if (!pwd_has_letter || !pwd_has_nonletter)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password must contain both letters and nonletters")));
|
||||
|
||||
#ifdef USE_CRACKLIB
|
||||
/* call cracklib to check password */
|
||||
if (FascistCheck(password, CRACKLIB_DICTPATH))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password is easily cracked")));
|
||||
/* call cracklib to check password */
|
||||
if (FascistCheck(password, CRACKLIB_DICTPATH))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("password is easily cracked")));
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized password type: %d", password_type);
|
||||
break;
|
||||
}
|
||||
|
||||
/* all checks passed, password is ok */
|
||||
|
Reference in New Issue
Block a user