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