Force TCP socket disconnect on imap login failure?

Paul Kudla (SCOM.CA Internet Services Inc.) paul at scom.ca
Tue May 24 08:54:07 UTC 2022


for what its worth this is a python script that i use for the database 
driven iptables updater for my asterisk server

again same ideas but it gets the job done.

It's a lot of work to get stuff like this going but may help point 
someone in the right directions balance wise pending on there system / 
network setup.

The django script is intelligent as it looks at the ip addresses already 
blacklisted and updates the list adding or subtracting ip address 
changes within the database

can answer in more detail, mainly for reference.

example iptables output :

# /sbin/iptables -L INPUT -n | more
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all  --  92.204.135.144       0.0.0.0/0
ACCEPT     all  --  104.205.0.0/16       0.0.0.0/0
ACCEPT     all  --  174.95.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.94.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.93.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.92.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.91.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.90.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.89.0.0/16        0.0.0.0/0
ACCEPT     all  --  174.88.0.0/16        0.0.0.0/0
ACCEPT     all  --  209.171.88.0/24      0.0.0.0/0
ACCEPT     all  --  72.12.174.230        0.0.0.0/0
ACCEPT     all  --  72.136.0.0/16        0.0.0.0/0
ACCEPT     all  --  10.0.0.0/8           0.0.0.0/0
ACCEPT     all  --  67.171.153.140       0.0.0.0/0
ACCEPT     all  --  99.235.148.110       0.0.0.0/0
ACCEPT     all  --  67.69.69.0/24        0.0.0.0/0
ACCEPT     all  --  204.237.0.0/16       0.0.0.0/0
ACCEPT     all  --  65.39.148.0/25       0.0.0.0/0
ACCEPT     all  --  72.143.119.178       0.0.0.0/0
ACCEPT     all  --  99.244.67.244        0.0.0.0/0
ACCEPT     all  --  69.60.225.80         0.0.0.0/0
ACCEPT     all  --  198.200.68.0/24      0.0.0.0/0
ACCEPT     all  --  185.58.85.0/24       0.0.0.0/0
ACCEPT     all  --  172.97.0.0/16        0.0.0.0/0
ACCEPT     all  --  184.151.0.0/16       0.0.0.0/0
DROP       tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5038
DROP       tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:80
DROP       all  --  213.175.208.0/24     0.0.0.0/0
DROP       all  --  50.24.0.0/24         0.0.0.0/0
DROP       all  --  20.98.78.0/24        0.0.0.0/0
DROP       all  --  116.106.197.0/24     0.0.0.0/0
DROP       all  --  45.95.169.0/24       0.0.0.0/0
DROP       all  --  193.253.211.0/24     0.0.0.0/0
DROP       all  --  65.49.20.0/24        0.0.0.0/0
DROP       all  --  107.189.1.0/24       0.0.0.0/0
DROP       all  --  107.189.3.0/24       0.0.0.0/0
DROP       all  --  209.141.51.0/24      0.0.0.0/0
DROP       all  --  75.119.155.0/24      0.0.0.0/0
DROP       all  --  45.133.1.0/24        0.0.0.0/0
DROP       all  --  185.166.84.0/24      0.0.0.0/0
DROP       all  --  116.105.218.0/24     0.0.0.0/0
DROP       all  --  216.37.36.0/24       0.0.0.0/0
DROP       all  --  216.245.220.0/24     0.0.0.0/0
DROP       all  --  205.185.121.0/24     0.0.0.0/0


based on django model(s)

#IP Blacklistings		
class IpBlock(models.Model):
	id 				= models.AutoField(primary_key=True)
	ipaddress 		= models.CharField(verbose_name='IP Address', 
max_length=40, null=True, blank=False,unique=False)
	action      	= models.CharField(max_length=15, 
choices=ip_action_choices, verbose_name='Firewall', default = 'D', 
null=True, blank=True)
	syslog			= models.TextField(verbose_name='Last Syslog', 
max_length=1000, null=True, blank=True, default = '')
	whois			= models.TextField(verbose_name='Whois', max_length=1500, 
null=True, blank=True, default = '')
	asterisk		= models.BooleanField('Asterisk', default = False )
	last_datetime	= models.DateTimeField(verbose_name='Date Last Updated 
Server', null=True, blank=True, default = timezone.now)
	accountid    	= models.ForeignKey(Contacts,verbose_name='Reference', 
default = '2594',null=False, blank=True,related_name = 'blacklist_soldto')
	syslog2			= models.TextField(verbose_name='Last Syslog', 
max_length=1000, null=True, blank=True, default = 'Denied due to 
Unauthorized Use')
	last_program	= models.CharField(verbose_name='Last Program', 
max_length=20, null=True, blank=True, default = '')
	
	
	
	class Meta:
		ordering = ['ipaddress',]
		db_table = u'blocked_ip'
		verbose_name = u"Currently Blocked IP's"
		verbose_name_plural = u"Currently Blocked Ip's"

class IpCount(models.Model):
	ipaddress 				= models.GenericIPAddressField(verbose_name='IP Address', 
max_length=17,blank=False,primary_key=True, unique=True)
	counthour   			= models.IntegerField(verbose_name='Current IP Count 
This Hour', null=True, blank=True, default='0')
	counttotal  			= models.IntegerField(verbose_name='Total IP Count This 
Month', null=True, blank=True, default='0')
	asterisk_counthour   	= models.IntegerField(verbose_name='Asterisk IP 
Count This Hour', null=True, blank=True, default='0')
	asterisk_counttotal   	= models.IntegerField(verbose_name='Asterisk IP 
Count This Month', null=True, blank=True, default='0')
	syslog   				= models.TextField(verbose_name='Syslog (What Hacked Me 
Last)', max_length=1000, null=True, blank=True, default = '')
	whois					= models.TextField(verbose_name='Whois', max_length=1500, 
null=True, blank=True, default = '')
	last_datetime			= models.DateTimeField(verbose_name='Date Last Updated 
Server', null=True, blank=True, default = timezone.now)
	last_program			= models.CharField(verbose_name='Last Program', 
max_length=20, null=True, blank=True, default = '')
	action					= models.BooleanField('Marked As Bad', default = False )

	class Meta:
		ordering = ['ipaddress',]
		db_table = u'ip_count'
		verbose_name = u"Current IP Count"
		verbose_name_plural = u"Current IP Counts"




---------------------------------------------------------------------------
#!/usr/bin/env python2
#update.cidr
#Modified for iptables

#iptables -A FORWARD -s 8.8.8.8 -j DROP
#iptables -I INPUT -s 30.30.0.0/255.255.0.0 -j DROP

import sys
import os
import string
import psycopg2
import commands



def DROPIP(ipaddress) :
         command = '/sbin/iptables -A INPUT -s %s -j DROP' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -A OUTPUT -s %s -j DROP' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -A FORWARD -s %s -j DROP' %str(ipaddress)
         yyerror = commands.getoutput(command)

         print yyerror

def ACCEPTIP(ipaddress) :
         command = '/sbin/iptables -I INPUT -s %s -j ACCEPT' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -I OUTPUT -s %s -j ACCEPT' 
%str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -I FORWARD -s %s -j ACCEPT' 
%str(ipaddress)
         yyerror = commands.getoutput(command)

def DELETEIP(ipaddress) :
         #Drop the drops
         command = '/sbin/iptables -D INPUT -s %s -j DROP' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -D OUTPUT -s %s -j DROP' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -D FORWARD -s %s -j DROP' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -D INPUT -s %s -j ACCEPT' %str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -D OUTPUT -s %s -j ACCEPT' 
%str(ipaddress)
         yyerror = commands.getoutput(command)
         command = '/sbin/iptables -D FORWARD -s %s -j ACCEPT' 
%str(ipaddress)
         yyerror = commands.getoutput(command)


#ipaddress = '179.126.80.0/24'
#DELETEIP(ipaddress)
#sys.exit()

#Am I already running
command = '/bin/ps -axww | grep python'
yyerror = commands.getoutput(command)

#print yyerror
count = 0

yyerror = yyerror.split('\n')
#print yyerror


for nn in range (0,len(yyerror)) :
         yy = yyerror[nn]
         if 'iptables.update' in yy :
                 count = count + 1

#print
#print count

if count >= 2 :
         print 'Already Updating ..... '
         print 'Exiting ......'
         sys.exit()


print 'Connecting to DB ...'

conn = psycopg2.connect(host='10.220.0.2', port = 5433, 
database='scom_billing', user='', password='')
pg = conn.cursor()

print 'Connected to DB'
print 'Getting Current IP List ....'
command = ("""select action,ipaddress from blocked_ip where asterisk = 
true or action = 'A' order by action  """ ) #Go get any unassigned orders
pg.execute(command)
firewalldata = pg.fetchall()

#iptables --line-numbers -n --list
#Go get the existing firewall list


command = '/sbin/iptables -L INPUT -n'
print command
data = commands.getoutput(command)

data = data.split('\n')
currentlist = []

print len(data)


#Update list for DROP or ACCEPT and ip block
for nn in range (0,len(data)) :
         y = str(data[nn])
         #print
         #print y
         #print
         if 'Chain INPUT (policy ACCEPT)' in y or 'target     prot opt 
source               destination' in y :
                 print 'Skipping ...'
                 print

         else : #Process the line
                 #print 'Processing this Line'
                 try :
                         if 'DROP' in y :
                                 status = 'D'
                         else :
                                 status = 'A'

                         ip = y.split('--  ')
                         #print ip
                         ip = ip[1]
                         #print ip
                         ip = ip.split(' ')
                         #print ip
                         ip = ip[0]
                         #print ip
                         #print 'appending to list'


                         currentlist.append(status)
                         currentlist.append(ip)

                 except :
                         print 'Bad Data Skipping ...'


print
print
print'Full list Currently In Firewall ...'
#print currentlist


#sys.exit()

print 'Got the list ... Working'
print
print
blacklist = [] #This is the converted list to iptable compatable formats


for x in range (0,len(firewalldata)) : #data = ipdata from db
         #Internal Sample - ['A', '10.220.0.0/16']
         #DB Sample - ('A', '67.55.27.171')

         y = firewalldata[x]
         #print 'firewall data %s' %str(y)
         #print
         #print

         #sys.exit()

         ipaddress = str(y[1])
         #print 'DB Ip Address %s' %str(ipaddress)


         if ipaddress <> 'ALL' :
                 done = 0
                 #print 'IP In  : %s' %str(ipaddress)
                 #Modify ipaddress for cidr mapping
                 if ipaddress.count('.') == 1 : #10.
                         ipaddress = ipaddress + '0.0.0/8'
                         done = 1
                 if ipaddress.count('.') == 2 and done == 0 : #10.0.
                         ipaddress = ipaddress + '0.0/16'
                         done = 1
                 if ipaddress.count('.') == 3 and 
ipaddress[len(ipaddress)-1] == '.' and done == 0 : #10.0.0.
                         ipaddress = ipaddress + '0/24'

                 #print 'IP Out: %s' %str(ipaddress)

                 #Now process the tables ie update/delete/change the entries

                 blacklist.append(str(y[0])) #set the status
                 blacklist.append(str(ipaddress) ) #Set the ip block to 
manage


#print 'Current List In Scom Blacklistings'
#print badlist

print 'Processing .... My IP Black List Entries'
for n in range (0,len(blacklist),2) : #0 - action,1 - ip block
         blacklistaction = str(blacklist[n])
         blacklistip = str(blacklist[n+1])
         #Now go check the iptable list to see if i have an entry
         #print 'Processing Entry %s for IP %s with Action %s' 
%(str(n),blacklistip,blacklistaction)
         #print len(currentlist)
         try :
                 nn = currentlist.index(blacklistip)
                 nn = nn-1
                 #Is this current black list ip currently in the iptables?
                 iptablesaction = str(currentlist[nn])
                 iptablesip = str( currentlist[nn+1] )
                 #Do i have a matching ip block?
                 if blacklistip == iptablesip : #We found a matching bl 
entry already in iptables.
                         if blacklistaction == iptablesaction : #Rule is 
good as is skip
                                 #print 'Found A Current Rule that 
matches, skipping ... %s' %str(blacklistip)
                                 del currentlist[nn+1]
                                 del currentlist[nn]


                         elif ipblacklistaction <> iptablesaction : #We 
have a matching block but have to update the list
                                 DELETEIP(str(iptablesip)) #Drop the 
existing ip from the tables (precautionary)
                                 if blacklistaction == 'A' :
                                         #print 'Adding to Accept 
IPTABLES List'
                                         ACCEPTIP(str(ipblacklistip))
                                 elif blacklistaction == 'D' :
                                         #print 'Adding to Drop IPTABLES 
List'
                                         DROPIP(str(ipblacklistip))

                                 print 'Updated Mismatch IPTABLES for %s 
...' %str(ipblacklistip)
                                 del currentlist[nn+1]
                                 del currentlist[nn]


         except :
                 #e = sys.exc_info()[0]
                 #print e
                 #We did not find anything in the tables, add new entry
                 print 'Pricessing Entry : %s ' %str(n)
                 if blacklistaction == 'A' :
                         print 'Adding to Accept IPTABLES List %s' 
%str(blacklistip)
                         ACCEPTIP(blacklistip)
                 elif blacklistaction == 'D' :
                         print 'Adding to Drop IPTABLES List %s' 
%str(blacklistip)
                         DROPIP(blacklistip)

                 #print 'Updated IPTABLES with new entry %s with Action 
: %s' %(blacklistip,blacklistaction)

#Ok the blacklist is god again, see if there are any left over iptables 
rules that we need to delete
print len(currentlist)

if len(currentlist) <> 0 :
         print 'Cleaning up %s extra iptables ....' %str(len(currentlist))
         for nn in range (0,len(currentlist),2) :
                 iptablesip = str( currentlist[nn+1] )
                 print 'Deleting %s from iptables' %str(iptablesip)
                 DELETEIP(str(iptablesip))



sys.exit()

------------------------------------------------------------------------------



Happy Tuesday !!!
Thanks - paul

Paul Kudla


Scom.ca Internet Services <http://www.scom.ca>
004-1009 Byron Street South
Whitby, Ontario - Canada
L1N 4S3

Toronto 416.642.7266
Main 1.866.411.7266
Fax 1.888.892.7266
Email paul at scom.ca

On 5/24/2022 3:36 AM, Jan Hugo Prins wrote:
> Just a few comments.
> 
> - The below commands drops ALL future connections to the IMAP ports and 
> not just the one from that specific IP address.
> - It all depends on the ordering of the rest of your iptables rules. A 
> lot of iptables setups have an accept related / established in the top 
> of the INPUT chain and then indeed the traffic will continue as long as 
> the connection is established. If you put a correct drop rule in the top 
> of your iptables INPUT chain it will block all traffic including any 
> related/established.
> 
> Fail2Ban is able to insert such a drop rule in the top of the INPUT 
> chain and thereby block all further tries.
> This is exactly how I have setup my fail2ban and it works.
> 
> The first few lines of my iptables input chain look like this:
> 
>    29M 2249M f2b-dovecot  tcp  --  *      * 0.0.0.0/0            
> 0.0.0.0/0            multiport dports 110,143,993,995
> 9969K 2545M f2b-sasl   tcp  --  *      *       0.0.0.0/0 
> 0.0.0.0/0            multiport dports 25,465
> 9691K 2788M ACCEPT     all  --  lo     *       0.0.0.0/0 0.0.0.0/0
>   134M  257G ACCEPT     all  --  *      *       0.0.0.0/0 
> 0.0.0.0/0            state RELATED,ESTABLISHED
> 
> Jan Hugo Prins
> 
> 
> On 5/23/22 23:16, Hippo Man wrote:
>> OOPS! I incorrectly copied and pasted the iptables command in my 
>> previous message. Here is the correct iptables command:
>>
>> iptables -I INPUT -p tcp -m multiport --destination-port 143,993 -d 
>> aaa.bbb.ccc.ddd -j DROP
>>
>> This command successfully blocks *future* connections to ports 143 and 
>> 993 from that IP address, but as I mentioned, it doesn't kill the 
>> currently open connection.
>>
>> -- 
>> hippoman at gmail.com
>>  Take a hippopotamus to lunch today.
>>
>>
>> On Mon, May 23, 2022 at 4:54 PM Hippo Man <hippoman at gmail.com> wrote:
>>
>>     Thank you, but fail2ban doesn't do what I need. Here is why ...
>>
>>     I have used fail2ban and also my own homegrown log monitor program
>>     for this purpose. In both cases, I can detect the failed imap
>>     logins and then cause the following command to be run ...
>>
>>     iptables -I INPUT -p tcp --destination-port aaa.bbb.ccc.ddd -j DROP
>>
>>     However, this does not drop connections that are existing and
>>     already open. It will only drop *future* connections from that IP
>>     address to port 143.
>>
>>     This is why I want to kill the existing connection. Even after
>>     that "iptables" command is issued, the entity which is connected
>>     to the imap port can continue to send more and more imap commands.
>>
>>     If I can drop the TCP connection as soon as an imap login fails
>>     and also issue that kind of "iptables" command, then the client
>>     would have to reconnect in order to retry other login attempts.
>>     Those future connections would then be successfully blocked by
>>     that iptables rule.
>>
>>     And even if I issue a "tcpdrop" command instead of just the
>>     "iptables" command, it doesn't kill the already-open connection.
>>     It just force-blocks future connections.
>>
>>     I'm thinking of patching the dovecot source code to create a
>>     personal version which immediately disconnects from the socket
>>     after login failure. Of course, I would prefer not to do that, if
>>     there is another way to accomplish this.
>>
>>     -- 
>>     hippoman at gmail.com
>>      Take a hippopotamus to lunch today.
>>
>>
>>     On Mon, May 23, 2022 at 4:24 PM Jan Hugo Prins <jhp at jhprins.org>
>>     wrote:
>>
>>         Look at fail2ban.
>>         Should be able to do that for you.
>>
>>         Jan Hugo
>>
>>
>>         On 5/23/22 21:11, Lloyd Zusman wrote:
>>>         I'm running dovecot 2.2.13 under Debian 8.
>>>
>>>         I'd like to force an immediate TCP socket disconnect after
>>>         any imap login attempt that fails.
>>>
>>>         Right now, if invalid credentials are supplied during an imap
>>>         login, the client can keep retrying logins with different
>>>         credentials. However, I want to prevent that from occurring
>>>         by causing the socket connection to be closed as soon as
>>>         there is any failed login attempt.
>>>
>>>         I haven't been able to find any |dovecot| configuration
>>>         setting which could control this behavior, but I'm hoping
>>>         that I just missed something.
>>>
>>>         Thank you very much for any suggestions.
>>>
>>>         -- 
>>>         hippoman at gmail.com
>>>          Take a hippopotamus to lunch today.
>>
> 
> 
> -- 
> This message has been scanned for viruses and
> dangerous content by *MailScanner* <http://www.mailscanner.info/>, and is
> believed to be clean.


More information about the dovecot mailing list