upload
This commit is contained in:
		
							
								
								
									
										63
									
								
								backends/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								backends/base.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| """ | ||||
| Bazowa klasa dla backendów firewall | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
|  | ||||
| class FirewallBackend: | ||||
|     """Bazowa klasa dla backendów firewall""" | ||||
|      | ||||
|     def __init__(self, config): | ||||
|         """ | ||||
|         Args: | ||||
|             config: ConfigParser object z konfiguracją | ||||
|         """ | ||||
|         self.config = config | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|          | ||||
|     def ban_ip(self, ip, duration): | ||||
|         """ | ||||
|         Banuje IP na określony czas | ||||
|          | ||||
|         Args: | ||||
|             ip: Adres IP do zbanowania | ||||
|             duration: Czas bana w sekundach | ||||
|              | ||||
|         Returns: | ||||
|             bool: True jeśli ban się powiódł | ||||
|         """ | ||||
|         raise NotImplementedError("Subclasses must implement ban_ip()") | ||||
|          | ||||
|     def unban_ip(self, ip): | ||||
|         """ | ||||
|         Usuwa ban dla IP | ||||
|          | ||||
|         Args: | ||||
|             ip: Adres IP do odbanowania | ||||
|              | ||||
|         Returns: | ||||
|             bool: True jeśli odbanowanie się powiodło | ||||
|         """ | ||||
|         raise NotImplementedError("Subclasses must implement unban_ip()") | ||||
|          | ||||
|     def is_banned(self, ip): | ||||
|         """ | ||||
|         Sprawdza czy IP jest zbanowany | ||||
|          | ||||
|         Args: | ||||
|             ip: Adres IP do sprawdzenia | ||||
|              | ||||
|         Returns: | ||||
|             bool: True jeśli IP jest zbanowany | ||||
|         """ | ||||
|         raise NotImplementedError("Subclasses must implement is_banned()") | ||||
|          | ||||
|     def test_availability(self): | ||||
|         """ | ||||
|         Sprawdza czy backend jest dostępny w systemie | ||||
|          | ||||
|         Returns: | ||||
|             bool: True jeśli backend jest dostępny | ||||
|         """ | ||||
|         return True | ||||
							
								
								
									
										102
									
								
								backends/csf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								backends/csf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| """ | ||||
| Backend dla ConfigServer Security & Firewall (CSF) | ||||
| """ | ||||
|  | ||||
| import subprocess | ||||
| from pathlib import Path | ||||
| from .base import FirewallBackend | ||||
|  | ||||
|  | ||||
| class CSFBackend(FirewallBackend): | ||||
|     """Backend dla ConfigServer Firewall""" | ||||
|      | ||||
|     def __init__(self, config): | ||||
|         super().__init__(config) | ||||
|         self.csf_path = config.get('backend_csf', 'csf_path',  | ||||
|                                    fallback='/usr/sbin/csf') | ||||
|          | ||||
|         if not self.test_availability(): | ||||
|             self.logger.warning(f"CSF not found at {self.csf_path}") | ||||
|              | ||||
|     def test_availability(self): | ||||
|         """Sprawdza czy CSF jest zainstalowany""" | ||||
|         return Path(self.csf_path).exists() | ||||
|          | ||||
|     def ban_ip(self, ip, duration): | ||||
|         """ | ||||
|         Banuje IP używając CSF | ||||
|          | ||||
|         CSF używa: | ||||
|         - csf -d IP "comment" - permanent deny | ||||
|         - csf -td IP duration "comment" - temporary deny | ||||
|         """ | ||||
|         try: | ||||
|             # Temporary deny na określony czas (w sekundach) | ||||
|             cmd = [ | ||||
|                 self.csf_path, '-td', ip,  | ||||
|                 str(duration),  | ||||
|                 f"LogMon auto-ban" | ||||
|             ] | ||||
|              | ||||
|             result = subprocess.run( | ||||
|                 cmd,  | ||||
|                 capture_output=True,  | ||||
|                 text=True, | ||||
|                 timeout=10 | ||||
|             ) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"CSF ban successful: {result.stdout.strip()}") | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.error(f"CSF ban failed: {result.stderr.strip()}") | ||||
|                 return False | ||||
|                  | ||||
|         except subprocess.TimeoutExpired: | ||||
|             self.logger.error(f"CSF ban command timed out for {ip}") | ||||
|             return False | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error banning IP with CSF: {e}") | ||||
|             return False | ||||
|              | ||||
|     def unban_ip(self, ip): | ||||
|         """Usuwa ban używając CSF""" | ||||
|         try: | ||||
|             # Remove temporary ban | ||||
|             cmd = [self.csf_path, '-tr', ip] | ||||
|             result = subprocess.run( | ||||
|                 cmd,  | ||||
|                 capture_output=True,  | ||||
|                 text=True, | ||||
|                 timeout=10 | ||||
|             ) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"CSF unban successful for {ip}") | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.warning(f"CSF unban may have failed: {result.stderr.strip()}") | ||||
|                 # CSF czasem zwraca error nawet gdy się udało | ||||
|                 return True | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error unbanning IP with CSF: {e}") | ||||
|             return False | ||||
|              | ||||
|     def is_banned(self, ip): | ||||
|         """Sprawdza czy IP jest zbanowany w CSF""" | ||||
|         try: | ||||
|             cmd = [self.csf_path, '-g', ip] | ||||
|             result = subprocess.run( | ||||
|                 cmd,  | ||||
|                 capture_output=True,  | ||||
|                 text=True, | ||||
|                 timeout=10 | ||||
|             ) | ||||
|              | ||||
|             output = result.stdout.lower() | ||||
|             return "deny" in output or "drop" in output | ||||
|              | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error checking ban status: {e}") | ||||
|             return False | ||||
							
								
								
									
										17
									
								
								backends/init.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								backends/init.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| """ | ||||
| LogMon Backends - Integracje z różnymi firewallami | ||||
| """ | ||||
|  | ||||
| from .base import FirewallBackend | ||||
| from .csf import CSFBackend | ||||
| from .nftables import NFTablesBackend | ||||
| from .iptables import IPTablesBackend | ||||
| from .ufw import UFWBackend | ||||
|  | ||||
| __all__ = [ | ||||
|     'FirewallBackend', | ||||
|     'CSFBackend', | ||||
|     'NFTablesBackend', | ||||
|     'IPTablesBackend', | ||||
|     'UFWBackend' | ||||
| ] | ||||
							
								
								
									
										104
									
								
								backends/iptables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								backends/iptables.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| """ | ||||
| Backend dla iptables | ||||
| """ | ||||
|  | ||||
| import subprocess | ||||
| from .base import FirewallBackend | ||||
|  | ||||
|  | ||||
| class IPTablesBackend(FirewallBackend): | ||||
|     """Backend dla iptables""" | ||||
|      | ||||
|     def __init__(self, config): | ||||
|         super().__init__(config) | ||||
|         self.chain = config.get('backend_iptables', 'chain_name',  | ||||
|                                fallback='LOGMON_BLOCK') | ||||
|          | ||||
|         if self.test_availability(): | ||||
|             self.setup_chain() | ||||
|         else: | ||||
|             self.logger.warning("iptables not available") | ||||
|              | ||||
|     def test_availability(self): | ||||
|         """Sprawdza czy iptables jest dostępny""" | ||||
|         try: | ||||
|             result = subprocess.run( | ||||
|                 ['iptables', '--version'],  | ||||
|                 capture_output=True, | ||||
|                 timeout=5 | ||||
|             ) | ||||
|             return result.returncode == 0 | ||||
|         except: | ||||
|             return False | ||||
|              | ||||
|     def setup_chain(self): | ||||
|         """Tworzy chain jeśli nie istnieje""" | ||||
|         try: | ||||
|             # Sprawdź czy chain istnieje | ||||
|             cmd = ['iptables', '-L', self.chain, '-n'] | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode != 0: | ||||
|                 # Utwórz chain | ||||
|                 subprocess.run(['iptables', '-N', self.chain], check=True, timeout=5) | ||||
|                  | ||||
|                 # Dodaj do INPUT na początku | ||||
|                 subprocess.run( | ||||
|                     ['iptables', '-I', 'INPUT', '1', '-j', self.chain],  | ||||
|                     check=True,  | ||||
|                     timeout=5 | ||||
|                 ) | ||||
|                  | ||||
|                 self.logger.info(f"Created iptables chain: {self.chain}") | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error setting up iptables chain: {e}") | ||||
|              | ||||
|     def ban_ip(self, ip, duration): | ||||
|         """Banuje IP używając iptables""" | ||||
|         try: | ||||
|             cmd = [ | ||||
|                 'iptables', '-I', self.chain, '1', | ||||
|                 '-s', ip, '-j', 'DROP', | ||||
|                 '-m', 'comment', '--comment', f'LogMon ban {duration}s' | ||||
|             ] | ||||
|              | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"iptables ban successful for {ip}") | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.error(f"iptables ban failed: {result.stderr.decode()}") | ||||
|                 return False | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error banning IP with iptables: {e}") | ||||
|             return False | ||||
|              | ||||
|     def unban_ip(self, ip): | ||||
|         """Usuwa ban używając iptables""" | ||||
|         try: | ||||
|             cmd = ['iptables', '-D', self.chain, '-s', ip, '-j', 'DROP'] | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"iptables unban successful for {ip}") | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.warning(f"iptables unban may have failed") | ||||
|                 return False | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error unbanning IP with iptables: {e}") | ||||
|             return False | ||||
|              | ||||
|     def is_banned(self, ip): | ||||
|         """Sprawdza czy IP jest zbanowany""" | ||||
|         try: | ||||
|             cmd = ['iptables', '-L', self.chain, '-n', '--line-numbers'] | ||||
|             result = subprocess.run(cmd, capture_output=True, text=True, timeout=5) | ||||
|             return ip in result.stdout | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error checking ban status: {e}") | ||||
|             return False | ||||
							
								
								
									
										120
									
								
								backends/nftables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								backends/nftables.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| """ | ||||
| Backend dla nftables | ||||
| """ | ||||
|  | ||||
| import subprocess | ||||
| from .base import FirewallBackend | ||||
|  | ||||
|  | ||||
| class NFTablesBackend(FirewallBackend): | ||||
|     """Backend dla nftables""" | ||||
|      | ||||
|     def __init__(self, config): | ||||
|         super().__init__(config) | ||||
|         self.table = config.get('backend_nftables', 'table_name',  | ||||
|                                fallback='filter') | ||||
|         self.chain = config.get('backend_nftables', 'chain_name',  | ||||
|                                fallback='logmon_block') | ||||
|          | ||||
|         if self.test_availability(): | ||||
|             self.setup_chain() | ||||
|         else: | ||||
|             self.logger.warning("nftables not available") | ||||
|              | ||||
|     def test_availability(self): | ||||
|         """Sprawdza czy nftables jest dostępny""" | ||||
|         try: | ||||
|             result = subprocess.run( | ||||
|                 ['nft', '--version'],  | ||||
|                 capture_output=True, | ||||
|                 timeout=5 | ||||
|             ) | ||||
|             return result.returncode == 0 | ||||
|         except: | ||||
|             return False | ||||
|              | ||||
|     def setup_chain(self): | ||||
|         """Tworzy chain jeśli nie istnieje""" | ||||
|         try: | ||||
|             # Sprawdź czy chain istnieje | ||||
|             cmd = ['nft', 'list', 'chain', 'inet', self.table, self.chain] | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode != 0: | ||||
|                 # Utwórz chain z priorytetem input | ||||
|                 cmd = [ | ||||
|                     'nft', 'add', 'chain', 'inet', self.table, self.chain, | ||||
|                     '{', 'type', 'filter', 'hook', 'input', 'priority', '0', ';', '}' | ||||
|                 ] | ||||
|                 subprocess.run(cmd, check=True, timeout=5) | ||||
|                 self.logger.info(f"Created nftables chain: {self.chain}") | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error setting up nftables chain: {e}") | ||||
|              | ||||
|     def ban_ip(self, ip, duration): | ||||
|         """Banuje IP używając nftables""" | ||||
|         try: | ||||
|             # Dodaj regułę DROP | ||||
|             cmd = [ | ||||
|                 'nft', 'add', 'rule', 'inet', self.table, self.chain, | ||||
|                 'ip', 'saddr', ip, 'counter', 'drop', | ||||
|                 'comment', f'"LogMon ban {duration}s"' | ||||
|             ] | ||||
|              | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"nftables ban successful for {ip}") | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.error(f"nftables ban failed: {result.stderr.decode()}") | ||||
|                 return False | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error banning IP with nftables: {e}") | ||||
|             return False | ||||
|              | ||||
|     def unban_ip(self, ip): | ||||
|         """Usuwa ban używając nftables""" | ||||
|         try: | ||||
|             # Znajdź handle reguły | ||||
|             cmd = ['nft', '-a', 'list', 'chain', 'inet', self.table, self.chain] | ||||
|             result = subprocess.run(cmd, capture_output=True, text=True, timeout=5) | ||||
|              | ||||
|             if result.returncode != 0: | ||||
|                 return False | ||||
|                  | ||||
|             # Parsuj output i znajdź handle | ||||
|             for line in result.stdout.split('\n'): | ||||
|                 if ip in line and '# handle' in line: | ||||
|                     try: | ||||
|                         handle = line.split('# handle')[1].strip() | ||||
|                          | ||||
|                         # Usuń regułę | ||||
|                         cmd = [ | ||||
|                             'nft', 'delete', 'rule', 'inet',  | ||||
|                             self.table, self.chain, 'handle', handle | ||||
|                         ] | ||||
|                         subprocess.run(cmd, check=True, timeout=5) | ||||
|                         self.logger.debug(f"nftables unban successful for {ip}") | ||||
|                         return True | ||||
|                          | ||||
|                     except Exception as e: | ||||
|                         self.logger.error(f"Error parsing handle: {e}") | ||||
|                          | ||||
|             return False | ||||
|              | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error unbanning IP with nftables: {e}") | ||||
|             return False | ||||
|              | ||||
|     def is_banned(self, ip): | ||||
|         """Sprawdza czy IP jest zbanowany""" | ||||
|         try: | ||||
|             cmd = ['nft', 'list', 'chain', 'inet', self.table, self.chain] | ||||
|             result = subprocess.run(cmd, capture_output=True, text=True, timeout=5) | ||||
|             return ip in result.stdout | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error checking ban status: {e}") | ||||
|             return False | ||||
							
								
								
									
										78
									
								
								backends/ufw.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								backends/ufw.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| """ | ||||
| Backend dla UFW (Uncomplicated Firewall) | ||||
| """ | ||||
|  | ||||
| import subprocess | ||||
| from .base import FirewallBackend | ||||
|  | ||||
|  | ||||
| class UFWBackend(FirewallBackend): | ||||
|     """Backend dla UFW""" | ||||
|      | ||||
|     def test_availability(self): | ||||
|         """Sprawdza czy UFW jest dostępny""" | ||||
|         try: | ||||
|             result = subprocess.run( | ||||
|                 ['ufw', 'version'],  | ||||
|                 capture_output=True, | ||||
|                 timeout=5 | ||||
|             ) | ||||
|             return result.returncode == 0 | ||||
|         except: | ||||
|             return False | ||||
|              | ||||
|     def ban_ip(self, ip, duration): | ||||
|         """Banuje IP używając UFW""" | ||||
|         try: | ||||
|             # UFW nie wspiera natywnie timeout, więc używamy prostego deny | ||||
|             cmd = ['ufw', 'deny', 'from', ip] | ||||
|              | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"UFW ban successful for {ip}") | ||||
|                 # UFW wymaga reload | ||||
|                 subprocess.run(['ufw', 'reload'], capture_output=True, timeout=5) | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.error(f"UFW ban failed: {result.stderr.decode()}") | ||||
|                 return False | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error banning IP with UFW: {e}") | ||||
|             return False | ||||
|              | ||||
|     def unban_ip(self, ip): | ||||
|         """Usuwa ban używając UFW""" | ||||
|         try: | ||||
|             cmd = ['ufw', 'delete', 'deny', 'from', ip] | ||||
|             result = subprocess.run(cmd, capture_output=True, timeout=5) | ||||
|              | ||||
|             if result.returncode == 0: | ||||
|                 self.logger.debug(f"UFW unban successful for {ip}") | ||||
|                 subprocess.run(['ufw', 'reload'], capture_output=True, timeout=5) | ||||
|                 return True | ||||
|             else: | ||||
|                 self.logger.warning(f"UFW unban may have failed") | ||||
|                 return False | ||||
|                  | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error unbanning IP with UFW: {e}") | ||||
|             return False | ||||
|              | ||||
|     def is_banned(self, ip): | ||||
|         """Sprawdza czy IP jest zbanowany""" | ||||
|         try: | ||||
|             cmd = ['ufw', 'status', 'numbered'] | ||||
|             result = subprocess.run(cmd, capture_output=True, text=True, timeout=5) | ||||
|              | ||||
|             # Szukaj IP z DENY | ||||
|             for line in result.stdout.split('\n'): | ||||
|                 if ip in line and 'DENY' in line: | ||||
|                     return True | ||||
|                      | ||||
|             return False | ||||
|              | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error checking ban status: {e}") | ||||
|             return False | ||||
							
								
								
									
										42
									
								
								config.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								config.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| [general] | ||||
| debug = false | ||||
| log_file = /var/log/logmon.log | ||||
| pid_file = /var/run/logmon.pid | ||||
| backend = csf | ||||
|  | ||||
| [backend_csf] | ||||
| csf_path = /usr/sbin/csf | ||||
| # Dodatkowe opcje CSF | ||||
|  | ||||
| [backend_nftables] | ||||
| table_name = filter | ||||
| chain_name = logmon_block | ||||
|  | ||||
| [backend_iptables] | ||||
| chain_name = LOGMON_BLOCK | ||||
|  | ||||
| [backend_ufw] | ||||
| # UFW nie wymaga dodatkowych parametrów | ||||
|  | ||||
| [module_postfix] | ||||
| enabled = true | ||||
| log_file = /var/log/mail.log | ||||
| # Alternatywnie dla systemd: | ||||
| # use_journald = true | ||||
| # journald_unit = postfix.service | ||||
|  | ||||
| # Parametry detekcji | ||||
| max_failures = 5 | ||||
| time_window = 60 | ||||
| ban_duration = 86400 | ||||
|  | ||||
| # Wzorce do wykrywania | ||||
| patterns = auth_failed,sasl_failed | ||||
|  | ||||
| [pattern_auth_failed] | ||||
| regex = authentication failed | ||||
| score = 1 | ||||
|  | ||||
| [pattern_sasl_failed] | ||||
| regex = SASL [A-Z\-\d]+ authentication failed | ||||
| score = 2 | ||||
							
								
								
									
										477
									
								
								logmon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								logmon.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,477 @@ | ||||
| #!/usr/bin/env python3 | ||||
| """ | ||||
| LogMon - Modularny demon do monitorowania logów i blokowania IP | ||||
| Autor: System Administrator | ||||
| Wersja: 1.0 | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| import time | ||||
| import signal | ||||
| import logging | ||||
| import argparse | ||||
| import configparser | ||||
| from collections import defaultdict, deque | ||||
| from datetime import datetime, timedelta | ||||
| from pathlib import Path | ||||
|  | ||||
| # Importy z lokalnych modułów | ||||
| from modules import PostfixModule | ||||
| from backends import CSFBackend, NFTablesBackend, IPTablesBackend, UFWBackend | ||||
|  | ||||
|  | ||||
| class LogMonDaemon: | ||||
|     """Główny demon LogMon""" | ||||
|      | ||||
|     def __init__(self, config_file): | ||||
|         """ | ||||
|         Inicjalizacja demona | ||||
|          | ||||
|         Args: | ||||
|             config_file: Ścieżka do pliku konfiguracyjnego INI | ||||
|         """ | ||||
|         self.config = configparser.ConfigParser() | ||||
|         self.config.read(config_file) | ||||
|          | ||||
|         self.running = False | ||||
|         self.ip_tracker = defaultdict(lambda: deque()) | ||||
|         self.banned_ips = {} | ||||
|          | ||||
|         # Konfiguracja logowania | ||||
|         self.setup_logging() | ||||
|          | ||||
|         # Wybór backendu firewall | ||||
|         self.backend = self.load_backend() | ||||
|          | ||||
|         # Ładowanie modułów monitorowania | ||||
|         self.modules = self.load_modules() | ||||
|          | ||||
|         # Obsługa sygnałów systemowych | ||||
|         signal.signal(signal.SIGTERM, self.signal_handler) | ||||
|         signal.signal(signal.SIGINT, self.signal_handler) | ||||
|         signal.signal(signal.SIGHUP, self.signal_reload) | ||||
|          | ||||
|         self.logger.info("LogMon daemon initialized") | ||||
|          | ||||
|     def setup_logging(self): | ||||
|         """Konfiguracja systemu logowania""" | ||||
|         debug = self.config.getboolean('general', 'debug', fallback=False) | ||||
|         log_file = self.config.get('general', 'log_file',  | ||||
|                                    fallback='/var/log/logmon.log') | ||||
|          | ||||
|         level = logging.DEBUG if debug else logging.INFO | ||||
|          | ||||
|         # Usuń istniejące handlery | ||||
|         logging.basicConfig( | ||||
|             level=level, | ||||
|             format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | ||||
|             handlers=[ | ||||
|                 logging.FileHandler(log_file), | ||||
|                 logging.StreamHandler(sys.stdout) | ||||
|             ] | ||||
|         ) | ||||
|          | ||||
|         self.logger = logging.getLogger('LogMon') | ||||
|         self.logger.info(f"Logging initialized (debug={debug})") | ||||
|          | ||||
|     def load_backend(self): | ||||
|         """Ładuje odpowiedni backend firewall""" | ||||
|         backend_name = self.config.get('general', 'backend', fallback='csf') | ||||
|          | ||||
|         self.logger.info(f"Loading backend: {backend_name}") | ||||
|          | ||||
|         backend_map = { | ||||
|             'csf': CSFBackend, | ||||
|             'nftables': NFTablesBackend, | ||||
|             'iptables': IPTablesBackend, | ||||
|             'ufw': UFWBackend | ||||
|         } | ||||
|          | ||||
|         if backend_name not in backend_map: | ||||
|             raise ValueError(f"Unknown backend: {backend_name}") | ||||
|              | ||||
|         backend_class = backend_map[backend_name] | ||||
|         backend = backend_class(self.config) | ||||
|          | ||||
|         # Sprawdź dostępność | ||||
|         if not backend.test_availability(): | ||||
|             self.logger.warning(f"Backend {backend_name} may not be available!") | ||||
|              | ||||
|         return backend | ||||
|          | ||||
|     def load_modules(self): | ||||
|         """Ładuje moduły monitorowania""" | ||||
|         modules = [] | ||||
|          | ||||
|         # Postfix module | ||||
|         if self.config.getboolean('module_postfix', 'enabled', fallback=False): | ||||
|             try: | ||||
|                 module = PostfixModule(self.config, self) | ||||
|                 modules.append(module) | ||||
|                 self.logger.info("Loaded Postfix module") | ||||
|             except Exception as e: | ||||
|                 self.logger.error(f"Error loading Postfix module: {e}") | ||||
|                  | ||||
|         # Tutaj można dodać więcej modułów w przyszłości | ||||
|         # if self.config.getboolean('module_ssh', 'enabled', fallback=False): | ||||
|         #     modules.append(SSHModule(self.config, self)) | ||||
|          | ||||
|         if not modules: | ||||
|             self.logger.warning("No modules loaded!") | ||||
|              | ||||
|         return modules | ||||
|          | ||||
|     def signal_handler(self, signum, frame): | ||||
|         """Obsługa sygnałów SIGTERM i SIGINT""" | ||||
|         self.logger.info(f"Received signal {signum}, shutting down gracefully...") | ||||
|         self.running = False | ||||
|          | ||||
|     def signal_reload(self, signum, frame): | ||||
|         """Obsługa sygnału SIGHUP - reload konfiguracji""" | ||||
|         self.logger.info("Received SIGHUP, reloading configuration...") | ||||
|         # TODO: Implementacja reload konfiguracji | ||||
|          | ||||
|     def track_failure(self, ip, score=1): | ||||
|         """ | ||||
|         Śledzi nieudane próby logowania dla danego IP | ||||
|          | ||||
|         Args: | ||||
|             ip: Adres IP atakującego | ||||
|             score: Punkty za dane niepowodzenie (domyślnie 1) | ||||
|         """ | ||||
|         now = datetime.now() | ||||
|          | ||||
|         # Dodaj wpis do trackera | ||||
|         self.ip_tracker[ip].append((now, score)) | ||||
|          | ||||
|         # Usuń stare wpisy spoza okna czasowego | ||||
|         time_window = self.config.getint('module_postfix', 'time_window',  | ||||
|                                          fallback=60) | ||||
|         cutoff = now - timedelta(seconds=time_window) | ||||
|          | ||||
|         while self.ip_tracker[ip] and self.ip_tracker[ip][0][0] < cutoff: | ||||
|             self.ip_tracker[ip].popleft() | ||||
|              | ||||
|         # Oblicz całkowity wynik | ||||
|         total_score = sum(score for _, score in self.ip_tracker[ip]) | ||||
|         failures_count = len(self.ip_tracker[ip]) | ||||
|          | ||||
|         max_failures = self.config.getint('module_postfix', 'max_failures',  | ||||
|                                          fallback=5) | ||||
|          | ||||
|         self.logger.debug( | ||||
|             f"IP {ip}: {failures_count} failures, score {total_score}/{max_failures}" | ||||
|         ) | ||||
|          | ||||
|         # Sprawdź czy przekroczono limit | ||||
|         if total_score >= max_failures and ip not in self.banned_ips: | ||||
|             self.ban_ip(ip) | ||||
|         elif total_score >= max_failures and ip in self.banned_ips: | ||||
|             self.logger.debug(f"IP {ip} already banned, ignoring") | ||||
|              | ||||
|     def ban_ip(self, ip): | ||||
|         """ | ||||
|         Banuje adres IP | ||||
|          | ||||
|         Args: | ||||
|             ip: Adres IP do zbanowania | ||||
|         """ | ||||
|         ban_duration = self.config.getint('module_postfix', 'ban_duration',  | ||||
|                                          fallback=86400) | ||||
|          | ||||
|         self.logger.warning( | ||||
|             f"Banning IP {ip} for {ban_duration} seconds " | ||||
|             f"({ban_duration/3600:.1f} hours)" | ||||
|         ) | ||||
|          | ||||
|         if self.backend.ban_ip(ip, ban_duration): | ||||
|             expiry_time = datetime.now() + timedelta(seconds=ban_duration) | ||||
|             self.banned_ips[ip] = expiry_time | ||||
|              | ||||
|             self.logger.info( | ||||
|                 f"Successfully banned {ip} until {expiry_time.strftime('%Y-%m-%d %H:%M:%S')}" | ||||
|             ) | ||||
|              | ||||
|             # Wyczyść tracker dla tego IP | ||||
|             if ip in self.ip_tracker: | ||||
|                 del self.ip_tracker[ip] | ||||
|         else: | ||||
|             self.logger.error(f"Failed to ban {ip}") | ||||
|              | ||||
|     def unban_ip(self, ip): | ||||
|         """ | ||||
|         Odbania adres IP | ||||
|          | ||||
|         Args: | ||||
|             ip: Adres IP do odbanowania | ||||
|         """ | ||||
|         self.logger.info(f"Unbanning expired IP {ip}") | ||||
|          | ||||
|         if self.backend.unban_ip(ip): | ||||
|             if ip in self.banned_ips: | ||||
|                 del self.banned_ips[ip] | ||||
|             self.logger.info(f"Successfully unbanned {ip}") | ||||
|         else: | ||||
|             self.logger.error(f"Failed to unban {ip}") | ||||
|              | ||||
|     def unban_expired(self): | ||||
|         """Usuwa bany, które wygasły""" | ||||
|         now = datetime.now() | ||||
|         expired = [ip for ip, expiry in self.banned_ips.items()  | ||||
|                    if now >= expiry] | ||||
|          | ||||
|         for ip in expired: | ||||
|             self.unban_ip(ip) | ||||
|              | ||||
|     def save_state(self): | ||||
|         """Zapisuje stan banów do pliku (opcjonalnie)""" | ||||
|         # TODO: Implementacja persystencji stanu | ||||
|         pass | ||||
|          | ||||
|     def load_state(self): | ||||
|         """Ładuje stan banów z pliku (opcjonalnie)""" | ||||
|         # TODO: Implementacja persystencji stanu | ||||
|         pass | ||||
|          | ||||
|     def daemonize(self): | ||||
|         """Przechodzi w tryb demona (fork)""" | ||||
|         pid_file = self.config.get('general', 'pid_file',  | ||||
|                                    fallback='/var/run/logmon.pid') | ||||
|          | ||||
|         # Sprawdź czy już działa | ||||
|         if os.path.exists(pid_file): | ||||
|             with open(pid_file, 'r') as f: | ||||
|                 old_pid = int(f.read().strip()) | ||||
|                  | ||||
|             # Sprawdź czy proces istnieje | ||||
|             try: | ||||
|                 os.kill(old_pid, 0) | ||||
|                 self.logger.error(f"Daemon already running with PID {old_pid}") | ||||
|                 sys.exit(1) | ||||
|             except OSError: | ||||
|                 # Proces nie istnieje, usuń stary PID file | ||||
|                 os.remove(pid_file) | ||||
|          | ||||
|         # Pierwszy fork | ||||
|         try: | ||||
|             pid = os.fork() | ||||
|             if pid > 0: | ||||
|                 # Parent process | ||||
|                 sys.exit(0) | ||||
|         except OSError as e: | ||||
|             self.logger.error(f"Fork #1 failed: {e}") | ||||
|             sys.exit(1) | ||||
|              | ||||
|         # Odłącz od terminala | ||||
|         os.chdir('/') | ||||
|         os.setsid() | ||||
|         os.umask(0) | ||||
|          | ||||
|         # Drugi fork | ||||
|         try: | ||||
|             pid = os.fork() | ||||
|             if pid > 0: | ||||
|                 # Parent process | ||||
|                 sys.exit(0) | ||||
|         except OSError as e: | ||||
|             self.logger.error(f"Fork #2 failed: {e}") | ||||
|             sys.exit(1) | ||||
|              | ||||
|         # Zapisz PID | ||||
|         pid = os.getpid() | ||||
|         with open(pid_file, 'w') as f: | ||||
|             f.write(str(pid)) | ||||
|              | ||||
|         self.logger.info(f"Daemon started with PID {pid}") | ||||
|          | ||||
|         # Przekieruj standardowe wyjścia | ||||
|         sys.stdout.flush() | ||||
|         sys.stderr.flush() | ||||
|          | ||||
|         devnull = open(os.devnull, 'r+') | ||||
|         os.dup2(devnull.fileno(), sys.stdin.fileno()) | ||||
|          | ||||
|         # Stdout i stderr zostawiamy dla logowania | ||||
|          | ||||
|     def cleanup(self): | ||||
|         """Sprzątanie przed zakończeniem""" | ||||
|         pid_file = self.config.get('general', 'pid_file',  | ||||
|                                    fallback='/var/run/logmon.pid') | ||||
|          | ||||
|         # Usuń PID file | ||||
|         if os.path.exists(pid_file): | ||||
|             try: | ||||
|                 os.remove(pid_file) | ||||
|             except Exception as e: | ||||
|                 self.logger.error(f"Error removing PID file: {e}") | ||||
|                  | ||||
|     def print_status(self): | ||||
|         """Wyświetla status demona""" | ||||
|         print("\n=== LogMon Status ===") | ||||
|         print(f"Backend: {self.backend.__class__.__name__}") | ||||
|         print(f"Modules: {len(self.modules)}") | ||||
|         print(f"Currently banned IPs: {len(self.banned_ips)}") | ||||
|         print(f"Tracked IPs: {len(self.ip_tracker)}") | ||||
|          | ||||
|         if self.banned_ips: | ||||
|             print("\nBanned IPs:") | ||||
|             for ip, expiry in sorted(self.banned_ips.items(),  | ||||
|                                     key=lambda x: x[1]): | ||||
|                 remaining = (expiry - datetime.now()).total_seconds() | ||||
|                 if remaining > 0: | ||||
|                     print(f"  {ip:15s} - expires in {remaining/60:.1f} minutes") | ||||
|                 else: | ||||
|                     print(f"  {ip:15s} - EXPIRED") | ||||
|                      | ||||
|         print("\n") | ||||
|          | ||||
|     def run(self, daemonize=True): | ||||
|         """ | ||||
|         Główna pętla demona | ||||
|          | ||||
|         Args: | ||||
|             daemonize: Czy przejść w tryb demona (fork) | ||||
|         """ | ||||
|         if daemonize: | ||||
|             self.daemonize() | ||||
|              | ||||
|         self.running = True | ||||
|         self.logger.info("LogMon daemon started") | ||||
|          | ||||
|         # Uruchom moduły | ||||
|         for module in self.modules: | ||||
|             try: | ||||
|                 module.start() | ||||
|             except Exception as e: | ||||
|                 self.logger.error(f"Error starting module: {e}") | ||||
|                  | ||||
|         # Główna pętla | ||||
|         try: | ||||
|             while self.running: | ||||
|                 try: | ||||
|                     # Sprawdź wygasłe bany co 10 sekund | ||||
|                     self.unban_expired() | ||||
|                      | ||||
|                     # Wyświetl status co minutę (tylko w debug mode) | ||||
|                     if self.config.getboolean('general', 'debug', fallback=False): | ||||
|                         if int(time.time()) % 60 == 0: | ||||
|                             self.logger.debug( | ||||
|                                 f"Status: {len(self.banned_ips)} banned, " | ||||
|                                 f"{len(self.ip_tracker)} tracked" | ||||
|                             ) | ||||
|                      | ||||
|                     # Krótkie oczekiwanie | ||||
|                     time.sleep(10) | ||||
|                      | ||||
|                 except KeyboardInterrupt: | ||||
|                     self.logger.info("Keyboard interrupt received") | ||||
|                     break | ||||
|                 except Exception as e: | ||||
|                     self.logger.error(f"Error in main loop: {e}", exc_info=True) | ||||
|                     time.sleep(1) | ||||
|                      | ||||
|         finally: | ||||
|             # Zatrzymaj moduły | ||||
|             self.logger.info("Stopping modules...") | ||||
|             for module in self.modules: | ||||
|                 try: | ||||
|                     module.stop() | ||||
|                 except Exception as e: | ||||
|                     self.logger.error(f"Error stopping module: {e}") | ||||
|                      | ||||
|             # Sprzątanie | ||||
|             self.cleanup() | ||||
|              | ||||
|             self.logger.info("LogMon daemon stopped") | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     """Główna funkcja programu""" | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description='LogMon - Log Monitoring and IP Blocking Daemon', | ||||
|         formatter_class=argparse.RawDescriptionHelpFormatter, | ||||
|         epilog=""" | ||||
| Przykłady użycia: | ||||
|   %(prog)s -c /etc/logmon/config.ini          # Uruchom jako demon | ||||
|   %(prog)s -c /etc/logmon/config.ini -f       # Uruchom w foreground | ||||
|   %(prog)s -c /etc/logmon/config.ini -f -d    # Uruchom w foreground z debugiem | ||||
|    | ||||
| Sygnały: | ||||
|   SIGTERM, SIGINT - graceful shutdown | ||||
|   SIGHUP          - reload konfiguracji (TODO) | ||||
|         """ | ||||
|     ) | ||||
|      | ||||
|     parser.add_argument( | ||||
|         '-c', '--config', | ||||
|         default='/etc/logmon/config.ini', | ||||
|         help='Ścieżka do pliku konfiguracyjnego (domyślnie: /etc/logmon/config.ini)' | ||||
|     ) | ||||
|      | ||||
|     parser.add_argument( | ||||
|         '-f', '--foreground', | ||||
|         action='store_true', | ||||
|         help='Uruchom w foreground (nie demonizuj)' | ||||
|     ) | ||||
|      | ||||
|     parser.add_argument( | ||||
|         '-d', '--debug', | ||||
|         action='store_true', | ||||
|         help='Włącz tryb debug (nadpisuje ustawienie z config.ini)' | ||||
|     ) | ||||
|      | ||||
|     parser.add_argument( | ||||
|         '-t', '--test', | ||||
|         action='store_true', | ||||
|         help='Test konfiguracji i wyjście' | ||||
|     ) | ||||
|      | ||||
|     parser.add_argument( | ||||
|         '--version', | ||||
|         action='version', | ||||
|         version='LogMon 1.0' | ||||
|     ) | ||||
|      | ||||
|     args = parser.parse_args() | ||||
|      | ||||
|     # Sprawdź czy plik konfiguracyjny istnieje | ||||
|     if not os.path.exists(args.config): | ||||
|         print(f"Error: Configuration file not found: {args.config}", file=sys.stderr) | ||||
|         sys.exit(1) | ||||
|          | ||||
|     # Sprawdź uprawnienia root | ||||
|     if os.geteuid() != 0 and not args.test: | ||||
|         print("Error: This program must be run as root", file=sys.stderr) | ||||
|         sys.exit(1) | ||||
|          | ||||
|     try: | ||||
|         # Inicjalizacja demona | ||||
|         daemon = LogMonDaemon(args.config) | ||||
|          | ||||
|         # Nadpisz debug jeśli podano w argumentach | ||||
|         if args.debug: | ||||
|             daemon.config.set('general', 'debug', 'true') | ||||
|             daemon.setup_logging() | ||||
|              | ||||
|         # Tryb testowy | ||||
|         if args.test: | ||||
|             print("Configuration test successful") | ||||
|             daemon.print_status() | ||||
|             sys.exit(0) | ||||
|              | ||||
|         # Uruchom demona | ||||
|         daemon.run(daemonize=not args.foreground) | ||||
|          | ||||
|     except KeyboardInterrupt: | ||||
|         print("\nInterrupted by user") | ||||
|         sys.exit(0) | ||||
|     except Exception as e: | ||||
|         print(f"Fatal error: {e}", file=sys.stderr) | ||||
|         import traceback | ||||
|         traceback.print_exc() | ||||
|         sys.exit(1) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										23
									
								
								logmon.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								logmon.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| [Unit] | ||||
| Description=LogMon - Log Monitoring and IP Blocking Daemon | ||||
| After=network.target syslog.target | ||||
|  | ||||
| [Service] | ||||
| Type=forking | ||||
| User=root | ||||
| Group=root | ||||
| WorkingDirectory=/opt/logmon | ||||
| ExecStart=/usr/bin/python3 /opt/logmon/logmon.py -c /etc/logmon/config.ini | ||||
| ExecReload=/bin/kill -HUP $MAINPID | ||||
| PIDFile=/var/run/logmon.pid | ||||
| Restart=on-failure | ||||
| RestartSec=10s | ||||
|  | ||||
| # Security hardening | ||||
| PrivateTmp=yes | ||||
| NoNewPrivileges=false | ||||
| ProtectSystem=strict | ||||
| ReadWritePaths=/var/log /var/run /etc/csf | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										54
									
								
								modules/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								modules/base.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| """ | ||||
| Bazowa klasa dla modułów monitorowania logów | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| import time | ||||
| import threading | ||||
|  | ||||
|  | ||||
| class LogModule: | ||||
|     """Bazowa klasa dla modułów monitorowania""" | ||||
|      | ||||
|     def __init__(self, config, daemon): | ||||
|         """ | ||||
|         Args: | ||||
|             config: ConfigParser object z konfiguracją | ||||
|             daemon: Referencja do głównego demona | ||||
|         """ | ||||
|         self.config = config | ||||
|         self.daemon = daemon | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|         self.running = False | ||||
|         self.thread = None | ||||
|          | ||||
|     def start(self): | ||||
|         """Uruchamia moduł w osobnym wątku""" | ||||
|         if self.running: | ||||
|             self.logger.warning("Module already running") | ||||
|             return | ||||
|              | ||||
|         self.running = True | ||||
|         self.thread = threading.Thread(target=self._run, daemon=True) | ||||
|         self.thread.start() | ||||
|         self.logger.info(f"{self.__class__.__name__} started") | ||||
|          | ||||
|     def stop(self): | ||||
|         """Zatrzymuje moduł""" | ||||
|         self.running = False | ||||
|         if self.thread and self.thread.is_alive(): | ||||
|             self.thread.join(timeout=5) | ||||
|         self.logger.info(f"{self.__class__.__name__} stopped") | ||||
|          | ||||
|     def _run(self): | ||||
|         """Główna pętla modułu - do nadpisania w klasach potomnych""" | ||||
|         raise NotImplementedError("Subclasses must implement _run()") | ||||
|          | ||||
|     def process_line(self, line): | ||||
|         """ | ||||
|         Przetwarza pojedynczą linię logu | ||||
|          | ||||
|         Args: | ||||
|             line: Linia tekstu z logu | ||||
|         """ | ||||
|         raise NotImplementedError("Subclasses must implement process_line()") | ||||
							
								
								
									
										8
									
								
								modules/init.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								modules/init.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| """ | ||||
| LogMon Modules - Moduły monitorowania różnych aplikacji | ||||
| """ | ||||
|  | ||||
| from .base import LogModule | ||||
| from .postfix import PostfixModule | ||||
|  | ||||
| __all__ = ['LogModule', 'PostfixModule'] | ||||
							
								
								
									
										120
									
								
								modules/postfix.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								modules/postfix.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| """ | ||||
| Moduł monitorujący Postfix SMTP server | ||||
| """ | ||||
|  | ||||
| import re | ||||
| import time | ||||
| from .base import LogModule | ||||
|  | ||||
|  | ||||
| class PostfixModule(LogModule): | ||||
|     """Moduł monitorujący Postfix""" | ||||
|      | ||||
|     def __init__(self, config, daemon): | ||||
|         super().__init__(config, daemon) | ||||
|          | ||||
|         # Kompiluj wzorce regex dla wydajności | ||||
|         self.patterns = self._load_patterns() | ||||
|          | ||||
|         # Regex do wyciągania IP z logów Postfix | ||||
|         # Obsługuje zarówno unknown[IP] jak i hostname[IP] | ||||
|         self.ip_pattern = re.compile( | ||||
|             r'(?:unknown|[\w\-\.]+)\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]' | ||||
|         ) | ||||
|          | ||||
|         # Ścieżka do pliku logu | ||||
|         self.log_file = config.get('module_postfix', 'log_file',  | ||||
|                                    fallback='/var/log/mail.log') | ||||
|          | ||||
|         self.logger.info(f"Loaded {len(self.patterns)} patterns for Postfix") | ||||
|          | ||||
|     def _load_patterns(self): | ||||
|         """Ładuje wzorce z konfiguracji""" | ||||
|         patterns = [] | ||||
|         pattern_names = self.config.get('module_postfix', 'patterns',  | ||||
|                                        fallback='').split(',') | ||||
|          | ||||
|         for name in pattern_names: | ||||
|             name = name.strip() | ||||
|             if not name: | ||||
|                 continue | ||||
|                  | ||||
|             section = f'pattern_{name}' | ||||
|             if section not in self.config: | ||||
|                 self.logger.warning(f"Pattern section '{section}' not found in config") | ||||
|                 continue | ||||
|                  | ||||
|             try: | ||||
|                 regex = self.config.get(section, 'regex') | ||||
|                 score = self.config.getint(section, 'score', fallback=1) | ||||
|                  | ||||
|                 patterns.append({ | ||||
|                     'name': name, | ||||
|                     'regex': re.compile(regex, re.IGNORECASE), | ||||
|                     'score': score | ||||
|                 }) | ||||
|                  | ||||
|                 self.logger.debug(f"Loaded pattern '{name}': {regex} (score: {score})") | ||||
|                  | ||||
|             except Exception as e: | ||||
|                 self.logger.error(f"Error loading pattern '{name}': {e}") | ||||
|                  | ||||
|         return patterns | ||||
|          | ||||
|     def _run(self): | ||||
|         """Główna pętla - tail -f na pliku logu""" | ||||
|         self.logger.info(f"Tailing log file: {self.log_file}") | ||||
|          | ||||
|         try: | ||||
|             with open(self.log_file, 'r') as f: | ||||
|                 # Przejdź na koniec pliku | ||||
|                 f.seek(0, 2) | ||||
|                  | ||||
|                 while self.running: | ||||
|                     line = f.readline() | ||||
|                      | ||||
|                     if line: | ||||
|                         self.process_line(line.strip()) | ||||
|                     else: | ||||
|                         # Brak nowych linii, czekaj chwilę | ||||
|                         time.sleep(0.1) | ||||
|                          | ||||
|         except FileNotFoundError: | ||||
|             self.logger.error(f"Log file not found: {self.log_file}") | ||||
|         except PermissionError: | ||||
|             self.logger.error(f"Permission denied reading: {self.log_file}") | ||||
|         except Exception as e: | ||||
|             self.logger.error(f"Error tailing log: {e}") | ||||
|              | ||||
|     def process_line(self, line): | ||||
|         """ | ||||
|         Przetwarza linię z logu Postfix | ||||
|          | ||||
|         Przykłady linii: | ||||
|         - postfix/smtpd[1234]: warning: unknown[1.2.3.4]: SASL LOGIN authentication failed | ||||
|         - postfix/smtpd[1234]: connect from unknown[1.2.3.4] | ||||
|         """ | ||||
|         # Wyciągnij IP | ||||
|         ip_match = self.ip_pattern.search(line) | ||||
|         if not ip_match: | ||||
|             return | ||||
|              | ||||
|         ip = ip_match.group(1) | ||||
|          | ||||
|         # Pomiń lokalne IP | ||||
|         if ip.startswith('127.') or ip.startswith('192.168.') or ip.startswith('10.'): | ||||
|             return | ||||
|              | ||||
|         # Sprawdź wzorce | ||||
|         for pattern in self.patterns: | ||||
|             if pattern['regex'].search(line): | ||||
|                 self.logger.debug( | ||||
|                     f"Pattern '{pattern['name']}' matched for IP {ip}" | ||||
|                 ) | ||||
|                 self.logger.debug(f"Line: {line}") | ||||
|                  | ||||
|                 # Zgłoś niepowodzenie do demona | ||||
|                 self.daemon.track_failure(ip, pattern['score']) | ||||
|                  | ||||
|                 # Tylko pierwszy pasujący wzorzec | ||||
|                 break | ||||
							
								
								
									
										5
									
								
								utils/init.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								utils/init.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| """ | ||||
| LogMon Utils - Narzędzia pomocnicze | ||||
| """ | ||||
|  | ||||
| __all__ = [] | ||||
							
								
								
									
										40
									
								
								utils/logger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								utils/logger.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| """ | ||||
| Pomocnicze funkcje do logowania | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| import logging.handlers | ||||
|  | ||||
|  | ||||
| def setup_rotating_logger(name, log_file, max_bytes=10485760, backup_count=5, level=logging.INFO): | ||||
|     """ | ||||
|     Konfiguruje logger z rotacją plików | ||||
|      | ||||
|     Args: | ||||
|         name: Nazwa loggera | ||||
|         log_file: Ścieżka do pliku logu | ||||
|         max_bytes: Maksymalny rozmiar pliku (domyślnie 10MB) | ||||
|         backup_count: Liczba backup plików | ||||
|         level: Poziom logowania | ||||
|          | ||||
|     Returns: | ||||
|         Logger object | ||||
|     """ | ||||
|     logger = logging.getLogger(name) | ||||
|     logger.setLevel(level) | ||||
|      | ||||
|     # Rotating file handler | ||||
|     handler = logging.handlers.RotatingFileHandler( | ||||
|         log_file, | ||||
|         maxBytes=max_bytes, | ||||
|         backupCount=backup_count | ||||
|     ) | ||||
|      | ||||
|     formatter = logging.Formatter( | ||||
|         '%(asctime)s - %(name)s - %(levelname)s - %(message)s' | ||||
|     ) | ||||
|     handler.setFormatter(formatter) | ||||
|      | ||||
|     logger.addHandler(handler) | ||||
|      | ||||
|     return logger | ||||
		Reference in New Issue
	
	Block a user
	 Mateusz Gruszczyński
					Mateusz Gruszczyński