[exim-dev] Remembering failed login id

Top Page
Delete this message
Reply to this message
Author: Todd Lyons
Date:  
To: exim-dev
Subject: [exim-dev] Remembering failed login id
I use Lena's code to block brute force cracking on my servers. We
have a fair amount of users who are SOHO type configurations, where 3
or 4 people are connecting behind a consumer grade NAT device. When
one person's password is wrong on one device (iphone on LAN or
multiple computers in the office), and it's hitting the smtp auth
service quickly, the IP gets added to the blacklist and it blocks the
entire office from being able to send emails.

When that happens, it's convenient to be able to easily see who it was
that triggered the block. Current exim code stores the authenticated
login id variable *if* it passes, but only has this failed login id in
a function local variable. If the failed login id is copied to a
global variable, it could then be expanded in the QUIT and NOTQUIT
acl's, where I can log it along with the $host_address that is
currently being logged.

(Sorry for that last run-on sentence, it just seems wrong to split it
any particular part.)

The (very small) patch is attached. It builds without error for me.
Look towards the end of this message for session captures illustrating
its function.

Questions:
1) Does anybody find this useful? (besides me)
2) Does this variable need to be cleared somewhere else at some point?
To me, it does not. But if you think it does, can anybody think of
one or more "best locations/times" to reset this variable to NULL? I
don't think it's a valid session to have EHLO->AUTH then
STARTTLS->EHLO->AUTH, but I can imagine a scenario where it might
EHLO->AUTH, fail, then RSET->EHLO->AUTH and succeed, etc. (Not likely,
but possible).
3) Does anything long wrong code-wise?
4) Any other questions I've not thought of?

...Todd


******************* Session Captures:

All of these use the following simple ACL segments:

acl_smtp_notquit = acl_check_notquit
acl_smtp_quit = acl_check_quit

acl_check_quit:
  warn    condition      = ${if def:authentication_failed}
          condition      = $authentication_failed
          logwrite       = quit after auth failed $authenticated_fail_id


acl_check_notquit:
  warn    condition      = ${if def:authentication_failed}
          condition      = $authentication_failed
          logwrite       = closed connection after auth failed
$authenticated_fail_id



========== try, fail then QUIT =============

[todd@tlyons ~/projects/exim/src (master_authentication_failed_id)]$
swaks --pipe '/home/todd/projects/exim/src/build-Linux-i386/exim -bh
199.101.162.50' -f me@??? -t you@??? --auth PLAIN
--auth-user me2@??? --auth-password wrong
=== Trying pipe to /home/todd/projects/exim/src/build-Linux-i386/exim
-bh 199.101.162.50...
=== Connected to /home/todd/projects/exim/src/build-Linux-i386/exim
-bh 199.101.162.50.
>>> host in hosts_connection_nolog? no (option unset)
>>> host in host_lookup? yes (matched "*")
>>> looking up host name for 199.101.162.50
>>> IP address lookup yielded mailc-ef.linkedin.com
>>> gethostbyname2 looked up these IP addresses:
>>> name=mailc-ef.linkedin.com address=199.101.162.50
>>> checking addresses for mailc-ef.linkedin.com
>>> 199.101.162.50 OK
>>> host in host_reject_connection? no (option unset)
>>> host in sender_unqualified_hosts? no (option unset)
>>> host in recipient_unqualified_hosts? no (option unset)
>>> host in helo_verify_hosts? no (option unset)
>>> host in helo_try_verify_hosts? no (option unset)
>>> host in helo_accept_junk_hosts? no (option unset)
>>> using ACL "acl_check_connect"
>>> processing "warn"
>>> check hosts = !^.*\\d+\.com\$ : ^.*\\d+[x.-]\\d+[x.-]\\d+[x.-].*
>>> host in "!^.*\d+.com$ : ^.*\d+[x.-]\d+[x.-]\d+[x.-].*"? no (end of list)
>>> warn: condition test failed in ACL "acl_check_connect"
>>> processing "accept"
>>> accept: condition test succeeded in ACL "acl_check_connect"

<-
<- **** SMTP testing session as if from host 199.101.162.50
<- **** but without any ident (RFC 1413) callback.
<- **** This is not for real!
<-
<- 220-tlyons.ivenue.net, ESMTP Exim 4.80_223-c5c2182-XX, Thu, 12 Sep 2013
<- 220-09:33:29 -0700
<- 220 RFC's enforced
-> EHLO tlyons
>>> host in pipelining_advertise_hosts? no (end of list)
>>> host in auth_advertise_hosts? yes (matched "*")
>>> host in tls_advertise_hosts? no (option unset)

<- 250-tlyons.ivenue.net Hello mailc-ef.linkedin.com [199.101.162.50]
<- 250-SIZE 52428800
<- 250-8BITMIME
<- 250-ETRN
<- 250-EXPN
<- 250-AUTH PLAIN
<- 250 HELP
-> AUTH PLAIN AG1lMkBleGFtcGxlLmNvbQB3cm9uZw==
>>> plain authenticator server_condition:
>>> $auth1 =
>>> $auth2 = me2@???
>>> $auth3 = wrong
>>> $1 =
>>> $2 = me2@???
>>> $3 = wrong
>>> expanded string: 0

LOG: plain authenticator failed for mailc-ef.linkedin.com (tlyons)
[199.101.162.50]: 535 Incorrect authentication data
(set_id=me2@???)
<** 535 Incorrect authentication data
*** No authentication type succeeded
-> QUIT
>>> using ACL "acl_check_quit"
>>> processing "warn"
>>> check condition = ${if def:authentication_failed}
>>>                 = true
>>> check condition = $authentication_failed
>>>                 = 1
>>> check logwrite = quit after auth failed $authenticated_fail_id
>>>                = quit after auth failed  (set_id=me2@???)

LOG: quit after auth failed (set_id=me2@???)
>>> warn: condition test succeeded in ACL "acl_check_quit"
>>> end of ACL "acl_check_quit": implicit DENY

<- 221 tlyons.ivenue.net closing connection
=== Connection closed with child process.


========== try, fail then NOTQUIT ==========

[todd@tlyons ~/projects/exim/src (master_authentication_failed_id)]$
/home/todd/projects/exim/src/build-Linux-i386/exim -bh 199.101.162.50

**** SMTP testing session as if from host 199.101.162.50
**** but without any ident (RFC 1413) callback.
**** This is not for real!

>>> host in hosts_connection_nolog? no (option unset)
>>> host in host_lookup? yes (matched "*")
>>> looking up host name for 199.101.162.50
>>> IP address lookup yielded mailc-ef.linkedin.com
>>> gethostbyname2 looked up these IP addresses:
>>> name=mailc-ef.linkedin.com address=199.101.162.50
>>> checking addresses for mailc-ef.linkedin.com
>>> 199.101.162.50 OK
>>> host in host_reject_connection? no (option unset)
>>> host in sender_unqualified_hosts? no (option unset)
>>> host in recipient_unqualified_hosts? no (option unset)
>>> host in helo_verify_hosts? no (option unset)
>>> host in helo_try_verify_hosts? no (option unset)
>>> host in helo_accept_junk_hosts? no (option unset)
>>> using ACL "acl_check_connect"
>>> processing "warn"
>>> check hosts = !^.*\\d+\.com\$ : ^.*\\d+[x.-]\\d+[x.-]\\d+[x.-].*
>>> host in "!^.*\d+.com$ : ^.*\d+[x.-]\d+[x.-]\d+[x.-].*"? no (end of list)
>>> warn: condition test failed in ACL "acl_check_connect"
>>> processing "accept"
>>> accept: condition test succeeded in ACL "acl_check_connect"

220-tlyons.ivenue.net, ESMTP Exim 4.80_223-c5c2182-XX, Thu, 12 Sep 2013
220-09:34:19 -0700
220 RFC's enforced
EHLO tlyons
>>> host in pipelining_advertise_hosts? no (end of list)
>>> host in auth_advertise_hosts? yes (matched "*")
>>> host in tls_advertise_hosts? no (option unset)

250-tlyons.ivenue.net Hello mailc-ef.linkedin.com [199.101.162.50]
250-SIZE 52428800
250-8BITMIME
250-ETRN
250-EXPN
250-AUTH PLAIN
250 HELP
AUTH PLAIN AG1lMkBleGFtcGxlLmNvbQB3cm9uZw==
>>> plain authenticator server_condition:
>>> $auth1 =
>>> $auth2 = me2@???
>>> $auth3 = wrong
>>> $1 =
>>> $2 = me2@???
>>> $3 = wrong
>>> expanded string: 0

535 Incorrect authentication data
LOG: plain authenticator failed for mailc-ef.linkedin.com (tlyons)
[199.101.162.50]: 535 Incorrect authentication data
(set_id=me2@???)
>>> using ACL "acl_check_notquit"
>>> processing "warn"
>>> check condition = ${if def:authentication_failed}
>>>                 = true
>>> check condition = $authentication_failed
>>>                 = 1
>>> check logwrite = closed connection after auth failed $authenticated_fail_id
>>>                = closed connection after auth failed  (set_id=me2@???)

LOG: closed connection after auth failed (set_id=me2@???)
>>> warn: condition test succeeded in ACL "acl_check_notquit"
>>> end of ACL "acl_check_notquit": implicit DENY

421 tlyons.ivenue.net lost input connection


======== try, fail, RSET, try, fail then QUIT ========
(This one shows that it correctly logs the second failed attempt when
it finally hits the QUIT acl. This is ok because I also utilize
Lena's code for not allowing rapid-fire AUTH attempts in the same
session, which implements longer and longer delays between prompts.)

[todd@tlyons ~/projects/exim/src (master_authentication_failed_id)]$
/home/todd/projects/exim/src/build-Linux-i386/exim -bh 199.101.162.50

**** SMTP testing session as if from host 199.101.162.50
**** but without any ident (RFC 1413) callback.
**** This is not for real!

>>> host in hosts_connection_nolog? no (option unset)
>>> host in host_lookup? yes (matched "*")
>>> looking up host name for 199.101.162.50
>>> IP address lookup yielded mailc-ef.linkedin.com
>>> gethostbyname2 looked up these IP addresses:
>>> name=mailc-ef.linkedin.com address=199.101.162.50
>>> checking addresses for mailc-ef.linkedin.com
>>> 199.101.162.50 OK
>>> host in host_reject_connection? no (option unset)
>>> host in sender_unqualified_hosts? no (option unset)
>>> host in recipient_unqualified_hosts? no (option unset)
>>> host in helo_verify_hosts? no (option unset)
>>> host in helo_try_verify_hosts? no (option unset)
>>> host in helo_accept_junk_hosts? no (option unset)
>>> using ACL "acl_check_connect"
>>> processing "warn"
>>> check hosts = !^.*\\d+\.com\$ : ^.*\\d+[x.-]\\d+[x.-]\\d+[x.-].*
>>> host in "!^.*\d+.com$ : ^.*\d+[x.-]\d+[x.-]\d+[x.-].*"? no (end of list)
>>> warn: condition test failed in ACL "acl_check_connect"
>>> processing "accept"
>>> accept: condition test succeeded in ACL "acl_check_connect"

220-tlyons.ivenue.net, ESMTP Exim 4.80_223-c5c2182-XX, Thu, 12 Sep 2013
220-09:35:57 -0700
220 RFC's enforced
EHLO tlyons
>>> host in pipelining_advertise_hosts? no (end of list)
>>> host in auth_advertise_hosts? yes (matched "*")
>>> host in tls_advertise_hosts? no (option unset)

250-tlyons.ivenue.net Hello mailc-ef.linkedin.com [199.101.162.50]
250-SIZE 52428800
250-8BITMIME
250-ETRN
250-EXPN
250-AUTH PLAIN
250 HELP
AUTH PLAIN AG1lMkBleGFtcGxlLmNvbQB3cm9uZw==
>>> plain authenticator server_condition:
>>> $auth1 =
>>> $auth2 = me2@???
>>> $auth3 = wrong
>>> $1 =
>>> $2 = me2@???
>>> $3 = wrong
>>> expanded string: 0

535 Incorrect authentication data
LOG: plain authenticator failed for mailc-ef.linkedin.com (tlyons)
[199.101.162.50]: 535 Incorrect authentication data
(set_id=me2@???)
RSET
250 Reset OK
EHLO tlyons
>>> host in smtp_accept_max_nonmail_hosts? yes (matched "*")
>>> host in pipelining_advertise_hosts? no (end of list)
>>> host in auth_advertise_hosts? yes (matched "*")
>>> host in tls_advertise_hosts? no (option unset)

250-tlyons.ivenue.net Hello mailc-ef.linkedin.com [199.101.162.50]
250-SIZE 52428800
250-8BITMIME
250-ETRN
250-EXPN
250-AUTH PLAIN
250 HELP
AUTH PLAIN AG1mMkBleGFtcGxlLmNvbQB3cm9uZw==
>>> plain authenticator server_condition:
>>> $auth1 =
>>> $auth2 = mf2@???
>>> $auth3 = wrong
>>> $1 =
>>> $2 = mf2@???
>>> $3 = wrong
>>> expanded string: 0

535 Incorrect authentication data
LOG: plain authenticator failed for mailc-ef.linkedin.com (tlyons)
[199.101.162.50]: 535 Incorrect authentication data
(set_id=mf2@???)
quit
>>> using ACL "acl_check_quit"
>>> processing "warn"
>>> check condition = ${if def:authentication_failed}
>>>                 = true
>>> check condition = $authentication_failed
>>>                 = 1
>>> check logwrite = quit after auth failed $authenticated_fail_id
>>>                = quit after auth failed  (set_id=mf2@???)

LOG: quit after auth failed (set_id=mf2@???)
>>> warn: condition test succeeded in ACL "acl_check_quit"
>>> end of ACL "acl_check_quit": implicit DENY

221 tlyons.ivenue.net closing connection


--
The total budget at all receivers for solving senders' problems is $0.
If you want them to accept your mail and manage it the way you want,
send it the way the spec says to. --John Levine