Compare commits
No commits in common. "69bd49a606bd8f136bb13c391b67c0c48c61bd71" and "0feb3e2b3bda8c02947b3330abdd967970a5e67f" have entirely different histories.
69bd49a606
...
0feb3e2b3b
2 changed files with 1 additions and 186 deletions
|
@ -1,3 +1,2 @@
|
||||||
click
|
click
|
||||||
click_config_file
|
click_config_file
|
||||||
mariadb
|
|
|
@ -9,16 +9,11 @@ import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import SysLogHandler
|
from logging.handlers import SysLogHandler
|
||||||
import subprocess
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import click
|
import click
|
||||||
import click_config_file
|
import click_config_file
|
||||||
import mariadb
|
|
||||||
|
|
||||||
|
|
||||||
class SmtpdWatcher:
|
class SmtpdWatcher:
|
||||||
"""SMTPd watcher for failed connections"""
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.config = kwargs
|
self.config = kwargs
|
||||||
|
@ -35,156 +30,6 @@ class SmtpdWatcher:
|
||||||
'smtpd_watcher.log'
|
'smtpd_watcher.log'
|
||||||
)
|
)
|
||||||
self._init_log()
|
self._init_log()
|
||||||
self.banned_ips = self._read_banned_ips()
|
|
||||||
self.mail_users = self._get_mail_user()
|
|
||||||
if self.config['mail_log_file'] == '-':
|
|
||||||
logfile = sys.stdin
|
|
||||||
else:
|
|
||||||
logfile = open(self.config['mail_log_file'], 'r', encoding='utf-8')
|
|
||||||
for line in logfile:
|
|
||||||
if not self._process_log_file(line):
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def _read_banned_ips(self):
|
|
||||||
ips = {}
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/bin/fail2ban-client', 'get', 'postfix', 'banned'],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
ips['postfix'] = json.loads(result.stdout)
|
|
||||||
self._log.debug(
|
|
||||||
"Banned IPs in postfix jail: %s",
|
|
||||||
ips['postfix']
|
|
||||||
)
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/bin/fail2ban-client', 'get', 'postfix-sasl', 'banned'],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
ips['postfix-sasl'] = json.loads(result.stdout)
|
|
||||||
self._log.debug(
|
|
||||||
"Banned IPs in postfix-sasl jail: %s",
|
|
||||||
ips['postfix-sasl']
|
|
||||||
)
|
|
||||||
result = subprocess.run(
|
|
||||||
['ufw', 'status', 'numbered'],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
ips['ufw'] = []
|
|
||||||
for line in result.stdout:
|
|
||||||
if 'DENY IN' in line:
|
|
||||||
split_line = line.split(' ')
|
|
||||||
ips['ufw'].append(split_line[4])
|
|
||||||
self._log.debug(
|
|
||||||
"Traffic denied to IPs in UFW: %s",
|
|
||||||
ips['ufw']
|
|
||||||
)
|
|
||||||
return ips
|
|
||||||
|
|
||||||
def _process_log_file(self, line):
|
|
||||||
ban = False
|
|
||||||
if 'authentication failure' in line:
|
|
||||||
ip_match = re.search(r'\[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\]', line)
|
|
||||||
if ip_match:
|
|
||||||
ip = ip_match.group(1)
|
|
||||||
else:
|
|
||||||
self._log.debug(
|
|
||||||
"Didn't find an IP in log file '%s'",
|
|
||||||
line
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
target_user_match = re.search(r'sasl_username=([^ ]*)', line)
|
|
||||||
if target_user_match:
|
|
||||||
target_user = target_user_match.group(1)
|
|
||||||
if not self._check_mail_user(target_user):
|
|
||||||
ban = True
|
|
||||||
else:
|
|
||||||
self._log.debug(
|
|
||||||
"There is no SASL username field in log line, so banning IP '%s'. Log line: '%s'",
|
|
||||||
ip,
|
|
||||||
line
|
|
||||||
)
|
|
||||||
ban = True
|
|
||||||
if ban:
|
|
||||||
if ip not in self.banned_ips['postfix']:
|
|
||||||
self._fail2ban_ban_ip('postfix', ip)
|
|
||||||
if ip not in self.banned_ips['postfix-sasl']:
|
|
||||||
self._fail2ban_ban_ip('postfix-sasl', ip)
|
|
||||||
if ip not in self.banned_ips['ufw']:
|
|
||||||
self._ufw_deny_ip(ip)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _ufw_deny_ip(self, ip):
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/sbin/ufw', 'deny', 'from', ip],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
self._log.debug(
|
|
||||||
"Denying traffic from IP '%s' in UFW result: %s",
|
|
||||||
ip,
|
|
||||||
result.stdout
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _fail2ban_ban_ip(self, jail, ip):
|
|
||||||
result = subprocess.run(
|
|
||||||
['/usr/bin/fail2ban-client', 'set', jail, 'banip', ip],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
self._log.debug(
|
|
||||||
"Adding ban to IP '%s' in jail '%s' result: %s",
|
|
||||||
ip,
|
|
||||||
jail,
|
|
||||||
result.stdout
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _check_mail_user(self, user):
|
|
||||||
if user != '':
|
|
||||||
for mail_user in self.mail_users:
|
|
||||||
if user in mail_user:
|
|
||||||
self._log.debug(
|
|
||||||
"User '%s' match mail database user '%s'",
|
|
||||||
user,
|
|
||||||
mail_user
|
|
||||||
)
|
|
||||||
return mail_user
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _get_mail_user(self):
|
|
||||||
self._log.debug(
|
|
||||||
"Getting all mail users from database '%s'...",
|
|
||||||
self.config['db_name']
|
|
||||||
)
|
|
||||||
mail_users = []
|
|
||||||
try:
|
|
||||||
conn = mariadb.connect(
|
|
||||||
host=self.config['db_host'],
|
|
||||||
port=self.config['db_port'],
|
|
||||||
user=self.config['db_user'],
|
|
||||||
password=self.config['db_password']
|
|
||||||
)
|
|
||||||
cur = conn.cursor()
|
|
||||||
cur.execute(self.config['db_sql_query'])
|
|
||||||
for email in cur:
|
|
||||||
mail_users.append(email)
|
|
||||||
except mariadb.Error as error:
|
|
||||||
self._log.error(
|
|
||||||
"Error connecting to the database: %s",
|
|
||||||
error
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
conn.close()
|
|
||||||
return mail_users
|
|
||||||
|
|
||||||
def _init_log(self):
|
def _init_log(self):
|
||||||
''' Initialize log object '''
|
''' Initialize log object '''
|
||||||
|
@ -240,35 +85,6 @@ class SmtpdWatcher:
|
||||||
@click.option('--log-file', '-l', help="File to store all debug messages.")
|
@click.option('--log-file', '-l', help="File to store all debug messages.")
|
||||||
# @click.option("--dummy","-n", is_flag=True,
|
# @click.option("--dummy","-n", is_flag=True,
|
||||||
# help="Don't do anything, just show what would be done.")
|
# help="Don't do anything, just show what would be done.")
|
||||||
@click.option(
|
|
||||||
'--mail-log-file', '-m',
|
|
||||||
default='/var/log/mail.log', help='Mail log file to read'
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--db-host', '-H',
|
|
||||||
default='127.0.0.1',
|
|
||||||
help='MariaDB host name for mail database'
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--db-port', '-p',
|
|
||||||
default=3306,
|
|
||||||
help='MariaDB host port for mail database'
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--db-user', '-u',
|
|
||||||
default=os.environ['USER'],
|
|
||||||
help='MariaDB user name for mail database'
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--db-password', '-P',
|
|
||||||
default='',
|
|
||||||
help='MariaDB user password for mail database'
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--db-sql-query', '-q',
|
|
||||||
default='SELECT email FROM mail.users',
|
|
||||||
help='MariaDB SQL query to get all users\' emails'
|
|
||||||
)
|
|
||||||
@click_config_file.configuration_option()
|
@click_config_file.configuration_option()
|
||||||
def __main__(**kwargs):
|
def __main__(**kwargs):
|
||||||
return SmtpdWatcher(**kwargs)
|
return SmtpdWatcher(**kwargs)
|
||||||
|
|
Loading…
Reference in a new issue