#!/usr/local/bin/python # # abck - Part of the ABMGMT package from TundraWare Inc. # Copyright (c) 2001, TundraWare Inc., All Rights Reserved. # See the accompanying file called, 1-ABMGMT-License.txt # for Licensing Terms # # Build a report of all unauthorized access attempts # # Usage: abck [String To Match In Log Record] ########## #################### # 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.6 2001/07/16 22:50:39 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{1,3}$" #################### # 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 resolution 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("\nLog Record: %s\n Who Gets Message for: <%s>? %s [%s] " % (logrecord, 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 (len(sys.argv) == 1) or logrecord.count(sys.argv[1]): sendto = ProcessLogRecord(logrecord) if sendto: abuserfile.write("abnot \"" + logrecord + "\" " + sendto + "\n") logfile.close() abuserfile.close()