diff --git a/abck b/abck new file mode 100755 index 0000000..fc7e055 --- /dev/null +++ b/abck @@ -0,0 +1,230 @@ +#!/usr/local/bin/python +# Build a report of all unauthorized access attempts + +#################### +# Imports +#################### + +import commands +import re +import sys + +#################### +# Booleans +#################### + +FALSE = 0 == 1 +TRUE = not FALSE + +DONE = FALSE + +#################### +# Constants And Literals +#################### + +ANS = ";; ANSWER SECTION:" +AUTH = ";; AUTHORITY SECTION:" +DIG = "/usr/bin/dig -t ptr -x " +LOG = "/var/log/messages" +OUT = "./abusers" + +VERSION = "$Id: abck,v 1.1 2001/07/16 20:00:50 tundra Exp $" + +#################### +# Data Structures +#################### + +# Dictionary of keywords indicating attack, and position of host address/name +# in their log records + +AttackKeys = { + "refused" : 8, + "unauthorized" : 7 + } + +# Cache dictionary of all attacking hosts discovered this run of the program + +NameCache = {} + +#################### +# Regular Expression Handlers +#################### + +# Regular Expression which describes a legit IP quad address + +IPQuad = r"^((\d{1,3}\.){3}(\d\d?\d?))$" + +#################### +# Function Definitions +#################### + +# Return the ending substring of a host name with 'depth' number of dots +# in it + +def HostDepth(host, depth=2): + + # Break the address down into components + components = host.split(".") + + # And return the recombined pieces we want + return '.'.join(components[-depth:]) + +#################### + +# Check a name, see if it's an IP quad, and if so, return reverse. +# If not, return the original name. +# +# This is better than a socket.gethostbyaddr() call because +# this will return the authority information for an address +# if no explicit reverse resolution is found. + +def CheckIPReverse(hostquad): + + # If it's an IP address in quad form, reverse resolve it + if re.match(IPQuad, hostquad): + + DIGResults = commands.getoutput(DIG + hostquad).splitlines() + + ansname = "" + authname = hostquad + # Results must either have an Answer or Authority record + if ( DIGResults.count(ANS) + DIGResults.count(AUTH)): + + i = 0 + while i < len(DIGResults): + + if DIGResults[i].startswith(ANS): + ansname = DIGResults[i+1].split()[4] + + if DIGResults[i].startswith(AUTH): + authname = DIGResults[i+1].split()[-2] + + i += 1 + + if ansname: + hostname = ansname + else: + hostname = authname + + # Get rid of trailing dot, if any + if hostname[-1] == '.': + hostname = hostname[:-1] + + else: + hostname = hostquad + + return hostname + + +#################### + +# Paw through a log record, doing any reverse rosolution needed, +# confirm with user, and return name of the host to notify about +# the instrusion attempt. A null return means the user want to +# skip this record. + + +def ProcessLogRecord(logrecord): + + # Check for each known attack keyword + + sendto = "" + for attackkey in AttackKeys.keys(): + + logfield = logrecord.split() + if logrecord.count(attackkey): + + # Different attack records put the hostquad in different places + hostquad = logfield[AttackKeys[attackkey]] + if hostquad[-1] == ',': + hostquad = hostquad[:-1] # Strip trailing dots + + # Go do a reverse resolution if we need to + hostname = CheckIPReverse(hostquad) + + # Check for the case of getting a PTR record back + hostname = ReversePTR(hostname) + + # Check if we've seen this abuser before + if NameCache.has_key(hostname): + sendto = NameCache[hostname] + + # New one + else: + depth = 2 + DONE=FALSE + while not DONE: + + # Set depth of default response + default = HostDepth(hostname, depth) + + # Ask the user about it + st = raw_input("Who Gets Message for: <%s>? %s [%s] " % + (hostname[-40:], + " " * (40 - len(hostname)), + default)) + + # Parse the response + if st == "!": # Skip this record + sendto = "" + DONE = TRUE + + elif st.lower() == "r": # Less depth in recipient name + if depth > 2: + depth -= 1 + + elif st.lower() == "l": # More depth in recipient name + if depth < len(hostname.split('.')): + depth += 1 + + else: + if st: # User keyed in their own recipient + sendto = st + else: # User accepted the default + sendto = default + DONE = TRUE + + NameCache[hostname] = sendto # Cache it + + return sendto + +#################### + +# Check the passed hostname and see if it looks like a PTR record. +# If so, strip out the address portion, reverse it, and trying +# doing another reverse lookup. If not, just return the original hostname. + +def ReversePTR(hostname): + + tmp = hostname.split("in-addr.arpa") + + if len(tmp) > 1: # Looks like a PTR record + + tmp = tmp[0].split('.') # Get addr components + tmp.reverse() # and reverse their order + # Take the 1st four quads (some PTR records have more) + # and see if we can dig out a reverse. + hostname = CheckIPReverse('.'.join(tmp[1:5])) + + return hostname + + +#------------------------- Program Entry And Mail Loop -----------------------# + +logfile = open(LOG) +abuserfile = open(OUT, "w") + +# Read in the whole log logfileile +for logrecord in logfile.read().splitlines(): + + # Go check the record in no command line constraint given + # or a constraint is given and exits in the record + if not sys.argv[1] or logrecord.count(sys.argv[1]): + + sendto = ProcessLogRecord(logrecord) + if sendto: + abuserfile.write("abnot \"" + logrecord + "\" " + + sendto + "\n") + +logfile.close() +abuserfile.close()