git subrepo commit (merge) mailcow/src/mailcow-dockerized

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "32243e56"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "e2b4b6f6"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: I51e2016ef5ab88a8b0bdc08551b18f48ceef0aa5
diff --git a/mailcow/src/mailcow-dockerized/data/Dockerfiles/netfilter/server.py b/mailcow/src/mailcow-dockerized/data/Dockerfiles/netfilter/server.py
index 04f6c47..08c8727 100644
--- a/mailcow/src/mailcow-dockerized/data/Dockerfiles/netfilter/server.py
+++ b/mailcow/src/mailcow-dockerized/data/Dockerfiles/netfilter/server.py
@@ -92,15 +92,16 @@
   global exit_code

   if not r.get('F2B_REGEX'):

     f2bregex = {}

-    f2bregex[1] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed'

-    f2bregex[2] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'

-    f2bregex[3] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'

-    f2bregex[4] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'

-    f2bregex[5] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'

-    f2bregex[6] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'

-    f2bregex[7] = 'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'

-    f2bregex[8] = '-login: Aborted login \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'

-    f2bregex[9] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'

+    f2bregex[1] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'

+    f2bregex[2] = 'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'

+    f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed'

+    f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'

+    f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'

+    f2bregex[6] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'

+    f2bregex[7] = '-login: Aborted login \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'

+    f2bregex[8] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'

+    f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'

+    f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'

     r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))

   else:

     try:

@@ -122,8 +123,10 @@
     time.sleep(10)

     with lock:

       filter4_table = iptc.Table(iptc.Table.FILTER)

+      filter6_table = iptc.Table6(iptc.Table6.FILTER)

       filter4_table.refresh()

-      for f in [filter4_table]:

+      filter6_table.refresh()

+      for f in [filter4_table, filter6_table]:

         forward_chain = iptc.Chain(f, 'FORWARD')

         input_chain = iptc.Chain(f, 'INPUT')

         for chain in [forward_chain, input_chain]:

@@ -195,7 +198,14 @@
         if rule not in chain.rules:

           chain.insert_rule(rule)

     else:

-      pass

+      with lock:

+        chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')

+        rule = iptc.Rule6()

+        rule.src = net

+        target = iptc.Target(rule, "REJECT")

+        rule.target = target

+        if rule not in chain.rules:

+          chain.insert_rule(rule)

     r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME)

   else:

     logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))

@@ -217,7 +227,14 @@
       if rule in chain.rules:

         chain.delete_rule(rule)

   else:

-    pass

+    with lock:

+      chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')

+      rule = iptc.Rule6()

+      rule.src = net

+      target = iptc.Target(rule, "REJECT")

+      rule.target = target

+      if rule in chain.rules:

+        chain.delete_rule(rule)

   r.hdel('F2B_ACTIVE_BANS', '%s' % net)

   r.hdel('F2B_QUEUE_UNBAN', '%s' % net)

   if net in bans:

@@ -235,13 +252,26 @@
       if rule not in chain.rules and not unban:

         logCrit('Add host/network %s to blacklist' % net)

         chain.insert_rule(rule)

-        r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))

+        r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) 

       elif rule in chain.rules and unban:

         logCrit('Remove host/network %s from blacklist' % net)

         chain.delete_rule(rule)

         r.hdel('F2B_PERM_BANS', '%s' % net)

   else:

-    pass

+    with lock:

+      chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW')

+      rule = iptc.Rule6()

+      rule.src = net

+      target = iptc.Target(rule, "REJECT")

+      rule.target = target

+      if rule not in chain.rules and not unban:

+        logCrit('Add host/network %s to blacklist' % net)

+        chain.insert_rule(rule)

+        r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) 

+      elif rule in chain.rules and unban:

+        logCrit('Remove host/network %s from blacklist' % net)

+        chain.delete_rule(rule)

+        r.hdel('F2B_PERM_BANS', '%s' % net)

 

 def quit(signum, frame):

   global quit_now

@@ -254,7 +284,8 @@
     unban(net)

   with lock:

     filter4_table = iptc.Table(iptc.Table.FILTER)

-    for filter_table in [filter4_table]:

+    filter6_table = iptc.Table6(iptc.Table6.FILTER)

+    for filter_table in [filter4_table, filter6_table]:

       filter_table.autocommit = False

       forward_chain = iptc.Chain(filter_table, "FORWARD")

       input_chain = iptc.Chain(filter_table, "INPUT")

@@ -337,7 +368,41 @@
           table.commit()

         table.autocommit = True

       except:

-        print('Error running SNAT4, retrying...')

+        print('Error running SNAT4, retrying...') 

+

+def snat6(snat_target):

+  global lock

+  global quit_now

+

+  def get_snat6_rule():

+    rule = iptc.Rule6()

+    rule.src = os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64')

+    rule.dst = '!' + rule.src

+    target = rule.create_target("SNAT")

+    target.to_source = snat_target

+    return rule

+

+  while not quit_now:

+    time.sleep(10)

+    with lock:

+      try:

+        table = iptc.Table6('nat')

+        table.refresh()

+        chain = iptc.Chain(table, 'POSTROUTING')

+        table.autocommit = False

+        if get_snat6_rule() not in chain.rules:

+          logInfo('Added POSTROUTING rule for source network %s to SNAT target %s' % (get_snat6_rule().src, snat_target))

+          chain.insert_rule(get_snat6_rule())

+          table.commit()

+        else:

+          for position, item in enumerate(chain.rules):

+            if item == get_snat6_rule():

+              if position != 0:

+                chain.delete_rule(get_snat6_rule())

+          table.commit()

+        table.autocommit = True

+      except:

+        print('Error running SNAT6, retrying...') 

 

 def autopurge():

   while not quit_now:

@@ -403,7 +468,7 @@
       if Counter(new_whitelist) != Counter(WHITELIST):

         WHITELIST = new_whitelist

         logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))

-    time.sleep(60.0 - ((time.time() - start_time) % 60.0))

+    time.sleep(60.0 - ((time.time() - start_time) % 60.0)) 

 

 def blacklistUpdate():

   global quit_now

@@ -414,7 +479,7 @@
     new_blacklist = []

     if list:

       new_blacklist = genNetworkList(list)

-    if Counter(new_blacklist) != Counter(BLACKLIST):

+    if Counter(new_blacklist) != Counter(BLACKLIST): 

       addban = set(new_blacklist).difference(BLACKLIST)

       delban = set(BLACKLIST).difference(new_blacklist)

       BLACKLIST = new_blacklist

@@ -425,7 +490,7 @@
       if delban:

         for net in delban:

           permBan(net=net, unban=True)

-    time.sleep(60.0 - ((time.time() - start_time) % 60.0))

+    time.sleep(60.0 - ((time.time() - start_time) % 60.0)) 

 

 def initChain():

   # Is called before threads start, no locking

@@ -442,6 +507,18 @@
     rule.target = target

     if rule not in chain.rules:

       chain.insert_rule(rule)

+  # IPv6

+  if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), "MAILCOW") in iptc.Table6(iptc.Table6.FILTER).chains:

+    iptc.Table6(iptc.Table6.FILTER).create_chain("MAILCOW")

+  for c in ['FORWARD', 'INPUT']:

+    chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c)

+    rule = iptc.Rule6()

+    rule.src = '::/0'

+    rule.dst = '::/0'

+    target = iptc.Target(rule, "MAILCOW")

+    rule.target = target

+    if rule not in chain.rules:

+      chain.insert_rule(rule)

 

 if __name__ == '__main__':

 

@@ -465,6 +542,17 @@
     except ValueError:

       print(os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address')

 

+  if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') != 'n':

+    try:

+      snat_ip = os.getenv('SNAT6_TO_SOURCE')

+      snat_ipo = ipaddress.ip_address(snat_ip)

+      if type(snat_ipo) is ipaddress.IPv6Address:

+        snat6_thread = Thread(target=snat6,args=(snat_ip,))

+        snat6_thread.daemon = True

+        snat6_thread.start()

+    except ValueError:

+      print(os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address')

+

   autopurge_thread = Thread(target=autopurge)

   autopurge_thread.daemon = True

   autopurge_thread.start()