LCOV - code coverage report
Current view: top level - src/backend/libpq - crypt.c (source / functions) Hit Total Coverage
Test: PostgreSQL Lines: 29 65 44.6 %
Date: 2017-09-29 13:40:31 Functions: 3 5 60.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * crypt.c
       4             :  *    Functions for dealing with encrypted passwords stored in
       5             :  *    pg_authid.rolpassword.
       6             :  *
       7             :  * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  * src/backend/libpq/crypt.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : #include "postgres.h"
      15             : 
      16             : #include <unistd.h>
      17             : #ifdef HAVE_CRYPT_H
      18             : #include <crypt.h>
      19             : #endif
      20             : 
      21             : #include "catalog/pg_authid.h"
      22             : #include "common/md5.h"
      23             : #include "libpq/crypt.h"
      24             : #include "libpq/scram.h"
      25             : #include "miscadmin.h"
      26             : #include "utils/builtins.h"
      27             : #include "utils/syscache.h"
      28             : #include "utils/timestamp.h"
      29             : 
      30             : 
      31             : /*
      32             :  * Fetch stored password for a user, for authentication.
      33             :  *
      34             :  * On error, returns NULL, and stores a palloc'd string describing the reason,
      35             :  * for the postmaster log, in *logdetail.  The error reason should *not* be
      36             :  * sent to the client, to avoid giving away user information!
      37             :  */
      38             : char *
      39           0 : get_role_password(const char *role, char **logdetail)
      40             : {
      41           0 :     TimestampTz vuntil = 0;
      42             :     HeapTuple   roleTup;
      43             :     Datum       datum;
      44             :     bool        isnull;
      45             :     char       *shadow_pass;
      46             : 
      47             :     /* Get role info from pg_authid */
      48           0 :     roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
      49           0 :     if (!HeapTupleIsValid(roleTup))
      50             :     {
      51           0 :         *logdetail = psprintf(_("Role \"%s\" does not exist."),
      52             :                               role);
      53           0 :         return NULL;            /* no such user */
      54             :     }
      55             : 
      56           0 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      57             :                             Anum_pg_authid_rolpassword, &isnull);
      58           0 :     if (isnull)
      59             :     {
      60           0 :         ReleaseSysCache(roleTup);
      61           0 :         *logdetail = psprintf(_("User \"%s\" has no password assigned."),
      62             :                               role);
      63           0 :         return NULL;            /* user has no password */
      64             :     }
      65           0 :     shadow_pass = TextDatumGetCString(datum);
      66             : 
      67           0 :     datum = SysCacheGetAttr(AUTHNAME, roleTup,
      68             :                             Anum_pg_authid_rolvaliduntil, &isnull);
      69           0 :     if (!isnull)
      70           0 :         vuntil = DatumGetTimestampTz(datum);
      71             : 
      72           0 :     ReleaseSysCache(roleTup);
      73             : 
      74             :     /*
      75             :      * Password OK, but check to be sure we are not past rolvaliduntil
      76             :      */
      77           0 :     if (!isnull && vuntil < GetCurrentTimestamp())
      78             :     {
      79           0 :         *logdetail = psprintf(_("User \"%s\" has an expired password."),
      80             :                               role);
      81           0 :         return NULL;
      82             :     }
      83             : 
      84           0 :     return shadow_pass;
      85             : }
      86             : 
      87             : /*
      88             :  * What kind of a password verifier is 'shadow_pass'?
      89             :  */
      90             : PasswordType
      91          19 : get_password_type(const char *shadow_pass)
      92             : {
      93          19 :     if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
      94           6 :         return PASSWORD_TYPE_MD5;
      95          13 :     if (strncmp(shadow_pass, "SCRAM-SHA-256$", strlen("SCRAM-SHA-256$")) == 0)
      96           3 :         return PASSWORD_TYPE_SCRAM_SHA_256;
      97          10 :     return PASSWORD_TYPE_PLAINTEXT;
      98             : }
      99             : 
     100             : /*
     101             :  * Given a user-supplied password, convert it into a verifier of
     102             :  * 'target_type' kind.
     103             :  *
     104             :  * If the password is already in encrypted form, we cannot reverse the
     105             :  * hash, so it is stored as it is regardless of the requested type.
     106             :  */
     107             : char *
     108           8 : encrypt_password(PasswordType target_type, const char *role,
     109             :                  const char *password)
     110             : {
     111           8 :     PasswordType guessed_type = get_password_type(password);
     112             :     char       *encrypted_password;
     113             : 
     114           8 :     if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
     115             :     {
     116             :         /*
     117             :          * Cannot convert an already-encrypted password from one format to
     118             :          * another, so return it as it is.
     119             :          */
     120           3 :         return pstrdup(password);
     121             :     }
     122             : 
     123           5 :     switch (target_type)
     124             :     {
     125             :         case PASSWORD_TYPE_MD5:
     126           3 :             encrypted_password = palloc(MD5_PASSWD_LEN + 1);
     127             : 
     128           3 :             if (!pg_md5_encrypt(password, role, strlen(role),
     129             :                                 encrypted_password))
     130           0 :                 elog(ERROR, "password encryption failed");
     131           3 :             return encrypted_password;
     132             : 
     133             :         case PASSWORD_TYPE_SCRAM_SHA_256:
     134           2 :             return pg_be_scram_build_verifier(password);
     135             : 
     136             :         case PASSWORD_TYPE_PLAINTEXT:
     137           0 :             elog(ERROR, "cannot encrypt password with 'plaintext'");
     138             :     }
     139             : 
     140             :     /*
     141             :      * This shouldn't happen, because the above switch statements should
     142             :      * handle every combination of source and target password types.
     143             :      */
     144           0 :     elog(ERROR, "cannot encrypt password to requested type");
     145             :     return NULL;                /* keep compiler quiet */
     146             : }
     147             : 
     148             : /*
     149             :  * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
     150             :  *
     151             :  * 'shadow_pass' is the user's correct password or password hash, as stored
     152             :  * in pg_authid.rolpassword.
     153             :  * 'client_pass' is the response given by the remote user to the MD5 challenge.
     154             :  * 'md5_salt' is the salt used in the MD5 authentication challenge.
     155             :  *
     156             :  * In the error case, optionally store a palloc'd string at *logdetail
     157             :  * that will be sent to the postmaster log (but not the client).
     158             :  */
     159             : int
     160           0 : md5_crypt_verify(const char *role, const char *shadow_pass,
     161             :                  const char *client_pass,
     162             :                  const char *md5_salt, int md5_salt_len,
     163             :                  char **logdetail)
     164             : {
     165             :     int         retval;
     166             :     char        crypt_pwd[MD5_PASSWD_LEN + 1];
     167             : 
     168           0 :     Assert(md5_salt_len > 0);
     169             : 
     170           0 :     if (get_password_type(shadow_pass) != PASSWORD_TYPE_MD5)
     171             :     {
     172             :         /* incompatible password hash format. */
     173           0 :         *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
     174             :                               role);
     175           0 :         return STATUS_ERROR;
     176             :     }
     177             : 
     178             :     /*
     179             :      * Compute the correct answer for the MD5 challenge.
     180             :      *
     181             :      * We do not bother setting logdetail for any pg_md5_encrypt failure
     182             :      * below: the only possible error is out-of-memory, which is unlikely, and
     183             :      * if it did happen adding a psprintf call would only make things worse.
     184             :      */
     185             :     /* stored password already encrypted, only do salt */
     186           0 :     if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
     187             :                         md5_salt, md5_salt_len,
     188             :                         crypt_pwd))
     189             :     {
     190           0 :         return STATUS_ERROR;
     191             :     }
     192             : 
     193           0 :     if (strcmp(client_pass, crypt_pwd) == 0)
     194           0 :         retval = STATUS_OK;
     195             :     else
     196             :     {
     197           0 :         *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     198             :                               role);
     199           0 :         retval = STATUS_ERROR;
     200             :     }
     201             : 
     202           0 :     return retval;
     203             : }
     204             : 
     205             : /*
     206             :  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
     207             :  *
     208             :  * 'shadow_pass' is the user's correct password hash, as stored in
     209             :  * pg_authid.rolpassword.
     210             :  * 'client_pass' is the password given by the remote user.
     211             :  *
     212             :  * In the error case, optionally store a palloc'd string at *logdetail
     213             :  * that will be sent to the postmaster log (but not the client).
     214             :  */
     215             : int
     216          10 : plain_crypt_verify(const char *role, const char *shadow_pass,
     217             :                    const char *client_pass,
     218             :                    char **logdetail)
     219             : {
     220             :     char        crypt_client_pass[MD5_PASSWD_LEN + 1];
     221             : 
     222             :     /*
     223             :      * Client sent password in plaintext.  If we have an MD5 hash stored, hash
     224             :      * the password the client sent, and compare the hashes.  Otherwise
     225             :      * compare the plaintext passwords directly.
     226             :      */
     227          10 :     switch (get_password_type(shadow_pass))
     228             :     {
     229             :         case PASSWORD_TYPE_SCRAM_SHA_256:
     230           2 :             if (scram_verify_plain_password(role,
     231             :                                             client_pass,
     232             :                                             shadow_pass))
     233             :             {
     234           1 :                 return STATUS_OK;
     235             :             }
     236             :             else
     237             :             {
     238           1 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     239             :                                       role);
     240           1 :                 return STATUS_ERROR;
     241             :             }
     242             :             break;
     243             : 
     244             :         case PASSWORD_TYPE_MD5:
     245           3 :             if (!pg_md5_encrypt(client_pass,
     246             :                                 role,
     247             :                                 strlen(role),
     248             :                                 crypt_client_pass))
     249             :             {
     250             :                 /*
     251             :                  * We do not bother setting logdetail for pg_md5_encrypt
     252             :                  * failure: the only possible error is out-of-memory, which is
     253             :                  * unlikely, and if it did happen adding a psprintf call would
     254             :                  * only make things worse.
     255             :                  */
     256           0 :                 return STATUS_ERROR;
     257             :             }
     258           3 :             if (strcmp(crypt_client_pass, shadow_pass) == 0)
     259           1 :                 return STATUS_OK;
     260             :             else
     261             :             {
     262           2 :                 *logdetail = psprintf(_("Password does not match for user \"%s\"."),
     263             :                                       role);
     264           2 :                 return STATUS_ERROR;
     265             :             }
     266             :             break;
     267             : 
     268             :         case PASSWORD_TYPE_PLAINTEXT:
     269             : 
     270             :             /*
     271             :              * We never store passwords in plaintext, so this shouldn't
     272             :              * happen.
     273             :              */
     274           5 :             break;
     275             :     }
     276             : 
     277             :     /*
     278             :      * This shouldn't happen.  Plain "password" authentication is possible
     279             :      * with any kind of stored password hash.
     280             :      */
     281           5 :     *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
     282             :                           role);
     283           5 :     return STATUS_ERROR;
     284             : }

Generated by: LCOV version 1.11