Hello All,
Today I came across a strange problem.
I wrote a program to test ldap ssl/tls connection with OpenLDAP library. Something like the code snippet as follows:
int ret = LDAP_OPT_SUCCESS; int cert_flag = LDAP_OPT_X_TLS_NEVER; ... ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_flag); if (ret != LDAP_OPT_SUCCESS) { fprintf(stderr, "unable to set require cert option (LDAP_OPT_X_TLS_REQUIRE_CERT): %s\n", ldap_err2string(ret)); } ... // bind to the server
cert_flag = LDAP_OPT_X_TLS_DEMAND; ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_flag); if (ret != LDAP_OPT_SUCCESS) { fprintf(stderr, "unable to set require cert option (LDAP_OPT_X_TLS_REQUIRE_CERT): %s\n", ldap_err2string(ret)); } ... // bind to the server
The first binding is successful, as expected. However, the second binding is also successful, which is contrary to my expectation, because I didn't create any cert file yet.
Another observation here is that if the first binding with LDAP_OPT_X_TLS_NEVER is removed, and the second binding with LDAP_OPT_X_TLS_DEMAND set is done right from the beginning, then it will fail, as expected.
So, it seems the first value set to the option LDAP_OPT_X_TLS_REQUIRE_CERT will override the later values, isn't it? Is it possible to change this option's value on the fly (means different bindings use different values for this cert option)?
Thanks, Qiang
On Tue, 28 Feb 2012 16:40:23 -0500, Qiang Xu qixu@lexmark.com wrote:
Hello All,
Today I came across a strange problem.
I wrote a program to test ldap ssl/tls connection with OpenLDAP library. Something like the code snippet as follows: (...) ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_flag); ... // bind to the server
The first binding is successful, as expected. However, the second binding is also successful, which is contrary to my expectation, because I didn't create any cert file yet.
Possibly the answer lies in the code you did not show: Create an LDAP* (with which url/host?), connect, bind, unbind.
Another observation here is that if the first binding with LDAP_OPT_X_TLS_NEVER is removed, and the second binding with LDAP_OPT_X_TLS_DEMAND set is done right from the beginning, then it will fail, as expected.
Do you use the same LDAP* connection for both "bindings"? Its options are set when it is initialized. Try to unbind and then create a new LDAP*.
Thanks for your reply, Hallvard.
On Tue, Feb 28, 2012 at 6:23 PM, Hallvard B Furuseth < h.b.furuseth@usit.uio.no> wrote:
Possibly the answer lies in the code you did not show: Create an LDAP* (with which url/host?), connect, bind, unbind.
The complete code is quite long. But the essential parts are here. After these options are set, it goes with "ldap_start_tls_s(ldapHandle, NULL, NULL)" and "ldap_sasl_bind(ldapHandle, username, LDAP_SASL_SIMPLE, &password_ber, NULL, NULL, &msgid)". And if all is well with bind and search, then an unbind follows.
Do you use the same LDAP* connection for both "bindings"?
Its options are set when it is initialized. Try to unbind and then create a new LDAP*.
It is guaranteed that every bind is paired with an unbind operation. No doubt about that. Furthermore, these cert options are said to be global, having nothing to do with any specific ldap handle.
Just did some additional tests:
require_cert = LDAP_OPT_X_TLS_NEVER; printf("%s(), %d: require_cert is %d\n", __func__, __LINE__, require_cert); rc2 = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert); ERR_IF ( rc2 != LDAP_OPT_SUCCESS );
require_cert = LDAP_OPT_X_TLS_DEMAND; printf("%s(), %d: require_cert is %d\n", __func__, __LINE__, require_cert); rc2 = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert); ERR_IF ( rc2 != LDAP_OPT_SUCCESS );
...... // binding follows here
This time, the binding fails as expected (because I haven't created any cert file yet). So it looks the initial setting of this cert option doesn't let the later setting of the same option skipped. But why the second binding can succeed using LDAP_OPT_X_TLS_DEMAND after a successful binding using LDAP_OPT_X_TLS_NEVER?
Very confused here...
Looking forward to more suggestions, Qiang
On Tue, 28 Feb 2012 18:46:10 -0500, Qiang Xu qixu@lexmark.com wrote:
The complete code is quite long. But the essential parts are here. After these options are set, it goes with "ldap_start_tls_s(ldapHandle, NULL, NULL)" and "ldap_sasl_bind(ldapHandle, username, LDAP_SASL_SIMPLE, &password_ber, NULL, NULL, &msgid)". And if all is well with bind and search, then an unbind follows.
The essential parts here are creating the LDAP* with ldap_initialize() or whatever, and ldap_start_tls_s().
Do you use the same LDAP* connection for both "bindings"? Its options are set when it is initialized. Try to unbind and then create a new LDAP*.
It is guaranteed that every bind is paired with an unbind operation.
Note that ldap_unbind() is misnamed, it should have been called ldap_destroy(). It does send an unbind, but the more important part is that it destroys the LDAP*.
Furthermore, these cert options are said to be global, having nothing to do with any specific ldap handle.
Don't know where it says that. Options with LDAP* NULL are global, but global options are copied to newly created LDAP*s. Changing a global option has no effect on an existing LDAP*, unless the option itself really is global - if any option is. I suppose some SASL or TLS opts might be, if some SASL/TLS library does not support per-connection options.
Try this:
/* Usage: <program> [ serverhost [ 2nd arg prevents unbind/reinit ]] */
#include <assert.h> #include <ldap.h> #include <stdio.h>
int main(int argc, char **argv) { int i, r, flags[] = { LDAP_OPT_X_TLS_NEVER, LDAP_OPT_X_TLS_DEMAND }; LDAP *ld; i = LDAP_VERSION3; r = ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &i); assert(r == LDAP_OPT_SUCCESS); for (i = 0; i < 2; i++) { r = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &flags[i]); assert(r == LDAP_OPT_SUCCESS); if (i < argc-2) continue; r = ldap_initialize(&ld, argv[1]); assert(r == LDAP_SUCCESS); r = ldap_start_tls_s(ld, NULL, NULL); printf("#%d => %s%s", i+1, ldap_err2string(r), i ? "\n" : ", "); ldap_unbind_ext_s(ld, NULL, NULL); } return 0; }
./testprog servername ./testprog servername x
On Tue, Feb 28, 2012 at 8:25 PM, Hallvard B Furuseth < h.b.furuseth@usit.uio.no> wrote:
The essential parts here are creating the LDAP* with ldap_initialize() or whatever, and ldap_start_tls_s().
Note that ldap_unbind() is misnamed, it should have been called ldap_destroy(). It does send an unbind, but the more important part is that it destroys the LDAP*.
Got it. Thanks for your reminding, Hallvard.
Don't know where it says that. Options with LDAP* NULL are global,
but global options are copied to newly created LDAP*s. Changing a global option has no effect on an existing LDAP*, unless the option itself really is global - if any option is. I suppose some SASL or TLS opts might be, if some SASL/TLS library does not support per-connection options.
Try this:
/* Usage: <program> [ serverhost [ 2nd arg prevents unbind/reinit ]] */
#include <assert.h> #include <ldap.h> #include <stdio.h>
int main(int argc, char **argv) { int i, r, flags[] = { LDAP_OPT_X_TLS_NEVER, LDAP_OPT_X_TLS_DEMAND }; LDAP *ld; i = LDAP_VERSION3; r = ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &i); assert(r == LDAP_OPT_SUCCESS); for (i = 0; i < 2; i++) { r = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &flags[i]); assert(r == LDAP_OPT_SUCCESS); if (i < argc-2) continue; r = ldap_initialize(&ld, argv[1]); assert(r == LDAP_SUCCESS); r = ldap_start_tls_s(ld, NULL, NULL); printf("#%d => %s%s", i+1, ldap_err2string(r), i ? "\n" : ", "); ldap_unbind_ext_s(ld, NULL, NULL); } return 0; }
./testprog servername ./testprog servername x
Thanks a lot for your testing example. Based on your code, I did some minor customization to test the cert options:
#include <assert.h> #include <ldap.h> #include <stdio.h>
int main() { int i, r, flags[] = { LDAP_OPT_X_TLS_NEVER, LDAP_OPT_X_TLS_DEMAND }; LDAP *ld; const char *username = "cn=test,cn=Users,dc=my,dc=server,dc=com"; const char *password = "test"; struct berval password_ber = { 0, NULL }; int msgid = 0; LDAPMessage *result = NULL; LDAPControl **ctrls = NULL; int err = 0; char *matched = NULL; char *info = NULL; char **refs = NULL;
i = LDAP_VERSION3; r = ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &i); assert(r == LDAP_OPT_SUCCESS);
for (i = 0; i < 2; i++) { r = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &flags[i]); assert(r == LDAP_OPT_SUCCESS);
r = ldap_initialize(&ld, "ldaps://my.server.com:3269"); assert(r == LDAP_SUCCESS && ld != NULL);
password_ber.bv_val = ber_strdup(password); password_ber.bv_len = strlen(password_ber.bv_val); r = ldap_sasl_bind(ld, username, LDAP_SASL_SIMPLE, &password_ber, NULL, NULL, &msgid); printf("ldap_sasl_bind(): #%d => return status is %s\n", i+1, ldap_err2string(r)); printf("ldap_sasl_bind(): #%d => msgid is %d\n", i+1, msgid); assert(msgid != -1);
r = ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &result); assert(r != -1);
r = ldap_parse_result(ld, result, &err, &matched, &info, &refs, &ctrls, 1); printf("ldap_parse_result(): #%d => return status is %s\n", i+1, ldap_err2string(r)); printf("ldap_parse_result(): #%d => error is %s\n", i+1, ldap_err2string(err));
if (ctrls) ldap_controls_free(ctrls);
if (password_ber.bv_val) ber_memfree(password_ber.bv_val); if (matched) ber_memfree(matched); if (info) ber_memfree(info);
if (refs) ber_memvfree((void **)refs);
if (ld) ldap_unbind_ext(ld, NULL, NULL); }
return 0; }
The output is:
ldap_sasl_bind(): #1 => return status is Success ldap_sasl_bind(): #1 => msgid is 1 ldap_parse_result(): #1 => return status is Success ldap_parse_result(): #1 => error is Success ldap_sasl_bind(): #2 => return status is Success ldap_sasl_bind(): #2 => msgid is 1 ldap_parse_result(): #2 => return status is Success ldap_parse_result(): #2 => error is Success
So, it does seem that the value for the cert option LDAP_OPT_X_TLS_REQUIRE_CERT has a global effect. It seems to be per process, instead of per binding.
Is there a way to set this option on a per binding basis, so that different bindings can have different choices on the cert requirement?
Thanks, Qiang
Guess what? Just picked up a pearl in the sea of internet: http://www.mailinglistarchive.com/postfix-users@postfix.org/msg57688.html
Basically, it seems to be a feature introduced since the beginning of openldap 2.4 version. We need to set LDAP_OPT_X_TLS_REQUIRE_CERT on an ldap handle (already initialized), and set LDAP_OPT_X_TLS_NEWCTX (with a value 0) thereafter:
rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert); assert(rc == LDAP_OPT_SUCCESS);
rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &am_server); // am_server is 1, only if the code is compiled for server assert(rc == LDAP_OPT_SUCCESS);
Now the option works as per connection, rather than as per process.
Cheers, Qiang
Qiang Xu wrote:
Guess what? Just picked up a pearl in the sea of internet: http://www.mailinglistarchive.com/postfix-users@postfix.org/msg57688.html
Basically, it seems to be a feature introduced since the beginning of openldap 2.4 version. We need to set LDAP_OPT_X_TLS_REQUIRE_CERT on an ldap handle (already initialized), and set LDAP_OPT_X_TLS_NEWCTX (with a value 0) thereafter:
rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert); assert(rc == LDAP_OPT_SUCCESS); rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &am_server); //
am_server is 1, only if the code is compiled for server assert(rc == LDAP_OPT_SUCCESS);
Now the option works as per connection, rather than as per process.
Could someone of the OpenLDAP core developers please confirm this? Especially whether LDAP_OPT_X_TLS_NEWCTX is set to LDAP_OPT_OFF for "clients"?
Ciao, Michael.
2012/3/1 Michael Ströder michael@stroeder.com
Could someone of the OpenLDAP core developers please confirm this? Especially whether LDAP_OPT_X_TLS_NEWCTX is set to LDAP_OPT_OFF for "clients"?
At least this is what is mentioned in the man page:
http://linux.die.net/man/3/ldap_set_option *LDAP_OPT_X_TLS_NEWCTX* Instructs the library to create a new TLS library context. *invalue* must be *const int **. A non-zero value pointed to by * invalue* tells the library to create a context for a server. Just have a quick look at the code: // include/ldap.h #define LDAP_OPT_OFF ((void *) 0) #define LDAP_OPT_ON ((void *) &ber_pvt_opt_on) ... // libraries/liblber/options.c char ber_pvt_opt_on; /* used to get a non-NULL address for *_OPT_ON */
LDAP_OPT_OFF seems to be a NULL pointer. It seems not intended to be used to set the value for LDAP_OPT_X_TLS_NEWCTX, which requires a 'const int *' type (the address of a predefined integer value).
But yes, it had better be clarified by some developer or someone very familiar to OpenLDAP code.
Thanks, Qiang
Am Donnerstag 01 März 2012, 11:49:22 schrieb Michael Ströder:
Qiang Xu wrote:
Guess what? Just picked up a pearl in the sea of internet: http://www.mailinglistarchive.com/postfix-users@postfix.org/msg5768 8.html
Basically, it seems to be a feature introduced since the beginning of openldap 2.4 version. We need to set LDAP_OPT_X_TLS_REQUIRE_CERT on an ldap handle> (already initialized), and set LDAP_OPT_X_TLS_NEWCTX (with a value 0)
thereafter:
rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert); assert(rc == LDAP_OPT_SUCCESS); rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &am_server); //>
am_server is 1, only if the code is compiled for server
assert(rc == LDAP_OPT_SUCCESS);
Now the option works as per connection, rather than as per process.
Could someone of the OpenLDAP core developers please confirm this? Especially whether LDAP_OPT_X_TLS_NEWCTX is set to LDAP_OPT_OFF for "clients"?
No, as Qiang Xu already noted LDAP_OPT_OFF is defined as a NULL pointer, while LDAP_OPT_X_TLS_NEWCTX expects a pointer to a integer which has the value 0. Something like this should work for a client context:
int val = 0; ldap_set_option( ld, LDAP_OPT_X_TLS_NEWCTX, &val);
regards, Ralf
openldap-technical@openldap.org