#endif
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
*/
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.
- */
- 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:
-
+ /*
+ * 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++)
+ {
/*
- * For unencrypted passwords we can perform better checks
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
*/
-
- /* 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 */
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ get_password_type(password),
validUntil_datum,
validUntil_null);
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(password);
- else
- {
- if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
- encrypted_password))
- elog(ERROR, "password encryption failed");
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(encrypted_password);
- }
+ /* Encrypt the password to the requested format. */
+ char *shadow_pass;
+
+ shadow_pass = encrypt_password(password_type, stmt->role, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(shadow_pass);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ get_password_type(password),
validUntil_datum,
validUntil_null);
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(password);
- else
- {
- if (!pg_md5_encrypt(password, rolename, strlen(rolename),
- encrypted_password))
- elog(ERROR, "password encryption failed");
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(encrypted_password);
- }
+ /* Encrypt the password to the requested format. */
+ char *shadow_pass;
+
+ shadow_pass = encrypt_password(password_type, rolename, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(shadow_pass);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
- if (!isnull && isMD5(TextDatumGetCString(datum)))
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_authid_rolpassword - 1] = true;
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
- *
- * Original coding by Todd A. Brandys
+ * Functions for dealing with encrypted passwords stored in
+ * pg_authid.rolpassword.
*
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
return retval;
}
+/*
+ * What kind of a password verifier is 'shadow_pass'?
+ */
+PasswordType
+get_password_type(const char *shadow_pass)
+{
+ if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
+ return PASSWORD_TYPE_MD5;
+ return PASSWORD_TYPE_PLAINTEXT;
+}
+
+/*
+ * Given a user-supplied password, convert it into a verifier of
+ * 'target_type' kind.
+ *
+ * If the password looks like a valid MD5 hash, it is stored as it is.
+ * We cannot reverse the hash, so even if the caller requested a plaintext
+ * plaintext password, the MD5 hash is returned.
+ */
+char *
+encrypt_password(PasswordType target_type, const char *role,
+ const char *password)
+{
+ PasswordType guessed_type = get_password_type(password);
+ char *encrypted_password;
+
+ switch (target_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+
+ /*
+ * We cannot convert a hashed password back to plaintext, so just
+ * store the password as it was, whether it was hashed or not.
+ */
+ return pstrdup(password);
+
+ case PASSWORD_TYPE_MD5:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ encrypted_password = palloc(MD5_PASSWD_LEN + 1);
+
+ if (!pg_md5_encrypt(password, role, strlen(role),
+ encrypted_password))
+ elog(ERROR, "password encryption failed");
+ return encrypted_password;
+
+ case PASSWORD_TYPE_MD5:
+ return pstrdup(password);
+ }
+ }
+
+ /*
+ * This shouldn't happen, because the above switch statements should
+ * handle every combination of source and target password types.
+ */
+ elog(ERROR, "cannot encrypt password to requested type");
+}
+
/*
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* below: the only possible error is out-of-memory, which is unlikely, and
* if it did happen adding a psprintf call would only make things worse.
*/
- if (isMD5(shadow_pass))
- {
- /* stored password already encrypted, only do salt */
- if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
- return STATUS_ERROR;
- }
- }
- else
+ switch (get_password_type(shadow_pass))
{
- /* stored password is plain, double-encrypt */
- if (!pg_md5_encrypt(shadow_pass,
- role,
- strlen(role),
- crypt_pwd2))
- {
- return STATUS_ERROR;
- }
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
+ case PASSWORD_TYPE_MD5:
+ /* stored password already encrypted, only do salt */
+ if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ /* stored password is plain, double-encrypt */
+ if (!pg_md5_encrypt(shadow_pass,
+ role,
+ strlen(role),
+ crypt_pwd2))
+ {
+ return STATUS_ERROR;
+ }
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ default:
+ /* unknown password hash format. */
+ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
+ role);
return STATUS_ERROR;
- }
}
if (strcmp(client_pass, crypt_pwd) == 0)
* the password the client sent, and compare the hashes. Otherwise
* compare the plaintext passwords directly.
*/
- if (isMD5(shadow_pass))
+ switch (get_password_type(shadow_pass))
{
- if (!pg_md5_encrypt(client_pass,
- role,
- strlen(role),
- crypt_client_pass))
- {
+ case PASSWORD_TYPE_MD5:
+ if (!pg_md5_encrypt(client_pass,
+ role,
+ strlen(role),
+ crypt_client_pass))
+ {
+ /*
+ * We do not bother setting logdetail for pg_md5_encrypt
+ * failure: the only possible error is out-of-memory, which is
+ * unlikely, and if it did happen adding a psprintf call would
+ * only make things worse.
+ */
+ return STATUS_ERROR;
+ }
+ client_pass = crypt_client_pass;
+ break;
+ case PASSWORD_TYPE_PLAINTEXT:
+ break;
+
+ default:
+
/*
- * We do not bother setting logdetail for pg_md5_encrypt failure:
- * the only possible error is out-of-memory, which is unlikely,
- * and if it did happen adding a psprintf call would only make
- * things worse.
+ * This shouldn't happen. Plain "password" authentication should
+ * be possible with any kind of stored password hash.
*/
+ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+ role);
return STATUS_ERROR;
- }
- client_pass = crypt_client_pass;
}
if (strcmp(client_pass, shadow_pass) == 0)
#define USER_H
#include "catalog/objectaddress.h"
+#include "libpq/crypt.h"
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-
-/*
- * Types of password, for Password_encryption GUC and the password_type
- * argument of the check-password hook.
- */
-typedef enum PasswordType
-{
- PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
-} PasswordType;
-
-extern int Password_encryption; /* GUC */
+/* GUC. Is actually of type PasswordType. */
+extern int Password_encryption;
/* Hook to check passwords in CreateRole() and AlterRole() */
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
#define MD5_PASSWD_LEN 35
-#define isMD5(passwd) (strncmp(passwd, "md5", 3) == 0 && \
- strlen(passwd) == MD5_PASSWD_LEN)
-
-
extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum);
extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf);
extern bool pg_md5_encrypt(const char *passwd, const char *salt,
#include "datatype/timestamp.h"
-extern int get_role_password(const char *role, char **shadow_pass, char **logdetail);
+/*
+ * Types of password hashes or verifiers that can be stored in
+ * pg_authid.rolpassword.
+ *
+ * This is also used for the password_encryption GUC.
+ */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern PasswordType get_password_type(const char *shadow_pass);
+extern char *encrypt_password(PasswordType target_type, const char *role,
+ const char *password);
+
+extern int get_role_password(const char *role, char **shadow_pass,
+ char **logdetail);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,