diff options
Diffstat (limited to 'src/interfaces/libpq/fe-auth.c')
-rw-r--r-- | src/interfaces/libpq/fe-auth.c | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index ab454e6cd02..a3b80dc550f 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -136,7 +136,10 @@ pg_GSS_continue(PGconn *conn, int payloadlen) } if (maj_stat == GSS_S_COMPLETE) + { + conn->client_finished_auth = true; gss_release_name(&lmin_s, &conn->gtarg_nam); + } return STATUS_OK; } @@ -321,6 +324,9 @@ pg_SSPI_continue(PGconn *conn, int payloadlen) FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); } + if (r == SEC_E_OK) + conn->client_finished_auth = true; + /* Cleanup is handled by the code in freePGconn() */ return STATUS_OK; } @@ -735,6 +741,8 @@ pg_local_sendauth(PGconn *conn) strerror_r(errno, sebuf, sizeof(sebuf))); return STATUS_ERROR; } + + conn->client_finished_auth = true; return STATUS_OK; #else libpq_append_conn_error(conn, "SCM_CRED authentication method not supported"); @@ -806,6 +814,41 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq) } /* + * Translate a disallowed AuthRequest code into an error message. + */ +static const char * +auth_method_description(AuthRequest areq) +{ + switch (areq) + { + case AUTH_REQ_PASSWORD: + return libpq_gettext("server requested a cleartext password"); + case AUTH_REQ_MD5: + return libpq_gettext("server requested a hashed password"); + case AUTH_REQ_GSS: + case AUTH_REQ_GSS_CONT: + return libpq_gettext("server requested GSSAPI authentication"); + case AUTH_REQ_SSPI: + return libpq_gettext("server requested SSPI authentication"); + case AUTH_REQ_SCM_CREDS: + return libpq_gettext("server requested UNIX socket credentials"); + case AUTH_REQ_SASL: + case AUTH_REQ_SASL_CONT: + case AUTH_REQ_SASL_FIN: + return libpq_gettext("server requested SASL authentication"); + } + + return libpq_gettext("server requested an unknown authentication type"); +} + +/* + * Convenience macro for checking the allowed_auth_methods bitmask. Caller + * must ensure that type is not greater than 31 (high bit of the bitmask). + */ +#define auth_method_allowed(conn, type) \ + (((conn)->allowed_auth_methods & (1 << (type))) != 0) + +/* * Verify that the authentication request is expected, given the connection * parameters. This is especially important when the client wishes to * authenticate the server before any sensitive information is exchanged. @@ -814,6 +857,99 @@ static bool check_expected_areq(AuthRequest areq, PGconn *conn) { bool result = true; + const char *reason = NULL; + + StaticAssertDecl((sizeof(conn->allowed_auth_methods) * CHAR_BIT) > AUTH_REQ_MAX, + "AUTH_REQ_MAX overflows the allowed_auth_methods bitmask"); + + /* + * If the user required a specific auth method, or specified an allowed + * set, then reject all others here, and make sure the server actually + * completes an authentication exchange. + */ + if (conn->require_auth) + { + switch (areq) + { + case AUTH_REQ_OK: + + /* + * Check to make sure we've actually finished our exchange (or + * else that the user has allowed an authentication-less + * connection). + * + * If the user has allowed both SCRAM and unauthenticated + * (trust) connections, then this check will silently accept + * partial SCRAM exchanges, where a misbehaving server does + * not provide its verifier before sending an OK. This is + * consistent with historical behavior, but it may be a point + * to revisit in the future, since it could allow a server + * that doesn't know the user's password to silently harvest + * material for a brute force attack. + */ + if (!conn->auth_required || conn->client_finished_auth) + break; + + /* + * No explicit authentication request was made by the server + * -- or perhaps it was made and not completed, in the case of + * SCRAM -- but there is one special case to check. If the + * user allowed "gss", then a GSS-encrypted channel also + * satisfies the check. + */ +#ifdef ENABLE_GSS + if (auth_method_allowed(conn, AUTH_REQ_GSS) && conn->gssenc) + { + /* + * If implicit GSS auth has already been performed via GSS + * encryption, we don't need to have performed an + * AUTH_REQ_GSS exchange. This allows require_auth=gss to + * be combined with gssencmode, since there won't be an + * explicit authentication request in that case. + */ + } + else +#endif + { + reason = libpq_gettext("server did not complete authentication"); + result = false; + } + + break; + + case AUTH_REQ_PASSWORD: + case AUTH_REQ_MD5: + case AUTH_REQ_GSS: + case AUTH_REQ_GSS_CONT: + case AUTH_REQ_SSPI: + case AUTH_REQ_SCM_CREDS: + case AUTH_REQ_SASL: + case AUTH_REQ_SASL_CONT: + case AUTH_REQ_SASL_FIN: + + /* + * We don't handle these with the default case, to avoid + * bit-shifting past the end of the allowed_auth_methods mask + * if the server sends an unexpected AuthRequest. + */ + result = auth_method_allowed(conn, areq); + break; + + default: + result = false; + break; + } + } + + if (!result) + { + if (!reason) + reason = auth_method_description(areq); + + libpq_append_conn_error(conn, "auth method \"%s\" requirement failed: %s", + conn->require_auth, reason); + return result; + } /* * When channel_binding=require, we must protect against two cases: (1) we @@ -1008,6 +1144,9 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn) "fe_sendauth: error sending password authentication\n"); return STATUS_ERROR; } + + /* We expect no further authentication requests. */ + conn->client_finished_auth = true; break; } |