Date: Thu, 17 Dec 2009 17:35:55 -0800 From: Brandon Low <lostlogic@lostlogicx.com> To: freebsd-questions@freebsd.org Subject: Re: RFC: Fam/Python based script for bruteforce blocking Message-ID: <20091218013555.GJ73162@lostlogicx.com> In-Reply-To: <20091218013422.GI73162@lostlogicx.com> References: <20091218013422.GI73162@lostlogicx.com>
next in thread | previous in thread | raw e-mail | index | archive | help
Not sure why this didn't attach the first time. #!/usr/bin/env python import errno import logging import optparse import os import re import select import signal import subprocess import sys import time import datetime import _fam def getUpdateBlocks(pfctl, expire_seconds, blacklist_filename, table, limit_n): expire=str(expire_seconds) blacklist=blacklist_filename limit=limit_n baseArgs=(pfctl, '-t', table, '-T') def callAndLog(*args, **kwargs): c=subprocess.Popen(baseArgs + args, stderr=subprocess.PIPE, stdout=kwargs.get('stdout',subprocess.PIPE)) stdout,stderr=c.communicate() if stdout: logging.info(stdout) for line in (stderr if stderr else '').split('\n'): if not line: continue getattr(logging,'info' if line.find('ALTQ') < 0 else 'debug')(line) reParts=('(.*) erudite sshd\[[0-9]+\]: ', '(?:', '|'.join(('Invalid user .* from', 'Did not receive identification string from', 'error: PAM: authentication error for root from')), ') ', '(', '(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}', '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', ')\n?') r=re.compile(''.join(reParts)) df='%b %d %H:%M:%S' oneDay=datetime.timedelta(days=1) def processFile(now, ips, filename): with open(filename, 'r') as f: for line in f: m=r.match(line) if not m: continue d=datetime.datetime.strptime(m.group(1),df).replace(now.year) if d > now: d=d.replace(now.year-1) if now-d < oneDay: ips[m.group(2)]=ips.get(m.group(2),0) + 1 def updateBlocks(filename): logging.info("Updating blacklist...") ips={} now=datetime.datetime.now() processFile(now, ips, filename) logging.debug("Found %s IPs", len(ips)) logging.debug("Adding ips to pf table") callAndLog('add', *tuple(k for k,v in ips.iteritems() if v >= limit)) logging.debug("Expiring ips from pf table") callAndLog('expire', expire) logging.debug("Saving table state to file") with open(blacklist,'w') as blacklistFile: callAndLog('show', stdout=blacklistFile) logging.debug("Done") return updateBlocks def main(): parser=optparse.OptionParser() parser.add_option("-d", "--debug", action="store_true", help="Enable debug logging") parser.add_option("-a", "--auth_log", default="/var/log/auth.log", help="Authentication log filename") parser.add_option("-b", "--blacklist", default="/var/db/blacklist", help="Blacklist filename") parser.add_option("-l", "--log_file", default="/var/log/bruteforce.log", help="Log filename") parser.add_option("-p", "--pfctl", default="/sbin/pfctl", help="pfctl binary") parser.add_option("-e", "--expire", type="int", default=604800, help="Seconds to hold a grudge") parser.add_option("-t", "--table", default="bruteforce", help="Name of pf table to work on") parser.add_option("-i", "--limit", type="int", default=2, help="Number of invalid logins to get blacklisted") (opts, args)=parser.parse_args() if args: optparse.error("No non-option arguments expected") logging.basicConfig(filename=opts.log_file, level=logging.DEBUG if opts.debug else logging.INFO) fc=_fam.open() p=select.poll() p.register(fc, select.POLLIN|select.POLLPRI) fr=fc.monitorFile(opts.auth_log, None) updateBlocks=getUpdateBlocks( opts.pfctl, opts.expire, opts.blacklist, opts.table, opts.limit) while True: p.poll(60) update=False while fc.pending(): fe=fc.nextEvent() if fe.code in (_fam.Exists,_fam.Changed,_fam.Created): update=True if not fe.filename==opts.auth_log: raise "FAM event: wrong file" if update: updateBlocks(fe.filename) if __name__ == "__main__": main()
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20091218013555.GJ73162>