====== Exim SMTP Authentication against LDAP ====== I wanted to block repeated failed attempts against SMTP auth using fail2ban, and the easiest way for to achieve that was to have fail2ban block based on the exim4 mainlog. 2017-09-09 13:19:03 login authenticator failed for (User) [80.82.77.175]: 535 Incorrect authentication data (set_id=admin@domain.ca) 2017-09-09 13:24:19 login authenticator failed for (User) [80.82.77.175]: 535 Incorrect authentication data (set_id=support@domain.ca) 2017-09-09 13:29:34 login authenticator failed for (User) [80.82.77.175]: 535 Incorrect authentication data (set_id=postmaster@domain.ca) 2017-09-09 13:34:49 login authenticator failed for (User) [80.82.77.175]: 535 Incorrect authentication data (set_id=webmaster@domain.ca) All the mail users are stored in LDAP, anonymous bind searching is enabled, and I wanted to re-bind to check the authentication. I had to get the DN from the UID supplied by the SMTP connection. This is a pretty common method (used by dovecot, etc) but it's not directly supported by Exim without some extra lookups. Fortunately exim has all the bits and pieces needed to do the LDAP lookups, it's just a matter of stringing them together. I found 'exim -be' to be very handy in debugging the exim expansions for each section. First we need to find the user's LDAP DN based on a uid lookup of the supplied username: ${lookup ldapdn{ldap://localhost/ou=mail,dc=domain,dc=ca??sub?(uid=${quote_ldap:testuser@domain.ca})}} Connect to the LDAP server on localhost, use a BaseDN of ou=mail,dc=domain,dc=ca and return all attributes using ??, and a subtree search, with a filter (uid=...) :~$ exim -be > ${lookup ldapdn{ldap://localhost/ou=mail,dc=domain,dc=ca??sub?(uid=${quote_ldap:testuser@domain.ca})}} cn=Test User,ou=domain.ca,ou=mail,dc=domain,dc=ca > ^D And a little sub-shell substitution to check the result with the 'ldapsearch' command :~$ ldapsearch -xvvv -D$(exim -be '${lookup ldapdn{ldap://localhost/ou=mail,dc=domain,dc=ca??sub?(uid=${quote_ldap:testuser@domain.ca})}}') -W uid=test\* dn Based heavily on the example from the Exim documentation, the result is: plain: driver = plaintext public_name = PLAIN server_condition = ${if and{{ !eq{}{$auth2} }{ \ ldapauth{\ user="${quote_ldap:${lookup ldapdn{ldap://localhost/ou=mail,dc=domain,dc=ca??sub?(uid=${quote_ldap:$auth2})}}}" \ pass=${quote:$auth3} \ ldap://localhost/} }} } server_set_id = $auth2 server_prompts = : login: driver = plaintext public_name = LOGIN server_prompts = “Username:: : Password::” server_condition = ${if and{{ !eq{}{$auth1} }{ \ ldapauth{\ user="${quote_ldap:${lookup ldapdn{ldap://localhost/ou=mail,dc=domain,dc=ca??sub?(uid=${quote_ldap:$auth1})}}}" \ pass=${quote:$auth2} \ ldap://localhost/} }} } server_set_id = $auth1 This can be tested first by using exim debug: Generate the base64 encoded nullusernamenullpassword string needed for auth plain $ echo -en '\0testuser@domain.ca\0testpass' | base64 AHRlc3R1c2VyQGRvbWFpbi5jYQB0ZXN0cGFzcw== auth login uses 2 separate base64 strings for username (4oCcVXNlcm5hbWU6) and password (UGFzc3dvcmQ64oCd) $ echo -n 'testuser@domain.ca' | base64 dGVzdHVzZXJAZG9tYWluLmNh $ echo -n 'testpass' | base64 dGVzdHBhc3M= :~$ exim4 -bhc 66.55.44.33 -d+all ... 220 mail.domain.ca ESMTP Sat, 09 Sep 2017 17:14:32 -0400 17:14:32 22572 smtp_setup_msg entered ehlo mailhost.com ... 250-mail.domain.ca Hello mailhost.com [66.55.44.33] 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-AUTH PLAIN LOGIN 250-STARTTLS 250 HELP 17:14:40 22572 SMTP>> 250-mail.domain.ca Hello mailhost.com [66.55.44.33] 17:14:40 22572 250-SIZE 52428800 17:14:40 22572 250-8BITMIME 17:14:40 22572 250-PIPELINING 17:14:40 22572 250-AUTH PLAIN LOGIN 17:14:40 22572 250-STARTTLS 17:14:40 22572 250 HELP auth plain 17:14:53 22572 SMTP<< auth plain 17:14:53 22572 SMTP>> 334 334 ... AHRlc3R1c2VyQGRvbWFpbi5jYQB0ZXN0cGFzcw== ... 17:19:47 22572 expanding: $auth2 17:19:47 22572 result: testuser@domain.ca 17:19:47 22572 SMTP>> 235 Authentication succeeded 235 Authentication succeeded quit And then remotely using gnutls-bin for starttls :~$ gnutls-cli --starttls --port 25 mail.domain.ca Processed 174 CA certificate(s). Resolving 'mail.domain.ca'... Connecting to '77.66.55.44:25'... - Simple Client Mode: 220 mail.domain.ca ESMTP Sat, 09 Sep 2017 16:34:53 -0400 ehlo mailhost.com 250-mail.domain.ca Hello dhcp.provider.com [44.33.22.11] 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-AUTH PLAIN LOGIN 250-STARTTLS 250 HELP starttls 220 TLS go ahead ^d *** Starting TLS handshake ... ehlo mailhost.com 250-mail.domain.ca Hello dhcp.provider.com [44.33.22.11] 250-SIZE 52428800 250-8BITMIME 250-PIPELINING 250-AUTH PLAIN LOGIN 250-STARTTLS 250 HELP auth login 334 4oCcVXNlcm5hbWU6 dGVzdHVzZXJAZG9tYWluLmNh 334 UGFzc3dvcmQ64oCd dGVzdHBhc3M= 235 Authentication succeeded quit This fail2ban already had a /etc/fail2ban/filter.d/exim.conf file, so I could just use it as filter in a new section of /etc/fail2ban/jail.local [exim] enabled = true port = all filter = exim logpath = /var/log/exim4/mainlog You will probably want to have Exim listen to alternate ports (465/587), and allow auth only on encrypted connections: daemon_smtp_ports = 25 : 465 : 587 tls_on_connect_ports = 465 auth_advertise_hosts = localhost : ${if eq{$tls_cipher}{}{}{*}} Ref: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_plaintext_authenticator.html