- #!/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()