2009-11-19 05:57:56 +08:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* passwordcheck.c
|
|
|
|
*
|
|
|
|
*
|
2021-01-03 02:06:25 +08:00
|
|
|
* Copyright (c) 2009-2021, PostgreSQL Global Development Group
|
2009-11-19 05:57:56 +08:00
|
|
|
*
|
|
|
|
* Author: Laurenz Albe <laurenz.albe@wien.gv.at>
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2010-09-21 04:08:53 +08:00
|
|
|
* contrib/passwordcheck/passwordcheck.c
|
2009-11-19 05:57:56 +08:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
#ifdef USE_CRACKLIB
|
|
|
|
#include <crack.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "commands/user.h"
|
|
|
|
#include "fmgr.h"
|
2019-10-23 11:56:22 +08:00
|
|
|
#include "libpq/crypt.h"
|
2009-11-19 05:57:56 +08:00
|
|
|
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
|
2019-08-01 08:37:28 +08:00
|
|
|
/* Saved hook value in case of unload */
|
|
|
|
static check_password_hook_type prev_check_password_hook = NULL;
|
|
|
|
|
2009-11-19 05:57:56 +08:00
|
|
|
/* passwords shorter than this will be rejected */
|
|
|
|
#define MIN_PWD_LENGTH 8
|
|
|
|
|
|
|
|
extern void _PG_init(void);
|
2019-08-01 08:37:28 +08:00
|
|
|
extern void _PG_fini(void);
|
2009-11-19 05:57:56 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* check_password
|
|
|
|
*
|
|
|
|
* performs checks on an encrypted or unencrypted password
|
|
|
|
* ereport's if not acceptable
|
|
|
|
*
|
|
|
|
* username: name of role being created or changed
|
|
|
|
* password: new password (possibly already encrypted)
|
Remove support for password_encryption='off' / 'plain'.
Storing passwords in plaintext hasn't been a good idea for a very long
time, if ever. Now seems like a good time to finally forbid it, since we're
messing with this in PostgreSQL 10 anyway.
Remove the CREATE/ALTER USER UNENCRYPTED PASSSWORD 'foo' syntax, since
storing passwords unencrypted is no longer supported. ENCRYPTED PASSWORD
'foo' is still accepted, but ENCRYPTED is now just a noise-word, it does
the same as just PASSWORD 'foo'.
Likewise, remove the --unencrypted option from createuser, but accept
--encrypted as a no-op for backward compatibility. AFAICS, --encrypted was
a no-op even before this patch, because createuser encrypted the password
before sending it to the server even if --encrypted was not specified. It
added the ENCRYPTED keyword to the SQL command, but since the password was
already in encrypted form, it didn't make any difference. The documentation
was not clear on whether that was intended or not, but it's moot now.
Also, while password_encryption='on' is still accepted as an alias for
'md5', it is now marked as hidden, so that it is not listed as an accepted
value in error hints, for example. That's not directly related to removing
'plain', but it seems better this way.
Reviewed by Michael Paquier
Discussion: https://www.postgresql.org/message-id/16e9b768-fd78-0b12-cfc1-7b6b7f238fde@iki.fi
2017-05-08 16:26:07 +08:00
|
|
|
* password_type: PASSWORD_TYPE_* code, to indicate if the password is
|
|
|
|
* in plaintext or encrypted form.
|
2009-11-19 05:57:56 +08:00
|
|
|
* validuntil_time: password expiration time, as a timestamptz Datum
|
|
|
|
* validuntil_null: true if password expiration time is NULL
|
|
|
|
*
|
|
|
|
* This sample implementation doesn't pay any attention to the password
|
|
|
|
* expiration time, but you might wish to insist that it be non-null and
|
|
|
|
* not too far in the future.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
check_password(const char *username,
|
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
2017-02-01 19:11:37 +08:00
|
|
|
const char *shadow_pass,
|
|
|
|
PasswordType password_type,
|
2009-11-19 05:57:56 +08:00
|
|
|
Datum validuntil_time,
|
|
|
|
bool validuntil_null)
|
|
|
|
{
|
2019-08-01 08:37:28 +08:00
|
|
|
if (prev_check_password_hook)
|
|
|
|
prev_check_password_hook(username, shadow_pass,
|
|
|
|
password_type, validuntil_time,
|
|
|
|
validuntil_null);
|
|
|
|
|
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
2017-02-01 19:11:37 +08:00
|
|
|
if (password_type != PASSWORD_TYPE_PLAINTEXT)
|
2009-11-19 05:57:56 +08: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
2017-02-01 19:11:37 +08:00
|
|
|
/*
|
|
|
|
* 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),
|
2017-08-12 09:04:04 +08:00
|
|
|
errmsg("password must not equal user name")));
|
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
2017-02-01 19:11:37 +08:00
|
|
|
}
|
|
|
|
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;
|
2020-08-28 14:16:32 +08:00
|
|
|
#ifdef USE_CRACKLIB
|
|
|
|
const char *reason;
|
|
|
|
#endif
|
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
2017-02-01 19:11:37 +08:00
|
|
|
|
|
|
|
/* 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++)
|
|
|
|
{
|
2009-11-19 05:57:56 +08: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
2017-02-01 19:11:37 +08:00
|
|
|
* isalpha() does not work for multibyte encodings but let's
|
|
|
|
* consider non-ASCII characters non-letters
|
2009-11-19 05:57:56 +08: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
2017-02-01 19:11:37 +08:00
|
|
|
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")));
|
2009-11-19 05:57:56 +08:00
|
|
|
|
|
|
|
#ifdef USE_CRACKLIB
|
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
2017-02-01 19:11:37 +08:00
|
|
|
/* call cracklib to check password */
|
2020-08-28 14:16:32 +08:00
|
|
|
if ((reason = FascistCheck(password, CRACKLIB_DICTPATH)))
|
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
2017-02-01 19:11:37 +08:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
2020-08-28 14:16:32 +08:00
|
|
|
errmsg("password is easily cracked"),
|
|
|
|
errdetail_log("cracklib diagnostic: %s", reason)));
|
2009-11-19 05:57:56 +08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* all checks passed, password is ok */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Module initialization function
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
_PG_init(void)
|
|
|
|
{
|
|
|
|
/* activate password checks when the module is loaded */
|
2019-08-01 08:37:28 +08:00
|
|
|
prev_check_password_hook = check_password_hook;
|
2009-11-19 05:57:56 +08:00
|
|
|
check_password_hook = check_password;
|
|
|
|
}
|
2019-08-01 08:37:28 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Module unload function
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
_PG_fini(void)
|
|
|
|
{
|
|
|
|
/* uninstall hook */
|
|
|
|
check_password_hook = prev_check_password_hook;
|
|
|
|
}
|