From bc19a55bf1d4db3a09f8030484faf8a824a9805d Mon Sep 17 00:00:00 2001 From: Phil Pennock Date: Sat, 5 Feb 2011 00:22:28 -0500 Subject: [PATCH] LDAP TLS negotiation support. closes bug 230 Applies patches provided by Adam Ciarcinski of NetBSD in bug 230. Adds documentation. Tested the negotiation and server verification, not tested the client certificate presentation but looks sane. --- doc/doc-docbook/spec.xfpt | 86 +++++++++++++++++++++++++++++++++++++++ doc/doc-txt/ChangeLog | 2 + doc/doc-txt/NewStuff | 10 +++++ src/src/globals.c | 7 ++++ src/src/globals.h | 7 ++++ src/src/lookups/ldap.c | 58 ++++++++++++++++++++++++++ src/src/readconf.c | 7 ++++ 7 files changed, 177 insertions(+) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 659a469bf..9dacb979c 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -6885,6 +6885,12 @@ The URL may begin with &`ldap`& or &`ldaps`& if your LDAP library supports secure (encrypted) LDAP connections. The second of these ensures that an encrypted TLS connection is used. +.new +With sufficiently modern LDAP libraries, Exim supports forcing TLS over regular +LDAP connections, rather than the SSL-on-connect &`ldaps`&. +See the &%ldap_start_tls%& option. +.wen + .section "LDAP quoting" "SECID68" .cindex "LDAP" "quoting" @@ -12393,7 +12399,14 @@ listed in more than one group. .section "Data lookups" "SECID101" .table2 .row &%ibase_servers%& "InterBase servers" +.row &%ldap_ca_cert_dir%& "dir of CA certs to verify LDAP server's" +.row &%ldap_ca_cert_file%& "file of CA certs to verify LDAP server's" +.row &%ldap_cert_file%& "client cert file for LDAP" +.row &%ldap_cert_key%& "client key file for LDAP" +.row &%ldap_cipher_suite%& "TLS negotiation preference control" .row &%ldap_default_servers%& "used if no server in query" +.row &%ldap_require_cert%& "action to take without LDAP server cert" +.row &%ldap_start_tls%& "require TLS within LDAP" .row &%ldap_version%& "set protocol version" .row &%lookup_open_max%& "lookup files held open" .row &%mysql_servers%& "default MySQL servers" @@ -13805,6 +13818,56 @@ next attempt to deliver such a message, it gets removed. The incident is logged. +.new +.option ldap_ca_cert_dir main string unset +.cindex "LDAP", "TLS CA certificate directory" +This option indicates which directory contains CA certificates for verifying +a TLS certificate presented by an LDAP server. +While Exim does not provide a default value, your SSL library may. +Analogous to &%tls_verify_certificates%& but as a client-side option for LDAP +and constrained to be a directory. +.wen + + +.new +.option ldap_ca_cert_file main string unset +.cindex "LDAP", "TLS CA certificate file" +This option indicates which file contains CA certificates for verifying +a TLS certificate presented by an LDAP server. +While Exim does not provide a default value, your SSL library may. +Analogous to &%tls_verify_certificates%& but as a client-side option for LDAP +and constrained to be a file. +.wen + + +.new +.option ldap_cert_file main string unset +.cindex "LDAP" "TLS client certificate file" +This option indicates which file contains an TLS client certificate which +Exim should present to the LDAP server during TLS negotiation. +Should be used together with &%ldap_cert_key%&. +.wen + + +.new +.option ldap_cert_key main string unset +.cindex "LDAP" "TLS client key file" +This option indicates which file contains the secret/private key to use +to prove identity to the LDAP server during TLS negotiation. +Should be used together with &%ldap_cert_file%&, which contains the +identity to be proven. +.wen + + +.new +.option ldap_cipher_suite main string unset +.cindex "LDAP" "TLS cipher suite" +This controls the TLS cipher-suite negotiation during TLS negotiation with +the LDAP server. See &<>& for more details of the format of +cipher-suite options with OpenSSL (as used by LDAP client libraries). +.wen + + .option ldap_default_servers main "string list" unset .cindex "LDAP" "default servers" This option provides a list of LDAP servers which are tried in turn when an @@ -13813,6 +13876,29 @@ details of LDAP queries. This option is available only when Exim has been built with LDAP support. +.new +.option ldap_require_cert main string unset. +.cindex "LDAP" "policy for LDAP server TLS cert presentation" +This should be one of the values "hard", "demand", "allow", "try" or "never". +A value other than one of these is interpreted as "never". +See the entry "TLS_REQCERT" in your system man page for ldap.conf(5). +Although Exim does not set a default, the LDAP library probably defaults +to hard/demand. +.wen + + +.new +.option ldap_start_tls main boolean false +.cindex "LDAP" "whether or not to negotiate TLS" +If set, Exim will attempt to negotiate TLS with the LDAP server when +connecting on a regular LDAP port. This is the LDAP equivalent of SMTP's +"STARTTLS". This is distinct from using "ldaps", which is the LDAP form +of SSL-on-connect. +In the event of failure to negotiate TLS, the action taken is controlled +by &%ldap_require_cert%&. +.wen + + .option ldap_version main integer unset .cindex "LDAP" "protocol version, forcing" This option can be used to force Exim to set a specific protocol version for diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 6e1bd4566..083870af6 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -32,6 +32,8 @@ NM/02 Fix wide character breakage in the rfc2047 coding NM/03 Allow underscore in dnslist lookups Fixes bug 1026. Patch from Graeme Fowler +PP/04 Bugzilla 230: Support TLS-enabled LDAP (in addition to ldaps). + Code patches from Adam Ciarcinski of NetBSD. Exim version 4.74 diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 3a3ad5de5..55bde992d 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -9,6 +9,16 @@ test from the snapshots or the CVS before the documentation is updated. Once the documentation is updated, this file is reduced to a short list. +Version 4.75 +------------ + + 1. In addition to the existing LDAP and LDAP/SSL ("ldaps") support, there + is now LDAP/TLS support, given sufficiently modern OpenLDAP client + libraries. The following global options have been added in support of + this: ldap_ca_cert_dir, ldap_ca_cert_file, ldap_cert_file, ldap_cert_key, + ldap_cipher_suite, ldap_require_cert, ldap_start_tls. + + Version 4.74 ------------ diff --git a/src/src/globals.c b/src/src/globals.c index 6653d62df..8631c7d8c 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -60,8 +60,15 @@ uschar *ibase_servers = NULL; #endif #ifdef LOOKUP_LDAP +uschar *eldap_ca_cert_dir = NULL; +uschar *eldap_ca_cert_file = NULL; +uschar *eldap_cert_file = NULL; +uschar *eldap_cert_key = NULL; +uschar *eldap_cipher_suite = NULL; uschar *eldap_default_servers = NULL; +uschar *eldap_require_cert = NULL; int eldap_version = -1; +BOOL eldap_start_tls = FALSE; #endif #ifdef LOOKUP_MYSQL diff --git a/src/src/globals.h b/src/src/globals.h index db7a79bf7..bdc9bcf6d 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -35,7 +35,14 @@ extern uschar *ibase_servers; #endif #ifdef LOOKUP_LDAP +extern uschar *eldap_ca_cert_dir; /* Directory with CA certificates */ +extern uschar *eldap_ca_cert_file; /* CA certificate file */ +extern uschar *eldap_cert_file; /* Certificate file */ +extern uschar *eldap_cert_key; /* Certificate key file */ +extern uschar *eldap_cipher_suite; /* Allowed cipher suite */ extern uschar *eldap_default_servers; /* List of default servers */ +extern uschar *eldap_require_cert; /* Peer certificate checking strategy */ +extern BOOL eldap_start_tls; /* Use STARTTLS */ extern int eldap_version; /* LDAP version */ #endif diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index 943e431db..ddf803e21 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -431,6 +431,60 @@ if (lcp == NULL) } #endif /* LDAP_OPT_X_TLS */ + #ifdef LDAP_OPT_X_TLS_CACERTFILE + if (eldap_ca_cert_file != NULL) + { + ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file); + } + #endif + #ifdef LDAP_OPT_X_TLS_CACERTDIR + if (eldap_ca_cert_dir != NULL) + { + ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); + } + #endif + #ifdef LDAP_OPT_X_TLS_CERTFILE + if (eldap_cert_file != NULL) + { + ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); + } + #endif + #ifdef LDAP_OPT_X_TLS_KEYFILE + if (eldap_cert_key != NULL) + { + ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); + } + #endif + #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE + if (eldap_cipher_suite != NULL) + { + ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); + } + #endif + #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 + /* Now add this connection to the chain of cached connections */ lcp = store_get(sizeof(LDAP_CONNECTION)); @@ -467,6 +521,10 @@ if (!lcp->bound || { DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n", (lcp->bound)? "re-" : "", user, password); + if (eldap_start_tls) + { + ldap_start_tls_s(lcp->ld, NULL, NULL); + } if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE)) == -1) { diff --git a/src/src/readconf.c b/src/src/readconf.c index 0b78958e4..b9d3747a5 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -262,7 +262,14 @@ static optionlist optionlist_config[] = { { "ignore_fromline_local", opt_bool, &ignore_fromline_local }, { "keep_malformed", opt_time, &keep_malformed }, #ifdef LOOKUP_LDAP + { "ldap_ca_cert_dir", opt_stringptr, &eldap_ca_cert_dir }, + { "ldap_ca_cert_file", opt_stringptr, &eldap_ca_cert_file }, + { "ldap_cert_file", opt_stringptr, &eldap_cert_file }, + { "ldap_cert_key", opt_stringptr, &eldap_cert_key }, + { "ldap_cipher_suite", opt_stringptr, &eldap_cipher_suite }, { "ldap_default_servers", opt_stringptr, &eldap_default_servers }, + { "ldap_require_cert", opt_stringptr, &eldap_require_cert }, + { "ldap_start_tls", opt_bool, &eldap_start_tls }, { "ldap_version", opt_int, &eldap_version }, #endif { "local_from_check", opt_bool, &local_from_check }, -- 2.25.1