#!/usr/bin/env python3 import os import time import pickle import ipaddress import re import subprocess import sys import signal import fcntl import traceback from datetime import datetime, timedelta from collections import defaultdict, deque LOCK_FILE = '/var/run/spamblock.lock' def check_single_instance(): global lock_fd try: lock_fd = open(LOCK_FILE, 'w') fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) lock_fd.write(str(os.getpid())) lock_fd.flush() except IOError: print("Outra instância do script já está em execução!") sys.exit(1) check_single_instance() terminating = False def handle_sigterm(signum, frame): global terminating if terminating: return terminating = True log_info("Sinal SIGTERM recebido. Salvando estado e encerrando...") save_state() sys.exit(0) signal.signal(signal.SIGTERM, handle_sigterm) # -------------------- CONFIGURAÇÕES -------------------- LOG_FILE = "/usr/local/assp/maillog.txt" STATE_FILE = "/root/include/state.pkl" WHITELIST_FILE = "/root/include/whitelist.txt" WHITEEXP_FILE = "/root/include/whiteexp.txt" HOURS = 12 DELTA_HOURS = timedelta(hours=HOURS) expressions_config = [ {"expr": "forwarded to", "limit": 4, "conversion_threshold": 2, "conversion_mask": 24}, {"expr": "InvalidAddress", "limit": 2, "conversion_threshold": 2, "conversion_mask": 24}, {"expr": "preHeaderRe", "limit": 1, "conversion_threshold": 2, "conversion_mask": 24}, # {"expr": "RelayAttempt", "limit": 2, "conversion_threshold": 2, "conversion_mask": 24}, {"expr": "DenyStrict", "limit": 2, "conversion_threshold": 2, "conversion_mask": 24}, ] whitelist = [] whiteexp_list = [] whitelist_timestamp = 0 whiteexp_timestamp = 0 DEBUG_MODE = True DEBUG_LOG_FILE = "/root/include/debug.log" FLUSH_INTERVAL = 60 # -------------------- FIM CONFIGURAÇÕES -------------------- # -------------------- VARIÁVEIS DE ESTATÍSTICAS -------------------- stats_expr = {cfg["expr"]: {"ips": 0, "networks": 0} for cfg in expressions_config} flush_stats_expr = {cfg["expr"]: {"ips": 0, "networks": 0} for cfg in expressions_config} stats_whitelist = 0 stats_whiteexp = 0 blocked_ips_count = 0 blocked_redes_count = 0 blocked_total_ips = 0 oldest_block_time = None newest_block_time = None flush_ips = 0 flush_redes = 0 flush_whitelist = 0 flush_whiteexp = 0 stats_conversion_ignored = 0 flush_conversion_ignored = 0 stats_whitelist_removed_ips = 0 stats_whitelist_removed_networks = 0 flush_whitelist_removed_ips = 0 flush_whitelist_removed_networks = 0 # -------------------- FIM DAS VARIÁVEIS DE ESTATÍSTICAS -------------------- # -------------------- FUNÇÕES DE LOG -------------------- def log_info(msg): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") try: print(f"[INFO] {now} {msg}", flush=True, file=sys.stdout) except Exception as e: print(f"[ERROR] Falha ao exibir log_info: {e}", flush=True, file=sys.stderr) def log_csf(msg): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[CSF] {now} {msg}", flush=True) def log_debug(msg): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[DEBUG] {now} {msg}", flush=True) def write_debug_log(msg): if DEBUG_MODE: try: with open(DEBUG_LOG_FILE, "a") as f: f.write(f"{msg}\n\n") # Restaura duas quebras de linha except Exception as e: print(f"[DEBUG] Erro ao escrever no debug.log: {e}", flush=True) # -------------------- FIM DAS FUNÇÕES DE LOG -------------------- # -------------------- FUNÇÕES PARA DEFAULTDICT -------------------- def default_deque(): return deque() def make_occurrences(): return defaultdict(default_deque) def default_set(): return set() def make_suspect_subnet(): return defaultdict(default_set) def make_lines_dict(): return defaultdict(list) # -------------------- FIM DAS FUNÇÕES PARA DEFAULTDICT -------------------- # -------------------- VARIÁVEIS GLOBAIS -------------------- occurrences = defaultdict(make_occurrences) debug_lines = defaultdict(make_lines_dict) global_suspect = defaultdict(set) global_filters = defaultdict(set) blocked = set() # -------------------- FIM VARIÁVEIS GLOBAIS -------------------- # -------------------- FUNÇÕES DE PERSISTÊNCIA -------------------- def save_state(): state = { "occurrences": occurrences, "blocked": blocked, "global_suspect": dict(global_suspect), "global_filters": dict(global_filters), "stats_whitelist": stats_whitelist, "stats_whiteexp": stats_whiteexp, "stats_conversion_ignored": stats_conversion_ignored, "whitelist": whitelist, "whiteexp_list": whiteexp_list, "whitelist_timestamp": whitelist_timestamp, "whiteexp_timestamp": whiteexp_timestamp, "stats_whitelist_removed_ips": stats_whitelist_removed_ips, "stats_whitelist_removed_networks": stats_whitelist_removed_networks, } try: with open(STATE_FILE, "wb") as f: pickle.dump(state, f) print(f"[STATE] Estado salvo em {STATE_FILE}", flush=True) except Exception as e: print(f"[STATE] Erro ao salvar estado: {e}", flush=True) print_memory_usage() def load_state(): global occurrences, blocked, global_suspect, global_filters, stats_whitelist, stats_whiteexp, stats_conversion_ignored, whitelist, whiteexp_list, whitelist_timestamp, whiteexp_timestamp, stats_whitelist_removed_ips, stats_whitelist_removed_networks if os.path.exists(STATE_FILE): try: if os.path.getsize(STATE_FILE) == 0: print("[STATE] Arquivo de estado vazio. Iniciando com estado vazio.", flush=True) return with open(STATE_FILE, "rb") as f: state = pickle.load(f) occurrences.update(state.get("occurrences", {})) blocked.update(state.get("blocked", set())) global_suspect.update(state.get("global_suspect", {})) global_filters.update(state.get("global_filters", {})) stats_whitelist = state.get("stats_whitelist", 0) stats_whiteexp = state.get("stats_whiteexp", 0) stats_conversion_ignored = state.get("stats_conversion_ignored", 0) whitelist = state.get("whitelist", []) whiteexp_list = state.get("whiteexp_list", []) whitelist_timestamp = state.get("whitelist_timestamp", 0) whiteexp_timestamp = state.get("whiteexp_timestamp", 0) stats_whitelist_removed_ips = state.get("stats_whitelist_removed_ips", 0) stats_whitelist_removed_networks = state.get("stats_whitelist_removed_networks", 0) prune_entire_state() print(f"[STATE] Estado carregado de {STATE_FILE}", flush=True) except EOFError: print("[STATE] Arquivo de estado vazio (EOFError). Iniciando com estado vazio.", flush=True) except Exception as e: print(f"[STATE] Falha ao carregar estado: {e}", flush=True) def prune_entire_state(): now = datetime.now() for expr, ip_map in occurrences.items(): for ip, dq in ip_map.items(): prune_old(dq, now, expr, ip) def prune_old(deque_timestamps, current_time, expr, ip): cutoff = current_time - DELTA_HOURS while deque_timestamps and deque_timestamps[0] < cutoff: deque_timestamps.popleft() if debug_lines[expr][ip]: debug_lines[expr][ip].pop(0) # -------------------- FIM FUNÇÕES DE PERSISTÊNCIA -------------------- # -------------------- FUNÇÕES AUXILIARES -------------------- def print_memory_usage(): total_records = 0 total_size = 0 for expr, ip_map in occurrences.items(): for ip, dq in ip_map.items(): total_records += len(dq) total_size += sys.getsizeof(dq) total_size_mb = total_size / (1024 * 1024) print(f"[MEMÓRIA] Total de registros não pruned: {total_records}", flush=True) print(f"[MEMÓRIA] Tamanho estimado: {total_size_mb:.2f} MB", flush=True) def check_file_timestamp(file_path, last_timestamp): try: current_timestamp = os.path.getmtime(file_path) return current_timestamp != last_timestamp except FileNotFoundError: return False def count_occurrences_entries(): total = 0 for expr, ip_map in occurrences.items(): for ip, dq in ip_map.items(): total += len(dq) return total def load_whitelist(): global whitelist, whitelist_timestamp whitelist = [] seen_networks = set() try: with open(WHITELIST_FILE, "r") as f: for line in f: line = line.strip() if not line: continue parts = line.split("#", 1) entry = parts[0].strip() comment = parts[1].strip() if len(parts) > 1 else "" if not entry: continue try: network = ipaddress.ip_network(entry if '/' in entry else entry + "/32", strict=False) if network in seen_networks: log_info(f"Duplicata ignorada em whitelist: {entry}") continue seen_networks.add(network) whitelist.append((network, comment)) except Exception: print(f"[WHITELIST] Entrada inválida ignorada: {entry}", flush=True) continue num_entries = len(whitelist) total_ips = sum(net.num_addresses for net, _ in whitelist) ips_count = sum(1 for net, _ in whitelist if net.prefixlen == 32) redes_count = sum(1 for net, _ in whitelist if net.prefixlen != 32) whitelist_timestamp = os.path.getmtime(WHITELIST_FILE) print(f"[WHITELIST] Carregada whitelist: {num_entries} entradas sendo IPS: {ips_count} + Redes: {redes_count}, totalizando {total_ips} IPS em whitelist.", flush=True) except Exception as e: log_info(f"Erro ao carregar whitelist de {WHITELIST_FILE}: {e}") if DEBUG_MODE: log_debug(f"Stack trace: {''.join(traceback.format_exc())}") whitelist = [] def load_whiteexp(): global whiteexp_list, whiteexp_timestamp whiteexp_list = [] seen_expressions = set() try: with open(WHITEEXP_FILE, "r") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue if line in seen_expressions: log_info(f"Duplicata ignorada em whiteexp: {line}") continue seen_expressions.add(line) whiteexp_list.append(line) whiteexp_timestamp = os.path.getmtime(WHITEEXP_FILE) print(f"[WHITEEXP] Carregadas {len(whiteexp_list)} expressões de exceção.", flush=True) except Exception as e: log_info(f"Erro ao carregar expressões: {e}") if DEBUG_MODE: log_debug(f"Stack trace: {''.join(traceback.format_exc())}") whiteexp_list = [] def get_whiteexp_match(line): for exp in whiteexp_list: if exp.lower() in line.lower(): return exp return None def ip_in_whitelist(ip_str): try: ip_obj = ipaddress.ip_address(ip_str) except ValueError: return False for net, _ in whitelist: try: if ip_obj in net: return True except Exception: continue return False def get_whitelist_comment(ip_str): try: ip_obj = ipaddress.ip_address(ip_str) except ValueError: return "" for net, comment in whitelist: try: if ip_obj in net: return comment except Exception: continue return "" def sync_blocked(): global blocked, blocked_ips_count, blocked_redes_count, blocked_total_ips, oldest_block_time, newest_block_time new_blocked = set() ips_count = 0 redes_count = 0 total_ips = 0 oldest_time = None newest_time = None deny_file = "/etc/csf/csf.deny" try: with open(deny_file, "r") as f: for line in f: if "SPAMBLOCK" in line: parts = line.split() if parts: entry = parts[0] new_blocked.add(entry) try: net_obj = ipaddress.ip_network(entry, strict=False) total_ips += net_obj.num_addresses if net_obj.prefixlen == 32: ips_count += 1 else: redes_count += 1 except Exception: total_ips += 1 ips_count += 1 timestamp_match = re.search(r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", line) if timestamp_match: timestamp = datetime.strptime(timestamp_match.group(1), "%Y-%m-%d %H:%M:%S") if oldest_time is None or timestamp < oldest_time: oldest_time = timestamp if newest_time is None or timestamp > newest_time: newest_time = timestamp blocked = new_blocked blocked_ips_count = ips_count blocked_redes_count = redes_count blocked_total_ips = total_ips oldest_block_time = oldest_time newest_block_time = newest_time log_info("Sincronização de 'blocked' concluída.") except Exception as e: log_info(f"Erro ao sincronizar 'blocked': {e}") def calculate_active_block_stats(): global stats_expr stats_expr = {cfg["expr"]: {"ips": 0, "networks": 0} for cfg in expressions_config} deny_file = "/etc/csf/csf.deny" try: with open(deny_file, "r") as f: for line in f: if "SPAMBLOCK" in line: parts = line.split() if not parts: continue entry = parts[0] comment = " ".join(parts[1:]) if len(parts) > 1 else "" exprs = re.findall(r"\[(.*?)\]", comment) if not exprs: continue expr_list = exprs[0].split(" / ") if "/" in entry: for expr in expr_list: if expr in stats_expr: stats_expr[expr]["networks"] += 1 else: for expr in expr_list: if expr in stats_expr: stats_expr[expr]["ips"] += 1 except Exception as e: log_info(f"Erro ao calcular estatísticas ativas: {e}") def print_stats(): print("[STATS] Estatísticas dos bloqueios ativos (após sincronização):") if oldest_block_time and newest_block_time: time_diff = newest_block_time - oldest_block_time hours_diff = time_diff.total_seconds() / 3600 total_entries = blocked_ips_count + blocked_redes_count if hours_diff > 0: blocks_per_hour = total_entries / hours_diff time_diff_str = str(time_diff) print(f" Intervalo de IP/Rede bloqueados: {oldest_block_time.strftime('%Y-%m-%d %H:%M:%S')} - {newest_block_time.strftime('%Y-%m-%d %H:%M:%S')} (Duração: {time_diff_str}, Taxa: {blocks_per_hour:.2f} bloqueios/hora)") else: print(f" Intervalo de IP/Rede bloqueados: {oldest_block_time.strftime('%Y-%m-%d %H:%M:%S')} - {newest_block_time.strftime('%Y-%m-%d %H:%M:%S')} (Duração: 0s, Taxa: N/A bloqueios/hora)") else: print(" Intervalo de IP/Rede bloqueados: Nenhum bloqueio registrado.") for expr, counts in stats_expr.items(): flush_counts = flush_stats_expr[expr] print(f" {expr}: {counts['ips']} IP(s) individuais (total), {flush_counts['ips']} neste flush e {counts['networks']} rede(s) (total), {flush_counts['networks']} neste flush.") print(f" Whitelist: {stats_whitelist} IP(s) ignorados (total), {flush_whitelist} neste flush.") print(f" Whiteexp: {stats_whiteexp} IP(s) ignorados (total), {flush_whiteexp} neste flush.") print(f" Total Bloqueios: {blocked_ips_count} IP(s) individuais (total), {flush_ips} neste flush e {blocked_redes_count} rede(s) (total), {flush_redes} neste flush.") print("\n[RESUMO] Performance nas últimas 12 horas:") total_events = count_occurrences_entries() events_per_hour = total_events / HOURS if HOURS > 0 else 0 print(f" Total de eventos registrados: {total_events}") print(f" Taxa de eventos por hora: {events_per_hour:.2f}") print(f" IPs bloqueados: {blocked_ips_count}") print(f" Redes bloqueadas: {blocked_redes_count}") print(f" IPs ignorados por whitelist: {stats_whitelist}") print(f" IPs ignorados por whiteexp: {stats_whiteexp}") print(f" IPs excluídos do bloqueio por whitelist: {stats_whitelist_removed_ips} (total), {flush_whitelist_removed_ips} neste flush") print(f" Redes excluídas do bloqueio por whitelist: {stats_whitelist_removed_networks} (total), {flush_whitelist_removed_networks} neste flush") print(f" Conversão de redes ignoradas: {stats_conversion_ignored} (total), {flush_conversion_ignored} neste flush") # -------------------- FIM FUNÇÕES AUXILIARES -------------------- # -------------------- FUNÇÕES DE EXECUÇÃO DO CSF -------------------- def run_csf_d(entry, comment=None): try: if comment: subprocess.run(["csf", "-d", entry, comment], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) log_csf(f"Bloqueado: {entry} com comentário: {comment}") else: subprocess.run(["csf", "-d", entry], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) log_csf(f"Bloqueado: {entry}") except subprocess.CalledProcessError as e: log_csf(f"Erro ao bloquear {entry}: {e}") def run_csf_dr(entry): try: subprocess.run(["csf", "-dr", entry], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) log_csf(f"Desbloqueado: {entry}") except subprocess.CalledProcessError as e: log_csf(f"Erro ao desbloquear {entry}: {e}") def unblock_whitelisted_entries(): global blocked, blocked_ips_count, blocked_redes_count, blocked_total_ips, stats_whitelist_removed_ips, stats_whitelist_removed_networks, flush_whitelist_removed_ips, flush_whitelist_removed_networks deny_file = "/etc/csf/csf.deny" entries_to_remove = [] try: with open(deny_file, "r") as f: lines = f.readlines() for line in lines: if "SPAMBLOCK" not in line: continue parts = line.split() if not parts: continue entry = parts[0] comment = " ".join(parts[1:]) if len(parts) > 1 else "" try: blocked_net = ipaddress.ip_network(entry, strict=False) if '/' in entry: # É uma rede for whitelist_net, wl_comment in whitelist: if blocked_net.overlaps(whitelist_net): wl_entry = str(whitelist_net.with_prefixlen if whitelist_net.prefixlen < 32 else whitelist_net.network_address) reason = f"Conflito com whitelist {wl_entry}" + (f" #{wl_comment}" if wl_comment else "") entries_to_remove.append((entry, reason)) break else: # É um IP individual if ip_in_whitelist(entry): wl_comment = get_whitelist_comment(entry) reason = f"IP na whitelist {entry}" + (f" #{wl_comment}" if wl_comment else "") entries_to_remove.append((entry, reason)) except ValueError as e: log_info(f"Erro ao processar entrada {entry}: {e}") continue if entries_to_remove: debug_lines = [] for entry, reason in entries_to_remove: if entry in blocked: run_csf_dr(entry) blocked.remove(entry) log_msg = f"Entrada {entry} removida do bloqueio: {reason}" log_info(log_msg) debug_lines.append(f"#{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {log_msg}") try: net_obj = ipaddress.ip_network(entry, strict=False) if net_obj.prefixlen == 32: blocked_ips_count -= 1 blocked_total_ips -= 1 stats_whitelist_removed_ips += 1 flush_whitelist_removed_ips += 1 else: blocked_redes_count -= 1 blocked_total_ips -= net_obj.num_addresses stats_whitelist_removed_networks += 1 flush_whitelist_removed_networks += 1 except ValueError: blocked_ips_count -= 1 blocked_total_ips -= 1 stats_whitelist_removed_ips += 1 flush_whitelist_removed_ips += 1 log_msg = f"Total de {len(entries_to_remove)} entradas desbloqueadas por estarem na whitelist" log_info(log_msg) debug_lines.append(f"#{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {log_msg}") if DEBUG_MODE: write_debug_log("\n".join(debug_lines)) calculate_active_block_stats() except Exception as e: log_info(f"Erro ao verificar entradas bloqueadas contra whitelist: {e}") # -------------------- FIM FUNÇÕES DE EXECUÇÃO DO CSF -------------------- # -------------------- FUNÇÕES DE PROCESSAMENTO -------------------- def process_line(line): m = re.search(r"([A-Za-z]{3}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*?(\d+\.\d+\.\d+\.\d+)", line) if not m: return timestamp_str, ip = m.groups() try: timestamp = datetime.strptime(timestamp_str, "%b-%d-%y %H:%M:%S") except Exception: return if timestamp < datetime.now() - DELTA_HOURS: return for item in expressions_config: expr_str = item["expr"] limit = item["limit"] if expr_str in line: dq = occurrences[expr_str][ip] prune_old(dq, timestamp, expr_str, ip) dq.append(timestamp) if len(debug_lines[expr_str][ip]) < limit: debug_lines[expr_str][ip].append(line.strip()) if len(dq) >= limit: whiteexp_match = get_whiteexp_match(line) if whiteexp_match: global stats_whiteexp, flush_whiteexp stats_whiteexp += 1 flush_whiteexp += 1 log_info(f"IP {ip} atingiu o limite [{expr_str}] mas está na whiteexp [{whiteexp_match}], ignorando bloqueio.") if DEBUG_MODE: current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") debug_msg = f"#{current_time} ip {ip} ignorado por whiteexp [{whiteexp_match}]\n{line.strip()}" write_debug_log(debug_msg) return if ip_in_whitelist(ip): global stats_whitelist, flush_whitelist stats_whitelist += 1 flush_whitelist += 1 comment_whitelist = get_whitelist_comment(ip) comment_str = f" #{comment_whitelist}" if comment_whitelist else "" log_info(f"IP {ip} atingiu o limite [{expr_str}] mas está na whitelist{comment_str}, ignorando bloqueio.") if DEBUG_MODE: current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment_whitelist = get_whitelist_comment(ip) comment_str = f" #{comment_whitelist}" if comment_whitelist else "" debug_msg = f"#{current_time} ip {ip} [{expr_str}] ignorado por whitelist{comment_str}" write_debug_log(debug_msg) return else: block_decision(expr_str, ip, line) break def block_decision(expr_str, ip, original_line): global flush_ips, flush_redes, flush_stats_expr, stats_conversion_ignored, flush_conversion_ignored expr_config = next((cfg for cfg in expressions_config if cfg["expr"] == expr_str), {}) conversion_mask = expr_config.get("conversion_mask", 24) conversion_threshold = expr_config.get("conversion_threshold", 2) net = str(ipaddress.ip_network(f"{ip}/{conversion_mask}", strict=False)) if net in blocked: return global_suspect[net].add(ip) global_filters[net].add(expr_str) current_ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") net_obj = ipaddress.ip_network(net, strict=False) whitelist_overlap = any(ip_in_whitelist(str(ip_addr)) for ip_addr in net_obj) if len(global_suspect[net]) >= conversion_threshold: if whitelist_overlap: log_info(f"Conversão para rede {net} abortada: sub-rede contém IP(s) em whitelist. Mantendo bloqueios individuais.") stats_conversion_ignored += 1 flush_conversion_ignored += 1 if ip not in blocked: comment = f"{current_ts} [{expr_str}] SPAMBLOCK" run_csf_d(ip, comment) blocked.add(ip) flush_ips += 1 stats_expr[expr_str]["ips"] += 1 flush_stats_expr[expr_str]["ips"] += 1 if DEBUG_MODE: debug_msg = f"#{current_ts} bloqueio individual mantido ip {ip} [{expr_str}] - conversão abortada por whitelist em {net}" if ip in debug_lines[expr_str]: debug_msg += "\n" + "\n".join(debug_lines[expr_str][ip]) write_debug_log(debug_msg) if ip in debug_lines[expr_str]: del debug_lines[expr_str][ip] if DEBUG_MODE: debug_msg = f"#{current_ts} conversao abortada para {net} - IPs suspeitos: {', '.join(sorted(global_suspect[net]))} - Filtro(s): {' / '.join(sorted(global_filters[net]))} - Sub-rede contém IP em whitelist" write_debug_log(debug_msg) else: log_info(f"IP {ip} disparou a conversão para bloqueio em rede {net}") combined_filters = " / ".join(sorted(global_filters[net])) comment = f"{current_ts} [{combined_filters}] SPAMBLOCK" removed_ips = [] for ip_ind in list(global_suspect[net]): if ip_ind in blocked: run_csf_dr(ip_ind) removed_ips.append(ip_ind) blocked.remove(ip_ind) if removed_ips: log_debug(f"Removendo bloqueios individuais para os IPs: {', '.join(removed_ips)}") run_csf_d(net, comment) blocked.add(net) flush_redes += 1 for exp in sorted(global_filters[net]): stats_expr[exp]["networks"] += 1 flush_stats_expr[exp]["networks"] += 1 if DEBUG_MODE: debug_msg = f"#{current_ts} conversao ip {ip} em rede {net} [{combined_filters}]" for exp in sorted(global_filters[net]): if ip in debug_lines[exp]: debug_msg += "\n" + "\n".join(debug_lines[exp][ip]) write_debug_log(debug_msg) for exp in sorted(global_filters[net]): if ip in debug_lines[exp]: del debug_lines[exp][ip] else: if ip not in blocked: comment = f"{current_ts} [{expr_str}] SPAMBLOCK" run_csf_d(ip, comment) blocked.add(ip) flush_ips += 1 stats_expr[expr_str]["ips"] += 1 flush_stats_expr[expr_str]["ips"] += 1 if DEBUG_MODE: debug_msg = f"#{current_ts} bloqueio ip {ip} [{expr_str}]" if ip in debug_lines[expr_str]: debug_msg += "\n" + "\n".join(debug_lines[expr_str][ip]) write_debug_log(debug_msg) if ip in debug_lines[expr_str]: del debug_lines[expr_str][ip] # -------------------- FIM FUNÇÕES DE PROCESSAMENTO -------------------- # -------------------- FUNÇÕES DE STATUS -------------------- def update_status(): global whitelist_timestamp, whiteexp_timestamp, flush_ips, flush_redes, flush_whitelist, flush_whiteexp, flush_stats_expr, flush_conversion_ignored, flush_whitelist_removed_ips, flush_whitelist_removed_networks flush_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print("=========================") print(f"FLUSH {flush_time}") print("=========================") if check_file_timestamp(WHITELIST_FILE, whitelist_timestamp): load_whitelist() whitelist_timestamp = os.path.getmtime(WHITELIST_FILE) if check_file_timestamp(WHITEEXP_FILE, whiteexp_timestamp): load_whiteexp() whiteexp_timestamp = os.path.getmtime(WHITEEXP_FILE) sync_blocked() unblock_whitelisted_entries() calculate_active_block_stats() print(f"[SPAMBLOCK COUNT] {blocked_ips_count + blocked_redes_count} entradas sendo IPS: {blocked_ips_count} + Redes: {blocked_redes_count} em csf.deny, totalizando {blocked_total_ips} IPs bloqueados.") print_stats() print() flush_ips = 0 flush_redes = 0 flush_whitelist = 0 flush_whiteexp = 0 flush_conversion_ignored = 0 flush_whitelist_removed_ips = 0 flush_whitelist_removed_networks = 0 flush_stats_expr = {cfg["expr"]: {"ips": 0, "networks": 0} for cfg in expressions_config} save_state() def follow_log_rotate(path): f = open(path, "r") f.seek(0, os.SEEK_END) current_inode = os.fstat(f.fileno()).st_ino last_status = time.time() while True: line = f.readline() if line: yield line else: try: current_size = os.stat(path).st_size except FileNotFoundError: time.sleep(1) continue if f.tell() > current_size: log_info(f"Arquivo truncado (copytruncate). Reposicionando {path}") f.seek(0, os.SEEK_SET) try: if os.stat(path).st_ino != current_inode: log_info(f"Logrotate detectado, reabrindo {path}") f.close() f = open(path, "r") current_inode = os.fstat(f.fileno()).st_ino f.seek(0, os.SEEK_END) except FileNotFoundError: time.sleep(1) continue if time.time() - last_status >= FLUSH_INTERVAL: update_status() last_status = time.time() time.sleep(1) # -------------------- FIM FUNÇÕES DE STATUS -------------------- def main(): if len(sys.argv) > 1 and sys.argv[1].lower() == "clear": if os.path.exists(STATE_FILE): try: os.remove(STATE_FILE) print(f"[STATE] Arquivo de estado {STATE_FILE} removido. Iniciando com estado zerado.") except Exception as e: print(f"[STATE] Erro ao remover o arquivo de estado: {e}") load_state() load_whitelist() load_whiteexp() sync_blocked() print(f"[SPAMBLOCK COUNT] {blocked_ips_count + blocked_redes_count} entradas sendo IPS: {blocked_ips_count} + Redes: {blocked_redes_count} em csf.deny, totalizando {blocked_total_ips} IPs bloqueados.") save_state() log_info(f"Monitorando em tempo real: {LOG_FILE}") try: for line in follow_log_rotate(LOG_FILE): process_line(line) except KeyboardInterrupt: print("\n[INFO] Interrupção detectada (Ctrl+C). Salvando estado...") save_state() print("[INFO] Encerrando o script.") sys.exit(0) if __name__ == "__main__": main()