diff options
Diffstat (limited to 'src/backend/libpq/crypt.c')
-rw-r--r-- | src/backend/libpq/crypt.c | 159 |
1 files changed, 119 insertions, 40 deletions
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index e1c10137d9f..893ce6e967b 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -1,10 +1,8 @@ /*------------------------------------------------------------------------- * * 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 @@ -106,6 +104,65 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail) } /* + * 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. * * 'shadow_pass' is the user's correct password or password hash, as stored @@ -135,32 +192,40 @@ md5_crypt_verify(const char *role, const char *shadow_pass, * 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) @@ -198,22 +263,36 @@ plain_crypt_verify(const char *role, const char *shadow_pass, * 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) |