diff --git a/README.md b/README.md index 2fb4002..1aed365 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,12 @@ Manual Installation: License: -------- -fail2ban-digest is free software; you can redistribute it and/or modify it under the +Fail2Ban is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. -fail2ban-digest is distributed in the hope that it will be useful, but WITHOUT ANY +Fail2Ban is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. diff --git a/bin/fail2ban_digest b/bin/fail2ban_digest index 2f7af0e..588077e 100755 --- a/bin/fail2ban_digest +++ b/bin/fail2ban_digest @@ -17,7 +17,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta from dbm import gnu as dbm from email.message import EmailMessage from smtplib import SMTP @@ -34,83 +34,17 @@ import re import sys db_location = '/var/lib/fail2ban/digest' -db_creation_date_key = u'db_creation_date' +db_creation_date_key = 'db_creation_date' db_date_format = '%Y-%m-%d %H:%M:%S' default_mail_template = Template('''Hi,\n -This is a digest email of the ${count} banned IPs between ${creation_date} and ${date_now}: + this is a digest email of banned IPs since ${creation_date} UTC and ${date_now} ${digest} Regards, -Fail2ban Digest +Fail2ban digest ''') -default_html_template = Template(''' - - - - - -

Hi,

-

This is a digest email of the ${count} banned IPs between ${creation_date} and ${date_now}:

- - - - - - -${digest} -
#IPsWhen
-

Regards,

-

Fail2Ban Digest

- - -''') -html_tr_template = Template(''' - ${count} - ${ip} - ${events} - -''') -html_error_template = Template(''' - ${error_msg} - -''') - -class Ban: - def __init__(self, ip, events): - self.ip = ip - self.events = [] - for event in events: - self.events.append(utc_to_local(event)) - self.events.sort class store_yesno(argparse.Action): def __init__(self, option_strings, dest, nargs = None, **kwargs): @@ -146,12 +80,6 @@ def ip_address(string): else: raise argparse.ArgumentTypeError('%s is not a valid IPv4 or IPv6 address' % string) -def utc_to_local(date_string): - try: - return datetime.strptime(date_string, db_date_format).replace(tzinfo=timezone.utc).astimezone().strftime(db_date_format) - except ValueError: - return date_string - def close_db(db_fd): db_fd.close() atexit.unregister(close_db) @@ -178,7 +106,7 @@ def add(db, ip): db = db_busy_open(db_location + '/' + db + '.dbm', 'c', 30) if db_creation_date_key not in db.keys(): db[db_creation_date_key] = datetime.utcnow().strftime(db_date_format).encode('UTF-8') - event_date = ('%s, ' % datetime.utcnow().strftime(db_date_format)).encode('UTF-8') + event_date = ('%s, ' % datetime.now().strftime(db_date_format)).encode('UTF-8') try: db[ip] += event_date except KeyError: @@ -186,7 +114,7 @@ def add(db, ip): close_db(db) return -def digest(db, delete, sort): +def digest(db, delete): db_file = db_location + '/' + db + '.dbm' new_db_file = db_location + '/.' + db + '.dbm' try: @@ -202,48 +130,32 @@ def digest(db, delete, sort): os.rename(new_db_file, db_file) try: - db_creation_date = utc_to_local(db[db_creation_date_key].decode('UTF-8')) + db_creation_date = db[db_creation_date_key].decode('UTF-8') except KeyError as e: db_creation_date = 'not found' - events_list = [] + event_list = [] for ip in db.keys(): if ip.decode('UTF-8') == db_creation_date_key: continue - events_list.append(Ban(ip.decode('UTF-8'), db[ip].decode('UTF-8').split(', ')[:-1])) + event_list.append((ip.decode('UTF-8'), db[ip].decode('UTF-8').split(', ')[:-1])) close_db(db) - events_list.sort(key=lambda x: x.events[0]) # sort by date - if sort: - events_list.sort(key=lambda x: len(x.events), reverse=True) + event_list.sort(key = lambda x: len(x[1]), reverse = True) msg = '' - msg_html = '' - for ban in events_list: - msg_html += html_tr_template.substitute(count = len(ban.events), ip = ban.ip, events = '
'.join(ban.events)) - msg += '%3d event(s) for IP %-42s: %s\n' %(len(ban.events), ban.ip, ', '.join(ban.events)) - return (len(events_list), db_creation_date, msg, msg_html) + for ip, events in event_list: + msg += '%3d event(s) for IP %-42s: %s\n' %(len(events), ip, ', '.join(events)) + return (db_creation_date, msg) -def mail_digest(db, mail_to, mail_from, delete, html, quiet, sort): +def mail_digest(db, mail_to, mail_from, delete): msg = EmailMessage() - date_now = datetime.now().strftime(db_date_format) - count, creation_date, dgst, dgst_html = digest(db, delete, sort) + date_now = datetime.utcnow().strftime(db_date_format) + creation_date, dgst = digest(db, delete) if dgst == '': - if quiet: - return - else: - dgst = ' No ban event recorded for the named time frame.' - dgst_html = html_error_template.substitute(error_msg = dgst) + dgst = 'no ban event recorded for the named time frame' msg.set_content(default_mail_template.substitute( - count = count, - creation_date = creation_date, - date_now = date_now, - digest = dgst - )) - if html: - msg.add_alternative(default_html_template.substitute( - count = count, creation_date = creation_date, date_now = date_now, - digest = dgst_html - ), subtype = 'html') + digest = dgst, + )) msg['To'] = mail_to msg['From'] = mail_from msg['Subject'] = '[Fail2Ban] %s: digest for %s %s' % (db, socket.gethostname(), date_now) @@ -256,9 +168,9 @@ def main(args): if args.cmd == 'add': add(args.database, args.ip) elif args.cmd == 'digest': - print(digest(args.database, args.delete, args.sort)[2]) + print(digest(args.database, args.delete)[1]) elif args.cmd == 'maildigest': - mail_digest(args.database, args.to, args.mail_from, args.delete, args.html, args.quiet, args.sort) + mail_digest(args.database, args.to, args.mail_from, args.delete) elif args.cmd is None: print('No action specified') return @@ -307,12 +219,6 @@ if __name__ == '__main__': default = False, help = 'do / don\'t delete current database, next call to add will create a new empty one' ) - subcommands[sc].add_argument( - '--sort', '--no-sort', - action = store_yesno, - default = True, - help = 'do / don\'t sort the digest by repeat event occurrences.' - ) sc = 'maildigest' subcommands[sc] = subparsers.add_parser( @@ -329,30 +235,12 @@ if __name__ == '__main__': default = True, help = 'do / don\'t delete current database, next call to add will create a new empty one' ) - subcommands[sc].add_argument( - '--html', '--no-html', - action = store_yesno, - default = False, - help = 'do / don\'t send the digest in HTML format.' - ) subcommands[sc].add_argument( '--mail-from', action = 'store', default = 'Fail2ban at {0} '.format(socket.gethostname()), help = 'Use FROM address for the email From header. Default: Fail2ban at {0} (automatically detected system hostname)'.format(socket.gethostname()) ) - subcommands[sc].add_argument( - '--quiet', '--no-quiet', - action = store_yesno, - default = False, - help = 'do / don\'t send digest if there are no ban events recorded for the named time frame' - ) - subcommands[sc].add_argument( - '--sort', '--no-sort', - action = store_yesno, - default = True, - help = 'do / don\'t sort the digest by repeat event occurrences.' - ) subcommands[sc].add_argument( '--to', action = 'store',