#!/usr/bin/python

# Copyright (C) 2001 John Leach. <john@johnleach.co.uk>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

from string import *
import getopt
import sys
import fileinput
import socket
import re
import signal

def keyquit(signum, frame):
	print 'Aborted by user, exiting'
	sys.exit(-1)


def usage():
	print """packetparser v0.3 - John Leach <john@ecsc.co.uk> - http://people.ecsc.co.uk/~john
Usage: packetparser.py [OPTION]... [LOGFILE]

Options:
  -i, --interface	network interface to show (e.g: eth0)
  
Output format:
  -c,--console		console text format (default)
  -w,--web		web format, html
  -n,--nons		don't resolve ips to hostnames
  -p,--noports		don't resolve port numbers to service names

Misc:
  -h, --help		this message
"""

dns_cache = {}
services_by_ip = {}
line_count = 0

def read_services(filename):
	services_by_ip = {}
	fi = fileinput
	for line in fi.input(filename):
		if (line[0]<>"#") and (line <> ""):
			fields = split(line)
			if len(fields)>1: 
				ports = split(fields[1],"/")
				if not services_by_ip.has_key(ports[1]): 
					services_by_ip[ports[1]] = {}
				services_by_ip[ports[1]][ports[0]] = fields[0]

	return services_by_ip

def port_to_service(port,protocol):
	port = str(port)
	protocol = lower(protocol)
	global services_by_ip
	if services_by_ip.has_key(protocol):
		if services_by_ip[protocol].has_key(port):
			return services_by_ip[protocol][port]
	return port

def ip_to_host(ip):
	global dns_cache
	if dns_cache.has_key(ip): 
		return dns_cache[ip]
	else:
		try:
			(hostname, aliaslist, ipaddrlist) = socket.gethostbyaddr(ip)
		except:
			hostname = ip
		dns_cache[ip] = hostname
		return hostname

def process_packet(packet):
	global line_count, output

	# some protocols don't have src and dst ports (icmp for example)
	if not packet.has_key("DPT"):
		packet["DPT"] = "";
	if not packet.has_key("SPT"):
		packet["SPT"] = "";

	line_count = line_count + 1

	# attempt to resolve ips
	if donslookup:
		try:
			packet["SRC"] = ip_to_host(packet["SRC"])
		except: pass
		try:
			packet["DST"] = ip_to_host(packet["DST"])
		except: pass

	# resolve port names
	if doportlookup:
		packet["DPT"] = port_to_service(packet["DPT"],packet["PROTO"])
		packet["SPT"] = port_to_service(packet["SPT"],packet["PROTO"])

	# console output
	if output==1:
		print "%s: %s:%s\t =>\t %s:%s\tL:%s T:%s" % (packet["TIMESTAMP"], 
											packet["SRC"],
											packet["SPT"],
											packet["DST"],
											packet["DPT"],
											packet["LEN"],
											packet["TTL"])

	# web output
	if output==2:
		print """	<TR class="readrow%i">
				<TD><CODE><FONT SIZE=1>%s</CODE></TD>
				<TD><CODE><FONT SIZE=1>%s</CODE></TD>
				<TD><CODE><FONT SIZE=1>%s:%s</CODE></TD>
				<TD><CODE><FONT SIZE=1>%s</CODE></TD>
				<TD><CODE><FONT SIZE=1>%s</CODE></TD>
				<TD><CODE><FONT SIZE=1>%s</CODE></TD>
	</TR>""" 			% (	line_count % 2,
							packet["TIMESTAMP"],
							packet["PROTO"],
							packet["SRC"],
							packet["SPT"],
							packet["DPT"],
							packet["TTL"],
							packet["LEN"])


# Handle CTRL+C
signal.signal(signal.SIGINT, keyquit)

# defaults
interface = ""
output = 1
donslookup = 1
doportlookup = 1

# parse command line options
try:
	opts, args = getopt.getopt(sys.argv[1:], "cwhi:np", 
					["console", "web", "help",
					"interface=","nons", "noports"])
except getopt.error:
	usage()
	sys.exit(2)

# check all command line options and react accordingly
for o, value in opts:
	if o in ("-h","--help"):
		usage()
		sys.exit()
	
	if o in ("-i","--interface"):
		interface = value

	if o in ("-c","--console"):
		output = 1

	if o in ("-w","--web"):
		output = 2

	if o in ("-n","--nons"):
		donslookup = 0

	if o in ("-p","--noports"):
		doportlookup = 0

# setup file list, if no files are specified, use stdin
if args==[]: filename = ["-"]
else: filename = args

# put files in reverse order
filename.reverse()

# load the services file into an associative array
services_by_ip = read_services("/etc/services")

# output the table info if we're in web mode
if output==2:
	print """
	<TABLE CELLSPACING=0 CELLPADDING=5 BORDER=1>
	<TR>
		<TD><P class="label">Timestamp</P></TD>
		<TD><P class="label">Proto</P></TD>
		<TD><P class="label">Source</P></TD>
		<TD><P class="label">Port</P></TD>
		<TD><P class="label">TTL</P></TD>
		<TD><P class="label">Length</P></TD>
	</TR>"""

filedata = fileinput

regexp = re.compile(" IN=%s" % interface);

for file in filename:
	try:
		for line in filedata.input(file):
			# only process lines related to our interface
			if regexp.search(line):
				line = line[:-1] # remove the \n
				fields = re.split(" +", line) # split up by any number of spaces
				packet = {"DPT":"", "SPT":"", "PROTO":"", "DST":"", "SRC":"", "LEN":""}
				packet["TIMESTAMP"] = join(fields[:3]," ") # first 3 fields are date/time
				for field in fields: # parse all KEY=DATA entries in the line
					if find(field,"=")>0:
						pair = split(field,"=")
						packet[strip(pair[0])] = strip(pair[1])
				process_packet(packet) # go print it out or whatever
					
	except IOError:
		print sys.exc_value
		pass

# close up the table if we're in web mode
if output==2: 
	print "</TABLE>"
