#!/usr/bin/python

# AttackLog.py - Collect and format the "Unrecognized access" log from the SMC 7004BR router.

# Usage: AttackLog.py [logfilename]

# If logfilename is not given, the script formats the cache kept by the router
# and then exits.  If a log file name is given, the script will poll the router
# three times a day, and put the results in the log file.

# Copywright (c) 2003 - John W. Peterson (linux AT saccade DOT com)
# Permission is granted to copy and use, so long as this copywrite notice
# remains intact, and any improvements are forwarded back to the original author.

import urllib, socket, time, os, sgmllib, htmllib, formatter, re, string, sys

# This is the default address on the local network for the SMC router's home page.
SMChost = '192.168.123.254'

# The SMC router requires a password to access the log data.  If the environment
# variable "SMCPassword" is set, then that will be used for the password, otherwise
# the script will prompt for it.

def fold(s):
	"""Wrap string s a bit to improve readability"""
	while len(s) > 0:
		print s[:80]
		s = s[80:]

def safeGetHostname(ip_addr):
	"""Get the hostname, but return UNKNOWN if DNS fails"""
	try:
		# gethostbyaddr is just too dog slow on Windows
		if (sys.platform != 'win32'):
			return socket.gethostbyaddr(ip_addr)[0]
		else:
			return ip_addr
	except:
		return 'UNKNOWN (%s)' % ip_addr
	
class menuParser(htmllib.HTMLParser):
	"""HTML parser class to extract the login information out of the SMC home page"""
	def __init__(self, router_password):
		htmllib.HTMLParser.__init__( self, formatter.NullFormatter() )
		self.loginData = []
		self.router_password = router_password

	def do_input( self, attrs ):
		"""Grab all of the INPUT tags in order to build the POST data"""
		d = dict(attrs)
		if (d['type'] == 'HIDDEN'):
			self.loginData.append((d['name'], d['value']))
			if (d['name'] == 'PSWD'):
				self.loginPSWD = d['value']	# This changes on each invocation
		if (d['type'] == 'PASSWORD'):
			self.loginData.append((d['name'], self.router_password))

def LoadPortDictionary():
	"""Load the IANA (http://www.iana.org/assignments/port-numbers) port numbers as a guide"""
	# The port-numbers file uses the syntax:
	#   portname	number/TYPE		Description
	# where TYPE = TCP | UDB
	port_dict = {}
	portRegexp = re.compile('([^ \t]+)[ \t]+([0-9]+)/([tu][dc]p)')
	f = file('port-numbers.txt','r')
	lastPort = -99
	while 1:
		s = f.readline()
		if (s == ''):
			break
		if (s[0] != '#'):
			p = portRegexp.search(s)
			if (p):
				port = int(p.group(2))
				if (port != lastPort):
					port_dict[port] = p.group(1)
	f.close()
	return port_dict

PortDict = LoadPortDictionary()

def ReportAttacks(sinceTime = 0, logFile = None):
	"""Query the SMC Barricade router for attacks on it, and report all attacks since sinceTime"""

	passwd = os.getenv('SMCPassword')
	if (passwd == None):
		passwd = raw_input('SMC Password:')
	mp = menuParser( passwd )

	login = urllib.urlopen('http://%s/menu.htm' % SMChost)
	loginText = login.read()
	mp.feed(loginText)
	login.close()

	mp.loginData = urllib.urlencode(mp.loginData)

	loginStr = """POST /cgi-bin/logi HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)
Host: %s
Content-Length: %d

""" % (SMChost, len(mp.loginData))

	if (loginText.find('Log in') > 0):
		# Log into the SMC...
		# Using urllib.urlopen waits for the status response before sending the
		# actual POST data.  This hangs with the SMC server, which seems to expect
		# the data and header in one go.
		#login2 = urllib.urlopen('http://%s/cgi-bin/logi' % SMChost, mp.loginData)
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		s.connect((SMChost,80))
		s.send(loginStr + mp.loginData)
		s.recv(2024)	# Should check for successful login
		s.close()

	attackData = urllib.urlopen('http://%s/syslog.htm' % SMChost)
	attackText = attackData.read()

	# The data coming back from the SMC is actually a bunch of JavaScript code
	# that gets turned into a web page on the fly.
	raw_attacks = attackText.split(';')
	attacks = []
	# This will parse the time (ms since now), TCP address and port out of the 'Unrecognized access' string
	logRegexp = re.compile("L2\(([0-9]+),.*from ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):[0-9]+ to ([TU][DC]P) port ([0-9]+)'")
	now = time.time()	

	if (logFile):
		log = file(logFile, 'a')
		log.write('----Checking at %s------\n' % time.strftime("%d-%b-%y %I:%M %p", time.localtime()))
		
	for a in raw_attacks:
		if a.find('Unrecognized access') != -1:
			m = logRegexp.search(a)
			if (m):
				when = now - int(m.group(1))/1000.0
				if (when > sinceTime):
					whenStr = time.strftime("%d-%b-%y %H:%M:%S",time.localtime(when))
					host = safeGetHostname(m.group(2))
					kind = m.group(3)
					port = int(m.group(4))
					if (PortDict.has_key(port)):
						portDesc = PortDict[port]
					else:
						portDesc = "unknown(%d)" % port
					attack = "%s (%10.2f) %s (%s) %s" % (whenStr, when, portDesc, kind, host)
					if (logFile):
						log.write(attack + "\n")
					else:
						print attack
	if (logFile):
		log.close()
	return when

if (len(sys.argv) > 1):
	lastCheck = 0
	while (1):
		lastCheck = ReportAttacks( lastCheck, sys.argv[1] )
		time.sleep(8 * 3600)
else:
	print ReportAttacks()


