hidden bugs

Exim/LDAP/TLS: chasing a bug

Exim is an MTA (Mail Transfer Agent) used by numerous UNIX systems. The first version was written in 1995 by Philip Hazel for the Cambridge university IT departement. Based on smail, Exim has evolved to become a flexible and robust MTA. Its flexibility is largely due to the use of modulesallowing the MTA to talk to many databases and directories. I have worked in the past on integrating Exim (and other softwares) with an LDAP server. To kind of summarize this work, I have published this “howto”: Setting Up A Mail Server Using Exim4, Clamav, Dovecot, SpamAssassin And Many More On Debian Lenny

This document being now, quite obsolete, I decided to freshen it, and rewrite it with newer version of involved software and some improvements. Among which: secure LDAP connexion using startTLS… But there comes the bugs
So basically, I was just setting up Exim as I did before and everything went just very smooth. I switched my openldap server to provide both ldaps:/// and ldap:/// with the StartTLS options. The purpose of this article is not to shed a light on either or both of those security layers, so if you want to know more about it, or understand the difference between each of them, take a look there. But once I try to force Exim to use TLS, LDAP lookups don’t work anymore.
trying to narrow down the issue reveled as an quite tricky task.

The error:

At firt glance the error is far from being self explanatory:

gave DEFER: failed to initiate TLS processing on an LDAP session to server ldap.middle.earth:389 - ldap_start_tls_s() returned -11: Connect error

Looking at the server side debug (openldap started in debug mode) is even more missleading, as it report the error “Sucess”…

tls_read: want=5, got=5
0000: 30 05 02 01 02 0....
ldap_read: want=8 error=Success
5235dcba ber_get_next on fd 17 failed errno=0 (Success)
5235dcba connection_read(17): input error=-2 id=1002, closing.
5235dcba connection_closing: readying conn=1002 sd=17 for close
5235dcba connection_close: conn=1002 sd=17
5235dcba daemon: removing 17

A network dump can also help you, given I have the server private key in order to decrypt the traffic:

TLS client terminate tpc donnexion unexpectedly at the end of the TLShandshake

Looking in the netdump, it’s obvious that exim is closing the connexion, even though the TLS handshake seems to be complete and both parties agreed on a common way to proceed.

The GnuTLS mess:

I should have mentioned it at the begining but I am using debian here, and the default packages from the distrib. When talking about encryption, this is pretty important because most of the Debian packages are compiled against GnuTLS instead of openSSL. Debian have made this choice due to their interpretation of the GNU GPL. There are currently a number of people complaining how GnuTLS, but don’t expect debian to change this policy unless some licensing changes happen in the meanwhile.
So, having discovered this amount of bugs related to GnuTLS, I decided to re-compile the ldap libraries against openSSL.

A sharper look:

Despite having every componenet now recompiled against openSSL, the LDAP lookup is still not working. But now looking at the network dump, gives sensible informations.

TLS wireshark

So basically, it seems the server side certificate is rejected by exim, thus sending the LDAPserver a TLS alert “unknow certificate”. As I am mainly interested in encryption, more than authentication, I am using a self signed certificate, so thiserror could be normal. I say “coudl be” because after double checking the Exim configuration I see:

ldap_default_servers = ldap.middle.earth::389
ldap_start_tls = true
ldap_require_cert = allow

According to the exim documentation and the ldap.conf(5) man page, the later option should allow for TLS session to take place using server side self signed certificate.
Taking a closer look at the exim debug I can see that exim seems to use the “try” option to verify the server certificate:

30430 after ldap_url_parse: host=ldap.middle.earth port=389
30430 ldap_initialize with URL ldap://ldap.middle.earth:389/
30430 initialized for LDAP (v3) server ldap.middle.earth:389
30430 LDAP_OPT_X_TLS_TRY set
30430 binding with user=uid=exim,dc=middle,dc=earth password=oops
30430 failed to initiate TLS processing on an LDAP session to server ldap.middle.earth:389 - ldap_start_tls_s() returned -11: Connect error
30430 lookup deferred: failed to initiate TLS processing on an LDAP session to server ldap.middle.earth:389 - ldap_start_tls_s() returned -11: Connect error

It is now clear that something in not quite right in TLS handling within ldap connections in exim code. Let’s give it a look then!

The code:

With the debug output we had above, we can identify the souce file involved and the part of the code to look at. And in src/lookups/ldap.C there is something that doesn’t look quite right to me. It sounds like the ldap URL parsing overrides connection parameters using the “LDAP_OPT_X_TLS” variable:

/* If not using ldapi and TLS is available, set appropriate TLS options: hard
for "ldaps" and soft otherwise. */

#ifdef LDAP_OPT_X_TLS
if (!ldapi)
  {
  int tls_option;
  if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
    {
    tls_option = LDAP_OPT_X_TLS_HARD;
    DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_HARD set\n");
    }
  else
    {
    tls_option = LDAP_OPT_X_TLS_TRY;
    DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_TRY set\n");
    }
  ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
  }
#endif /* LDAP_OPT_X_TLS */

This is a blatent implementation error, as it would prevent anyone from using a self signed certificate for ldaps:/// or ldap:///+TLS. Only could you fallback to plain LDAP if the server doesn’t send any certificate. But this is not what I want to acheive.

Later in the same file is a section that clearly relates to that specific option that doesn’t seems to work : ldap_require_cert:

#ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
if (eldap_require_cert != NULL)
  {
  int cert_option = LDAP_OPT_X_TLS_NEVER;
  if (Ustrcmp(eldap_require_cert, "hard") == 0)
    {
    cert_option = LDAP_OPT_X_TLS_HARD;
    }
  else if (Ustrcmp(eldap_require_cert, "demand") == 0)
    {
    cert_option = LDAP_OPT_X_TLS_DEMAND;
    }
  else if (Ustrcmp(eldap_require_cert, "allow") == 0)
    {
    cert_option = LDAP_OPT_X_TLS_ALLOW;
    }
  else if (Ustrcmp(eldap_require_cert, "try") == 0)
    {
    cert_option = LDAP_OPT_X_TLS_TRY;
    }
  ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
  }
#endif

Everything looks all right there and I am not sure which section should be investigated.
I have found two different parts of the code that seem to be doing the same thing, but none of which is acctually doing it! I am lost… Lost, but not stranded yet! (If you recognise this quote feel free to comment).

The patch:

After googling aournd the opnldap library topic I find some interesting posts which shed a light on this topic.
To start with, the first and the second ones, explains that the LDAP_OPT_X_TLS is ised in old LDAPv2 to establish ldaps:/// connections only and has been deprecated, so should not be used anymore (at least with openldap libraries).
And the most important part lies in this post I found: http://www.openldap.org/lists/openldap-software/200706/msg00173.html, it basically says that the ldap connection options cannot be set at the session level, but instead have to be set globally using a NULL LDAP handle as te first argument of the ldap_set_option function.
Given those informations (and few things I find can be improved), I have written a little patch, apply it to the debian source packages (Exim 4.80-7).
This patch also implements changes from the exim bug 1375, without which you still can’t use LDAP with TLS properly. The patch is available here

Once applied the LDAP lookups work OK!
So, to summarize:

  • Re-compile the LDAP librabry againsr openSSL instead of GnuTLS.
  • Patch Exim using the patch provided bellow
  • Re-compile Exim against openSSL, the freshly built LDAP lib and the patch

The #Exim IRC have been of great help narrowing down this issue, special thanks to Todd Lyons (aka cannonball).


Posted

in

, ,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.