From 0206e0886c70f242c8f8c92fe1b0f30006639f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B6rn=20J=C3=B6rger?= Date: Fri, 17 Mar 2023 01:33:40 +0100 Subject: [PATCH 1/5] implemented exponentially incrementing bantime, removed active_window code that did nothing, cleanly initialized dictionary --- data/Dockerfiles/netfilter/server.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 0b0e2a41..de4b1766 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -174,20 +174,15 @@ def ban(address): net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) net = str(net) - if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW: - bans[net] = { 'attempts': 0 } - active_window = RETRY_WINDOW - else: - active_window = time.time() - bans[net]['last_attempt'] + if not net in bans: + bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} bans[net]['attempts'] += 1 bans[net]['last_attempt'] = time.time() - active_window = time.time() - bans[net]['last_attempt'] - if bans[net]['attempts'] >= MAX_ATTEMPTS: cur_time = int(round(time.time())) - logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60)) + logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60 * 2 ** bans[net]['ban_counter'])) if type(ip) is ipaddress.IPv4Address: with lock: chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') @@ -206,7 +201,7 @@ def ban(address): rule.target = target if rule not in chain.rules: chain.insert_rule(rule) - r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME) + r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME * 2 ** bans[net]['ban_counter']) else: logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) @@ -238,7 +233,8 @@ def unban(net): r.hdel('F2B_ACTIVE_BANS', '%s' % net) r.hdel('F2B_QUEUE_UNBAN', '%s' % net) if net in bans: - del bans[net] + bans[net]['attempts'] = 0 + bans[net]['ban_counter'] += 1 def permBan(net, unban=False): global lock @@ -432,7 +428,7 @@ def autopurge(): unban(str(net)) for net in bans.copy(): if bans[net]['attempts'] >= MAX_ATTEMPTS: - if time.time() - bans[net]['last_attempt'] > BAN_TIME: + if time.time() - bans[net]['last_attempt'] > BAN_TIME * 2 ** bans[net]['ban_counter']: unban(net) def isIpNetwork(address): From 1233613bea73a22442eb000dd36376f59d7b1ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B6rn=20J=C3=B6rger?= Date: Fri, 17 Mar 2023 14:41:37 +0100 Subject: [PATCH 2/5] implemented handling of max_bantime and ban_time_increment flag --- data/Dockerfiles/netfilter/server.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index de4b1766..5533b4b3 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -67,11 +67,15 @@ def refreshF2boptions(): if not r.get('F2B_OPTIONS'): f2boptions = {} f2boptions['ban_time'] = int + f2boptions['max_ban_time'] = int + f2boptions['ban_time_increment'] = bool f2boptions['max_attempts'] = int f2boptions['retry_window'] = int f2boptions['netban_ipv4'] = int f2boptions['netban_ipv6'] = int f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 + f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') or 10000 + f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') or True f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32 @@ -147,6 +151,7 @@ def ban(address): global lock refreshF2boptions() BAN_TIME = int(f2boptions['ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) MAX_ATTEMPTS = int(f2boptions['max_attempts']) RETRY_WINDOW = int(f2boptions['retry_window']) NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) @@ -182,7 +187,8 @@ def ban(address): if bans[net]['attempts'] >= MAX_ATTEMPTS: cur_time = int(round(time.time())) - logCrit('Banning %s for %d minutes' % (net, BAN_TIME / 60 * 2 ** bans[net]['ban_counter'])) + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] + logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) if type(ip) is ipaddress.IPv4Address: with lock: chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') @@ -201,7 +207,7 @@ def ban(address): rule.target = target if rule not in chain.rules: chain.insert_rule(rule) - r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + BAN_TIME * 2 ** bans[net]['ban_counter']) + r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME) else: logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) @@ -421,6 +427,8 @@ def autopurge(): time.sleep(10) refreshF2boptions() BAN_TIME = int(f2boptions['ban_time']) + MAX_BAN_TIME = int(f2boptions['max_ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) MAX_ATTEMPTS = int(f2boptions['max_attempts']) QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') if QUEUE_UNBAN: @@ -428,7 +436,9 @@ def autopurge(): unban(str(net)) for net in bans.copy(): if bans[net]['attempts'] >= MAX_ATTEMPTS: - if time.time() - bans[net]['last_attempt'] > BAN_TIME * 2 ** bans[net]['ban_counter']: + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] + TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt'] + if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME: unban(net) def isIpNetwork(address): From c28a6b89f030caca6e208790f80751588d1868ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B6rn=20J=C3=B6rger?= Date: Fri, 17 Mar 2023 18:22:16 +0100 Subject: [PATCH 3/5] Added ban_time_increment and max_ban_time to UI --- data/web/api/openapi.yaml | 12 +++++++++++- data/web/inc/functions.fail2ban.inc.php | 4 ++++ data/web/lang/lang.de-de.json | 2 ++ data/web/lang/lang.en-gb.json | 2 ++ data/web/lang/lang.es-es.json | 2 ++ data/web/lang/lang.fr-fr.json | 4 +++- data/web/lang/lang.it-it.json | 2 ++ data/web/lang/lang.nl-nl.json | 2 ++ data/web/templates/admin/tab-config-f2b.twig | 8 ++++++++ 9 files changed, 36 insertions(+), 2 deletions(-) diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index 5e07c4b3..65bd1211 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -3176,8 +3176,10 @@ paths: example: attr: ban_time: "86400" + ban_time_increment: "1" blacklist: "10.100.6.5/32,10.100.8.4/32" max_attempts: "5" + max_ban_time: "86400" netban_ipv4: "24" netban_ipv6: "64" retry_window: "600" @@ -3191,11 +3193,17 @@ paths: description: the backlisted ips or hostnames separated by comma type: string ban_time: - description: the time a ip should be banned + description: the time an ip should be banned type: number + ban_time_increment: + description: if the time of the ban should increase each time + type: boolean max_attempts: description: the maximum numbe of wrong logins before a ip is banned type: number + max_ban_time: + description: the maximum time an ip should be banned + type: number netban_ipv4: description: the networks mask to ban for ipv4 type: number @@ -4113,10 +4121,12 @@ paths: response: value: ban_time: 604800 + ban_time_increment: 1 blacklist: |- 45.82.153.37/32 92.118.38.52/32 max_attempts: 1 + max_ban_time: 604800 netban_ipv4: 32 netban_ipv6: 128 perm_bans: diff --git a/data/web/inc/functions.fail2ban.inc.php b/data/web/inc/functions.fail2ban.inc.php index 2a7f11e8..2c4aa41d 100644 --- a/data/web/inc/functions.fail2ban.inc.php +++ b/data/web/inc/functions.fail2ban.inc.php @@ -239,7 +239,9 @@ function fail2ban($_action, $_data = null) { $is_now = fail2ban('get'); if (!empty($is_now)) { $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); + $ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0; $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']); + $max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']); $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']); $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']); $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); @@ -256,6 +258,8 @@ function fail2ban($_action, $_data = null) { } $f2b_options = array(); $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time; + $f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false; + $f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time; $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4; $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 8ff1cf06..4bd4b3fa 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -175,10 +175,12 @@ "empty": "Keine Einträge vorhanden", "excludes": "Diese Empfänger ausschließen", "f2b_ban_time": "Bannzeit in Sekunden", + "f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann", "f2b_blacklist": "Blacklist für Netzwerke und Hosts", "f2b_filter": "Regex-Filter", "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. Die Aktualisierung der Liste dauert einige Sekunden.", "f2b_max_attempts": "Max. Versuche", + "f2b_max_ban_time": "Maximale Bannzeit in Sekunden", "f2b_netban_ipv4": "Netzbereich für IPv4-Banns (8-32)", "f2b_netban_ipv6": "Netzbereich für IPv6-Banns (8-128)", "f2b_parameters": "Fail2ban-Parameter", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index bfac011e..df83987c 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -177,10 +177,12 @@ "empty": "No results", "excludes": "Excludes these recipients", "f2b_ban_time": "Ban time (s)", + "f2b_ban_time_increment": "Ban time is incremented with each ban", "f2b_blacklist": "Blacklisted networks/hosts", "f2b_filter": "Regex filters", "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. List updates will take a few seconds to be applied.", "f2b_max_attempts": "Max. attempts", + "f2b_max_ban_time": "Max. ban time (s)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Fail2ban parameters", diff --git a/data/web/lang/lang.es-es.json b/data/web/lang/lang.es-es.json index d9c3bfd3..e56e6bdd 100644 --- a/data/web/lang/lang.es-es.json +++ b/data/web/lang/lang.es-es.json @@ -141,9 +141,11 @@ "empty": "Sin resultados", "excludes": "Excluye a estos destinatarios", "f2b_ban_time": "Tiempo de restricción (s)", + "f2b_ban_time_increment": "Tiempo de restricción se incrementa con cada restricción", "f2b_blacklist": "Redes y hosts en lista negra", "f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. Las actualizaciones de la lista tardarán unos segundos en aplicarse.", "f2b_max_attempts": "Max num. de intentos", + "f2b_max_ban_time": "Max tiempo de restricción (s)", "f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)", "f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)", "f2b_parameters": "Parametros Fail2ban", diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json index 402e66f9..d64f62f7 100644 --- a/data/web/lang/lang.fr-fr.json +++ b/data/web/lang/lang.fr-fr.json @@ -172,11 +172,13 @@ "edit": "Editer", "empty": "Aucun résultat", "excludes": "Exclure ces destinataires", - "f2b_ban_time": "Durée du bannissement(s)", + "f2b_ban_time": "Durée du bannissement (s)", + "f2b_ban_time_increment": "Durée du bannissement est augmentée à chaque bannissement", "f2b_blacklist": "Réseaux/Domaines sur Liste Noire", "f2b_filter": "Filtre(s) Regex", "f2b_list_info": "Un hôte ou un réseau sur liste noire l'emportera toujours sur une entité de liste blanche. L'application des mises à jour de liste prendra quelques secondes.", "f2b_max_attempts": "Nb max. de tentatives", + "f2b_max_ban_time": "Max. durée du bannissement (s)", "f2b_netban_ipv4": "Taille du sous-réseau IPv4 pour l'application du bannissement (8-32)", "f2b_netban_ipv6": "Taille du sous-réseau IPv6 pour l'application du bannissement (8-128)", "f2b_parameters": "Paramètres Fail2ban", diff --git a/data/web/lang/lang.it-it.json b/data/web/lang/lang.it-it.json index d8d6978c..4d21547c 100644 --- a/data/web/lang/lang.it-it.json +++ b/data/web/lang/lang.it-it.json @@ -175,10 +175,12 @@ "empty": "Nessun risultato", "excludes": "Esclude questi destinatari", "f2b_ban_time": "Tempo di blocco (s)", + "f2b_ban_time_increment": "Tempo di blocco aumenta ad ogni blocco", "f2b_blacklist": "Host/reti in blacklist", "f2b_filter": "Filtri Regex", "f2b_list_info": "Un host oppure una rete in blacklist, avrà sempre un peso maggiore rispetto ad una in whitelist. L'aggiornamento della lista richiede alcuni secondi per la sua entrata in azione.", "f2b_max_attempts": "Tentativi massimi", + "f2b_max_ban_time": "Tempo massimo di blocco (s)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_parameters": "Parametri Fail2ban", diff --git a/data/web/lang/lang.nl-nl.json b/data/web/lang/lang.nl-nl.json index 774627ca..4c2ea0b1 100644 --- a/data/web/lang/lang.nl-nl.json +++ b/data/web/lang/lang.nl-nl.json @@ -168,10 +168,12 @@ "empty": "Geen resultaten", "excludes": "Exclusief", "f2b_ban_time": "Verbanningstijd (s)", + "f2b_ban_time_increment": "Verbanningstijd wordt verhoogd met elk verbanning", "f2b_blacklist": "Netwerken/hosts op de blacklist", "f2b_filter": "Regex-filters", "f2b_list_info": "Een host of netwerk op de blacklist staat altijd boven eenzelfde op de whitelist. Het doorvoeren van wijzigingen kan enkele seconden in beslag nemen.", "f2b_max_attempts": "Maximaal aantal pogingen", + "f2b_max_ban_time": "Maximaal verbanningstijd (s)", "f2b_netban_ipv4": "Voer de IPv4-subnetgrootte in waar de verbanning van kracht moet zijn (8-32)", "f2b_netban_ipv6": "Voer de IPv6-subnetgrootte in waar de verbanning van kracht moet zijn (8-128)", "f2b_parameters": "Fail2ban", diff --git a/data/web/templates/admin/tab-config-f2b.twig b/data/web/templates/admin/tab-config-f2b.twig index bbd3e367..c15fb72f 100644 --- a/data/web/templates/admin/tab-config-f2b.twig +++ b/data/web/templates/admin/tab-config-f2b.twig @@ -12,6 +12,14 @@ +
+ + +
+
+ + +
From e010f0814321cafe65be6c0e932df2fb99a2dd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B6rn=20J=C3=B6rger?= Date: Wed, 29 Mar 2023 15:18:11 +0200 Subject: [PATCH 4/5] verify options after loading them, set defaults if options are missing or invalid --- data/Dockerfiles/netfilter/server.py | 35 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 5533b4b3..2bf8b2b4 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -66,30 +66,37 @@ def refreshF2boptions(): global exit_code if not r.get('F2B_OPTIONS'): f2boptions = {} - f2boptions['ban_time'] = int - f2boptions['max_ban_time'] = int - f2boptions['ban_time_increment'] = bool - f2boptions['max_attempts'] = int - f2boptions['retry_window'] = int - f2boptions['netban_ipv4'] = int - f2boptions['netban_ipv6'] = int - f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 - f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') or 10000 - f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') or True - f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 - f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 - f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 32 - f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 128 + f2boptions['ban_time'] = r.get('F2B_BAN_TIME') + f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') + f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') + f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') + f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') + f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') + f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') + verifyF2boptions(f2boptions) r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) else: try: f2boptions = {} f2boptions = json.loads(r.get('F2B_OPTIONS')) + verifyF2boptions(f2boptions) except ValueError: print('Error loading F2B options: F2B_OPTIONS is not json') quit_now = True exit_code = 2 +def verifyF2boptions(f2boptions): + verifyF2boption(f2boptions,'ban_time', 1800) + verifyF2boption(f2boptions,'max_ban_time', 10000) + verifyF2boption(f2boptions,'ban_time_increment', True) + verifyF2boption(f2boptions,'max_attempts', 10) + verifyF2boption(f2boptions,'retry_window', 600) + verifyF2boption(f2boptions,'netban_ipv4', 32) + verifyF2boption(f2boptions,'netban_ipv6', 128) + +def verifyF2boption(f2boptions, f2boption, f2bdefault): + f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault + def refreshF2bregex(): global f2bregex global quit_now From 096e2a41e96c981bccf4629d0df4803d5f756481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B6rn=20J=C3=B6rger?= Date: Wed, 29 Mar 2023 17:09:25 +0200 Subject: [PATCH 5/5] Push verified options to redis after each check --- data/Dockerfiles/netfilter/server.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 2bf8b2b4..d0c652e2 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -64,8 +64,10 @@ def refreshF2boptions(): global f2boptions global quit_now global exit_code + + f2boptions = {} + if not r.get('F2B_OPTIONS'): - f2boptions = {} f2boptions['ban_time'] = r.get('F2B_BAN_TIME') f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') @@ -73,18 +75,17 @@ def refreshF2boptions(): f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') - verifyF2boptions(f2boptions) - r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) else: try: - f2boptions = {} f2boptions = json.loads(r.get('F2B_OPTIONS')) - verifyF2boptions(f2boptions) except ValueError: print('Error loading F2B options: F2B_OPTIONS is not json') quit_now = True exit_code = 2 + verifyF2boptions(f2boptions) + r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) + def verifyF2boptions(f2boptions): verifyF2boption(f2boptions,'ban_time', 1800) verifyF2boption(f2boptions,'max_ban_time', 10000)