Log LDAP "diagnostic messages"?
Hi hackers,
Some LDAP error codes are a bit vague. For example:
LDAP_CONNECT_ERROR Indicates a connection problem.
LDAP_PROTOCOL_ERROR A protocol violation was detected.
To learn more, you have to call
ldap_get_option(LDAP_OPT_DIAGNOSTIC_MESSAGE). Should we do that? For
example, instead of:
LOG: could not start LDAP TLS session: Protocol error
... you could see:
LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostic message: unsupported extended operation
Well, that may not be the most illuminating example, but that's a
message sent back by the LDAP server that we're currently throwing
away, and can be used to distinguish between unsupported TLS versions,
missing StartTLS extension and various other cases. Perhaps that
particular message would also be available via your LDAP server's
logs, if you can access them, but in some cases we're throwing away
client-side messages that are not available anywhere else like "TLS:
unable to get CN from peer certificate", "TLS: hostname does not match
CN in peer certificate" and more.
Something like the attached.
--
Thomas Munro
http://www.enterprisedb.com
Attachments:
ldap-diagnostic-message.patchapplication/octet-stream; name=ldap-diagnostic-message.patchDownload
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index dd7de7c3a4..1f4f2d4f29 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2292,6 +2292,33 @@ CheckBSDAuth(Port *port, char *user)
#ifdef USE_LDAP
/*
+ * Return an error detail message built from the current LDAP diagnostic
+ * message, allocated with palloc.
+ */
+static const char *
+GetLDAPErrorDetail(LDAP *ldap)
+{
+ char *detail;
+ char *message;
+ int rc;
+
+ rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
+ if (rc != LDAP_SUCCESS)
+ detail =
+ psprintf(_("LDAP diagnostic message unavailable: %s"),
+ ldap_err2string(rc));
+ else if (message == NULL)
+ detail = pstrdup(_("No LDAP diagnostic message available."));
+ else
+ {
+ detail = psprintf(_("LDAP diagnostic message: %s"), message);
+ ldap_memfree(message);
+ }
+
+ return detail;
+}
+
+/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
*/
@@ -2370,9 +2397,13 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
+ char *detail = GetLDAPErrorDetail(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r)),
+ errdetail("%s", detail)));
+ pfree(detail);
return STATUS_ERROR;
}
}
@@ -2461,9 +2492,13 @@ CheckLDAPAuth(Port *port)
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
+ char *detail = GetLDAPErrorDetail(ldap);
+
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
- port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r)),
+ errdetail("%s", detail)));
+ pfree(detail);
return STATUS_ERROR;
}
@@ -2485,9 +2520,13 @@ CheckLDAPAuth(Port *port)
if (r != LDAP_SUCCESS)
{
+ char *detail = GetLDAPErrorDetail(ldap);
+
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(r))));
+ filter, port->hba->ldapserver, ldap_err2string(r)),
+ errdetail("%s", detail)));
+ pfree(detail);
pfree(filter);
return STATUS_ERROR;
}
@@ -2517,12 +2556,15 @@ CheckLDAPAuth(Port *port)
dn = ldap_get_dn(ldap, entry);
if (dn == NULL)
{
+ char *detail = GetLDAPErrorDetail(ldap);
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(error))));
+ filter, port->hba->ldapserver, ldap_err2string(error)),
+ errdetail("%s", detail)));
+ pfree(detail);
pfree(filter);
ldap_msgfree(search_message);
return STATUS_ERROR;
@@ -2537,12 +2579,14 @@ CheckLDAPAuth(Port *port)
r = ldap_unbind_s(ldap);
if (r != LDAP_SUCCESS)
{
+ char *detail = GetLDAPErrorDetail(ldap);
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
(errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ fulluser, port->hba->ldapserver, ldap_err2string(error)),
+ errdetail("%s", detail)));
pfree(fulluser);
return STATUS_ERROR;
}
On Wed, Jul 26, 2017 at 6:51 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:
Hi hackers,
Some LDAP error codes are a bit vague. For example:
LDAP_CONNECT_ERROR Indicates a connection problem.
LDAP_PROTOCOL_ERROR A protocol violation was detected.To learn more, you have to call
ldap_get_option(LDAP_OPT_DIAGNOSTIC_MESSAGE). Should we do that? For
example, instead of:LOG: could not start LDAP TLS session: Protocol error
... you could see:
LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostic message: unsupported extended operationWell, that may not be the most illuminating example, but that's a
message sent back by the LDAP server that we're currently throwing
away, and can be used to distinguish between unsupported TLS versions,
missing StartTLS extension and various other cases. Perhaps that
particular message would also be available via your LDAP server's
logs, if you can access them, but in some cases we're throwing away
client-side messages that are not available anywhere else like "TLS:
unable to get CN from peer certificate", "TLS: hostname does not match
CN in peer certificate" and more.
+1.
Something like the attached.
The patch prints errdetail() as "No LDAP diagnostic message
available." when LDAP doesn't provide diagnostics. May be some error
messages do not have any diagnostic information. In that case above
error detail may be confusing. May be we should just omit error
details when diagnostic message is not available.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 26, 2017 at 10:40 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
On Wed, Jul 26, 2017 at 6:51 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:Something like the attached.
The patch prints errdetail() as "No LDAP diagnostic message
available." when LDAP doesn't provide diagnostics. May be some error
messages do not have any diagnostic information. In that case above
error detail may be confusing. May be we should just omit error
details when diagnostic message is not available.
Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere. For example, on my system,
trying to log in with the wrong password causes an "Invalid
credentials" error with no extra "DETAIL:" line logged, but trying to
use TLS when it hasn't been configured properly logs a helpful
diagnostic message.
Thanks for the review!
I also noticed a couple of other things in passing and fixed them in
this new version:
1. In one place we call ldap_get_option(LDAP_OPT_ERROR_NUMBER) after
ldap_unbind_s(). My man page says "Once [ldap_unbind()] is called,
the connection to the LDAP server is closed, and the ld structure is
invalid." So I don't think we should do that, even if it didn't
return LDAP_SUCCESS. I have no idea if any implementation would
actually fail to unbind and what state the LDAP object would be in if
it did: this is essentially the destructor function for LDAP
connections, so what are you supposed to do after that? But using the
LDAP object again seems wrong to me.
2. In several code paths we don't call ldap_unbind() on the way out,
which is technically leaking a connection. Failure to authenticate is
FATAL to the backend anyway so it probably doesn't matter much, but
hanging up without saying goodbye is considered discourteous by
some[1]https://www.ldap.com/the-ldap-unbind-operation.
Thoughts anyone?
[1]: https://www.ldap.com/the-ldap-unbind-operation
--
Thomas Munro
http://www.enterprisedb.com
Attachments:
ldap-diagnostic-message-v2.patchapplication/octet-stream; name=ldap-diagnostic-message-v2.patchDownload
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index dd7de7c3a4..1977a575e7 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2292,6 +2292,27 @@ CheckBSDAuth(Port *port, char *user)
#ifdef USE_LDAP
/*
+ * Return a palloc'd copy of the current LDAP diagnostic message, or NULL if
+ * there is none.
+ */
+static char *
+GetLDAPDiagnosticMessage(LDAP *ldap)
+{
+ char *result = NULL;
+ char *message;
+ int rc;
+
+ rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
+ if (rc == LDAP_SUCCESS && message != NULL)
+ {
+ result = pstrdup(message);
+ ldap_memfree(message);
+ }
+
+ return result;
+}
+
+/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
*/
@@ -2317,9 +2338,14 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r))));
+ (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
@@ -2370,9 +2396,14 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
}
@@ -2461,9 +2492,15 @@ CheckLDAPAuth(Port *port)
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
- port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
@@ -2485,9 +2522,15 @@ CheckLDAPAuth(Port *port)
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(r))));
+ filter, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(filter);
return STATUS_ERROR;
}
@@ -2517,12 +2560,17 @@ CheckLDAPAuth(Port *port)
dn = ldap_get_dn(ldap, entry);
if (dn == NULL)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(error))));
+ filter, port->hba->ldapserver, ldap_err2string(error)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(filter);
ldap_msgfree(search_message);
return STATUS_ERROR;
@@ -2539,10 +2587,9 @@ CheckLDAPAuth(Port *port)
{
int error;
- (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
- (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
+ fulluser, port->hba->ldapserver)));
pfree(fulluser);
return STATUS_ERROR;
}
@@ -2566,17 +2613,23 @@ CheckLDAPAuth(Port *port)
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
- ldap_unbind(ldap);
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(r))));
+ fulluser, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(fulluser);
return STATUS_ERROR;
}
+ ldap_unbind(ldap);
pfree(fulluser);
return STATUS_OK;
On Thu, Jul 27, 2017 at 5:20 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:
Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere.
Rebased after bf6b9e94.
--
Thomas Munro
http://www.enterprisedb.com
Attachments:
ldap-diagnostic-message-v3.patchapplication/octet-stream; name=ldap-diagnostic-message-v3.patchDownload
From ba07d876d30c6259159c9e541ff3beda663fc90c Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Tue, 8 Aug 2017 10:03:59 +1200
Subject: [PATCH] Log diagnostic messages if errors occur during LDAP auth.
Diagnostic messages seem likely to help users diagnose root
causes more easily, so let's report them as errdetail.
In passing, remove some preexisting code that tries to reuse an
LDAP object after calling ldap_unbind, which seems unsafe.
Author: Thomas Munro
Reviewed-By: Ashutosh Bapat
Discussion: https://postgr.es/m/CAEepm=2_dA-SYpFdmNVwvKsEBXOUj=K4ooKovHmvj6jnMdt8dw@mail.gmail.com
---
src/backend/libpq/auth.c | 73 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 63 insertions(+), 10 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index cb30fc7b71..3d0b03419d 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2306,6 +2306,27 @@ CheckBSDAuth(Port *port, char *user)
#ifdef USE_LDAP
/*
+ * Return a palloc'd copy of the current LDAP diagnostic message, or NULL if
+ * there is none.
+ */
+static char *
+GetLDAPDiagnosticMessage(LDAP *ldap)
+{
+ char *result = NULL;
+ char *message;
+ int rc;
+
+ rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
+ if (rc == LDAP_SUCCESS && message != NULL)
+ {
+ result = pstrdup(message);
+ ldap_memfree(message);
+ }
+
+ return result;
+}
+
+/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
*/
@@ -2331,9 +2352,14 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r))));
+ (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
@@ -2384,9 +2410,14 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
}
@@ -2472,9 +2503,15 @@ CheckLDAPAuth(Port *port)
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
- port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
return STATUS_ERROR;
}
@@ -2497,9 +2534,15 @@ CheckLDAPAuth(Port *port)
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(r))));
+ filter, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(filter);
return STATUS_ERROR;
@@ -2531,12 +2574,17 @@ CheckLDAPAuth(Port *port)
dn = ldap_get_dn(ldap, entry);
if (dn == NULL)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(error))));
+ filter, port->hba->ldapserver, ldap_err2string(error)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
@@ -2554,10 +2602,9 @@ CheckLDAPAuth(Port *port)
{
int error;
- (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
- (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
+ fulluser, port->hba->ldapserver)));
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
@@ -2583,18 +2630,24 @@ CheckLDAPAuth(Port *port)
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
- ldap_unbind(ldap);
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(r))));
+ fulluser, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("Diagnostic message: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
+ ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
--
2.13.2
On Thu, Aug 10, 2017 at 5:09 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:
On Thu, Jul 27, 2017 at 5:20 PM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere.Rebased after bf6b9e94.
Please add this to the next commitfest.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 10, 2017 at 1:16 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
Please add this to the next commitfest.
Done: https://commitfest.postgresql.org/14/1229/
--
Thomas Munro
http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Re: Thomas Munro 2017-08-10 <CAEepm=09jnV7hK5rTxPp816bMuve7dJGbjtEcjeXrhAELHFxqw@mail.gmail.com>
Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere.Rebased after bf6b9e94.
message ? errdetail("Diagnostic message: %s", message) : 0));
"Diagnostic message" doesn't really mean anything, and printing
"DETAIL: Diagnostic message: <something>" seems redundant to me. Maybe
drop that prefix? It should be clear from the context that this is a
message from the LDAP layer.
Or maybe simply append ": <message>" to the error message already shown?
Christoph
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Christoph Berg wrote:
Re: Thomas Munro 2017-08-10 <CAEepm=09jnV7hK5rTxPp816bMuve7dJGbjtEcjeXrhAELHFxqw@mail.gmail.com>
Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere.Rebased after bf6b9e94.
message ? errdetail("Diagnostic message: %s", message) : 0));
"Diagnostic message" doesn't really mean anything, and printing
"DETAIL: Diagnostic message: <something>" seems redundant to me. Maybe
drop that prefix? It should be clear from the context that this is a
message from the LDAP layer.
I think making it visible that the message comes from LDAP (rather than
Postgres or anything else) is valuable. How about this?
LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostics: unsupported extended operation.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Aug 16, 2017 at 8:44 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Christoph Berg wrote:
Re: Thomas Munro 2017-08-10 <CAEepm=09jnV7hK5rTxPp816bMuve7dJGbjtEcjeXrhAELHFxqw@mail.gmail.com>
Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere.Rebased after bf6b9e94.
message ? errdetail("Diagnostic message: %s", message) : 0));
"Diagnostic message" doesn't really mean anything, and printing
"DETAIL: Diagnostic message: <something>" seems redundant to me. Maybe
drop that prefix? It should be clear from the context that this is a
message from the LDAP layer.I think making it visible that the message comes from LDAP (rather than
Postgres or anything else) is valuable. How about this?LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostics: unsupported extended operation.
+1, pretty neat.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
The patch needs to address some comments in the previous mails, so
marking it as "waiting for author".
On Wed, Aug 16, 2017 at 11:13 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
On Wed, Aug 16, 2017 at 8:44 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Christoph Berg wrote:
Re: Thomas Munro 2017-08-10 <CAEepm=09jnV7hK5rTxPp816bMuve7dJGbjtEcjeXrhAELHFxqw@mail.gmail.com>
Agreed. Here's a version that skips those useless detail messages
using a coding pattern I found elsewhere.Rebased after bf6b9e94.
message ? errdetail("Diagnostic message: %s", message) : 0));
"Diagnostic message" doesn't really mean anything, and printing
"DETAIL: Diagnostic message: <something>" seems redundant to me. Maybe
drop that prefix? It should be clear from the context that this is a
message from the LDAP layer.I think making it visible that the message comes from LDAP (rather than
Postgres or anything else) is valuable. How about this?LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostics: unsupported extended operation.+1, pretty neat.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Sep 12, 2017 at 11:23 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
On Wed, Aug 16, 2017 at 11:13 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:On Wed, Aug 16, 2017 at 8:44 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Christoph Berg wrote:
"Diagnostic message" doesn't really mean anything, and printing
"DETAIL: Diagnostic message: <something>" seems redundant to me. Maybe
drop that prefix? It should be clear from the context that this is a
message from the LDAP layer.I think making it visible that the message comes from LDAP (rather than
Postgres or anything else) is valuable. How about this?LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostics: unsupported extended operation.+1, pretty neat.
Here is a new version adopting Alvaro's wording. I'll set this back
to "Needs review" status.
--
Thomas Munro
http://www.enterprisedb.com
Attachments:
ldap-diagnostic-message-v4.patchapplication/octet-stream; name=ldap-diagnostic-message-v4.patchDownload
From 38b5775ce695ff1002b3c2c6b02cdb71206b5100 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Tue, 8 Aug 2017 10:03:59 +1200
Subject: [PATCH] Log diagnostic messages if errors occur during LDAP auth.
Diagnostic messages seem likely to help users diagnose root
causes more easily, so let's report them as errdetail.
In passing, remove some preexisting code that tries to reuse an
LDAP object after calling ldap_unbind, which seems unsafe.
Author: Thomas Munro
Reviewed-By: Ashutosh Bapat, Christoph Berg, Alvaro Herrera
Discussion: https://postgr.es/m/CAEepm=2_dA-SYpFdmNVwvKsEBXOUj=K4ooKovHmvj6jnMdt8dw@mail.gmail.com
---
src/backend/libpq/auth.c | 73 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 63 insertions(+), 10 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 62ff624dbd..de6d0964da 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2306,6 +2306,27 @@ CheckBSDAuth(Port *port, char *user)
#ifdef USE_LDAP
/*
+ * Return a palloc'd copy of the current LDAP diagnostic message, or NULL if
+ * there is none.
+ */
+static char *
+GetLDAPDiagnosticMessage(LDAP *ldap)
+{
+ char *result = NULL;
+ char *message;
+ int rc;
+
+ rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
+ if (rc == LDAP_SUCCESS && message != NULL)
+ {
+ result = pstrdup(message);
+ ldap_memfree(message);
+ }
+
+ return result;
+}
+
+/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
*/
@@ -2331,9 +2352,14 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r))));
+ (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
@@ -2384,9 +2410,14 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
}
@@ -2500,9 +2531,15 @@ CheckLDAPAuth(Port *port)
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
- port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
return STATUS_ERROR;
}
@@ -2525,9 +2562,15 @@ CheckLDAPAuth(Port *port)
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(r))));
+ filter, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(filter);
return STATUS_ERROR;
@@ -2559,12 +2602,17 @@ CheckLDAPAuth(Port *port)
dn = ldap_get_dn(ldap, entry);
if (dn == NULL)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(error))));
+ filter, port->hba->ldapserver, ldap_err2string(error)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
@@ -2582,10 +2630,9 @@ CheckLDAPAuth(Port *port)
{
int error;
- (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
- (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
+ fulluser, port->hba->ldapserver)));
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
@@ -2611,18 +2658,24 @@ CheckLDAPAuth(Port *port)
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
- ldap_unbind(ldap);
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(r))));
+ fulluser, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
+ ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
--
2.13.5
On Wed, Sep 13, 2017 at 6:58 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:
On Tue, Sep 12, 2017 at 11:23 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:On Wed, Aug 16, 2017 at 11:13 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:On Wed, Aug 16, 2017 at 8:44 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Christoph Berg wrote:
"Diagnostic message" doesn't really mean anything, and printing
"DETAIL: Diagnostic message: <something>" seems redundant to me. Maybe
drop that prefix? It should be clear from the context that this is a
message from the LDAP layer.I think making it visible that the message comes from LDAP (rather than
Postgres or anything else) is valuable. How about this?LOG: could not start LDAP TLS session: Protocol error
DETAIL: LDAP diagnostics: unsupported extended operation.+1, pretty neat.
Here is a new version adopting Alvaro's wording. I'll set this back
to "Needs review" status.
Thanks for the updated patches.
Looks good to me. The patch applies cleanly on the latest HEAD,
compiles without any errors or warnings and make check passes. Marking
this as ready for committer.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I think the ldap_unbind() changes should be in a separate preliminary
patch to be committed separately and backpatched.
The other bits looks fine, with nitpicks
1. please move the new support function to the bottom of the section
dedicated to LDAP, and include a prototype
2. please wrap lines longer than 80 chars, other than error message
strings. (I don't know why this file plays fast & loose with
project-wide line length rules, but I also see no reason to continue
doing it.)
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
BTW I added --with-ldap and --with-pam to the configure line for the
reports in coverage.postgresql.org and the % covered in auth.c went from
24% to 18.9% (from very bad to terribly sad).
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 14, 2017 at 10:21 AM, Alvaro Herrera
<alvherre@alvh.no-ip.org> wrote:
BTW I added --with-ldap and --with-pam to the configure line for the
reports in coverage.postgresql.org and the % covered in auth.c went from
24% to 18.9% (from very bad to terribly sad).
Improved code coverage is becoming a fad.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 15, 2017 at 1:38 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Sep 14, 2017 at 10:21 AM, Alvaro Herrera
<alvherre@alvh.no-ip.org> wrote:BTW I added --with-ldap and --with-pam to the configure line for the
reports in coverage.postgresql.org and the % covered in auth.c went from
24% to 18.9% (from very bad to terribly sad).Improved code coverage is becoming a fad.
I don't think that this is necessarily bad for authentication. I mean,
you write something and make sure that it actually works, but you also
make sure that the code you have is *compatible* with libraries the
code is linking to. The second part is important to me. For example
with the SSL tests we can know if there is a breakage, and if this
involves our code of OpenSSL's say after a version upgrade.
Now I think as well that it is not completely the role of this patch
to provide more test coverage for LDAP now because infrastructure
lacks. One requirement that is showing up as a remark from Peter E
(from the pg_receivewal --endpos thread
/messages/by-id/49b529c1-0f44-d905-b33c-005ec0114d09@2ndquadrant.com),
is that we should have a simple perl module allowing to find easily if
Postgres is compiled with a given dependency or not so as tests can
decide if they have to be skipped or run.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 15, 2017 at 2:12 AM, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
I think the ldap_unbind() changes should be in a separate preliminary
patch to be committed separately and backpatched.
OK, here it is split into two patches.
The other bits looks fine, with nitpicks
1. please move the new support function to the bottom of the section
dedicated to LDAP, and include a prototype
OK.
2. please wrap lines longer than 80 chars, other than error message
strings. (I don't know why this file plays fast & loose with
project-wide line length rules, but I also see no reason to continue
doing it.)
Done for all lines I touched in the patch.
Thanks for the review!
--
Thomas Munro
http://www.enterprisedb.com
Attachments:
0001-Improve-LDAP-cleanup-code-in-error-paths.patchapplication/octet-stream; name=0001-Improve-LDAP-cleanup-code-in-error-paths.patchDownload
From c05ded9128a919f2d4f7759ca638d0852481abe1 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Fri, 15 Sep 2017 20:32:16 +1200
Subject: [PATCH 1/2] Improve LDAP cleanup code in error paths.
After calling ldap_unbind_s() we probably shouldn't try to use the LDAP
connection again to call ldap_get_option(), even if it failed. The OpenLDAP
man page for ldap_unbind[_s] says "Once it is called, the connection to the
LDAP server is closed, and the ld structure is invalid." Otherwise, as a
general rule we should probably call ldap_unbind() before returning in all
paths to avoid leaking resources. It is unlikely there is any practical
leak problem since failure to authenticate currently results in the backend
exiting soon afterwards.
Author: Thomas Munro
Reviewed-By: Alvaro Herrera
Discussion: https://postgr.es/m/20170914141205.eup4kxzlkagtmfac%40alvherre.pgsql
---
src/backend/libpq/auth.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 39a57d4835..847ded30c0 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2489,6 +2489,7 @@ CheckLDAPAuth(Port *port)
*c == '\\' ||
*c == '/')
{
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("invalid character in user name for LDAP authentication")));
pfree(passwd);
@@ -2505,6 +2506,7 @@ CheckLDAPAuth(Port *port)
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
@@ -2530,6 +2532,7 @@ CheckLDAPAuth(Port *port)
if (r != LDAP_SUCCESS)
{
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
filter, port->hba->ldapserver, ldap_err2string(r))));
@@ -2541,6 +2544,7 @@ CheckLDAPAuth(Port *port)
count = ldap_count_entries(ldap, search_message);
if (count != 1)
{
+ ldap_unbind(ldap);
if (count == 0)
ereport(LOG,
(errmsg("LDAP user \"%s\" does not exist", port->user_name),
@@ -2567,6 +2571,7 @@ CheckLDAPAuth(Port *port)
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
filter, port->hba->ldapserver, ldap_err2string(error))));
@@ -2585,12 +2590,9 @@ CheckLDAPAuth(Port *port)
r = ldap_unbind_s(ldap);
if (r != LDAP_SUCCESS)
{
- int error;
-
- (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
- (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
+ fulluser, port->hba->ldapserver)));
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
--
2.13.5
0002-Log-diagnostic-messages-if-errors-occur-during-LDAP-.patchapplication/octet-stream; name=0002-Log-diagnostic-messages-if-errors-occur-during-LDAP-.patchDownload
From 91b98c5f803b367b5e4e6a2c15391c24162a61c8 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Fri, 15 Sep 2017 21:40:59 +1200
Subject: [PATCH 2/2] Log diagnostic messages if errors occur during LDAP auth.
Diagnostic messages seem likely to help users diagnose root
causes more easily, so let's report them as errdetail.
Author: Thomas Munro
Reviewed-By: Ashutosh Bapat, Christoph Berg, Alvaro Herrera
Discussion: https://postgr.es/m/CAEepm=2_dA-SYpFdmNVwvKsEBXOUj=K4ooKovHmvj6jnMdt8dw@mail.gmail.com
---
src/backend/libpq/auth.c | 76 +++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 69 insertions(+), 7 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 847ded30c0..1a94fee34e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2305,6 +2305,8 @@ CheckBSDAuth(Port *port, char *user)
*/
#ifdef USE_LDAP
+static char *GetLDAPDiagnosticMessage(LDAP *ldap);
+
/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
@@ -2331,9 +2333,15 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r))));
+ (errmsg("could not set LDAP protocol version: %s",
+ ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
@@ -2384,9 +2392,16 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
+ char *message = GetLDAPDiagnosticMessage(*ldap);
+
ldap_unbind(*ldap);
ereport(LOG,
- (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ (errmsg("could not start LDAP TLS session: %s",
+ ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s",
+ message) : 0));
+ if (message)
+ pfree(message);
return STATUS_ERROR;
}
}
@@ -2506,10 +2521,17 @@ CheckLDAPAuth(Port *port)
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
- port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ port->hba->ldapbinddn, port->hba->ldapserver,
+ ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s",
+ message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
return STATUS_ERROR;
}
@@ -2532,10 +2554,16 @@ CheckLDAPAuth(Port *port)
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(r))));
+ filter, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s",
+ message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(filter);
return STATUS_ERROR;
@@ -2568,13 +2596,19 @@ CheckLDAPAuth(Port *port)
dn = ldap_get_dn(ldap, entry);
if (dn == NULL)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ldap_unbind(ldap);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(error))));
+ filter, port->hba->ldapserver,
+ ldap_err2string(error)),
+ message ? errdetail("LDAP diagnostics: %s",
+ message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
@@ -2618,23 +2652,51 @@ CheckLDAPAuth(Port *port)
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
- ldap_unbind(ldap);
if (r != LDAP_SUCCESS)
{
+ char *message = GetLDAPDiagnosticMessage(ldap);
+
+ ldap_unbind(ldap);
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(r))));
+ fulluser, port->hba->ldapserver, ldap_err2string(r)),
+ message ? errdetail("LDAP diagnostics: %s", message) : 0));
+ if (message)
+ pfree(message);
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
+ ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
return STATUS_OK;
}
+
+/*
+ * Return a palloc'd copy of the current LDAP diagnostic message, or NULL if
+ * there is none.
+ */
+static char *
+GetLDAPDiagnosticMessage(LDAP *ldap)
+{
+ char *result = NULL;
+ char *message;
+ int rc;
+
+ rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
+ if (rc == LDAP_SUCCESS && message != NULL)
+ {
+ result = pstrdup(message);
+ ldap_memfree(message);
+ }
+
+ return result;
+}
+
#endif /* USE_LDAP */
--
2.13.5
On 9/14/17 10:21, Alvaro Herrera wrote:
BTW I added --with-ldap and --with-pam to the configure line for the
reports in coverage.postgresql.org and the % covered in auth.c went from
24% to 18.9% (from very bad to terribly sad).
You can add src/test/ldap/ now to make up for some of that.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Eisentraut wrote:
On 9/14/17 10:21, Alvaro Herrera wrote:
BTW I added --with-ldap and --with-pam to the configure line for the
reports in coverage.postgresql.org and the % covered in auth.c went from
24% to 18.9% (from very bad to terribly sad).You can add src/test/ldap/ now to make up for some of that.
I did that (much to the chagrin of the sysadmin crew, because we now
have a new daemon on that machine -- sorry about that), and coverage of
auth.c jumped to 28%. However, if you look at the report for that file
you can see that a lot of code is not even compiled in, yet.
https://coverage.postgresql.org/src/backend/libpq/auth.c.gcov.html
Clearly we have a lot of work to do in that area ...
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/15/17 08:43, Thomas Munro wrote:
On Fri, Sep 15, 2017 at 2:12 AM, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
I think the ldap_unbind() changes should be in a separate preliminary
patch to be committed separately and backpatched.OK, here it is split into two patches.
I've looked this over.
In the 0001 patch, I would move the ldap_unbind() calls after the
ereport(LOG) calls. We do all the other resource cleanup (pfree() etc.)
after the ereport() calls, so it would be weird to do this one
differently. Also, in the second patch you move one of the
ldap_unbind() calls down anyway.
In the 0002 patch, I think this is a bit repetitive and could be
refactored even more. The end result could look like
ereport(LOG,
(errmsg("blah"),
errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 20, 2017 at 7:57 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
In the 0001 patch, I would move the ldap_unbind() calls after the
ereport(LOG) calls. We do all the other resource cleanup (pfree() etc.)
after the ereport() calls, so it would be weird to do this one
differently. Also, in the second patch you move one of the
ldap_unbind() calls down anyway.
Fair point. In that case there are a few others we should consider
moving down too for consistency, like in the attached.
In the 0002 patch, I think this is a bit repetitive and could be
refactored even more. The end result could look likeereport(LOG,
(errmsg("blah"),
errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
Thanks, that is much tidier. Done that way in the attached.
Here also is a small addition to your TAP test which exercises the
non-NULL code path because slapd rejects TLS by default with a
diagnostic message. I'm not sure if this is worth adding, since it
doesn't actually verify that the code path is reached (though you can
see that it is from the logs).
--
Thomas Munro
http://www.enterprisedb.com
Attachments:
0001-Improve-LDAP-cleanup-code-in-error-paths.patchapplication/octet-stream; name=0001-Improve-LDAP-cleanup-code-in-error-paths.patchDownload
From 5fb982a94827819a1c8e1d1f704c0306e1907108 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Fri, 15 Sep 2017 20:32:16 +1200
Subject: [PATCH 1/3] Improve LDAP cleanup code in error paths.
After calling ldap_unbind_s() we probably shouldn't try to use the LDAP
connection again to call ldap_get_option(), even if it failed. The OpenLDAP
man page for ldap_unbind[_s] says "Once it is called, the connection to the
LDAP server is closed, and the ld structure is invalid." Otherwise, as a
general rule we should probably call ldap_unbind() before returning in all
paths to avoid leaking resources. It is unlikely there is any practical
leak problem since failure to authenticate currently results in the backend
exiting soon afterwards.
Author: Thomas Munro
Reviewed-By: Alvaro Herrera, Peter Eisentraut
Discussion: https://postgr.es/m/20170914141205.eup4kxzlkagtmfac%40alvherre.pgsql
---
src/backend/libpq/auth.c | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 39a57d4835..1c3938c397 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2331,9 +2331,9 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
- ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not set LDAP protocol version: %s", ldap_err2string(r))));
+ ldap_unbind(*ldap);
return STATUS_ERROR;
}
@@ -2360,18 +2360,18 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
* should never happen since we import other files from
* wldap32, but check anyway
*/
- ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not load wldap32.dll")));
+ ldap_unbind(*ldap);
return STATUS_ERROR;
}
_ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
if (_ldap_start_tls_sA == NULL)
{
- ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not load function _ldap_start_tls_sA in wldap32.dll"),
errdetail("LDAP over SSL is not supported on this platform.")));
+ ldap_unbind(*ldap);
return STATUS_ERROR;
}
@@ -2384,9 +2384,9 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
- ldap_unbind(*ldap);
ereport(LOG,
(errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ ldap_unbind(*ldap);
return STATUS_ERROR;
}
}
@@ -2491,6 +2491,7 @@ CheckLDAPAuth(Port *port)
{
ereport(LOG,
(errmsg("invalid character in user name for LDAP authentication")));
+ ldap_unbind(ldap);
pfree(passwd);
return STATUS_ERROR;
}
@@ -2508,6 +2509,7 @@ CheckLDAPAuth(Port *port)
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ ldap_unbind(ldap);
pfree(passwd);
return STATUS_ERROR;
}
@@ -2533,6 +2535,7 @@ CheckLDAPAuth(Port *port)
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
filter, port->hba->ldapserver, ldap_err2string(r))));
+ ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
return STATUS_ERROR;
@@ -2554,6 +2557,7 @@ CheckLDAPAuth(Port *port)
count,
filter, port->hba->ldapserver, count)));
+ ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
@@ -2570,6 +2574,7 @@ CheckLDAPAuth(Port *port)
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
filter, port->hba->ldapserver, ldap_err2string(error))));
+ ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
@@ -2585,12 +2590,9 @@ CheckLDAPAuth(Port *port)
r = ldap_unbind_s(ldap);
if (r != LDAP_SUCCESS)
{
- int error;
-
- (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
- (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(error))));
+ (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
+ fulluser, port->hba->ldapserver)));
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
--
2.13.5
0002-Log-diagnostic-messages-if-errors-occur-during-LDAP-.patchapplication/octet-stream; name=0002-Log-diagnostic-messages-if-errors-occur-during-LDAP-.patchDownload
From 4fac26258008d688b546e56e9709959bbd899c0a Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Fri, 15 Sep 2017 21:40:59 +1200
Subject: [PATCH 2/3] Log diagnostic messages if errors occur during LDAP auth.
Diagnostic messages seem likely to help users diagnose root
causes more easily, so let's report them as errdetail.
Author: Thomas Munro
Reviewed-By: Ashutosh Bapat, Christoph Berg, Alvaro Herrera, Peter Eisentraut
Discussion: https://postgr.es/m/CAEepm=2_dA-SYpFdmNVwvKsEBXOUj=K4ooKovHmvj6jnMdt8dw@mail.gmail.com
---
src/backend/libpq/auth.c | 48 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 41 insertions(+), 7 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 1c3938c397..109e91689c 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2305,6 +2305,8 @@ CheckBSDAuth(Port *port, char *user)
*/
#ifdef USE_LDAP
+static int errdetail_for_ldap(LDAP *ldap);
+
/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
@@ -2332,7 +2334,9 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
ereport(LOG,
- (errmsg("could not set LDAP protocol version: %s", ldap_err2string(r))));
+ (errmsg("could not set LDAP protocol version: %s",
+ ldap_err2string(r)),
+ errdetail_for_ldap(*ldap)));
ldap_unbind(*ldap);
return STATUS_ERROR;
}
@@ -2385,7 +2389,9 @@ InitializeLDAPConnection(Port *port, LDAP **ldap)
#endif
{
ereport(LOG,
- (errmsg("could not start LDAP TLS session: %s", ldap_err2string(r))));
+ (errmsg("could not start LDAP TLS session: %s",
+ ldap_err2string(r)),
+ errdetail_for_ldap(*ldap)));
ldap_unbind(*ldap);
return STATUS_ERROR;
}
@@ -2508,7 +2514,9 @@ CheckLDAPAuth(Port *port)
{
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
- port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r))));
+ port->hba->ldapbinddn, port->hba->ldapserver,
+ ldap_err2string(r)),
+ errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
return STATUS_ERROR;
@@ -2534,7 +2542,8 @@ CheckLDAPAuth(Port *port)
{
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(r))));
+ filter, port->hba->ldapserver, ldap_err2string(r)),
+ errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
@@ -2573,7 +2582,9 @@ CheckLDAPAuth(Port *port)
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
- filter, port->hba->ldapserver, ldap_err2string(error))));
+ filter, port->hba->ldapserver,
+ ldap_err2string(error)),
+ errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
@@ -2618,23 +2629,46 @@ CheckLDAPAuth(Port *port)
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
- ldap_unbind(ldap);
if (r != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
- fulluser, port->hba->ldapserver, ldap_err2string(r))));
+ fulluser, port->hba->ldapserver, ldap_err2string(r)),
+ errdetail_for_ldap(ldap)));
+ ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
+ ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
return STATUS_OK;
}
+
+/*
+ * Add a detail error message text to the current error if one can be
+ * constructed from the LDAP 'diagnostic message'.
+ */
+static int
+errdetail_for_ldap(LDAP *ldap)
+{
+ char *message;
+ int rc;
+
+ rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
+ if (rc == LDAP_SUCCESS && message != NULL)
+ {
+ errdetail("LDAP diagnostics: %s", message);
+ ldap_memfree(message);
+ }
+
+ return 0;
+}
+
#endif /* USE_LDAP */
--
2.13.5
0003-Add-a-regression-test-to-log-an-LDAP-diagnostic-mess.patchapplication/octet-stream; name=0003-Add-a-regression-test-to-log-an-LDAP-diagnostic-mess.patchDownload
From e6a6252e2533d2bd891f82b77aabc0b31cc0f836 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Sun, 24 Sep 2017 23:36:28 +1300
Subject: [PATCH 3/3] Add a regression test to log an LDAP diagnostic message.
This exercises the new code, but doesn't verify the logged output.
Author: Thomas Munro
Reviewed-By:
Discussion: https://postgr.es/m/CAEepm%3D2_dA-SYpFdmNVwvKsEBXOUj%3DK4ooKovHmvj6jnMdt8dw%40mail.gmail.com
---
src/test/ldap/t/001_auth.pl | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/test/ldap/t/001_auth.pl b/src/test/ldap/t/001_auth.pl
index a7cac6210b..38760ece61 100644
--- a/src/test/ldap/t/001_auth.pl
+++ b/src/test/ldap/t/001_auth.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use TestLib;
use PostgresNode;
-use Test::More tests => 14;
+use Test::More tests => 15;
my ($slapd, $ldap_bin_dir, $ldap_schema_dir);
@@ -175,3 +175,12 @@ $node->reload;
$ENV{"PGPASSWORD"} = 'secret1';
test_access($node, 'test1', 0, 'combined LDAP URL and search filter');
+
+note "diagnostic message";
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', qq{local all all ldap ldapserver=$ldap_server ldapport=$ldap_port ldapprefix="uid=" ldapsuffix=",dc=example,dc=net" ldaptls=1});
+$node->reload;
+
+$ENV{"PGPASSWORD"} = 'secret1';
+test_access($node, 'test1', 2, 'any attempt fails due to unsupported TLS');
--
2.13.5
On 9/24/17 07:00, Thomas Munro wrote:
Fair point. In that case there are a few others we should consider
moving down too for consistency, like in the attached.
Thanks, that is much tidier. Done that way in the attached.
Here also is a small addition to your TAP test which exercises the
non-NULL code path because slapd rejects TLS by default with a
diagnostic message. I'm not sure if this is worth adding, since it
doesn't actually verify that the code path is reached (though you can
see that it is from the logs).
Committed.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Oct 13, 2017 at 3:59 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 9/24/17 07:00, Thomas Munro wrote:
Fair point. In that case there are a few others we should consider
moving down too for consistency, like in the attached.Thanks, that is much tidier. Done that way in the attached.
Here also is a small addition to your TAP test which exercises the
non-NULL code path because slapd rejects TLS by default with a
diagnostic message. I'm not sure if this is worth adding, since it
doesn't actually verify that the code path is reached (though you can
see that it is from the logs).Committed.
Thanks, and thanks also for follow-up commit 7d1b8e75. Looks like the
new macro arrived in OpenLDAP 2.4 but RHEL5 shipped with 2.3.
--
Thomas Munro
http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers