Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Refactor the code for verifying user's password.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 12 Dec 2016 10:48:13 +0000 (12:48 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 12 Dec 2016 10:48:13 +0000 (12:48 +0200)
Split md5_crypt_verify() into three functions:
* get_role_password() to fetch user's password from pg_authid, and check
  its expiration.
* md5_crypt_verify() to check an MD5 authentication challenge
* plain_crypt_verify() to check a plaintext password.

get_role_password() will be needed as a separate function by the upcoming
SCRAM authentication patch set. Most of the remaining functionality in
md5_crypt_verify() was different for MD5 and plaintext authentication, so
split that for readability.

While we're at it, simplify the *_crypt_verify functions by using
stack-allocated buffers to hold the temporary MD5 hashes, instead of
pallocing.

Reviewed by Michael Paquier.

Discussion: https://www.postgresql.org/message-id/3029e460-d47c-710e-507e-d8ba759d7cbb@iki.fi

src/backend/libpq/auth.c
src/backend/libpq/crypt.c
src/include/libpq/crypt.h

index 9b79dc517da472a400d77ce39c6d19c150a3975e..b8ebf1b6f39a66d93774d4ce3371401452d98440 100644 (file)
@@ -704,6 +704,7 @@ CheckMD5Auth(Port *port, char **logdetail)
 {
    char        md5Salt[4];     /* Password salt */
    char       *passwd;
+   char       *shadow_pass;
    int         result;
 
    if (Db_user_namespace)
@@ -722,12 +723,16 @@ CheckMD5Auth(Port *port, char **logdetail)
    sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4);
 
    passwd = recv_password_packet(port);
-
    if (passwd == NULL)
        return STATUS_EOF;      /* client wouldn't send password */
 
-   result = md5_crypt_verify(port->user_name, passwd, md5Salt, 4, logdetail);
+   result = get_role_password(port->user_name, &shadow_pass, logdetail);
+   if (result == STATUS_OK)
+       result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+                                 md5Salt, 4, logdetail);
 
+   if (shadow_pass)
+       pfree(shadow_pass);
    pfree(passwd);
 
    return result;
@@ -743,16 +748,21 @@ CheckPasswordAuth(Port *port, char **logdetail)
 {
    char       *passwd;
    int         result;
+   char       *shadow_pass;
 
    sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
 
    passwd = recv_password_packet(port);
-
    if (passwd == NULL)
        return STATUS_EOF;      /* client wouldn't send password */
 
-   result = md5_crypt_verify(port->user_name, passwd, NULL, 0, logdetail);
+   result = get_role_password(port->user_name, &shadow_pass, logdetail);
+   if (result == STATUS_OK)
+       result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
+                                   logdetail);
 
+   if (shadow_pass)
+       pfree(shadow_pass);
    pfree(passwd);
 
    return result;
index b4ca17431a231d7dd8173b77f09e5a36d890092d..7e9124f39e828a6bdaacf1b60d33cd805c7edfa0 100644 (file)
 
 
 /*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * Fetch stored password for a user, for authentication.
  *
- * 'client_pass' is the password response given by the remote user.  If
- * 'md5_salt' is not NULL, it is a response to an MD5 authentication
- * challenge, with the given salt.  Otherwise, it is a plaintext password.
+ * Returns STATUS_OK on success.  On error, returns STATUS_ERROR, and stores
+ * a palloc'd string describing the reason, for the postmaster log, in
+ * *logdetail.  The error reason should *not* be sent to the client, to avoid
+ * giving away user information!
  *
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * If the password is expired, it is still returned in *shadow_pass, but the
+ * return code is STATUS_ERROR.  On other errors, *shadow_pass is set to
+ * NULL.
  */
 int
-md5_crypt_verify(const char *role, char *client_pass,
-                char *md5_salt, int md5_salt_len, char **logdetail)
+get_role_password(const char *role, char **shadow_pass, char **logdetail)
 {
    int         retval = STATUS_ERROR;
-   char       *shadow_pass,
-              *crypt_pwd;
    TimestampTz vuntil = 0;
-   char       *crypt_client_pass = client_pass;
    HeapTuple   roleTup;
    Datum       datum;
    bool        isnull;
 
+   *shadow_pass = NULL;
+
    /* Get role info from pg_authid */
    roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
    if (!HeapTupleIsValid(roleTup))
@@ -70,7 +70,7 @@ md5_crypt_verify(const char *role, char *client_pass,
                              role);
        return STATUS_ERROR;    /* user has no password */
    }
-   shadow_pass = TextDatumGetCString(datum);
+   *shadow_pass = TextDatumGetCString(datum);
 
    datum = SysCacheGetAttr(AUTHNAME, roleTup,
                            Anum_pg_authid_rolvaliduntil, &isnull);
@@ -79,104 +79,151 @@ md5_crypt_verify(const char *role, char *client_pass,
 
    ReleaseSysCache(roleTup);
 
-   if (*shadow_pass == '\0')
+   if (**shadow_pass == '\0')
    {
        *logdetail = psprintf(_("User \"%s\" has an empty password."),
                              role);
+       pfree(*shadow_pass);
+       *shadow_pass = NULL;
        return STATUS_ERROR;    /* empty password */
    }
 
    /*
-    * Compare with the encrypted or plain password depending on the
-    * authentication method being used for this connection.  (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.)
+    * Password OK, now check to be sure we are not past rolvaliduntil
     */
-   if (md5_salt)
+   if (isnull)
+       retval = STATUS_OK;
+   else if (vuntil < GetCurrentTimestamp())
    {
-       /* MD5 authentication */
-       Assert(md5_salt_len > 0);
-       crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
-       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))
-           {
-               pfree(crypt_pwd);
-               return STATUS_ERROR;
-           }
-       }
-       else
+       *logdetail = psprintf(_("User \"%s\" has an expired password."),
+                             role);
+       retval = STATUS_ERROR;
+   }
+   else
+       retval = STATUS_OK;
+
+   return retval;
+}
+
+/*
+ * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
+ *
+ * 'shadow_pass' is the user's correct password or password hash, as stored
+ * in pg_authid.rolpassword.
+ * 'client_pass' is the response given by the remote user to the MD5 challenge.
+ * 'md5_salt' is the salt used in the MD5 authentication challenge.
+ *
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const char *role, const char *shadow_pass,
+                const char *client_pass,
+                const char *md5_salt, int md5_salt_len,
+                char **logdetail)
+{
+   int         retval;
+   char        crypt_pwd[MD5_PASSWD_LEN + 1];
+   char        crypt_pwd2[MD5_PASSWD_LEN + 1];
+
+   Assert(md5_salt_len > 0);
+
+   /*
+    * Compute the correct answer for the MD5 challenge.
+    *
+    * We do not bother setting logdetail for any pg_md5_encrypt failure
+    * 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))
        {
-           /* stored password is plain, double-encrypt */
-           char       *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
-
-           if (!pg_md5_encrypt(shadow_pass,
-                               role,
-                               strlen(role),
-                               crypt_pwd2))
-           {
-               pfree(crypt_pwd);
-               pfree(crypt_pwd2);
-               return STATUS_ERROR;
-           }
-           if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
-                               md5_salt, md5_salt_len,
-                               crypt_pwd))
-           {
-               pfree(crypt_pwd);
-               pfree(crypt_pwd2);
-               return STATUS_ERROR;
-           }
-           pfree(crypt_pwd2);
+           return STATUS_ERROR;
        }
    }
    else
    {
-       /* Client sent password in plaintext */
-       if (isMD5(shadow_pass))
+       /* stored password is plain, double-encrypt */
+       if (!pg_md5_encrypt(shadow_pass,
+                           role,
+                           strlen(role),
+                           crypt_pwd2))
        {
-           /* Encrypt user-supplied password to match stored MD5 */
-           crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
-           if (!pg_md5_encrypt(client_pass,
-                               role,
-                               strlen(role),
-                               crypt_client_pass))
-           {
-               pfree(crypt_client_pass);
-               return STATUS_ERROR;
-           }
+           return STATUS_ERROR;
+       }
+       if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+                           md5_salt, md5_salt_len,
+                           crypt_pwd))
+       {
+           return STATUS_ERROR;
        }
-       crypt_pwd = shadow_pass;
    }
 
-   if (strcmp(crypt_client_pass, crypt_pwd) == 0)
+   if (strcmp(client_pass, crypt_pwd) == 0)
+       retval = STATUS_OK;
+   else
    {
-       /*
-        * Password OK, now check to be sure we are not past rolvaliduntil
-        */
-       if (isnull)
-           retval = STATUS_OK;
-       else if (vuntil < GetCurrentTimestamp())
+       *logdetail = psprintf(_("Password does not match for user \"%s\"."),
+                             role);
+       retval = STATUS_ERROR;
+   }
+
+   return retval;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ *
+ * 'shadow_pass' is the user's correct password or password hash, as stored
+ * in pg_authid.rolpassword.
+ * 'client_pass' is the password given by the remote user.
+ *
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+plain_crypt_verify(const char *role, const char *shadow_pass,
+                  const char *client_pass,
+                  char **logdetail)
+{
+   int         retval;
+   char        crypt_client_pass[MD5_PASSWD_LEN + 1];
+
+   /*
+    * Client sent password in plaintext.  If we have an MD5 hash stored, hash
+    * the password the client sent, and compare the hashes.  Otherwise
+    * compare the plaintext passwords directly.
+    */
+   if (isMD5(shadow_pass))
+   {
+       if (!pg_md5_encrypt(client_pass,
+                           role,
+                           strlen(role),
+                           crypt_client_pass))
        {
-           *logdetail = psprintf(_("User \"%s\" has an expired password."),
-                                 role);
-           retval = STATUS_ERROR;
+           /*
+            * 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;
        }
-       else
-           retval = STATUS_OK;
+       client_pass = crypt_client_pass;
    }
+
+   if (strcmp(client_pass, shadow_pass) == 0)
+       retval = STATUS_OK;
    else
+   {
        *logdetail = psprintf(_("Password does not match for user \"%s\"."),
                              role);
-
-   if (crypt_pwd != shadow_pass)
-       pfree(crypt_pwd);
-   if (crypt_client_pass != client_pass)
-       pfree(crypt_client_pass);
+       retval = STATUS_ERROR;
+   }
 
    return retval;
 }
index 4ca8a75c4681118bcce523096ff17b7931c817b4..229ce76b61e87bca1fd6168626c3c96f4b1950fd 100644 (file)
 
 #include "datatype/timestamp.h"
 
-extern int md5_crypt_verify(const char *role, char *client_pass,
-                char *md5_salt, int md5_salt_len, char **logdetail);
+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,
+                int md5_salt_len, char **logdetail);
+extern int plain_crypt_verify(const char *role, const char *shadow_pass,
+                  const char *client_pass, char **logdetail);
 
 #endif