Newer
Older
abck / abck
#!/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.7 2001/07/18 02:45:26 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 == "s":              # 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()