#!/usr/bin/perl
#
# Chaosreader can trace TCP/UDP/... sessions and fetch application data
# from tcpdump or snoop logs. This is like an "any-snarf" program, it will
# fetch telnet sessions, FTP files, HTTP transfers (HTML, GIF, JPEG, ...),
# SMTP emails, etc ... from the captured data inside the network traffic
# logs. It creates a html index file that links to all the session details,
# including realtime replay programs for telnet, rlogin or IRC sessions;
# and reports such as image reports and HTTP GET/POST content reports.
# It also creates replay programs for telnet sessions, so that you can
# play them back in realtime (or even different speeds).
#
# Chaosreader can also run in standalone mode - where it invokes tcpdump or
# snoop (if they are available) to create the log files and then processes
# them.
#
# 15-Jun-2014, ver 0.96		https://github.com/brendangregg/Chaosreader
#
# Chaosreader currently lives in that github location. If 2014 sounds really
# old, you may want to run a web search for "chaosreader" in case it's updated
# somewhere else. Also see http//www.brendangregg.com/chaosreader.html.
#
# Recent versions:
#
# 11-Sep-2011, ver 0.95
# 24-Sep 2011, ver 0.95b
# 04-Jan 2012, ver 0.95c
# 10-Jan 2012, ver 0.95d
# 15-Mar 2013, ver 0.95e
# 15-Apr 2013, ver 0.95f
# 18-Apr 2013, ver 0.95g
# 12-Apr 2014, ver 0.95h
# 14-Apr 2014, ver 0.95i
# 12-jun 2014, ver 0.95.10
#
# QUICK USAGE:
#	tcpdump -s9000 -w out1; chaosreader out1; netscape index.html
#  or,
#	snoop -o out1; chaosreader out1; netscape index.html
#  or,
#	ethereal (save as "out1"); chaosreader out1; netscape index.html
#  or,
#	chaosreader -s 5; netscape index.html
#
#
# USAGE: chaosreader [-adehiknqrvxAHIRTUXY] [-D dir]
#                    [-b port[,...]] [-B port[,...]]
#                    [-j IPaddr[,...]] [-J IPaddr[,...]]
#                    [-l port[,...]] [-L port[,...]] [-m bytes[k]]
#                    [-M bytes[k]] [-o "time"|"size"|"type"|"ip"]
#                    [-p port[,...]] [-P port[,...]]
#                    infile [infile2 ...]
#
#        chaosreader -s [mins] | -S [mins[,count]]
# 	           [-z] [-f 'filter']
#
#    chaosreader           # Create application session files, indexes
#
#    -a, --application     # Create application session files (default)
#    -d, --preferdns       # Show DNS names instead of IP addresses
#    -e, --everything      # Create HTML 2-way & hex files for everything
#    -h                    # Print a brief help
#    --help                # Print verbose help (this) and version
#    --help2               # Print massive help
#    -i, --info            # Create info file
#    -q, --quiet           # Quiet, no output to screen
#    -r, --raw             # Create raw files
#    -v, --verbose         # Verbose - Create ALL files .. (except -e)
#    -x, --index           # Create index files (default)
#    -A, --noapplication   # Exclude application session files
#    -H, --hex             # Include hex dumps (slow)
#    -I, --noinfo          # Exclude info files
#    -R, --noraw           # Exclude raw files
#    -T, --notcp           # Exclude TCP traffic
#    -U, --noudp           # Exclude UDP traffic
#    -Y, --noicmp          # Exclude ICMP traffic
#    -X, --noindex         # Exclude index files
#    -k, --keydata         # Create extra files for keystroke analysis
#    -n, --names           # Include hostnames in hyperlinked HTTPlog (HTML)
#    -D dir    --dir dir        # Output all files to this directory
#    -b 25,79  --playtcp 25,79  # replay these TCP ports as well (playback)
#    -B 36,42  --playudp 36,42  # replay these UDP ports as well (playback)
#    -l 7,79   --htmltcp 7,79   # Create HTML for these TCP ports as well
#    -L 7,123  --htmludp 7,123  # Create HTML for these UDP ports as well
#    -m 1k     --min 1k         # Min size of connection to save ("k" for Kb)
#    -M 1024k  --max 1k         # Max size of connection to save ("k" for Kb)
#    -o size   --sort size      # sort Order: time/size/type/ip (Default time)
#    -p 21,23  --port 21,23     # Only examine these ports (TCP & UDP)
#    -P 80,81  --noport 80,81   # Exclude these ports (TCP & UDP)
#    -s 5      --runonce 5      # Standalone. Run tcpdump/snoop for 5 mins.
#    -S 5,10   --runmany 5,10   # Standalone, many. 10 samples of 5 mins each.
#    -S 5      --runmany 5      # Standalone, endless. 5 min samples forever.
#    -z        --runredo        # Standalone, redo. Rereads last run's logs.
#    -j 10.1.2.1  --ipaddr 10.1.2.1    # Only examine these IPs
#    -J 10.1.2.1  --noipaddr 10.1.2.1  # Exclude these IPs
#    -f 'port 7'  --filter 'port 7'    # With standalone, use this dump filter.
#
# eg1,
#      tcpdump -s9000 -w output1        # create tcpdump capture file
#      chaosreader output1              # extract recognised sessions, or,
#      chaosreader -ve output1          # gimme everything, or,
#      chaosreader -p 20,21,23 output1  # only ftp and telnet...
# eg2,
#      snoop -o output1                 # create snoop capture file instead
#      chaosreader output1              # extract recognised sessions...
# eg3,
#      chaosreader -S 2,5      # Standalone, sniff network 5 times for 2 mins
#                              # each. View index.html for progress (or .text)
#
# Output Files: Many will be created, run this in a clean directory.
#
#  Short example:
#   index.html			Html index (full details)
#   index.text			Text index
#   index.file			File index for standalone redo mode
#   image.html			HTML report of images
#   getpost.html		HTML report of HTTP GET/POST requests
#   session_0001.info		Info file describing TCP session #1
#   session_0001.telnet.html	HTML coloured 2-way capture (time sorted)
#   session_0001.telnet.raw	Raw data 2-way capture (time sorted)
#   session_0001.telnet.raw1	Raw 1-way capture (assembeled) server->client
#   session_0001.telnet.raw2	Raw 1-way capture (assembeled) client->server
#   session_0002.web.html	HTML coloured 2-way
#   session_0002.part_01.html	HTTP portion of the above, a HTML file
#   session_0003.web.html	HTML coloured 2-way
#   session_0003.part_01.jpeg	HTTP portion of the above, a JPEG file
#   session_0004.web.html	HTML coloured 2-way
#   session_0004.part_01.gif	HTTP portion of the above, a GIF file
#   session_0005.part_01.ftp-data.gz	An FTP transfer, a gz file.
#   ...
#
#  The convention is:
#   session_*		TCP Sessions
#   stream_*		UDP Streams
#   icmp_*		ICMP packets
#   index.html		HTML Index
#   index.text		Text Index
#   index.file		File Index for standalone redo mode only
#   image.html		HTML report of images
#   getpost.html	HTML report of HTTP GET/POST requests
#   *.info		Info file describing the Session/Stream
#   *.raw		Raw data 2-way capture (time sorted)
#   *.raw1 		Raw 1-way capture (assembeled) server->client
#   *.raw2		Raw 1-way capture (assembeled) client->server
#   *.replay		Session replay program (perl)
#   *.partial.*		Partial capture (tcpdump/snoop were aware of drops)
#   *.hex.html		2-way Hex dump, rendered in coloured HTML
#   *.hex.text		2-way Hex dump in plain text
#   *.X11.replay	X11 replay script (talks X11)
#   *.textX11.replay	X11 communicated text replay script (text only)
#   *.textX11.html	2-way text report, rendered in red/blue HTML
#   *.keydata		Keystroke delay data file. Used for SSH analysis.
#
# Modes:
#  * Normal - eg "chaosreader infile", this is where a tcpdump/snoop file
#    was created previously and chaosreader reads and processes it.
#  * Standalone, once - eg "chaosreader -s 10", this is where chaosreader
#    runs tcpdump/snoop and generates the log file, in this case for 10 i
#    minutes, and then processes the result. Some OS's may not have
#    tcpdump or snoop available so this will not work (instead you may be
#    able to get Ethereal, run it, save to a file, then use normal mode).
#    There is a master index.html and the report index.html in a sub dir,
#    which is of the format out_YYYYMMDD-hhmm, eg "out_20031003-2221".
#  * Standalone, many - eg "chaosreader -S 5,12", this is where chaosreader
#    runs tcpdump/snoop and generates many log files, in this case it
#    samples 12 times for 5 minutes each. While this is running, the master
#    index.html can be viewed to watch progress, which links to minor
#    index.html reports in each sub directory.
#  * Standalone, redo - eg "chaosreader -ve -z", (the -z), this is where
#    a standalone capture was previously performed - and now you would like
#    to reprocess the logs - perhaps with different options (in this case,
#    "-ve"). It reads index.file to determine which capture logs to read.
#  * Standalone, endless - eg "chaosreader -S 5", like standalone many -
#    but runs forever (if you ever had the need?). Watch your disk space!
#
# Note: this is a work in progress, some of the code is a little unpolished.
#
# Advice:
#  * Run chaosreader in an empty directory.
#  * Create small packet dumps. Chaosreader uses around 5x the dump size
#    in memory. A 100Mb file could need 500Mb of RAM to process.
#  * Your tcpdump may allow "-s0" (entire packet) instead of "-s9000".
#  * Beware of using too much disk space, especially standalone mode.
#  * If you capture too many small connections giving a huge index.html,
#    try using the -m option to ignore small connections. eg "-m 1k".
#  * snoop logs may actually work better. Snoop logs are based on RFC1761,
#    however there are many varients of tcpdump/libpcap and this program
#    cannot read them all. If you have Ethereal you can create snoop logs
#    during the "save as" option. On Solaris use "snoop -o logfile".
#  * tcpdump logs may not be portable between OSs that use different sized
#    timestamps or endian.
#  * Logs are best created in a memory filesystem for speed, usually /tmp.
#  * For X11 or VNC playbacks, first practise by replaying a recent captured
#    session of your own. The biggest problem is colour depth, your screen
#    must match the capture. For X11 check authentication (xhost +), for
#    VNC check the viewers options (-8bit, "Hextile", ...)
#  * SSH analysis can be performed with the "sshkeydata" program as
#    demonstrated on http://www.brendangregg.com/sshanalysis.html .
#    chaosreader provides the input files (*.keydata) that sshkeydata
#    analyses.
#
# Bugs: The following assumptions may cause problems (check for new vers);
#  * A lower port number = the service type. Eg with ports 31247 and 23,
#    the actual type of session is telnet (23). This may not work for
#    some things (eg, VNC).
#  * Time based order is more important for 2-way sessions (eg telnet),
#    SEQ order is more import for 1-way transfers (eg ftp-data).
#  * One particular TCP session isn't active for long enough that the SEQ
#    number loops (or even wraps).
#
# WARNING: Please don't use this software for anything illegal. That definition
# differs for every country, please check the law first.
# This is a great network troubleshooting and development tool, not a
# "cracking" or "hacking" tool - a misidentification that could render owning
# this software illegal in some countries.
#
# SEE ALSO: wireshark (GUI packet viewer), dsniff (sniffing toolkit)
#
# COPYRIGHT: Copyright (c) 2003, 2004 Brendan Gregg.
#            Copyright (c) 2008 Indian Larry.
#            Copyright (c) 2011, 2012, 2013 Jens Lechtenbörger.
#            Copyright (c) 2014 Pavel Hančar, Jens Lechtenbörger, Pex pexnet0@gmail.com.
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
# Authors: Brendan Gregg      [Sydney, Australia]
#          Indian Larry       [http://refrequelate.blogspot.com/]
#          Jens Lechtenbörger [Münster, Germany]
#          Pavel Hančar       [Brno, Czech Republic]
#		   Pex 				  [https://github.com/pexnet]
#
# Todo:
#  * Rework code to improve structure.
#  * Add more application protocol filters. ARP, RARP.
#  * Ensure current application filters are robust (more testing).
#  * Process captured filenames from FTP, HTTP and NFS transfers.
#  * Add more file types (magic numbers/frequency analysis).
#  * Process more IPv6 extension headers, ICMP types.
#
# 28-Sep-2003	Brendan Gregg	Began writing this.
# 08-Oct-2003	   "      "	Released version 0.7 beta
# 09-Oct-2003	   "      "  	Added telnet replays
# 12-Oct-2003	   "      "  	Added IRC ports and replays
# 19-Oct-2003	   "      "  	Made code more robust on different OSs
# 01-Nov-2003	   "	  "	Code cleanup, complex data types, IPv6, ICMP
# 03-Nov-2003	   "	  "	Added Standalone mode, standalone redo, ...
# 05-Nov-2003	   "	  "	Added Image indexes, GETPOST indexes
# 15-Nov-2003	   "	  "	Added HTTP proxy style log, hex dumps
# 27-Jan-2004	   "	  "	Released experimental X11 & VNC processing
# 30-Mar-2004	   "	  "	802.11b, sorts, less RAM used, tun packets.
# 01-May-2004	   "	  "	CLI enhanced, faster, SSH analysis.
#
# 11-Sep-2011, Jens Lechtenbörger:
# - Switch from GPLv2 to GPLv3
# - Integrate diff from
#   http://refrequelate.blogspot.com/2008/07/more-de-chunking-chaosreader-patch.html
#   to reassemble chunked HTTP transfers.
# - Parse linux cooked captures, which result from listening on "any"
#   interface.  (Chaosreader0.94 does not produce any output for such
#   pcap files.)
# - Use HTTP content-type to identify file types such as HTML, XML,
#   Javascript, CSS; use those types for better file extensions than
#   "data".
# - Uncompress gzip'ed data.
# - Add new command line switch to show host names in HTTPlog and to
#   create href-links from HTTPlog rows to the corresponding rows in
#   the table on index.html.
# - Several minor improvements (see comments with "JL:").
#
# 24-Sep-2011, Jens Lechtenbörger:
# - More systematic Content-Type handling based on MIME types.
# - More image types included in Image Report based on MIME types.
#
# 4-Jan-2012, Jens Lechtenbörger:
# - Parsing of DNS replies to show names instead of IP addresses (new
#   command line switch -d) and to save DNS replies as text files.
#
# 10-Feb-2012, Jens Lechtenbörger:
# - Use file magic (again) to detect MIME type if HTTP's Content-Type is
#   application/octet-stream.  (Some Web servers report images incorrectly.)
#
# 15-Mar-2013, Jens Lechtenbörger:
# - Create additional HTTP log file in text format.  That file contains one
#   line per GET request, which shows the referrer (if present) and indicates
#   whether cookies have been sent in the request or received in the reply.
#
# 15-Apr-2013, Jens Lechtenbörger:
# - Link additional HTTP log file from index.html.
# - Also look for images in plain/text Content-Types (seen in the wild).
# - Extend GET/POST report to include all GETs; not only those including a
#   question mark (with parameters).
#
# 18-Apr-2013, Jens Lechtenbörger:
# - Build new "External Image Report" (linked from index.html), where images
#   are embedded from their origin servers.
#   In contrast, the "Image Report" points to images on the local hard disk.
#   The new report may be more suitable for publication on Web pages as it
#   does not require to publish (potentially copyright protected) images.
# - Parse CNAME DNS replies to show original host names (which are hopefully
#   more familiar than aliases).
# - Show also empty parts on index.html that result from cache hits.
# - Create directory passed after switch "-D".
#
# 12-Apr-2014, Pavel Hančar:
# - Optimized hexadecimal dumps to use less memory.
# - Modified "IP Count" to "IP and MAC Count".
# - Fixed a few bugs concerning output.
#
# 14-Apr-2014, Jens Lechtenbörger:
# - Also create HTML files for ports 8118 (polipo) and 9050 (Tor) and treat
#   both as HTTP traffic (quick hack, works for me).
# - Improved handling of TCP streams with same source and destination IP
#   address (e.g., from localhost to localhost).
# 
# 12 Jun 2014 Pex
# - support for deflate
# Jens frequently calls this program with options "-vden -D <somedir>".

use Getopt::Long;
use Benchmark;
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
use IO::Uncompress::Inflate qw(inflate $InflateError) ;
use IO::Uncompress::RawInflate qw(rawinflate $RawInflateError) ;
use Net::DNS::Packet;


#####################
# --- Variables ---
#

#
#  Some defaults
#
$PERL = "/usr/bin/perl";		# perl path for replay scripts
$integerSize = length(pack('I',0));	# can make a difference for tcpdumps
$the_date = scalar localtime();		# this is printed in the reports
$WRAP = 108;				# wordwrap chars
$BM = 0;				# benchmark counter
$| = 1;					# flush output

#
#  The following is needed for old perl5 multiline matching. New perl5 uses
#  a "/s" on the RE (which is used in this program as well).
#
#$* = 1;					# old perl5

#
#  These ports have been selected to be saved as coloured 2-way HTML files
#
@Save_As_HTML_TCP_Ports = (21,23,25,79,80,109,110,119,143,513,514,1080,
 3128,4110,5000,5555,6660,6665,6666,6667,6668,7000,8000,8080,8118,9000,9050);
@Save_As_HTML_UDP_Ports = (53);

#
#  These ports have been selected to be saved as realtime playback scripts
#  (telnet, login, and numerous IRC ports)
#
@Save_As_TCP_Playback_Ports = (23,513,4110,5000,5555,6660,6666,6667,
 6668,7000,8000,9000);
@Save_As_UDP_Playback_Ports = (7);

#
#  These are the X11 ports to save as X11 playback scripts
#
@Save_As_X11_Playback_Ports = (6000,6001,6002,6003,6004,6005,6006,6007);

#
#  These X11 ports will have the text saved as coloured 2-way HTML files
#
@Save_As_HTML_X11_Ports = (6000,6001,6002,6003,6004,6005,6006,6007);

#
#  These are the VNC ports to save as VNC playback scripts
#
@Save_As_VNC_Playback_Ports = (5900,5901,5902,5903,5904,5905,5906,5907);

#
# --- Arguments ---
#
&Process_Command_Line_Arguments();

### Record program start
$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
$Bench{$BM}{text} = "Program Start";

#
#  Load some lookup tables for number -> name translations.
#
&Load_Etc_Services();
&Set_IP_Protocols();
&Set_ICMP_Types();
&Set_Result_Names();
&Set_X11_Codes();
&Set_X11_KeyCodes();
&Set_VNC_Codes();
&Set_MIME_Types(); #JL
&Set_DNS(); #JL

###########################
# --- MODE 1 - Normal --- #
###########################

#
#  Process log files,
#
if ($Arg{normal}) {
	#
	#  Initial values
	#
	$frame = 0; $number = 0;
	%IP = (); %TCP = (); %UDP = (); %ICMP = (); %Count = (); %Hex = ();

	### Print version
	&Print_Welcome();

	######################################
	# --- INPUT - Read Packet Log(s) ---
	#

	foreach $filename (@{$Arg{infiles}}) {
		#
		#  Check input file type and Open
		#
		&Open_Input_File($filename);

		#
		#  Read though the entire input file, saving all packet
		#  data in memory (mainly %TCP and %UDP).
		#
		&Read_Input_File();
	}


	#############################################
	# --- OUTPUT - Process TCP/UDP Sessions ---
	#

	### cd to output
	&Chdir($Arg{output_dir});
	&Print_Header2();

	### Determine Session and Stream time order
	%Index = (); %Image = (); %ExtImage = (); %GETPOST = ();
	&Sort_Index();

	#
	#  Process %TCP and create session* output files, write %Index
	#
	&Process_TCP_Sessions();

	#
	#  Process %UDP and create session* output files, write %Index
	#
	&Process_UDP_Streams();

	#
	# Process %ICMP
	#
	&Process_ICMP();

	#
	#  Create Index Files from %Index
	#
	&Create_Index_Files();
	&Create_Log_Files();

	###############
	# --- END ---
	#
	&Print_Footer1();
}


###############################
# --- MODE 2 - Standalone --- #
###############################

elsif ($Arg{standalone}) {

   ############################################################
   # --- STANDALONE - Create Packet Logs and Process them ---
   #

   $limit = $Arg{count};
   $filenum = 0;

   ### Check for the sniffer command
   &Check_Command();

   ### cd to output
   &Chdir($Arg{output_dir});

   ### Print welcome
   &Print_Welcome();

   #
   #  MAIN LOOP
   #
   while ($limit != 0) {
	#
	#  Create a meaningful directory and filename
	#
	@Times = localtime();
	$dirname = sprintf("out_%d%02d%02d-%02d%02d",($Times[5]+1900),
	 $Times[4],$Times[3],$Times[2],$Times[1]);
	$filename = "$dirname.log";

	#
	#  Initial values
	#
	$frame = 0; $number = 0;
	%IP = (); %TCP = (); %UDP = (); %ICMP = (); %Count = (); %Hex = ();

	#
	#  Record details in a Master Index
	#
	$Master[$filenum]{starttime} = scalar localtime();
	$Master[$filenum]{duration} = - time();			# will +end time
	$Master[$filenum]{dir} = $dirname;
	$Master[$filenum]{file} = $filename;

	#
	#  Create and cd to output dir
	#
	mkdir ("$dirname",0755) || die "ERROR01: Couldn't mkdir (perms?): $!\n";
	chdir "$dirname" || die "ERROR02: Couldn't cd $dirname: $!\n";

	print "\nCreating log: $dirname/$filename\n" unless $Arg{quiet};

	#
	#  fork, so that one process can exec tcpdump/snoop while the other
	#  sleeps and then kills it.
	#
	$pid = fork();
	die "ERROR03: Can't fork (resources?): $!\n" if (! defined $pid);

	if ($pid == 0) {
		###############################
		# --- CREATE - Packet Log ---
		#

		print "Running: $command $filename $Arg{filter}\n"
		 unless $Arg{quiet};
		### exec, so $pid points to sniffer
		exec("$command $filename $Arg{filter}") &&
			die "ERROR04: couldn't run $command file: $!\n";
	} else {
		### Wait for logfile to be populated
		sleep($Arg{mins} * 60);

		### Kill child (TERM, INT)
		kill 15, $pid;
		kill 2, $pid;
	}
	exit if $pid == 0;	# check for impossibility


	### Record end time, duration, size
	$Master[$filenum]{endtime} = scalar localtime();
	$Master[$filenum]{duration} += time();
	# finish writing the log before reading it's size
	system("sync") if (($^O eq "linux") || ($^O eq "solaris"));
	$Master[$filenum]{size} = -s "$filename";

	print "\nProcessing:   $dirname/$filename\n" unless $Arg{quiet};
	$bak = $Arg{quiet}; $Arg{quiet} = 1;

	###############################
	# --- INPUT - Process Log ---
	#
	&Open_Input_File($filename);

	### Populate memory (%TCP, %UDP, ...).
	&Read_Input_File();

	#############################################
	# --- OUTPUT - Process TCP/UDP Sessions ---
	#

	### Determine Session and Stream time order
	%Index = (); %Image = (); %ExtImage = (); %GETPOST = ();
	&Sort_Index();

	### Process %TCP, %UDP, ..., create output fies, write %Index
	&Process_TCP_Sessions();
	&Process_UDP_Streams();
	&Process_ICMP();

	### Create Index Files from %Index
	&Create_Index_Files();
	&Create_Log_Files();


	chdir ".." || die "ERROR05: Couldn't cd ..: $!\n";

	$Arg{quiet} = $bak;

	### Create Master Index from @Master
	&Create_Index_Master();

	$limit--;
	$filenum++;
   }

}


##########################
# --- MODE 3 - Redo  --- #
##########################

elsif ($Arg{redo}) {

   #############################################################
   # --- STANDALONE REDO - Redo last run from sniffer logs ---
   #

   $filenum = 0;

   ### Read index.file for logs to process
   &Load_Index_File();

   ### Print welcome
   &Print_Welcome();

   #
   #  MAIN LOOP
   #
   for ($index=0; $index <= $#Master; $index++) {

	### Get previous run values
	$dirname = $Master[$index]{dir};
	$filename = $Master[$index]{file};

	### Initial values
	$frame = 0; $number = 0;
	%IP = (); %TCP = (); %UDP = (); %ICMP = (); %Count = (); %Hex = ();

	### Create and cd to output dir
	chdir "$dirname" || die "ERROR06: Couldn't cd $dirname: $!\n";

	print "Processing:   $dirname/$filename\n" unless $Arg{quiet};
	$bak = $Arg{quiet}; $Arg{quiet} = 1;

	###############################
	# --- INPUT - Process Log ---
	#
	&Open_Input_File($filename);

	### Populate memory (%TCP, %UDP, ...).
	&Read_Input_File();

	#############################################
	# --- OUTPUT - Process TCP/UDP Sessions ---
	#

	### Determine Session and Stream time order
	%Index = (); %Image = (); %ExtImage = (); %GETPOST = ();
	&Sort_Index();

	### Process %TCP, %UDP, ..., create output fies, write %Index
	&Process_TCP_Sessions();
	&Process_UDP_Streams();
	&Process_ICMP();

	### Create Index Files from %Index
	&Create_Index_Files();
	&Create_Log_Files();

	chdir ".." || die "ERROR07: Couldn't cd ..: $!\n";
	$Arg{quiet} = $bak;

	$limit--;
	$filenum++;
   }
   ### Create Master Index from @Master
   &Create_Index_Master();
}


#
#  BENCHMARK REPORT
#
if ($Arg{bench}) {
	$Bench{++$BM}{mark} = new Benchmark;
	$Bench{$BM}{text} = "Program End";

	print "\nBenchmarks,\n\n";
	for ($bm=1; $bm <= $BM; $bm++) {
		$bdiff = timediff($Bench{$bm}{mark},$Bench{1}{mark});
		printf(" %-32s %s\n",$Bench{$bm}{text},timestr($bdiff));
	}
}


#####################
# --- SUBROUTINES ---

# (Most of these subroutines are used as shortcuts to code, not traditional
#  scoped subroutines as with other languages)



# Open_Input_File - open the packet log specified. This checks the header
#	of the file to determine whether it is a tcpdump/libpcap or snoop
#	log (including several styles of tcpdump/libpcap).
#
sub Open_Input_File {

	my $infile = shift;
	my ($length,$size);

	$Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
	$Bench{$BM}{text} = "Open Input File";

	print "Opening, $infile\n\n" unless $Arg{quiet};

	#
	#  Open packet log
	#
	open(INFILE,$infile) || die "Can't open $infile: $!\n";
	binmode(INFILE);	# for backward OSs

	#
	#  Fetch header
	#
	$length = read(INFILE,$header,8);
	die "ERROR08: Can't read from $infile\n" if $length < 8;

	### Print status
	print "Reading file contents,\n" unless $Arg{quiet};
	$SIZE = -s $infile;

	#
	#  Try to determine if this is a tcpdump or a snoop file
	#
	($ident) = unpack('a8',$header);

	if ($ident =~ /^snoop/) {

		$TYPE = "snoop";
		$length = read(INFILE,$header,8);
		($version,$type) = unpack('NN',$header);

	} elsif ($ident =~ /^\241\262\303\324|^\324\303\262\241/ ||
		 $ident =~ /^\241\262\315\064|^\064\315\262\241/) {

		$TYPE = "tcpdump";
		$ident = unpack('a4',$header);  # try again
		# standard/modified defines style, 1/2 defines endian
		if ($ident =~ /^\241\262\303\324/) { $STYLE = "standard1"; }
		if ($ident =~ /^\324\303\262\241/) { $STYLE = "standard2"; }
		if ($ident =~ /^\241\262\315\064/) { $STYLE = "modified1"; }
		if ($ident =~ /^\064\315\262\241/) { $STYLE = "modified2"; }
		if ($STYLE =~ /1$/) {
			# reread in big-endian
			($ident,$major,$minor) = unpack('a4nn',$header);
		} else {
			# reread in little-endian
			($ident,$major,$minor) = unpack('a4vv',$header);
		}

		#
		#  Check tcpdump header carefully to ensure this is ver 2.4.
		#
		if ($major != 2 && $minor != 4) {
			#
			#  Die if this is an unknown version. (there could
			#  be new vers of tcpdump/libpcap in the future).
			#
			print STDERR "ERROR09: Wrong tcpdump version ";
			print STDERR "($version.$type).\n(expected 2.4).\n";
			exit 1;
	}
		#
		#  Nudge the filehandle past the rest of the header...
		#
		$length = read(INFILE,$header_rest,16);

	} else {
		#
		#  Die - unknown file format
		#
		print STDERR "ERROR10: Input dosen't look like a tcpdump or ";
		print STDERR "snoop output file.\n\tIf it is tcpdump, it ";
		print STDERR "may be a wrong or new version.\n";
		exit 1;
	}

	### Record the filename into the global %Arg
	$Arg{infile} = $infile;
}



# Read_Input_File - this subroutine loops through the records in the packet
#  log, storing all the TCP and UDP data into %TCP and %UDP. (see the end
#  of the program for the structure of these data types). %Count is also
#  populated with various frequency counts.
#
sub Read_Input_File {
   my ($trailers,$pppoe_verNtype,$pppoe_code,$pppoe_id,$pppoe_length,
    $ppp_protocol,$wless_fc,$wless_version,$wless_type,$wless_duration,
    $wless_subtype,$wless_from,$wless_to,$wless_flag,$wless_WEP,
    $wless_bss,$wless_src,$wless_dest,$wless_cksum,$llc_head,$llc_control,
    $llc_org,$llc_type,$wless_OK,$bytes,$counter,$packets);

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Read Input File - start";

   local $packet = 0;			# counter
   if ($TYPE eq "snoop") {
   	$bytes = 16;
   } else {
	$bytes = 24;
   }

   #
   # --- Pass #1, Store IP data in memory (%IP) --
   #
   while (1) {
	#
	# --- Read Record from Log ---
	#
	if ($TYPE eq "snoop") {
		&Read_Snoop_Record();		# will "last" on error
		$packet_data = $snoop_data;
		$packet_time = $snoop_seconds;
		$packet_timefull = $snoop_seconds + $snoop_msecs/1000000;
		$record_size = $snoop_length_rec;
	} else {
		&Read_Tcpdump_Record();		# will "last" on error
		$packet_data = $tcpdump_data;
		$packet_time = $tcpdump_seconds;
		$packet_timefull = $tcpdump_seconds + $tcpdump_msecs/1000000;
		$record_size = $tcpdump_length + ($integerSize * 2 + 8);
	}

	### Print status summary
	unless ($Arg{quiet}) {
		$bytes += $record_size;
		if (($packet % 16) == 0) {
			printf("%s %2.0f%% (%d/%d)","\b"x24,
			 (100*$bytes/$SIZE),$bytes,$SIZE);
		}
	}

	#
	# --- Parse TCP/IP layers (a little ;) ---
	#

	#-------------------------------------------------------------------
	#
	#  Wireless, 802.11b
	#

	$decoded = 0;		# this flag is true if wireless was found

	# unpack a little first, (efficiency)
	($wless_fc) = unpack('H4',$packet_data);

	# this matches on possible send or receive wireless traffic, however
	# this could also be the start of an 802.3 frame - making this part
	# of a MAC address. (The IEEE list on OUIs had these as unassigned).
	if ($wless_fc =~ /^080[1256]/) {
		# now dig deeper,
		# (this is one form of 802.11 - the form we are interested
		# in, however note that there is a lot more to 802.11).
		($wless_fc,$wless_duration,$wless_bss,$wless_src,
		 $wless_dest,$wless_cksum,$llc_head,$llc_control,$llc_org,
		 $llc_type,$ether_data)
		 = unpack('nnH12H12H12na2CH6H4a*',$packet_data);

		$wless_to = $wless_fc & 1;

		# Check this is IP and encapsulated Ethernet,
		if (($llc_type eq "0800") && ($llc_org eq "000000")) {

		   ### Populate ether variables for use later on
		   $ether_type = $llc_type;
		   if ($wless_to) {
			$ether_dest = $wless_dest;
			$ether_src = $wless_src;
		   } else {
			$ether_dest = $wless_src;
			$ether_src = $wless_dest;
		   }

		   $decoded = 1;	# remember we did this
		}
		# (else try redecoding this using 802.3)
	}

	#-------------------------------------------------------------------
	#
	#  Tun device
	#

	# unpack a little first, (efficiency)
	($tun_id) = unpack('H8',$packet_data);

	# this checks if the frame looks like a tun device frame
	if ($tun_id eq "02000000") {
		# now dig deeper,
		($tun_id,$ether_data) = unpack('a4a*',$packet_data);
		$ether_src = "0";
		$ether_dest = "0";
		$ether_type = "0800";

		$decoded = 1;		# remember we did this
	}

	#-------------------------------------------------------------------
	#
	#  Ethernet, 802.3
	#

	### Unpack ether data
	($ether_dest,$ether_src,$ether_type,$ether_data) =
	 unpack('H12H12H4a*',$packet_data) unless $decoded;

	### Count ether types seen
	$Count{EtherType}{$ether_type}++;
	$CountMaster{EtherType}{$ether_type}++;

	#
	#  Process extended Ethernet types (wireless, PPPoE, VLAN)
	#

	### PPPoE
	if ($ether_type eq "8864") {
		($pppoe_verNtype,$pppoe_code,$pppoe_id,$pppoe_length,
		 $ppp_protocol,$ether_data) = unpack("CCnnna*",$ether_data);

		### Skip anything but data (we just want data - code 0)
		next if $pppoe_code != 0;

		# (May like to add code here later to process $ppp_protocol,
		# eg, to process LCP).
	}
	### VLAN tagged
	elsif ($ether_type eq "8100") {
	  ($vlan_PCP, $orig_ether_type, $ip_rest) = unpack('H4H4a*', $ether_data);
	  $ether_data = $ip_rest;
	}

	elsif (($ether_type ne "0800") && ($ether_type ne "86dd")) {
	    # JL: Try linux cooked capture
	    ($lptype,$lladdr_type,$lladdr_len,
	     $ether_src,$ll_dummy,$ether_type,$ether_data) =
		unpack('nnnH12nH4a*',$packet_data) unless $decoded;
	    if ($ether_type ne "0800") {
		next;
	    }
	}

	#-------------------------------------------------------------------
	#
	#  IP
	#

	### Check for IP ver
	($ip_verNihl,$ip_rest) = unpack('Ca*',$ether_data);
	$ip_ver = $ip_verNihl & 240;
	$ip_ver = $ip_ver >> 4;

	if ($ip_ver == 4) {

		#-----------------------------------------------------------
		#
		#  IPv4
		#

		### Unpack IP data
		($ip_verNihl,$ip_tos,$ip_length,$ip_ident,$ip_flagNfrag,
		 $ip_ttl,$ip_protocol,$ip_checksum,@ip_src[0..3],
		 @ip_dest[0..3],$ip_data) = unpack('CCnnnCCa2CCCCCCCCa*',
		 $ether_data);

		### Get frag and flag data
		$ip_frag = $ip_flagNfrag & 8191;
		$ip_flag = $ip_flagNfrag & 57344;
		$ip_flag = $ip_flag >> 13;
		$ip_MF = $ip_flag & 1;

		### Strip off IP options if present
		$ip_ihl = $ip_verNihl & 15;
		$ip_ihl = $ip_ihl << 2;
		$ip_options_num = $ip_ihl - 20;
		if ($ip_options_num > 0) {
			($ip_options,$ip_data) =
			 unpack("a${ip_options_num}a*",$ip_data);
		}

		### Strip off Ethernet trailers
		$ip_dlength = $ip_length - $ip_options_num - 20;
		($ip_data,$trailers) = unpack("a${ip_dlength}a*",$ip_data);

		### Build text strings of IP addresses
		$ip_src = sprintf("%u.%u.%u.%u",@ip_src);
		$ip_dest = sprintf("%u.%u.%u.%u",@ip_dest);

	} elsif ($ip_ver == 6) {

		#-----------------------------------------------------------
		#
		#  IPv6
		#
		($ip_verNihl,$ip_flow,$ip_length,$ip_next,$ip_hop,
		 @ip_src[0..15],@ip_dest[0..15],$ip_data) =
		 unpack('Ca3nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCa*',
		 $ether_data);
		$ip_protocol = $ip_next;

		### Build text strings of IP addresses
		$ip_src = sprintf("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x",
		 @ip_src);
		$ip_dest = sprintf("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x",
		 @ip_dest);

		### Compress IPv6 text Address
		$ip_src =~ s/:00:/:0:/g;
		$ip_src =~ s/:00:/:0:/g;
		$ip_dest =~ s/:00:/:0:/g;
		$ip_dest =~ s/:00:/:0:/g;
		$ip_src =~ s/(:0)+/::/;
		$ip_dest =~ s/(:0)+/::/;


		#
		#  Check for IPv6 Fragmentation (embedded)
		#
		if ($ip_protocol == 44) {
			($ip_next,$ip_reserved,$ip_fragNmf,$ip_ident,$ip_data)
			 = unpack('CCnNa*',$ip_data);
			 $ip_protocol = $ip_next;
			$ip_MF = $ip_fragNmf & 1;
			$ip_frag = $ip_fragNmf >> 3;
		} else {
			$ip_MF = 0;
			$ip_ident = 0;
			$ip_frag = 0;
		}

	} else {
		### Not IPv4 or IPv6 - could be LCP (skip for now)
		next;
	}

	### Count IP Protocols seen
	$Count{IPprotocol}{$ip_protocol}++;
	$CountMaster{IPprotocol}{$ip_protocol}++;

	### Count IP Addresses seen
	$Count{IP}{"$ip_src,$ether_src"}++;
	$CountMaster{IP}{$ip_src}++;

	### Generate unique IP id (not just the ident)
	$ip_id = &Generate_IP_ID($ip_src,$ip_dest,$ip_ident);

	#
	#  Store IP data in %IP so we can do frag reassembly next
	#
	if (! defined $IP{id}{$ip_id}{StartTime}) {
		$IP{time}{$packet_timefull}{ver} = $ip_ver;
		$IP{time}{$packet_timefull}{src} = $ip_src;
		$IP{time}{$packet_timefull}{dest} = $ip_dest;
		$IP{time}{$packet_timefull}{protocol} = $ip_protocol;
		$IP{time}{$packet_timefull}{frag}{$ip_frag} = $ip_data;
		if ($snoop_drops || $tcpdump_drops) {
			$IP{time}{$packet_timefull}{drops} = 1;
		}
		#
		#  If there are more fragments, remember this starttime
		#
		unless (($ip_MF == 0) && ($ip_frag == 0)) {
			$IP{id}{$ip_id}{StartTime} = $packet_timefull;
		}
		if (($ip_MF == 1) || ($ip_frag > 0)) {
			$IP{time}{$packet_timefull}{fragged} = 1;
		}
	} else {
		$start_time = $IP{id}{$ip_id}{StartTime};
		$IP{time}{$start_time}{frag}{$ip_frag} = $ip_data;
		if ($snoop_drops || $tcpdump_drops) {
			$IP{time}{$packet_timefull}{drops} = 1;
		}
		if ($ip_MF == 0) {
			#
			#  Comlpete this IP packet. This assumes that the
			#  last frag arrives last.
			#
			undef $IP{ident}{StartTime}{$ip_id};
		}
	}
	$packet++;
   }

   close INFILE;

   ### Print status summary
   unless ($Arg{quiet}) {
	printf("%s %2.0f%% (%d/%d)","\b"x24,
	 100,$bytes,$SIZE);
	print "\nReassembling packets,\n";
   }



   ###################################################################
   #  --- Pass #2, Reassemble IP data in %IP; create %TCP and %UDP ---
   #

   &Print_Header1() if $Arg{debug};
   $packets = $packet;
   $packet = 0;
   @Times = sort { $a <=> $b } ( keys(%{$IP{time}}) );
   foreach $time (@Times) {

	### Print status summary
	unless ($Arg{quiet}) {
		if (($packet % 16) == 0) {
			printf("%s %2.0f%% (%d/%d)","\b"x32,
			 (100*$packet/$packets),$packet,$packets);
		}
	}

	#
	#  Get IP data from %IP
	#
	$ip_ver = $IP{time}{$time}{ver};
	$ip_src = $IP{time}{$time}{src};
	$ip_dest = $IP{time}{$time}{dest};
	$ip_protocol = $IP{time}{$time}{protocol};
	$drops = $IP{time}{$time}{drops};
	undef $ip_data;

	#
	#  Reassemble IP frags
	#
	if (defined $IP{time}{$time}{fragged}) {
		@IP_Frags = sort {$a <=> $b} (keys(%{$IP{time}{$time}{frag}}));

		### If never recieved the start of the packet, skip
		if ($IP_Frags[0] != 0) { next; }

		foreach $ip_frag (@IP_Frags) {
			$ip_data .= $IP{time}{$time}{frag}{$ip_frag};
		}
	} else {
		$ip_data = $IP{time}{$time}{frag}{0};
	}
	$length = length($ip_data);

	#
	# --- UDP ---
	#
	if ($ip_protocol == 17 && $Arg{output_UDP}) {
		&Process_UDP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops);
	}

	#
	# --- TCP ---
	#
	if ($ip_protocol == 6 && $Arg{output_TCP}) {
		&Process_TCP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops);
	}

	#
	# --- ICMP ---
	#
	if ($ip_protocol == 1 && $Arg{output_ICMP}) {
		&Process_ICMP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops,
		 "ICMP");
	}

	#
	# --- ICMPv6 ---
	#
	if ($ip_protocol == 58 && $Arg{output_ICMP}) {
		&Process_ICMP_Packet($ip_data,$ip_src,$ip_dest,$time,$drops,
		 "ICMPv6");
	}

	#
	#  Skip packet if it isn't TCP (protocol = 6). (Will add routines for
	#  ICMP, ARP, RARP later on)...
	#

	$packet++;

	### Memory Cleanup
	delete $IP{time}{$time};

   }

   ### Memory Cleanup
   undef %IP;

   ### Print status summary
   unless ($Arg{quiet}) {
	printf("%s %2.0f%% (%d/%d)\n","\b"x24,
	 100,$packet,$packets);
   }

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Read Input File - end";
}



# Process_TCP_Packet - process a TCP packet and store it in memory. It takes
#	the raw ip data and populates the data structure %TCP. (and %Count).
#
sub Process_TCP_Packet {

	my $ip_data = shift;
	my $ip_src = shift;
	my $ip_dest = shift;
	my $time = shift;
	my $drops = shift;
	my $copy;

	#-------------------------------------------------------------------
	#
	#  TCP
	#

	### Unpack TCP data
	($tcp_src_port,$tcp_dest_port,$tcp_seq,$tcp_ack,$tcp_offset,$tcp_flags,
	 $tcp_header_rest,$tcp_data) = unpack('nnNNCCa6a*',$ip_data);

	### Strip off TCP options, if present
	$tcp_offset = $tcp_offset >> 4;		# chuck out reserved bits
	$tcp_offset = $tcp_offset << 2;		# now times by 4
	$tcp_options_num = $tcp_offset - 20;
	if ($tcp_options_num > 0) {
		($tcp_options,$tcp_data) =
		 unpack("a${tcp_options_num}a*",$tcp_data);
	}

	### Fetch length and FIN,RST flags
	$tcp_length_data = length($tcp_data);
	$tcp_fin = $tcp_flags & 1;
	$tcp_syn = $tcp_flags & 2;
	$tcp_rst = $tcp_flags & 4;
	$tcp_ack = $tcp_flags & 16;

	$copy = $tcp_data;

	#
	#  Generate $session_id as a unique id for this stream
	#  (this is built from host:port,host:port - sorting on port).
	#
	($session_id,$from_server) = &Generate_SessionID($ip_src,$tcp_src_port,
	 $ip_dest,$tcp_dest_port,"TCP");

	### Record direction if single SYN was seen
	if ($tcp_syn && ! $tcp_ack) {
		$TCP{id}{$session_id}{source} = $ip_src;
		$TCP{id}{$session_id}{source_port} = $tcp_src_port;
		# better repeat this,
		($session_id,$from_server) = &Generate_SessionID($ip_src,
		 $tcp_src_port,$ip_dest,$tcp_dest_port,"TCP");
	}

	### Count TCP Ports seen
	if ($from_server) {
		$Count{TCPport}{$tcp_src_port}++;
		$CountMaster{TCPport}{$tcp_src_port}++;
	} else {
		$Count{TCPport}{$tcp_dest_port}++;
		$CountMaster{TCPport}{$tcp_dest_port}++;
	}

	#
	#  Flag this session as a Partial if either tcpdump or snoop
	#  confesses to dropping packets.
	#
	$TCP{id}{$session_id}{Partial}++ if $drops;

	### Store size
	$TCP{id}{$session_id}{size} += length($tcp_data);

	### Store the packet timestamp for the first seen packet
	if (! defined $TCP{id}{$session_id}{StartTime}) {
		$TCP{id}{$session_id}{StartTime} = $time;

		### Store other info once
		if ($from_server) {
			$TCP{id}{$session_id}{src} = $ip_dest;
			$TCP{id}{$session_id}{dest} = $ip_src;
			$TCP{id}{$session_id}{src_port} = $tcp_dest_port;
			$TCP{id}{$session_id}{dest_port} = $tcp_src_port;
		} else {
			$TCP{id}{$session_id}{src} = $ip_src;
			$TCP{id}{$session_id}{dest} = $ip_dest;
			$TCP{id}{$session_id}{src_port} = $tcp_src_port;
			$TCP{id}{$session_id}{dest_port} = $tcp_dest_port;
		}
	}

	### Store the packet timestamp in case this is the last packet
	$TCP{id}{$session_id}{EndTime} = $time;

	### Print status line
	printf "%6s  %-45s  %s\n",$packet,$session_id,$length
	 if $Arg{debug};


	#
	# --- Store Session Data in Memory ---
	#
	# Since TCP is usually the bulk of the data, we minimise
	# the number of copies of data in memory. UDP and ICMP
	# are handled differently.

	if ($from_server) {
		#
		#  Populate %TCP{id}{}{time} with raw traffic by time.
		#  This is the master structure to store the data.
		#
		$TCP{id}{$session_id}{time}{$time}{data} .= $tcp_data;
		$TCP{id}{$session_id}{time}{$time}{dir} .= "A";

		#
		#
		#  Populate %TCP{id}{}{Aseq} with server to client
		#  1-way raw traffic, with the TCP sequence number as
		#  the key (for future reassembly).
		#
		#  This is a pointer to the time structure above,
		#  to save on memory used (originally stored a
		#  duplicate copy of the data).
		#
		if ((! defined $TCP{id}{$session_id}{Aseq}{$tcp_seq}) ||
		 (length(${$TCP{id}{$session_id}{Aseq}{$tcp_seq}}) <
		 length($tcp_data))) {
			$TCP{id}{$session_id}{Aseq}{$tcp_seq} =
			 \$TCP{id}{$session_id}{time}{$time}{data};
		}

	   } else {
		#
		#  Populate %TCP{id}{}{Btime} with raw 1-way traffic by time.
		#  This is the master structure to store the data.
		#
		$TCP{id}{$session_id}{time}{$time}{data} .= $tcp_data;
		$TCP{id}{$session_id}{time}{$time}{dir} .= "B";

		#
		#
		#  Populate %TCP{id}{}{Bseq} with client to server
		#  1-way raw traffic, with the TCP sequence number as
		#  the key (for future reassembly).
		#
		#  This is a pointer to the time structure above,
		#  to save on memory used (originally stored a
		#  duplicate copy of the data).
		#
		if ((! defined $TCP{id}{$session_id}{Bseq}{$tcp_seq}) ||
		 (length(${$TCP{id}{$session_id}{Bseq}{$tcp_seq}}) <
		 length($tcp_data))) {
			$TCP{id}{$session_id}{Bseq}{$tcp_seq} =
			 \$TCP{id}{$session_id}{time}{$time}{data};
		}
	}
	#
	#  Populate %Hex{TCP}{} with data necessary to generate coloured HTML 2-way
	#  traffic, if needed.
	#
	if ($Arg{output_hex}) {
		push(@{$Hex{"TCP"}{$session_id}}, [$from_server, $tcp_data]);
	}
}



# Process_UDP_Packet - process a UDP packet and store it in memory. It takes
#	the raw ip data and populates the data structure %UDP.
#
sub Process_UDP_Packet {

	my $ip_data = shift;
	my $ip_src = shift;
	my $ip_dest = shift;
	my $time = shift;
	my $drops = shift;
	my $copy;

	#-------------------------------------------------------------------
	#
	#  UDP
	#

	### Unpack UDP data
	($udp_src_port,$udp_dest_port,$udp_length,$udp_checksum,
	 $udp_data) = unpack('nnnna*',$ip_data);

	#
	#  Generate $session_id as a unique id for this stream
	#  (this is built from host:port,host:port - sorting on port).
	#
	($session_id,$from_server) = &Generate_SessionID($ip_src,$udp_src_port,
	 $ip_dest,$udp_dest_port,"UDP");

	#
	#  Flag this session as a Partial if either tcpdump or snoop
	#  confesses to dropping packets.
	#
	$UDP{id}{$session_id}{Partial}++ if $drops;

	### Store size
	$UDP{id}{$session_id}{size} += length($udp_data);

	### Count UDP ports seen
	if ($from_server) {
		$Count{UDPport}{$udp_src_port}++;
		$CountMaster{UDPport}{$udp_src_port}++;
	} else {
		$Count{UDPport}{$udp_dest_port}++;
		$CountMaster{UDPport}{$udp_dest_port}++;
	}

	# JL: DNS parsing
	if (($udp_src_port == 53) || ($udp_dest_port == 53)) {
	    &Process_DNS($udp_data, $session_id);
	}

	#
	# --- Store Stream Data in Memory ---
	#

	if ($from_server) {
		#
		#  Populate %UDP{id}{}{RawA} with server to client
		#  1-way raw traffic
		#
		$UDP{id}{$session_id}{RawA} .= $udp_data;

		#
		#  Populate %UDP{id}{}{BothHTML} with coloured HTML
		#  2-way traffic, blue for server to client
		#
		$copy = &Desex_HTML($udp_data);
		$UDP{id}{$session_id}{BothHTML} .=
			"<font color=\"blue\">$copy</font>";
	} else {
		#
		#  Populate %UDP{id}{}{RawB} with client to server
		#  1-way raw traffic
		#
		$UDP{id}{$session_id}{RawB} .= $udp_data;

		#
		#  Populate %UDP{id}{}{BothHTML} with coloured HTML
		#  2-way traffic, red for client to server
		#
		$copy = &Desex_HTML($udp_data);
		$UDP{id}{$session_id}{BothHTML} .=
			"<font color=\"red\">$copy</font>";
	}
	#
	#  Populate %Hex{UDP}{} with data necessary to generate coloured HTML 2-way
	#  traffic, if needed.
	#
	if ($Arg{output_hex}) {
		push(@{$Hex{"UDP"}{$session_id}}, [$from_server, $udp_data]);
	}

	#
	#  Populate %UDP{id}{}{time}{} with raw 1-way traffic by time
	#
	$UDP{id}{$session_id}{time}{$time} .= $udp_data;

	### Store the packet timestamp for the first seen packet
	if (! defined $UDP{id}{$session_id}{StartTime}) {
		$UDP{id}{$session_id}{StartTime} = $time;

		### Store other info once
		if ($from_server) {
			$UDP{id}{$session_id}{src} = $ip_dest;
			$UDP{id}{$session_id}{dest} = $ip_src;
			$UDP{id}{$session_id}{src_port} = $udp_dest_port;
			$UDP{id}{$session_id}{dest_port} = $udp_src_port;
		} else {
			$UDP{id}{$session_id}{src} = $ip_src;
			$UDP{id}{$session_id}{dest} = $ip_dest;
			$UDP{id}{$session_id}{src_port} = $udp_src_port;
			$UDP{id}{$session_id}{dest_port} = $udp_dest_port;
		}
	}

	### Store the packet timestamp in case this is the last packet
	$UDP{id}{$session_id}{EndTime} = $time;

	### Print status line
	printf "%6s  %-45s  %s\n",$packet,$session_id,$length
	 if $Arg{debug};

}



# Process_ICMP_Packet - process a ICMP packet and store it in memory. It takes
#	the raw ip data and populates the data structure %ICMP.
#	time is the session_id.
#
sub Process_ICMP_Packet {

	my $ip_data = shift;
	my $ip_src = shift;
	my $ip_dest = shift;
	my $time = shift;
	my $drops = shift;
	my $ver = shift;

	#-------------------------------------------------------------------
	#
	#  ICMP
	#

	### Unpack ICMP data
	($icmp_type,$icmp_code,$icmp_cksum,$icmp_rest) =
	 unpack('CCna*',$ip_data);

	#
	# --- Store ICMP data in memory ---
	#

	### Store Fields
	$ICMP{time}{$time}{type} = $icmp_type;
	$ICMP{time}{$time}{code} = $icmp_code;
	$ICMP{time}{$time}{src} = $ip_src;
	$ICMP{time}{$time}{dest} = $ip_dest;
	$ICMP{time}{$time}{ver} = $ver;

	#
	#  Flag this session as a Partial if either tcpdump or snoop
	#  confesses to dropping packets.
	#
	$ICMP{time}{$time}{Partial}++ if $drops;

	#
	#  Save data if ICMP echo/reply
	#
	if (($icmp_type == 0) || ($icmp_type == 8) ||
	 ($icmp_type == 128) || ($icmp_type == 129) || 1) {
		### Unpack some more
		($icmp_type,$icmp_code,$icmp_cksum,$icmp_id,$icmp_seq,
		 $icmp_data) = unpack('CCnnna*',$ip_data);
		### Save extra fields
		$ICMP{time}{$time}{id} = $icmp_id;
		$ICMP{time}{$time}{seq} = $icmp_seq;
		$ICMP{time}{$time}{data} = $icmp_data;
	}

	### Store size
	$ICMP{time}{$time}{size} += length($icmp_data);

	if ($icmp_data ne "") {
		#
		#  Populate %ICMP{time}{}{BothHTML} with coloured HTML
		#  1-way traffic, blue
		#
		$copy = &Desex_HTML($icmp_data);
		$ICMP{time}{$time}{BothHTML} .=
			"<font color=\"blue\">$copy</font>";
	}

	#
	#  Populate %Hex{ICMP}{} with data necessary to generate coloured HTML
	#  traffic, if needed. $from_server is always 1. session_id is time
	#
	if ($Arg{output_hex}) {
		push(@{$Hex{"ICMP"}{$time}}, [1, $icmp_data]);
	}

	### Print status line
	printf "%6s  %-45s  %s\n",$packet,"$ip_src,$ip_dest",$length
	 if $Arg{debug};
}



# Process_TCP_Sessions - this subroutine processes %TCP, saving the
# 	sessions to various "session*" files on disk. It populates %Index
#	with information on files that it has created. It also checks
#	the application port numbers and triggers further processing -
#	eg telnet replay files. Min/Max size checks are also done here.
#
sub Process_TCP_Sessions {

   my ($filename,$id_text,$id_html,$rawboth,$time,$raw);
   my @Time;

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Process TCP Sessions - start";

   #
   #  Loop through all TCP sessions
   #
   foreach $session_id (keys %{$TCP{id}}) {
	$number = $Index{Sort_Lookup}{"TCP:$session_id"};

	#
	#  Determine the service - usually by the lowest numbered port, eg,
	#  ports 51321 and 23 would give 23 (telnet).
	#
	$ip_src = $TCP{id}{$session_id}{src};
	$ip_dest = $TCP{id}{$session_id}{dest};
	$tcp_src_port = $TCP{id}{$session_id}{src_port};
	$tcp_dest_port = $TCP{id}{$session_id}{dest_port};
	($service,$client) = &Pick_Service_Port("TCP",$session_id,
	 $tcp_src_port,$tcp_dest_port);

	### Fetch text name for this port
	$service_name = $Services_TCP{$service} || $service || "0";

	#
	#  Don't actually save any files if CLI args say not to
	#
	if ($Arg{port_reject} && $Arg{Port_Rejected}{$service}) { next; }
	if ($Arg{port_accept} && !$Arg{Port_Accepted}{$service}) { next; }
	if ($Arg{ip_reject}) {
		if ($Arg{IP_Rejected}{$ip_src} || $Arg{IP_Rejected}{$ip_dest}) {
			next;
		}
	}
	if ($Arg{ip_accept}) {
		unless ($Arg{IP_Accepted}{$ip_src} ||
		 $Arg{IP_Accepted}{$ip_dest}) {
			next;
		}
	}

	#
	# --- Fetch RawBoth ---
	#
	# rawboth will contain the raw data in time order.
	$rawboth = "";
	foreach $time (sort {$a <=> $b}
	 (keys (%{$TCP{id}{$session_id}{time}}))) {
		$rawboth .= $TCP{id}{$session_id}{time}{$time}{data};
	}
	$length = length($rawboth);

	#
	# --- Check for Min and Max size ---
	#
	next if $length < $Arg{minbytes};
	next if (($Arg{maxbytes} != 0) && ($length > $Arg{maxbytes}));

	### Print status line
	$numtext = sprintf("%04d",$number);
	printf "%6s  %-45s  %s\n",$numtext,$session_id,$service_name
	 unless $Arg{quiet};


	#
	# --- Save Info File to Disk ---
	#
	if ($Arg{output_info}) {
		$filename = "session_${numtext}.info";
		$firsttime = localtime($TCP{id}{$session_id}{StartTime});
		$lasttime = localtime($TCP{id}{$session_id}{EndTime});
		$duration = ($TCP{id}{$session_id}{EndTime} -
	 	 $TCP{id}{$session_id}{StartTime});
		$duration = sprintf("%.0f",$duration);
		if ($TCP{id}{$session_id}{Partial}) { $partial = "yes"; }
		 else { $partial = "no"; }

		### Build output text
		$outtext = "$numtext===$session_id===$service===" .
		 "$service_name===$length\n\n" .
		 "Source addr : $ip_src\n" .
		 "Source port : $tcp_src_port\n" .
		 "Dest addr   : $ip_dest\n" .
		 "Dest port   : $tcp_dest_port\n" .
		 "Dest service: $service_name\n" .
		 "Length bytes: $length\n" .
		 "First time  : $firsttime\n" .
		 "Last time   : $lasttime\n" .
		 "Duration    : $duration seconds\n" .
		 "Partial     : $partial\n";

		### Write info file
		open (OUT,">$filename") ||
		 die "ERROR11: creating $filename $!\n";
		print OUT $outtext;
		close OUT;
	}


	#
	# --- Save Index data to Memory ---
	#

	## Fetch times
	$starttime = scalar localtime($TCP{id}{$session_id}{StartTime});
	$duration = ($TCP{id}{$session_id}{EndTime} -
	 $TCP{id}{$session_id}{StartTime});
	$duration = sprintf("%.0f",$duration);

	### Generate session strings
	($id_text,$id_html) = &Generate_TCP_IDs($session_id);

	### Construct HTML table row containing session data
	# JL: Added id attribute as link target
	$Index{HTML}[$number] = "<tr id=\"$number\">" .
	 "<td><i>$number.</i></td>" .
	 "<td><b>$starttime</b></td><td>$duration s</td><td> " .
	 "<font color=\"blue\">$id_html " .
	 "</font></td><td> <font color=\"red\">" .
	 "$service_name</font></td><td> <font color=\"green\"> " .
	 "$length bytes</font></td><td>\n";

	### Construct text line containing session data
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",$number,
	 $id_text,"($service_name)",$length);

	### Construct image info line (in case it is needed)
	$Image{HTML}[$number]{info} = "<tr><td><i>$number.</i>" .
	 "</td><td><b>$starttime</b></td><td> " .
	 "<font color=\"blue\">$id_html </font></td><td><td>\n";

	### JL: Construct external image info line (in case it is needed)
	$ExtImage{HTML}[$number]{info} = "<tr><td><i>$number.</i>" .
	 "</td><td><b>$starttime</b></td><td> " .
	 "<font color=\"blue\">$id_html </font></td><td><td>\n";

	### Construct GETPOST info line (in case it is needed)
	# starttime and host:port... are formatted differently so that
	# they are narrow and leave more room for the sub table.
	$GETPOST{HTML}[$number]{info} = "<tr><td><i>$number.</i>" .
	 "</td><td><b>$starttime</b></td><td> " .
	 "<font color=\"blue\">$id_html </font></td><td><td>\n";


	#
	# --- Save Raw Sessions to Disk ---
	#

	if ($Arg{output_raw}) {

		#
		#  Save ".raw" file, all raw 2-way data time-sorted.
		#
		$filename = "session_${numtext}.${service_name}.raw";
		open (OUT,">$filename") ||
		 die "ERROR12: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT $rawboth;
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<li><a href=\"$filename\">raw</a> ";

		#
		#  Save ".raw1" file, server->client 1-way data assembled.
		#
		$filename = "session_${numtext}.${service_name}.raw1";
		open (OUT,">$filename") ||
		 die "ERROR13: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT &TCP_Follow_RawA($session_id);
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<a href=\"$filename\">raw1</a> ";

		#
		#  Save ".raw2" file, client->server 1-way data assembled.
		#
		$filename = "session_${numtext}.${service_name}.raw2";
		open (OUT,">$filename") ||
		 die "ERROR14: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT &TCP_Follow_RawB($session_id);
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<a href=\"$filename\">raw2</a></li> ";
	}

	next unless $Arg{output_apps};

	#
	# --- Save Session as HTML ---
	#
	if ($Arg{Save_As_TCP_HTML}{$service} || $Arg{output_allhtml}) {
		&Save_Both_HTML("TCP",$session_id,$number,$service_name,
		 $id_html);
	}

	#
	# --- Save X11 Session as HTML ---
	#
	if ($Arg{Save_As_X11_HTML}{$service}) {
		#
		#  HTML Postprocessing can go here
		#
		&Generate_X11_HTML($session_id);
		&Process_BothHTML("TCP",$session_id,1);

		&Save_Both_HTML("TCP",$session_id,$number,"text$service_name",
		 $id_html);
	}


	#
	# --- Save Hex Dump as HTML ---
	#
	if ($Arg{output_hex}) {
		my ($text, $html) = &Process_Hex("TCP", $session_id);
		&Save_Hex_Text("TCP", $session_id, $number, $service_name,
			$id_text, $text);
		&Save_Hex_HTML("TCP", $session_id, $number, $service_name,
			$id_html, $html);
	}

	#
	# --- Process Application Data ---
	#

	if ($service == 20) {
		&Save_FTP_File($session_id,$number);
	}
	if ($service == 22) {
		&Save_Session_textSSH_files($session_id,$number,
		 "SSH",$id_html);
	}
	if ($Arg{keydata} && $Arg{Save_As_TCP_Playback}{$service}) {
		# The following is for special analysis,
		&Save_Session_Keydata($session_id,$number,
		 $service_name,$id_html);
	}
	if ($service == 25) {
		&Save_SMTP_Emails($session_id,$number);
	}
	if ($service == 80 or $service == 8080 or
	    $service == 3127 or $service == 1080 or
	    # JL: 8118 is HTTP(S) via polipo.
	    #     9050 is Tor (socks4a, but works good enough for me).
	    $service == 8118 or $service == 9050)  {
		&Save_HTTP_Files($session_id,$number,$service_name);
		&Process_HTTP($session_id,$number);
	}

	if ($Arg{Save_As_X11_Playback}{$service}) {
		&Save_Session_XReplay($session_id,$number,$service_name);
	}

	if ($Arg{Save_As_VNC_Playback}{$service}) {
		&Save_Session_VNCReplay_andHTML($session_id,$number,
		 $service_name,$id_html);
	}

	$raw = &TCP_Follow_RawB($session_id);
	if ($raw =~ /^\200\0\0p0\211/) {
		&Save_NFS_File($session_id,$number);
	}

	if ($Arg{Save_As_TCP_Playback}{$service}) {
		&Save_Session_Replay($session_id,$number,$service_name);
	}
   }

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Process TCP Sessions - end";
}


# Process_UDP_Streams - this subroutine processes %UDP, saving the
# 	sessions to various "session*" files on disk. It populates %Index
#	with information on the files that were created. It also checks
#	the application port numbers and triggers further processing -
#	eg DNS html output files.
#
sub Process_UDP_Streams {

   my ($filename,$id_html,$id_text,$time,$rawboth);

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Process UDP Sessions - start";

   #
   #  Loop through all UDP Streams
   #
   foreach $session_id (keys %{$UDP{id}}) {
	$number = $Index{Sort_Lookup}{"UDP:$session_id"};

	#
	#  Determine the service - usually by the lowest numbered port, eg,
	#  ports 51327 and 53 would give 53 (dns). (big assumption!)
	#
	$ip_src = $UDP{id}{$session_id}{src};
	$ip_dest = $UDP{id}{$session_id}{dest};
	$udp_src_port = $UDP{id}{$session_id}{src_port};
	$udp_dest_port = $UDP{id}{$session_id}{dest_port};
	($service,$client) = &Pick_Service_Port("UDP",$session_id,
	 $udp_src_port,$udp_dest_port);

	### Fetch text name for this port
	$service_name = $Services_UDP{$service} || $service || "0";

	#
	#  Don't actually save any files if CLI args say not to
	#
	if ($Arg{port_reject} && $Arg{Port_Rejected}{$service}) { next; }
	if ($Arg{port_accept} && !$Arg{Port_Accepted}{$service}) { next; }
	if ($Arg{ip_reject}) {
		if ($Arg{IP_Rejected}{$ip_src} || $Arg{IP_Rejected}{$ip_dest}) {
			next;
		}
	}
	if ($Arg{ip_accept}) {
		unless ($Arg{IP_Accepted}{$ip_src} ||
		 $Arg{IP_Accepted}{$ip_dest}) {
			next;
		}
	}

	#
	# --- Fetch RawBoth ---
	#
	# rawboth will contain the raw data in time order.
	$rawboth = "";
	foreach $time (sort {$a <=> $b}
	 (keys (%{$UDP{id}{$session_id}{time}}))) {
		$rawboth .= $UDP{id}{$session_id}{time}{$time};
	}
	$length = length($rawboth);

	#
	# --- Check for Min and Max Size ---
	#
	next if $length < $Arg{minbytes};
	next if (($Arg{maxbytes} != 0) && ($length > $Arg{maxbytes}));

	### Print status line
	$numtext = sprintf("%04d",$number);
	printf "%6s  %-45s  %s\n",$numtext,$session_id,$service_name
	 unless $Arg{quiet};

	#
	# --- Save Info File to Disk ---
	#
	if ($Arg{output_info}) {
		$filename = "stream_${numtext}.info";
		$firsttime = localtime($UDP{id}{$session_id}{StartTime});
		$lasttime = localtime($UDP{id}{$session_id}{EndTime});
		$duration = ($UDP{id}{$session_id}{EndTime} -
		$UDP{id}{$session_id}{StartTime});
		$duration = sprintf("%.0f",$duration);
		if ($UDP{id}{$session_id}{Partial}) { $partial = "yes"; }
		 else { $partial = "no"; }

		### Build output text
		$outtext = "$numtext===$session_id===$service===" .
		 "$service_name===$length\n\n" .
		 "Source addr : $ip_src\n" .
		 "Source port : $udp_src_port\n" .
		 "Dest addr   : $ip_dest\n" .
		 "Dest port   : $udp_dest_port\n" .
		 "Dest service: $service_name\n" .
		 "Length bytes: $length\n" .
		 "First time  : $firsttime\n" .
		 "Last time   : $lasttime\n" .
		 "Duration    : $duration seconds\n" .
		 "Partial     : $partial\n";

		### Write info file
		open (OUT,">$filename") ||
		 die "ERROR15: creating $filename $!\n";
		print OUT $outtext;
		close OUT;
	}


	#
	# --- Save Index data in Memory ---
	#

	### Fetch Times
	$starttime = scalar localtime($UDP{id}{$session_id}{StartTime});
	$duration = ($UDP{id}{$session_id}{EndTime} -
	 $UDP{id}{$session_id}{StartTime});
	$duration = sprintf("%.0f",$duration);

	### Construct HTML table row containing stream data
	if ($Arg{prefer_dns}) {
	    $ip_src = &Get_Name_For_IP($ip_src);
	    $ip_dest = &Get_Name_For_IP($ip_dest);
	}
	$id_html = "$ip_src:$udp_src_port &lt;-&gt; $ip_dest:$udp_dest_port";
	$Index{HTML}[$number] = "<tr><td><i>$number.</i></td>" .
	 "<td><b>$starttime</b></td><td>$duration s</td><td> " .
	 "<font color=\"blue\">$id_html " .
	 "</font></td><td> <font color=\"red\">" .
	 "<i>$service_name</i></font></td><td> <font color=\"green\"> " .
	 "$length bytes</font></td><td>\n";

	### Construct text line containing session data
	$id_text = "$ip_src:$udp_src_port <-> $ip_dest:$udp_dest_port";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",$number,
	 $id_text,"($service_name)",$length);


	#
	# --- Save Raw Stream to Disk ---
	#

	if ($Arg{output_raw}) {

		#
		#  Save ".raw" file, all raw 2-way data time-sorted.
		#
		$filename = "stream_${numtext}.${service_name}.raw";
		open (OUT,">$filename") ||
		 die "ERROR16: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT $rawboth;
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<li><a href=\"$filename\">raw</a> ";

		#
		#  Save ".raw1" file, server->client 1-way data time-sorted.
		#
		$filename = "stream_${numtext}.${service_name}.raw1";
		open (OUT,">$filename") ||
		 die "ERROR17: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT $UDP{id}{$session_id}{RawA};
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<a href=\"$filename\">raw1</a> ";

		#
		#  Save ".raw2" file, client->server 1-way data time-sorted.
		#
		$filename = "stream_${numtext}.${service_name}.raw2";
		open (OUT,">$filename") ||
		 die "ERROR18: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT $UDP{id}{$session_id}{RawB};
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<a href=\"$filename\">raw2</a></li> ";
	}

	next unless $Arg{output_apps};

	#
	# --- Save Stream as HTML ---
	#

	if ($Arg{Save_As_UDP_HTML}{$service} || $Arg{output_allhtml}) {
		#
		#  HTML Postprocessing can go here
		#
		&Process_BothHTML("UDP",$session_id);

		&Save_Both_HTML("UDP",$session_id,$number,$service_name);
	}

	#
	# --- Save Hex Dump as HTML ---
	#
	if ($Arg{output_hex}) {
		my ($text, $html) = &Process_Hex("UDP", $session_id);
		&Save_Hex_Text("UDP", $session_id, $number, $service_name,
			$id_text, $text);
		&Save_Hex_HTML("UDP", $session_id, $number, $service_name,
			$id_text, $html);
	}


	#
	# --- Process Application Data ---
	#
	if ($Arg{Save_As_UDP_Playback}{$service}) {
		&Save_Stream_Replay($session_id,$number,$service_name);
	}

	if ($service == 53) {
	        &Save_DNS_File($session_id,$number);
	}

   }

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Process UDP Sessions - end";
}



# Process_ICMP - this subroutine processes %ICMP.
#
sub Process_ICMP {

   my ($filename,$id_text,$id_html);

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Process ICMP Sessions - start";

   #
   #  Loop through all ICMP Streams
   #
   foreach $time (keys %{$ICMP{time}}) {
	$number = $Index{Sort_Lookup}{"ICMP:$time"};


	### Fetch Data
	$icmp_type = $ICMP{time}{$time}{type};
	$icmp_code = $ICMP{time}{$time}{code};
	$icmp_ver = $ICMP{time}{$time}{ver};
	$ip_src = $ICMP{time}{$time}{src};
	$ip_dest = $ICMP{time}{$time}{dest};
	$session_id = "$ip_src,$ip_dest";

	### Fetch text name for this port
	$type_name = $ICMP_Types{$icmp_type} || $icmp_type || "0";
	$service_name = $icmp_type;

	#
	#  Don't actually save any files if CLI args say not to
	#
	if ($Arg{ip_reject}) {
		if ($Arg{IP_Rejected}{$ip_src} || $Arg{IP_Rejected}{$ip_dest}){
			next;
		}
	}
	if ($Arg{ip_accept}) {
		unless ($Arg{IP_Accepted}{$ip_src} ||
		 $Arg{IP_Accepted}{$ip_dest}) {
			next;
		}
	}

	#
	# --- Check for Min and Max Size ---
	#
	$length = length($ICMP{time}{$time}{data});
	next if $length < $Arg{minbytes};
	next if (($Arg{maxbytes} != 0) && ($length > $Arg{maxbytes}));

	### Print status line
	$numtext = sprintf("%04d",$number);
	printf "%6s  %-45s  ICMP %s\n",$numtext,$session_id,$type_name
	 unless $Arg{quiet};

	#
	# --- Save Info File to Disk ---
	#
	if (($Arg{output_info}) && ($length > 0)) {
		$filename = "icmp_${numtext}.${service_name}.info";
		if ($ICMP{time}{$time}{Partial}) { $partial = "yes"; }
		 else { $partial = "no"; }
		$starttime = scalar localtime($time);

		### Build output text
		$outtext = "$numtext===$session_id===$icmp_type===" .
		 "$type_name===$length\n\n" .
		 "Source addr : $ip_src\n" .
		 "Dest addr   : $ip_dest\n" .
		 "ICMP version: $icmp_ver\n" .
		 "ICMP type   : $icmp_type\n" .
		 "ICMP code   : $icmp_code\n" .
		 "ICMP name   : $type_name\n" .
		 "Length bytes: $length\n" .
		 "Time        : $starttime\n" .
		 "Partial     : $partial\n";

		### Write info file
		open (OUT,">$filename") ||
		 die "ERROR19: creating $filename $!\n";
		print OUT $outtext;
		close OUT;
	}


	#
	# --- Save Index data in Memory ---
	#

	### Fetch Times
	$starttime = scalar localtime($time);

	### Construct HTML table row containing stream data
	$id_html = "$ip_src -&gt; $ip_dest";
	$Index{HTML}[$number] = "<tr><td><i>$number.</i></td>" .
	 "<td><b>$starttime</b></td><td>0 s</td><td> " .
	 "<font color=\"blue\">$id_html" .
	 "</font></td><td> <font color=\"red\">" .
	 "<i>$icmp_ver</i></font></td><td> <font color=\"green\"> " .
	 "$length bytes</font></td><td>$type_name\n";

	### Construct text line containing session data
	$id_text = "$ip_src -> $ip_dest";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",$number,
	 $id_text, "($icmp_ver $type_name)",$length);


	#
	# --- Save Raw Stream to Disk ---
	#

	if (($Arg{output_raw}) && ($length > 0)) {

		#
		#  Save ".raw" file, all raw 2-way data time-sorted.
		#
		$filename = "icmp_${numtext}.${service_name}.raw";
		open (OUT,">$filename") ||
		 die "ERROR20: creating $filename $!\n";
		binmode(OUT);		# for backward OSs
		print OUT $ICMP{time}{$time}{data};
		close OUT;

		### Update HTML index table with link
		$Index{HTML}[$number] .= "<li><a href=\"$filename\">raw</a> ";

	}

	#
	# --- Save Stream as HTML ---
	#

	if ($Arg{output_allhtml}) {
		#
		#  HTML Postprocessing can go here
		#
		&Process_BothHTML("ICMP",$time);

		&Save_Both_HTML("ICMP",$time,$number,$service_name,$id_html);
	}

	#
	# --- Save Hex Dump as HTML ---
	#
	if ($Arg{output_hex}) {
		my ($text, $html) = &Process_Hex("ICMP", $time);
		&Save_Hex_Text("ICMP", $session_id, $number, $service_name,
			$id_text, $text);
		&Save_Hex_HTML("ICMP", $session_id, $number, $service_name,
			$id_html, $html);
	}
   }

   $Bench{++$BM}{mark} = new Benchmark if $Arg{bench};
   $Bench{$BM}{text} = "Process ICMP Sessions - end";
}


# JL: Process_DNS - DNS processing.  Look for DNS replies and store
#     names for IP addresses into %DNS.
#     Also store CNAME aliases so that the "original" name can be retrieved.
#
sub Process_DNS {
	my $data = shift;
	my $session_id = shift;

	my $dns = Net::DNS::Packet->new(\$data);

	unless ($dns) {
	    #print "Failed to create Net::DNS::Packet!\n";
	    return;
	}

	$UDP{id}{$session_id}{DNS} = $dns->string;
	foreach my $rr ($dns->answer) {
	    if ($rr->type eq "A") {
		$DNS{$rr->address} = $rr->name;
	    }
	    if ($rr->type eq "CNAME") {
		$DNS{$rr->cname} = $rr->name;
	    }
	}
}



# Process_HTTP - HTTP processing. Looks for GETs and POSTs, and process them
#		into %GETPOST. Constructs a HTTP log in %HTTPlog.
# JL: Added host parameter
#
sub Process_HTTP {
	my ($junk,$var,$value,$term,$data,$request,$host,$site,$post,$get,$reply);
	my ($start,$src,$num,$req,$recv,$type,$status,$time1,$duration,$dest);
	my @Terms;
	my $index = 0;
	my $indexA = 0;
	my $indexB = 0;

	### Input
	my $session_id = shift;
	my $number = shift;
	my $partnum = 0;

	$src = $TCP{id}{$session_id}{src};
	$dest = $TCP{id}{$session_id}{dest};

	#
	#  Process
	#

	### Get packet times (may need to use seqs instead)
	@Times = sort{$a <=> $b} (keys(%{$TCP{id}{$session_id}{time}}));

	### Step through each packet
        for ($i=0; $i <= $#Times; $i++) {

           ### Fetch data from mem
           $time = $Times[$i];
           $request = $TCP{id}{$session_id}{time}{$time}{data};
	   $request =~ s/^\0\0*//;

	   #
	   # --- Do HTTPlog Processing ---
	   #

	   next unless $request =~ /^(GET|POST)\s/; # speed

	   ### Calc duration
           $time1 = $Times[$i+1] || $time;
	   $duration = $time1 - $time;

	   # some magic
	   $reply = "";
	   foreach $inc (1..16) {
		$next = $TCP{id}{$session_id}{time}{$Times[$i+$inc]}{data};
		$next =~ s/^\0\0*//;
		if ($next =~ /^U*\0*HTTP/) {
			$reply = $next;
			$time1 = $Times[$i+$inc] || $time;
        		$duration = $time1 - $time;
			last;
		} else {
			$request .= $next;
		}
	   }
	   $i++; # speed
	   $partnum++;
	   if ($request =~ /^GET \S* HTTP/) {

		### JL: Get the host string, referer, and cookies.
		($host) = $request =~ /\sHost:\s(\S*)\s/is;
		($referer) = $request =~ /\sReferer:\s(\S*)/is;
		($cookie) = $request =~ /\sCookie:\s(\S*)/is;
		($setcookie) = $reply =~ /\sSet-Cookie:\s(\S*)/is;

		### Get the site string
		($site) = $request =~ /^GET (\S*)\s/;
		if ($site =~ m:^/:) {
			# assume this was a http, missing the "http://host"
		        # JL: Prefer hostname over IP address
		        if ($Arg{httplog_html}) {
			    $site = "http://${host}$site";
			} else {
			    $site = "http://${dest}$site";
			}
		}

		### Get the status and mime type from reply
		($status)  = $reply =~ /HTTP\/\S*\s(\S*)/s;
		# JL: Be careful to use case insensitive matching
		($type) = $reply =~ /Content-Type:\s(\S*)/is;
		($size) = $reply =~ /Content-Length:\s(\S*)/is;
		$type = "-" if $type eq "";
		$size = 0 if $size eq "";

		$result = $Result_Names{$status} || "TCP_HIT";

		### Store the log entry
		$HTTPlog{time}{$time} =
			Print_Log_Line($number,$time,$duration,
				$src,$dest,$result,$status,$size,
				"GET",$site,"-","NONE","","-",$type);
		$HTTPtxtlog{time}{$time} =
			Print_TxtLog_Line($number,$time,
				$referer,$cookie,$setcookie,
				"GET",$site);
		$HTTPlog{notempty} = 1;

		### JL: External image data.
		if ( defined $ExtImage{HTML}[$number]{parts}[$partnum] ) {
		    $ExtImage{HTML}[$number]{links} .= "<img src=\"$site\"> ";
		}
	   } elsif ($request =~ /^POST .* HTTP/) {
		### Get the site string
		($site) = $request =~ /^POST (\S*)\s/;
		if ($site =~ m:^/:) {
			# assume this was a http, missing the "http://host"
			$site = "http://${dest}$site";
		}
		### JL: Get the host string, referer, and cookies.
		($host) = $request =~ /\sHost:\s(\S*)\s/is;
		($referer) = $request =~ /\sReferer:\s(\S*)/is;
		($cookie) = $request =~ /\sCookie:\s(\S*)/is;
		($setcookie) = $reply =~ /\sSet-Cookie:\s(\S*)/is;

		### Get the status and mime type
		($status)  = $reply =~ /HTTP\/\S*\s(\S*)/s;
		($type) = $reply =~ /Content-Type:\s(\S*)/is;
		($size) = $reply =~ /Content-Length:\s(\S*)/is;
		$type = "-" if $type eq "";
		$size = length($TCP{id}{$session_id}) if $size eq "";
		$result = $Result_Names{$status} || "TCP_HIT";

		### Store the log entry
		$HTTPlog{time}{$time} =
		    Print_Log_Line($number,$time,$duration,
				   $src,$dest,$result,$status,$size,
				   "POST",$site,"-","NONE","","-",$type);
		$HTTPtxtlog{time}{$time} =
			Print_TxtLog_Line($number,$time,
				$referer,$cookie,$setcookie,
				"POST",$site);
		$HTTPlog{notempty} = 1;

	   }

	   #
	   # --- Do GETPOST Processing ---
	   #
	   # JL: chaosreader 0.94 includes only URIs containing a question
	   # mark.  Why?  Go for all instead.
	   #if ($request =~ /^GET \S*\?\S* HTTP/) {
	   if ($request =~ /^GET \S* HTTP/) {

	     ### Get the GET string
	     ($site,$get) = $request =~ /^GET (\S*)\?(\S*)\s/;
	     if ($site eq "") {
		 ($site) = $request =~ /^GET (\S*)\s/;
	     }
	     # check it looks like a GET,
	     # JL: Why only those with parameters?
	     #if ($get =~ /=/) {

		#
		#  Populate %GETPOST with a table containing the GET data
		#
		if (! defined $GETPOST{HTML}[$number]{query}) {
			$GETPOST{HTML}[$number]{info} .=
			 "<font color=\"red\">GET</font></td><td width=70%>";
			$GETPOST{notempty} = 1;
		} else {
			$GETPOST{HTML}[$number]{query} .= "<hr>\n";
		}

		#
		#  Generate table of query key value pairs
		#
		$GETPOST{HTML}[$number]{query} .= "$site<br><table border=1>\n";
		@Terms = split(/&/,$get);
		foreach $term (@Terms) {
			($var,$value) = split(/=/,$term);
			$value =~ tr/+/ /;
			$value =~ s/%([a-f0-9][a-f0-9])/pack("C",hex($1))/egi;
			$value =~ s/</&lt;/g;
			$value =~ s/>/&gt;/g;
			$value =~ s/\n/<br>\n/g;
			$GETPOST{HTML}[$number]{query} .=
			 "<tr><td><b>$var</b></td>" .
			 "<td><font face=\"Courier\">$value</font></td></tr>\n";
		}
		$GETPOST{HTML}[$number]{query} .= "</table>\n";
	     #}

	   } elsif ($request =~ /^POST .* HTTP/) {

	     ### Get the POST strings
	     ($junk,$post,$junk1) = split(/\n\n|\r\n\r\n/,$request);

	     # check it looks like a POST
	     if ($post =~ /=/) {

		#
		#  Populate %GETPOST with a table containing the POST data
		#
		if (! defined $GETPOST{HTML}[$number]{query}) {
			$GETPOST{HTML}[$number]{info} .=
			 "<font color=\"red\">POST</font></td><td width=70%>";
			$GETPOST{notempty} = 1;
		} else {
			$GETPOST{HTML}[$number]{query} .= "<hr>\n";
		}

		($site) = $request =~ /^POST (\S*)\s/;

		$post =~ s/HTTP .*//s;

		#
		#  Generate table of query key value pairs
		#
		$GETPOST{HTML}[$number]{query} .= "$site<br><table border=1>\n";
		@Terms = split(/&/,$post);
		foreach $term (@Terms) {
			($var,$value) = split(/=/,$term);
			$value =~ tr/+/ /;
			$value =~
			 s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
			$value =~ s/</&lt;/g;
			$value =~ s/>/&gt;/g;
			$value =~ s/\n/<br>/g;
			$GETPOST{HTML}[$number]{query} .=
			 "<tr><td><b>$var</b></td>" .
			 "<td><font face=\"Courier\">$value</font></td></tr>\n";
		}
		$GETPOST{HTML}[$number]{query} .= "</table>\n";
	     }
	   }
	}
}


# Sort_Index - this creates a sort order for the master index.html, based
#	on the sort argument (defaults to sort by time).
#
sub Sort_Index {

	if ($Arg{sort} eq "size") {
		&Sort_Index_By_Size();
	} elsif ($Arg{sort} eq "type") {
		&Sort_Index_By_Type();
	} elsif ($Arg{sort} eq "ip") {
		&Sort_Index_By_IP();
	} else {
		&Sort_Index_By_Time();
	}
}


# Sort_Index_By_Time - this calculates an appropriate order for the index
#	files based on session start time.
#
sub Sort_Index_By_Time {
	my ($session_id,$time,$number);

	#
	#  Determine Session and Stream time order
	#
	foreach $session_id (keys %{$TCP{id}}) {
		$Index{Time_Order}{"TCP:$session_id"} =
		 $TCP{id}{$session_id}{StartTime};
	}
	foreach $session_id (keys %{$UDP{id}}) {
		$Index{Time_Order}{"UDP:$session_id"} =
		 $UDP{id}{$session_id}{StartTime};
	}
	foreach $time (keys %{$ICMP{time}}) {
		$Index{Time_Order}{"ICMP:$time"} = $time;
	}
	$number = 0;
	foreach $session (sort {$Index{Time_Order}{$a} <=>
	 $Index{Time_Order}{$b}} keys %{$Index{Time_Order}}) {
		$number++;
		$Index{Sort_Lookup}{$session} = $number;
	}
}


# Sort_Index_By_Size - this calculates an appropriate order for the index
#	files based on session size.
#
sub Sort_Index_By_Size {
	my ($session_id,$time,$number);

	#
	#  Determine Session and Stream size order
	#
	foreach $session_id (keys %{$TCP{id}}) {
		$Index{Size_Order}{"TCP:$session_id"} =
		 $TCP{id}{$session_id}{size};
	}
	foreach $session_id (keys %{$UDP{id}}) {
		$Index{Size_Order}{"UDP:$session_id"} =
		 $UDP{id}{$session_id}{size};
	}
	foreach $time (keys %{$ICMP{time}}) {
		$Index{Size_Order}{"ICMP:$time"} =
		 $ICMP{time}{$time}{size};
	}
	$number = 0;
	foreach $session (sort {$Index{Size_Order}{$b} <=>
	 $Index{Size_Order}{$a}} keys %{$Index{Size_Order}}) {
		$number++;
		$Index{Sort_Lookup}{$session} = $number;
	}
}


# Sort_Index_By_Type - this calculates an appropriate order for the index
#	files based on session type, followed by time.
#
sub Sort_Index_By_Type {
	my ($service,$tcp_src_port,$tcp_dest_port,$client,$udp_src_port,
	 $udp_dest_port,$session_id,$time,$number);

	#
	#  Determine Session and Stream time order
	#
	foreach $session_id (keys %{$TCP{id}}) {
		# Determine the service - usually by the lowest numbered port
		$tcp_src_port = $TCP{id}{$session_id}{src_port};
		$tcp_dest_port = $TCP{id}{$session_id}{dest_port};
		($service,$client) = &Pick_Service_Port("TCP",$session_id,
		 $tcp_src_port,$tcp_dest_port);

		$Index{Type_Order}{"TCP:$session_id"}{1} = 1;
		$Index{Type_Order}{"TCP:$session_id"}{2} = $service;
		$Index{Type_Order}{"TCP:$session_id"}{3} =
		 $TCP{id}{$session_id}{StartTime};
	}
	foreach $session_id (keys %{$UDP{id}}) {
        	# Determine the service - usually by the lowest numbered port
		$udp_src_port = $UDP{id}{$session_id}{src_port};
		$udp_dest_port = $UDP{id}{$session_id}{dest_port};
		($service,$client) = &Pick_Service_Port("UDP",$session_id,
		 $udp_src_port,$udp_dest_port);

		$Index{Type_Order}{"UDP:$session_id"}{1} = 2;
		$Index{Type_Order}{"UDP:$session_id"}{2} = $service;
		$Index{Type_Order}{"UDP:$session_id"}{3} =
		 $UDP{id}{$session_id}{StartTime};
	}
	foreach $time (keys %{$ICMP{time}}) {
		$Index{Type_Order}{"ICMP:$time"}{1} = 3;
		$Index{Type_Order}{"ICMP:$time"}{2} = 0;
		$Index{Type_Order}{"ICMP:$time"}{3} = $time;
	}

	# now we sort by TCP->UDP->IP then port then time.
	$number = 0;
	foreach $session (sort {
		$Index{Type_Order}{$a}{1} <=> $Index{Type_Order}{$b}{1} ||
		$Index{Type_Order}{$a}{2} <=> $Index{Type_Order}{$b}{2} ||
		$Index{Type_Order}{$a}{3} <=> $Index{Type_Order}{$b}{3}
	 } keys %{$Index{Type_Order}}) {
		$number++;
		$Index{Sort_Lookup}{$session} = $number;
	}
}


# Sort_Index_By_IP - this calculates an appropriate order for the index
#	files based on client IP, followed by time.
#
sub Sort_Index_By_IP {
	my ($service,$ip,$ip_dest,$ip_src,$client,
	 $session_id,$time,$number,$text,$html,$rest);
	my @IP;

	#
	#  Determine Session and Stream time order
	#
	foreach $session_id (keys %{$TCP{id}}) {
		# Determine source IP
		# here we use the same subroutine as the index.html
		# so that they match up.
		($text,$html) = &Generate_TCP_IDs($session_id);
		($ip,$rest) = split(/:/,$text,2);

		# Split on IPv4 or IPv6
		$IP = ();
		if ($ip =~ /\./) { @IP = split(/\./,$ip); }
		 else { $IP[0] = $ip; }

		$Index{Type_Order}{"TCP:$session_id"}{1} = $IP[0];
		$Index{Type_Order}{"TCP:$session_id"}{2} = $IP[1];
		$Index{Type_Order}{"TCP:$session_id"}{3} = $IP[2];
		$Index{Type_Order}{"TCP:$session_id"}{4} = $IP[3];
		$Index{Type_Order}{"TCP:$session_id"}{5} =
		 $TCP{id}{$session_id}{StartTime};
	}
	foreach $session_id (keys %{$UDP{id}}) {
		# Determine source IP
		$ip = $UDP{id}{$session_id}{src};

		# Split on IPv4 or IPv6
		$IP = ();
		if ($ip =~ /\./) { @IP = split(/\./,$ip); }
		 else { $IP[0] = $ip; }

		$Index{Type_Order}{"UDP:$session_id"}{1} = $IP[0];
		$Index{Type_Order}{"UDP:$session_id"}{2} = $IP[1];
		$Index{Type_Order}{"UDP:$session_id"}{3} = $IP[2];
		$Index{Type_Order}{"UDP:$session_id"}{4} = $IP[3];
		$Index{Type_Order}{"UDP:$session_id"}{5} =
		 $UDP{id}{$session_id}{StartTime};
	}
	foreach $time (keys %{$ICMP{time}}) {
		# Determine source IP
		$ip = $ICMP{time}{$time}{src};

		# Split on IPv4 or IPv6
		$IP = ();
		if ($ip =~ /\./) { @IP = split(/\./,$ip); }
		 else { $IP[0] = $ip; }

		$Index{Type_Order}{"ICMP:$time"}{1} = $IP[0];
		$Index{Type_Order}{"ICMP:$time"}{2} = $IP[1];
		$Index{Type_Order}{"ICMP:$time"}{3} = $IP[2];
		$Index{Type_Order}{"ICMP:$time"}{4} = $IP[3];
		$Index{Type_Order}{"ICMP:$time"}{5} = $time;
	}

	# now we sort by IP then time
	$number = 0;
	foreach $session (sort {
		$Index{Type_Order}{$a}{1} <=> $Index{Type_Order}{$b}{1} ||
		$Index{Type_Order}{$a}{2} <=> $Index{Type_Order}{$b}{2} ||
		$Index{Type_Order}{$a}{3} <=> $Index{Type_Order}{$b}{3} ||
		$Index{Type_Order}{$a}{4} <=> $Index{Type_Order}{$b}{4} ||
		$Index{Type_Order}{$a}{1} cmp $Index{Type_Order}{$b}{1} ||
		$Index{Type_Order}{$a}{5} <=> $Index{Type_Order}{$b}{5}
	 } keys %{$Index{Type_Order}}) {
		$number++;
		$Index{Sort_Lookup}{$session} = $number;
	}
}


# Print_Welcome - print short program welcome message.
#
sub Print_Welcome {
	unless ($Arg{quiet}) {
		print "Chaosreader ver 0.95.10\n\n";
	}
}


# Print_Header1 - print program welcome message.
#
sub Print_Header1 {
	unless ($Arg{quiet}) {
		print "Reading $TYPE log...\n";
		printf "%6s  %-45s  %s\n","Packet",
			"Session (host:port <=> host:port)","Length";
	}
}


# Print_Header2 - print header before loading the file
#
sub Print_Header2 {
	print "\nCreating files...\n" unless $Arg{quiet};
	printf "%6s  %-45s  %s\n","Num","Session (host:port <=> host:port)",
	 "Service" unless $Arg{quiet};
}


# Print_Footer1 - print footer at end of program.
#
sub Print_Footer1 {
	if ($Arg{output_index}) {
		print "\nindex.html created.\n" unless $Arg{quiet};
	}
}


# Chdir - change directory with error
#
sub Chdir {
	my $dir = shift;
	#
	#  This can be invoked with $Arg{output_dir}, so $dir won't
	#  always be defined - which is okay.
	#
	if (defined $dir) {
		chdir "$dir" ||
		 die "ERROR21: Can't cd to $dir: $!\n";
	}
}


# Create_Index_Files - Create the HTML and text index files. This reads
#	%Index and creates the files on disk.
#
sub Create_Index_Files {
   my ($html_index,$html_line,$html_links,$image_empty,$getpost_empty);
   $getpost_empty = $image_empty = "";

   if ($Arg{output_index}) {


	######################
	# --- index.html ---

	$image_empty = "(Empty) " unless $Image{notempty};
	$getpost_empty = "(Empty) " unless $GETPOST{notempty};
	$httplog_empty = "(Empty) " unless $HTTPlog{notempty};
	#
	#  Create HTML Index file containing all reports
	#
	open(FILE,">index.html") || die "ERROR22: creating index: $!\n";
	print FILE <<END_HTML;
<html>
<head><title>Chaosreader Report, $Arg{infile}</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader Report</font><br>
<font size=+1>File: $Arg{infile}, Type: $TYPE, Created at: $the_date</font><p>
<a href="image.html"><font color="blue"><b>Image Report</b></font></a>
 $image_empty - Click here for a report on captured images.<br>
<a href="extimage.html"><font color="blue"><b>External Image Report</b></font></a>
 $image_empty - Click here for a report embedding external images.<br>
<a href="getpost.html"><font color="blue"><b>GET/POST Report</b></font></a>
 $getpost_empty - Click here for a report on HTTP GETs and POSTs.<br>
<a href="$Arg{httplog_name}"><font color="blue"><b>HTTP Proxy Log</b></font></a>
 $httplog_empty - Click here for a generated proxy style HTTP log.<br>
<a href="$Arg{httplog_txt}"><font color="blue"><b>New HTTP Proxy Log</b></font></a>
 $httplog_empty - Click here for HTTP log with referers and Cookie indicators.<p>
<font size=+2>TCP/UDP/... Sessions</font><br>
<table border=2>
END_HTML
	for ($html_index=0; $html_index <= $#{$Index{HTML}}; $html_index++) {
		$html_line = $Index{HTML}[$html_index];
		next unless defined $html_line;
		print FILE "$html_line </td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>IP and MAC Count</font><br>
<table border=2>
<tr><th>IP</th><th>MAC</th><th>Count</th></tr>
END_HTML
	foreach $IP_MAC (sort {$Count{IP}{$b} <=> $Count{IP}{$a}}
	 keys %{$Count{IP}}) {
		#print FILE "<tr><td>$IP</td><td>$Count{IP}{$IP}</td></tr>\n";
		($ip, $mac) = split(',', $IP_MAC);
		print FILE "<tr><td>$ip</td><td>$mac</td><td>$Count{IP}{$IP_MAC}</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>TCP Port Count</font><br>
<table border=2>
END_HTML
	foreach $port (sort {$Count{TCPport}{$b} <=> $Count{TCPport}{$a}}
	 keys %{$Count{TCPport}}) {
		$port_text = $Services_TCP{$port} || $port || "0";
		print FILE "<tr><td>$port_text</td><td>$Count{TCPport}{$port}" .
		 "</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>UDP Port Count</font><br>
<table border=2>
END_HTML
	foreach $port (sort {$Count{UDPport}{$b} <=> $Count{UDPport}{$a}}
	 keys %{$Count{UDPport}}) {
		$port_text = $Services_UDP{$port} || $port || "0";
		print FILE "<tr><td>$port_text</td><td>$Count{UDPport}{$port}" .
		 "</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>IP Protocol Count</font><br>
<table border=2>
END_HTML
	foreach $protocol (sort {$Count{IPprotocol}{$b} <=>
	 $Count{IPprotocol}{$a}} keys %{$Count{IPprotocol}}) {
		$protocol_text = $IP_Protocols{$protocol};
		print FILE "<tr><td>$protocol_text</td><td>" .
		 "$Count{IPprotocol}{$protocol}</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>Ethernet Type Count</font><br>
<table border=2>
END_HTML
	foreach $type (sort {$Count{EtherType}{$b} <=> $Count{EtherType}{$a}}
	 keys %{$Count{EtherType}}) {
		print FILE "<tr><td>$type</td><td>$Count{EtherType}{$type}" .
		 "</td></tr>\n";
	}
	print FILE <<END_HTML;
</table>
</body>
</html>
END_HTML


	######################
	# --- index.text ---

	#
	#  Create Text index file
	#
	open(FILE,">index.text") || die "ERROR23: creating index: $!\n";
	print FILE "TCP/UDP/... Sessions\nFile: $Arg{infile}, "
	 . "Type: $TYPE, Created at: $the_date\n\n";
	print FILE @{$Index{Text}};
	close FILE;


	######################
	# --- image.html ---

	#
	#  Create HTML Image Index file to display images
	#
	open(FILE,">image.html") || die "ERROR24: creating index: $!\n";
	print FILE <<END_HTML;
<html>
<head><title>Chaosreader Image Report</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader Image Report</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<font size=+2>Images</font><br>
<table border=2>
END_HTML
	for ($html_index=0; $html_index <= $#{$Index{HTML}}; $html_index++) {
		$html_line = $Image{HTML}[$html_index]{info};
		$html_links = $Image{HTML}[$html_index]{links};
		next unless defined $html_links;
		print FILE "$html_line $html_links </td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
</body>
</html>
END_HTML


	######################
	# --- extimage.html ---

	#
	#  Create HTML External Image Index file to display images
	#
	open(FILE,">extimage.html") || die "ERROR24: creating index: $!\n";
	print FILE <<END_HTML;
<html>
<head><title>Chaosreader External Image Report</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader External Image Report</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<font size=+2>Images</font><br>
<table border=2>
END_HTML
	for ($html_index=0; $html_index <= $#{$Index{HTML}}; $html_index++) {
		$html_line = $ExtImage{HTML}[$html_index]{info};
		$html_links = $ExtImage{HTML}[$html_index]{links};
		next unless defined $html_links;
		print FILE "$html_line $html_links </td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
</body>
</html>
END_HTML


	######################
	# --- getpost.html ---

	#
	#  Create HTML GETPOST Index file to show HTTP GETs and POSTs
	#
	open(FILE,">getpost.html") || die "ERROR25: creating index: $!\n";
	print FILE <<END_HTML;
<html>
<head><title>Chaosreader GET/POST Report</title></head>
<body bgcolor="white" textcolor="black">
<font size=+3>Chaosreader GET/POST Report</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<font size=+2>HTTP GETs and POSTs</font><br>
<table border=2>
END_HTML
	for ($html_index=0; $html_index <= $#{$GETPOST{HTML}}; $html_index++) {
		$html_line = $GETPOST{HTML}[$html_index]{info};
		$html_links = $GETPOST{HTML}[$html_index]{query};
		next unless defined $html_links;
		print FILE "$html_line $html_links </td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
</body>
</html>
END_HTML

   }
}



# Create_Index_Master - Create the HTML and text master index files. This
#	reads @Master and creates the files on disk.
#
sub Create_Index_Master {

   my ($start,$end,$dir,$file,$index,$duration);

   if ($Arg{output_index}) {

	#
	#  Create most recent link
	#

	$dir = $Master[$#Master]{dir};
	$recentname = "most_recent_index";
	unlink("$recentname");
	# don't die on symlink error, it's not essential
	symlink("$dir","$recentname");

	#
	#  Create HTML Index file containing all reports
	#
	open(FILE,">index.html") || die "ERROR26: creating index: $!\n";
	print FILE <<END_HTML;
<html>
<head><title>Chaosreader Master Index</title></head>
<body bgcolor="white" textcolor="black" vlink="blue">
<font size=+3>Chaosreader Master Index</font><br>
<font size=+1>Created at: $the_date, Type: $TYPE</font><p>
<a href="$recentname/index.html"><font color="red">
<b>Most Recent Report</b></font></a>
 - Click here for the most recent index, and click reload for updates.<p>
<font size=+2>Chaosreader Reports</font><br>
<table border=2>
END_HTML
	for ($index=0; $index <= $#Master; $index++) {
		$start = $Master[$index]{starttime};
		$end = $Master[$index]{endtime};
		$dir = $Master[$index]{dir};
		$file = $Master[$index]{file};
		$size = $Master[$index]{size};
		$duration = $Master[$index]{duration};
		$html_line = "<tr><td><i>". ($index+1) . "</i></td>" .
		 "<td><b>$start</b></td><td><b>$end</b></td>\n" .
		 "<td>$duration s</td>" . "<td><font color=\"green\"> " .
		 "$size bytes</font></td>" .
		 "<td><a href=\"$dir/index.html\">$dir/$file</a></td></tr>\n";
		print FILE "$html_line </td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>IP Count</font><br>
<table border=2>
END_HTML
	foreach $IP (sort {$CountMaster{IP}{$b} <=> $CountMaster{IP}{$a}}
	 keys %{$CountMaster{IP}}) {
		print FILE "<tr><td>$IP</td><td>$CountMaster{IP}{$IP}" .
		 "</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>TCP Port Count</font><br>
<table border=2>
END_HTML
	foreach $port (sort {$CountMaster{TCPport}{$b} <=>
	 $CountMaster{TCPport}{$a}} keys %{$CountMaster{TCPport}}) {
		$port_text = $Services_TCP{$port} || $port || "0";
		print FILE "<tr><td>$port_text</td><td>" .
		 "$CountMaster{TCPport}{$port}</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>UDP Port Count</font><br>
<table border=2>
END_HTML
	foreach $port (sort {$CountMaster{UDPport}{$b} <=>
	 $CountMaster{UDPport}{$a}} keys %{$CountMaster{UDPport}}) {
		$port_text = $Services_UDP{$port} || $port || "0";
		print FILE "<tr><td>$port_text</td><td>" .
		 "$CountMaster{UDPport}{$port}</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>IP Protocol Count</font><br>
<table border=2>
END_HTML
	foreach $protocol (sort {$CountMaster{IPprotocol}{$b} <=>
	 $CountMaster{IPprotocol}{$a}} keys %{$CountMaster{IPprotocol}}) {
		$protocol_text = $IP_Protocols{$protocol};
		print FILE "<tr><td>$protocol_text</td><td>" .
		 "$CountMaster{IPprotocol}{$protocol}</td></tr>\n";
	}
	print FILE <<END_HTML;
</table><p>
<font size=+2>Ethernet Type Count</font><br>
<table border=2>
END_HTML
	foreach $type (sort {$CountMaster{EtherType}{$b} <=>
	 $CountMaster{EtherType}{$a}} keys %{$CountMaster{EtherType}}) {
		print FILE "<tr><td>$type</td><td>" .
		 "$CountMaster{EtherType}{$type}</td></tr>\n";
	}
	print FILE <<END_HTML;
</table>
</body>
</html>
END_HTML

	#
	#  Create Text index file
	#
	open(FILE,">index.text") || die "ERROR27: creating index: $!\n";
	print FILE "Master Indexes\nCreated at: $the_date, Type: $TYPE\n\n";
	for ($index=0; $index <= $#Master; $index++) {
		$start = $Master[$index]{starttime};
		$end = $Master[$index]{endtime};
		$dir = $Master[$index]{dir};
		$file = $Master[$index]{file};
		$size = $Master[$index]{size};
		$duration = $Master[$index]{duration};
		printf FILE "%-25s %3s s %8s b  %s\n",$start,$duration,
		 $size,"$dir/index.text";
	}
	close FILE;


	#
	#  Create index.file for redos
	#
	open(FILE,">index.file") || die "ERROR28: creating index: $!\n";
	for ($index=0; $index <= $#Master; $index++) {
		$dir = $Master[$index]{dir};
		$file = $Master[$index]{file};
		$start = $Master[$index]{starttime};
		$end = $Master[$index]{endtime};
		$duration = $Master[$index]{duration};
		print FILE "$dir\t$file\t$duration\t$start\t$end\n";
	}
	close FILE;
   }
}


# JL: Print a line for the HTTPlog
#
sub Print_Log_Line {
	my $number = shift;
        my $time = shift;
	my $duration = shift;
	my $src = shift;
	my $dest = shift;
	my $result = shift;
	my $status = shift;
	my $size = shift;
	my $method = shift;
	my $site = shift;
	my $type = shift;

	if ($Arg{httplog_html}) {
	    sprintf("<pre><a href=\"index.html#%d\">%d</a>" .
		    " %9d.%03d %6d " .
		    "%-15s %-15s %s/%03d %d %s %s %s %s%s/%s %s</pre><br/>\n",
		    $number,$number,
		    int($time),(($time - int($time))*1000),($duration*1000),
		    $src,$dest,$result,$status,$size,
		    $method,$site,"-","NONE","","-",$type);
	} else {
	    sprintf("%9d.%03d %6d %s %s/%03d %d %s %s %s %s%s/%s %s\n",
		    int($time),(($time - int($time))*1000),($duration*1000),
		    $src,$result,$status,$size,
		    $method,$site,"-","NONE","","-",$type);
	}
}

# JL: Print a line for the new text HTTPlog
#
sub Print_TxtLog_Line {
	my $number = shift;
        my $time = shift;
	my $referer = shift;
	my $cookie = shift;
	my $setcookie = shift;
	my $method = shift;
	my $site = shift;

	($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime($time);
	$referer = "Referer: " . $referer if $referer ne "";
	$cookie = "Cookie sent." if $cookie ne "";
	$setcookie = "Sets cookie." if $setcookie ne "";
	sprintf("%-4s %02d:%02d:%02d %s %s %s %s %s\n",
		$number,$hour,$minute,$second,
		$method,$site,$referer,$cookie,$setcookie);
}


# Create_Log_Files - create log files such as the HTTP log.
#
sub Create_Log_Files {
	#BDG some memory debug
	#system("pmap -x $$");

	#
	#  Create httplog file
        # JL: Don't use hardcoded filename
	#
        open(FILE,">$Arg{httplog_name}") || die "ERROR29: creating HTTP log: $!\n";
	foreach $time (sort { $a <=> $b }(keys (%{$HTTPlog{time}}))) {
		print FILE $HTTPlog{time}{$time};
	}

	close FILE;

        open(FILE,">$Arg{httplog_txt}") || die "ERROR29: creating HTTP text log: $!\n";

	foreach $time (sort { $a <=> $b }(keys (%{$HTTPtxtlog{time}}))) {
		print FILE $HTTPtxtlog{time}{$time};
	}

	close FILE;
}



# File_Type - return file extension for given data, else "data".
#
sub File_Type {
	my $data = $_[0];
	my $type = "";

	if ( $http_data eq "" ) {
	    return "empty";
	}
	if ( length($http_data) < 8 ) {
	    return "small";
	}
	if ($http_header =~ /Content-Encoding: deflate/ ){
		return "deflate";
	}

	if ($data =~ /^GIF8[7-9]/) 		{ $type = "gif"; }
	elsif ($data =~ /^\377.....(JPEG|JFIF)/) 	{ $type = "jpeg"; }
	elsif ($data =~ /^.PNG/) 	        { $type = "png"; } # JL
	elsif ($data =~ /^PK\003\004/) 		{ $type = "zip"; }
	elsif ($data =~ /^\%PDF/) 		{ $type = "pdf"; }
	elsif ($data =~ /^\037\213/) 		{ $type = "gz"; }
	elsif ($data =~ /^BZh/) 		{ $type = "bz2"; }
	elsif ($data =~ /^\177ELF/) 		{ $type = "elf"; }
	elsif ($data =~ /^\%!/) 		{ $type = "ps"; }
	elsif ($data =~ /<html>/i) 		{ $type = "html"; }
	elsif ($data =~ /<?xml/i) 		{ $type = "xml"; } # JL
	else { $type = "data"; }

	return $type;
}


# Is_Image - returns true if extension is for an image.
#
sub Is_Image {
	my $ext = shift;

	# JL: Use MIME types.
	return ($ext_types{$ext} eq "image");
}


# Desex_HTML - Removes HTML tags ("<" and ">") from data, so that it no
#		longer interferes when printed as HTML.
#
sub Desex_HTML {
	### Input
	my $data = shift;

	### Process
	# remove "<" and ">"s
	$data =~ s/</&lt;/g;
	$data =~ s/>/&gt;/g;

	### Return
	return $data;
}



# Process_BothHTML - Process the HTML 2-way session. Remove binary junk
#			that dosen't render well in a browser.
#
sub Process_BothHTML {
	### Input
	my $type = shift;
	my $session_id = shift;
	my $plain = shift;
	my $wrapped = "";
	my $index = 0;
	my $counter = 0;
	my $intag = 0;
	my ($char,$data);

	if ($type eq "TCP") {
		$data = $TCP{id}{$session_id}{BothHTML};
	} elsif ($type eq "UDP") {
		$data = $UDP{id}{$session_id}{BothHTML};
	} elsif ($type eq "ICMP") {
		$data = $ICMP{time}{$session_id}{BothHTML};
	}

	### Process (order dependant)
	$data =~ s/font color="red">     \0</font color="red"></g;
	$data =~ tr/\040-\176\n\r\f/./c;		# max 376, was 245
	if (defined $plain) {
	   # This is a plain style of line wrap
	   $data =~ s/([^\n\f<>]{$WRAP})/$&\n/g;
	} else {
	   # This is a fancy line wrap, a green ">" starts the wrapped lines
	   $data =~ s/([^\n\f<>]{$WRAP})/$&\n<font color="green">&gt;<\/font>/g;
	}

	### Save
	if ($type eq "TCP") {
		$TCP{id}{$session_id}{BothHTML} = $data;
	} elsif ($type eq "UDP") {
		$UDP{id}{$session_id}{BothHTML} = $data;
	} elsif ($type eq "ICMP") {
		$ICMP{time}{$session_id}{BothHTML} = $data;
	}

}

# Process_This_HTML - Process the HTML 2-way session. Remove binary junk
#			that dosen't render well in a browser.
#
sub Process_This_HTML {
	### Input
	my $data = shift;
	my $plain = shift;
	my $wrapped = "";
	my $index = 0;
	my $counter = 0;
	my $intag = 0;
	my ($char);

	### Process (order dependant)
	$data =~ s/font color="red">     \0</font color="red"></g;
	$data =~ tr/\040-\176\n\r\f/./c;		# max 376, was 245
	if (defined $plain) {
	   # This is a plain style of line wrap
	   $data =~ s/([^\n\f<>]{$WRAP})/$&\n/g;
	} else {
	   # This is a fancy line wrap, a green ">" starts the wrapped lines
	   $data =~ s/([^\n\f<>]{$WRAP})/$&\n<font color="green">&gt;<\/font>/g;
	}

	return $data;
}


# Process_Hex - Create the coloured HTML 2-way hex dump, and a text dump.
#		Uses data stored to data structure %Hex.
sub Process_Hex {
	### Input
	my $type = shift;
	my $session_id = shift;
	my $offset = 0;
	my (@Bytes,$byte,$colour,$from_server,$hexhtml,$hextext,$html,$pos,$text,$view,$view2,$viewhtml,$viewtext);


	### Process
	foreach $from_server_AND_data (@{$Hex{$type}{$session_id}}) {
		($from_server, $data) = @{$from_server_AND_data};
		$colour = $from_server ? "blue" : "red";
		$pos = 1 unless defined $pos;
		$hexhtml .= "<font color=\"$colour\">";
		$viewhtml .= "<font color=\"$colour\">";
		@Bytes = unpack("C*", $data);
		foreach $byte (@Bytes) {
			$view = chr($byte);
			$view =~ tr/\040-\176/./c;
			$view2 = $view;
			$view2 =~ s/</&lt;/g;
			$view2 =~ s/>/&gt;/g;
			$viewhtml .= $view2;
			$viewtext .= $view;
			$hexhtml .= sprintf("%2.2x",$byte);
			$hextext .= sprintf("%2.2x",$byte);
			$pos++;
			if ($pos > 16) {
				### Save text version
				$text .= sprintf("%6.08x",$offset) . "  $hextext  $viewtext\n";

				### Save HTML version
				$hexhtml .= "</font>";
				$viewhtml .= "</font>";
				$html .= '<font color="green">' . sprintf("%6.08x",$offset) .  "</font>  $hexhtml  $viewhtml\n";

				$pos = 1;
				$offset += 16;
				$hexhtml = "<font color=\"$colour\">";
				$viewhtml = "<font color=\"$colour\">";
				$hextext = $viewtext = "";
			}
			if ( ($pos != 1) && (($pos %2) == 1) ) {
				$hexhtml .= " ";
				$hextext .= " ";
			}
		}
		$hexhtml .= "</font>";
		$viewhtml .= "</font>";
	}

	return unless defined $pos;
	return ($text, $html) if $pos == 1;

	$short = 39 - length($hextext);
	$hexhtml .= " " x $short;
	$hextext .= " " x $short;

	### Save text version
	$text .= sprintf("%6.08x",$offset) . "  $hextext  $viewtext\n";

	### Save HTML version
	$html .= '<font color="green">' . sprintf("%6.08x",$offset) .  "</font>  $hexhtml  $viewhtml\n";

	return ($text, $html)
}


# Generate_X11_HTML - fetch the text from an X11 session and save
# 	as bidirectional 2-way coloured HTML.
#
# Todo: check if a text or keypress event can be split during
#  transmission and add code similar to X11 replay to handle this.
#
sub Generate_X11_HTML {
	my ($filename,$data,$copy,$xcode,$xbyte,$xlength,$xrest,$d,
	 $xlv,$xvalue,$pad,$y,$yold,$chars,$colour,$session_data,
	 $service_name,$colourold,$store,$keytype,$gotsome);
	my @Times;

	$session_data = "";

	### Input
	my $session_id = shift;
	$data = "";
	$service_name = "X11";

	### Processing
	my $session_text = $session_id;
	$session_text =~ s/,/ <-> /;

	### Fetch raw data
	$xserver = &TCP_Follow_RawA($session_id);

	#
	#  Determine endian of this transfer.
	#
	($xjunk,$xvalue,$xjunk) = unpack('nna*',$xserver);
	#
	#  Create aliases for "n" and "N".
	#
	if ($xvalue < 256) {
		$n = "n"; $N = "N";
	} else {
		$n = "v"; $N = "V";
	}
	#
	#  Determine keymap style - see &Set_X11_KeyCodes()
	#
	if ($xserver =~
	 /q...Q.*w...W.*e...E.*r...R.*t...T.*y...Y.*u...U.*i...I.*o...O.*p/) {
		$keytype = "linux";
	} else {
		$keytype = "sun";
	}

	#
	#  Fetch data from both directions, sorting on timestamps
	#
	@Times = sort{$a <=> $b} (keys %{$TCP{id}{$session_id}{time}});

	#
	# --- Main Loop ---
	#
	# (this needs to be a for loop!)
	for ($i=0; $i <= $#Times; $i++) {
	   $time = $Times[$i];

	   ### Fetch X11 data and direction as a colour
	   if (defined $TCP{id}{$session_id}{time}{$time}{dir}) {
		$copy = $TCP{id}{$session_id}{time}{$time}{data};
		if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") {
			$colour = "red";
		} else {
			$colour = "blue";
		}
	   }

	   $xrest = $copy;
	   #
	   #  Process through X11 codes
	   #
	   while (length($xrest) > 0) {
		### Fetch xcode and other values
		($xcode,$xbyte,$xlength,$xrest) = unpack("CC${n}a*",$xrest);
		$chars = "";

		#
		#  Fetch code values from $xrest, and trim
		#  $xrest. For most requests, the value length
		#  is a field (bytes 3,4) except for XErrors
		#  (code 0) where the total length is always 32.
		#
		if ($xcode == 0) {
			$xlv = 28;
		} else {
			$xlv = ($xlength - 1) * 4;
			$xlv = -$xlv if $xlv < 0;
		}

		### Fetch values for this xcode
		($xvalue,$xrest) = unpack("a${xlv}a*",$xrest);

		$store = 0;

		#
		#  Process a draw text event (76, 77)
		#
		if (($colour eq "blue") && (($xcode == 76)||($xcode == 77))) {
			# Check if this is a xImageText16Req
			if ($xcode == 77) { $xbyte *= 2; }

			($pad,$y,$chars) = unpack("a10${n}a$xbyte",$xvalue);
			if ($yold != $y) { $chars = "\n$chars"; }
			$chars =~ s/\0//g;

			$store = 1;
			$yold = $y;
		}

		#
		#  Process a key pressed event (2)
		#
		if (($colour eq "red") && ($xcode = "2")) {
			($pad,$caps,$pad) = unpack("a24${n}a*",$xvalue);

			#
			#  Translate the X11 KeyCode to the actual char
			#  (try "xmodmap -pke")
			#
			$chars = $KeyCode{$keytype}{$caps}{$xbyte};

			### Don't keep red \n's for neatness (keep blue ones)
			unless ($chars eq "\n") {
				$store = 1;
			}
		}

		#
		#  Process a text scroll event (by using 62 - copy area)
		#
		if (($colour eq "blue") && ($xcode == 62)) {
			$chars = "\n";
			$store = 1;
		}

		### Store data
		if ($store) {
			if ($colour ne $colourold) {
				$session_data .=
				 "</font><font color=\"$colour\">$chars";
			} else {
				$session_data .= $chars;
			}
			$colourold = $colour;
		}
	   }
	}

	$TCP{id}{$session_id}{BothHTML} = $session_data;
}


# Save_Both_HTML - Save bidirectional (coloured) data into a html file.
#
sub Save_Both_HTML {
	my ($filename);

	### Input
	my $type = shift;
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $session_text = shift;
	my $numtext = sprintf("%04d",$number);
	my ($base,$raw);

	$session_text = $session_id unless defined $session_text;

	### Processing
	$session_text =~ s/,/ <-> /;

	### Checks
	$ext = "";
	$session_data = "";
	if ($type eq "TCP") {
	   $base = "session";
	   #
	   # Note, the following is similar code for TCP, UDP and ICMP.
	   # However UDP and ICMP use a simple strategy to store and fetch
	   # the processed HTML; whereas TCP uses a complex yet memory
	   # efficient strategy. This is intentional - the way TCP has
	   # been stored has been tuned to reduce memory usage, as TCP has
	   # the bulk of the data (and the bulk of the memory problem). This
	   # has not been necessary with UDP and ICMP (yet).
	   #
	   if ($TCP{id}{$session_id}{BothHTML} ne "") {
		#
		#  If the BothHTML report has already been calculated, fetch
		#
		$session_data = $TCP{id}{$session_id}{BothHTML};
	   } else {
		#
		#  Generate a BothHTML report by following packets by time
		#
		foreach $time (sort {$a <=> $b}
		 (keys (%{$TCP{id}{$session_id}{time}}))) {
		   $raw = $TCP{id}{$session_id}{time}{$time}{data};
		   $raw = &Desex_HTML($raw);
		   next unless length($raw);
		   if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") {
			$session_data .= "<font color=\"blue\">$raw</font>";
		   } else {
			$session_data .= "<font color=\"red\">$raw</font>";
		   }
	   	}
	   	$session_data = &Process_This_HTML($session_data);
	   	$base = "session";
	   	if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	   }

	} elsif ($type eq "UDP") {
		$base = "stream";
		$session_data = $UDP{id}{$session_id}{BothHTML};
		if ($UDP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	} elsif ($type eq "ICMP") {
		$base = "icmp";
		$session_data = $ICMP{time}{$session_id}{BothHTML};
		if ($ICMP{time}{$session_id}{Partial}) { $ext = ".partial"; }
	} else {
		$base = "are_belong_to_us";
	}

	### Do nothing if there is no data ("26" is mostly due to colour tags)
	return unless ((defined $session_data)&&(length($session_data) > 26));

	### Output
        $filename = "${base}_${numtext}.${service_name}${ext}.html";
	open (OUT,">$filename") || die "ERROR30: file create, $filename: $!\n";
	binmode(OUT);
	print OUT "<HTML>\n<HEAD><TITLE>$number</TITLE></HEAD>" .
	 "<BODY bgcolor=\"white\">\n" .
	 "<H1>$service_name: $session_text</H1>\n" .
	 "<H2>File $Arg{infile}, Session $number</H2>\n" .
	 "<PRE WRAP=\"virtual\">\n" .
         $session_data . "</PRE>\n</BODY>\n</HTML>\n";
        close OUT;

	### Global Vars
	my $length = length($session_data);
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">as_html</a></li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename","",$length);
}


# Save_Hex_HTML - Save bidirectional (coloured) hex data into a html file.
#
sub Save_Hex_HTML {
	my ($filename);

	### Input
	my $type = shift;
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $session_text = shift;
	my $session_data = shift;
	my $numtext = sprintf("%04d",$number);
	my ($base);

	$session_text = $session_id unless defined $session_text;
	$session_data = "" unless defined $session_data;


	### Processing
	$session_text =~ s/,/ <-> /;

	### Checks
	$ext = "";
	if ($type eq "TCP") {
		$base = "session";
		if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	} elsif ($type eq "UDP") {
		$base = "stream";
		if ($UDP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	} elsif ($type eq "ICMP") {
		$base = "icmp";
		if ($ICMP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	}

	### Output
	$filename = "${base}_${numtext}.${service_name}${ext}.hex.html";
	open (OUT,">$filename") || die "ERROR31: file create, $filename: $!\n";
	binmode(OUT);
	print OUT "<HTML>\n<HEAD><TITLE>$number</TITLE></HEAD>" .
	 "<BODY bgcolor=\"white\">\n" .
	 "<H1>$service_name: $session_text</H1>\n" .
	 "<H2>File $Arg{infile}, Session $number</H2>\n" .
	 "<PRE WRAP=\"virtual\">\n" .
         $session_data . "</PRE>\n</BODY>\n</HTML>\n";
	close OUT;

	### Global Vars
	my $length = length($session_data);
	$Index{HTML}[$number] .= "<li>";
	$Index{HTML}[$number] .= "<a href=\"$filename\">hex</a></li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename","",$length);
}


# Save_Hex_Text - Save bidirectional hex data into a text file.
#
sub Save_Hex_Text {
	my ($filename);

	### Input
	my $type = shift;
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $session_text = shift;
	my $session_data = shift;
	my $numtext = sprintf("%04d",$number);
	my ($base);

	$session_text = $session_id unless defined $session_text;
	$session_data = "" unless defined $session_data;

	### Processing
	$session_text =~ s/,/ <-> /;

	### Checks
	$ext = "";
	if ($type eq "TCP") {
		$base = "session";
		if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	} elsif ($type eq "UDP") {
		$base = "stream";
		if ($UDP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	} elsif ($type eq "ICMP") {
		$base = "icmp";
		if ($ICMP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	}

	### Output
        $filename = "${base}_${numtext}.${service_name}${ext}.hex.text";
	open (OUT,">$filename") || die "ERROR32: file create, $filename: $!\n";
	binmode(OUT);
	print OUT "$service_name: $session_text\n" .
	 "File $Arg{infile}, Session $number\n\n$session_data\n";
        close OUT;

	### Global Vars
	my $length = length($session_data);
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename","",$length);
}


# Save_FTP_File - Save files from an active FTP session.
#
sub Save_FTP_File {
	my ($filename,$ftp_data,$length);
	my $session_id = shift;
	my $number = shift;
	my $numtext = sprintf("%04d",$number);
	my $service_name = "ftp-data";

	### Input
	$ftp_data = &TCP_Follow_RawB($session_id);
	if (! defined $ftp_data) {
		$ftp_data = &TCP_Follow_RawA($session_id);
	}

	### Checks
	$ftp_type = &File_Type($ftp_data);
	if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	 else { $ext = ""; }

	### Output
        $filename = "session_${numtext}.part_01.$service_name${ext}.$ftp_type";
        open (OUT,">$filename") || die "ERROR33: file create, $filename: $!\n";
	binmode(OUT);		# for backward OSs
        print OUT $ftp_data;
        close OUT;

	### Global Vars
	$length = length($ftp_data);
	$Index{HTML}[$number] .=
	 "<li><a href=\"$filename\">$filename</a> $length bytes</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename","",$length);
	if (&Is_Image($ftp_type)) {
		$Image{HTML}[$number]{links} .=
		 "<img src=\"$filename\"> ";
		$Image{notempty} = 1;
	}
}

# NOTE On Replays
#
# The essence of these is to playback the client/server data so that
# the original session can be replayed. There are two styles,
#
# Text Replays. These playback the text component to the application
# data to the screen. These usally work well. The actual text data is not
# cleaned up in any way, so to preserve escape sequences necessary to
# redisplay in the original style. Eg, telnet.
#
# GUI Replays, or Server/Client Replays. These often use TCP/IP to send
# the data back to the server or client to playback the session. These
# are less robust, mainly becuase negotiation can occur slightly differently
# causing nothing to be displayed. There is code here to redo the
# negotiation - but it is very difficult for this to be 100% robust.
# The main reasons the GUI replays fail are colour depth mismatch
# and dropped packets. Eg, X11.
#
# Both styles print the binary data within single quotes ' '. This
# creates perl programs that can't be "cat" (use cat -vet), or edited
# in vi (use vim) due to the raw binary data. A neater style would be to
# translate the binary data into octal or hex text streams, eg
# 'print "\015\012\087\012"'... Currently this is not used, as it would
# roughly increase the file size by a factor of 4. However plopping
# data in the middle of perl programs creates problems of it's own
# (see the unusual seds). At some point I may opt for the easier,
# although lengthier, method.


# Save_Session_Replay - Save a replay program for this session. eg, telnet.
#
sub Save_Session_Replay {
	my ($filename,$duration,$time);
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $numtext = sprintf("%04d",$number);

	### Output
	$filename = "session_${numtext}.${service_name}.replay";
	$duration = ($TCP{id}{$session_id}{EndTime} -
	 $TCP{id}{$session_id}{StartTime});
	$duration = sprintf("%.0f",$duration);
	open (REPLAY,">$filename") ||
	 die "ERROR34: creating $filename $!\n";
	binmode(REPLAY);	# for backward OSs

	#
	#  Create a perl program, that when run itself will print out
	#  the contents of the server 1-way stream, with pauses based on
	#  the packet arrival times (replay the session in realtime).
	#
	print REPLAY "#!$PERL\n";
	print REPLAY <<'END';
#
# This is a telnet/login replay program. It will replay a session using
# the timestamps from the packet log.
#
# USAGE: run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./session_0002.telnet.replay 2
#
# Auto generated by Chaosreader.
#
$| = 1;
$factor = $ARGV[0] || 1;
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
END

	#
	#  Sort the data on the timestamps, calculating timestamp differences
	#  to record in the replay program.
	#
	@Times = ();
	foreach $time (keys (%{$TCP{id}{$session_id}{time}})) {
		if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") {
			push(@Times,$time)
		}
	}
	@Times = sort { $a <=> $b } @Times;

	for ($i=0; $i <= $#Times; $i++) {		# required

		### Calculate time diff if possible
		if ($i == $#Times) {
			$timediff = 0;
		} else {
			$timediff = $Times[$i+1] - $Times[$i];
			if ($timediff < 0) { $timediff = 0; }
		}
		$time = $Times[$i];

		### Fetch data from mem
		$data = $TCP{id}{$session_id}{time}{$time}{data};

		#
		#  Clean the data a little (order important)
		#
		$data =~ s/\\/\\\\/g;	# backslash the backslashes
		$data =~ s/'/\\'/g;	# backslash single quotes

		#
		#  Now output the data in the replay program
		#
		print REPLAY "print '" . $data . "';\n";

		#
		#  This causes the replay program to pause
		#
		print REPLAY "ms($timediff);\n";
	}
	close REPLAY;

	### Better make it executable
	chmod (0755, "$filename");

	### Global Vars
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename" .
	 "</a> $duration seconds</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename","",$duration);
}


# Save_Session_textSSH_files - Save a replay program to display the SSH
#	session in a text format, a html form of this, and a key delay
#	data file.
#
# The program "sshkeydata" will take the key delay data file and estimate
# the original typed commands. (It also needs a key delay data file
# from a plaintext session such as telnet, which is generated by the
# Save_Session_Keydata subroutine).
#
# This has been designed with SSH ver 2 in mind.
#
sub Save_Session_textSSH_files {
	my ($filename1,$filename2,$filename3,$duration,$time,$data,$length,
	 $time0,$time1,$time2,$data0,$data1,$data2,$length0,$length1,$length2,
	 $dir0,$dir1,$dir2,$timediff,$timediff2,$outtime,$outsize,$datah,
	 $data00);
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $session_text = shift;
	my $numtext = sprintf("%04d",$number);
	my $delay = ""; 		# a text list of key delays
	my $html = "";			# a html form of output
	my $bytes = 0;			# data bytes of the connection
	my $minsize;			# The min client packet size
	my $state;

	$duration = ($TCP{id}{$session_id}{EndTime} -
	 $TCP{id}{$session_id}{StartTime});
	$duration2 = sprintf("%.2f",$duration);
	$duration = sprintf("%.0f",$duration);

	### Output
	$filename1 = "session_${numtext}.text${service_name}.replay";
	open (REPLAY,">$filename1") ||
	 die "ERROR35: creating $filename1 $!\n";
	binmode(REPLAY);	# for backward OSs

	#
	#  Create a perl program that replays details of the original
	#  SSH session. We print the direction of traffic and size,
	#  paused using the original delays.
	#
	print REPLAY "#!$PERL\n";
	print REPLAY <<'END';
#
# This is a text SSH replay program. It will replay details of the
# original SSH session using timestamps from the packet capture log.
#
# USAGE: run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./session_0002.textSSH.replay 2
#
# Auto generated by Chaosreader.
#
$| = 1;
$factor = $ARGV[0] || 1;
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
print <<'SUBEND';
SSH text analysis replay
------------------------
"*" is client traffic (including keystrokes), "." is the return text.
A number is a multiple of the previous char, eg ".32" is 32 return chars.

SUBEND
END

	#
	#  Sort the data on the timestamps, calculating timestamp differences
	#  to record in the replay program.
	#
	@Times = ();
	%PacketSize = ();
	foreach $time (keys (%{$TCP{id}{$session_id}{time}})) {
		if (length($TCP{id}{$session_id}{time}{$time}{data}) == 0) {
			next;
		}
		push(@Times,$time);
		if ($TCP{id}{$session_id}{time}{$time}{dir} eq "B") {
			### Frequency count sent sizes
			$data = $TCP{id}{$session_id}{time}{$time}{data};
			$length = length($data);
			$PacketSize{$length}++ if $length < 100;
		}
	}
	@Times = sort { $a <=> $b } @Times;
	$outtime = $Times[0];
	$outsize = 0;

	#
	#  Determine the client min size - this is the minimum length of
	#  a data packet, eg a keystroke.
	#
	foreach $length (sort {$PacketSize{$b} <=> $PacketSize{$a}}
	 (keys(%PacketSize))) {
		$minsize = $length;
		last;
	}

	# The very first packet
	$data00 = $TCP{id}{$session_id}{time}{$Times[0]}{data};

	### Process data
	for ($i=0; $i <= $#Times; $i++) {		# required

	   ### Calculate time diff if possible
	   $time0 = $Times[$i];
	   $time1 = $Times[$i+1];
	   $time2 = $Times[$i+2];
	   $time3 = $Times[$i+3];
	   if ($i == $#Times) {
		$timediff1 = 0;
		$timediff2 = 0;
	   } else {
	   	$timediff1 = $time1 - $time0;
	   	$timediff2 = $time2 - $time0;
	   	if ($timediff1 < 0) { $timediff1 = 0; }
	   }

	   ### Fetch data from mem, "0" is this packet...
	   $data0 = $TCP{id}{$session_id}{time}{$time0}{data};
	   $data1 = $TCP{id}{$session_id}{time}{$time1}{data};
	   $data2 = $TCP{id}{$session_id}{time}{$time2}{data};
	   $dir0 = $TCP{id}{$session_id}{time}{$time0}{dir};
	   $dir1 = $TCP{id}{$session_id}{time}{$time1}{dir};
	   $dir2 = $TCP{id}{$session_id}{time}{$time2}{dir};
	   $dir3 = $TCP{id}{$session_id}{time}{$time3}{dir};
	   $length0 = length($data0);
	   $length1 = length($data1);
	   $length2 = length($data2);

	   # working variables
	   $bytes += $length0;
	   $length = $length0;
	   $data = $data0;

	   ##################
	   #  Process Data
	   #
	   #  This is designed for a command line SSH session and
	   #  the calculations are based on many assumptions.
	   #
	   #  For example: if the client sends a small packet (which
	   #  we'll assume is a keystroke) and the server responds
	   #  with large packets (beyond merely echoing the keystroke),
	   #  then we can assume that this keystroke was the enter key,
	   #  and the large response was the output of the command.
	   #
	   #  There are two states - keystrokes and output text.
	   #
	   #  The follow code works well most of the time, and provides
	   #  meaningful results for non command line sessions.
	   #

	   #
	   # --- Server to Client ---
	   #
	   if ($dir0 eq "A") {
		if ($i > 3 || $data00 !~ /^ssh/i) {
			# a "." represents an encrypted server to client packet
			$data = ".";
			$html .= '<font color="blue">' . $data;
		} else {
			### Process initial plaintext negotiation

			# first we clean up the data,
			$data =~ tr/\040-\176/./c;
			$data =~ s/\\/\\\\/g;
			$data =~ s/'/\\'/g;
			$data .= "\n";
			$hdata = $data;
			$hdata = &Desex_HTML($hdata);

	   		# This is a fancy line wrap, adds a green ">"
	   		$hdata =~
		   s/([^\n\f<>]{$WRAP})/$&\n<font color="green">&gt;<\/font>/g;
			$html .= '<font color="blue">' . $hdata;
		}

		if ($state eq "output") {
		   if ($length0 > $minsize && $i > 3) {
			# This prints the length in the replay files
			# as a number following the symbol,
			# eg ".60" would mean a "." with length 60.
			# length actually means size beyond minsize.
			$length -= $minsize;
			$data .= "$length";
			$html .= "$length";
			$outsize += $length;
		   }

		### Data -> Keystrokes
		if ($dir1 eq "B" && $length1 == $minsize) {
			# Process the transition from command output back
			# to keystrokes.
			$data .= "\n";
			$html .= "\n";
			$delay .= "s $outsize\n";
			$delay .= sprintf("t %.6f\n",$time0 - $outtime);
			$delay .= "  \n";	# command delimiter
			$outsize = 0;
			$outtime = $time0;
			$state = "key";
		   }
		}
		$html .= '</font>';
	   }

	   #
	   # --- Client to Server ---
	   #
	   else {
		if ($i == 1) {
		   # PuTTY appears to have an unusual way to send keystrokes
		   # to the server, that differs to OpenSSH and Sun's SSH.
		   # Remember if this is a PuTTY session.
		   $sshtype = "putty" if $data =~ /PuTTY/;
		}

		### Keystroke
		if ($sshtype eq "") {
		   # If the client is sending a minsize packet and the server
		   # then responds, we assume this is a keystroke.
		   if ($length0 == $minsize && $dir1 eq "A") {
			$delay .= "k \n";
		   }
		} elsif ($sshtype eq "putty") {
		   # if the client is sending a minsize packet, followed by
		   # another packet, then a reply packet, and then a server
		   # response; we assume that this is a keystroke.
		   # (This processes PuTTY's doubled keystrokes).
		   if ($length0 == $minsize && $dir1 eq "B" && $dir2 eq "A") {
			$delay .= "k \n";
		   }
		}

		### Process initial plaintext negotiation
		if ($i > 3 || $data00 !~ /^ssh/i) {
			# a "*" represents an encrypted client to server packet
			$data = "*";
			$html .= '<font color="red">' . $data;
		} else {
			### Process initial plaintext negotiation

			# first we clean up the data,
			$data =~ tr/\040-\176/*/c;
			$data =~ s/\\/\\\\/g;
			$data =~ s/'/\\'/g;
			$data .= "\n";
			$hdata = $data;
			$hdata = &Desex_HTML($hdata);

   			# This is a fancy line wrap, adds a green ">"
   			$hdata =~
		   s/([^\n\f<>]{$WRAP})/$&\n<font color="green">&gt;<\/font>/g;
			$html .= '<font color="red">' . $hdata;
		}

		### Keystroke -> Keystroke delay
	 	if ($sshtype eq "") {
		   if ($length0 == $minsize && $dir1 eq "A" && $dir2 eq "B" &&
		    $length2 == $minsize) {
			# If this is a keystroke packet, and the next packet
			# is a response, and then another keystroke packet
			# is sent; then measure the keystroke delay.
			$timediff2 = $time2 - $time0;
			$delay .= sprintf("d %.6f\n",$timediff2);
			$outsize = 0;
			$outtime = $time0;
		   }
		} elsif ($sshtype eq "putty") {
		   if ($length0 == $minsize && $dir1 eq "A" && $dir2 eq "B" &&
		    $length2 == $minsize && $dir3 eq "B") {
			# This is the same idea as the above, but processes
			# PuTTY's doubled keystrokes.
			$timediff2 = $time2 - $time0;
			$delay .= sprintf("d %.6f\n",$timediff2);
			$outsize = 0;
			$outtime = $time0;
		   }
		}

		if ($length0 > $minsize && $i > 3) {
			#
			# This prints the length in the replay files
			# as a number following the symbol,
			# eg ".60" would mean a "." with length 60.
			# length actually means size beyond minsize.
			$length -= $minsize;
			$data .= "$length";
			$html .= "$length";
		}
		$html .= '</font>';

		### Keystrokes -> Data
		if ( ($length0 == $minsize &&
		 (($length1 + $length2) > ($minsize * 2))) ||
		 ($dir1 eq "A" && $dir2 eq "A") ) {
			$data .= "\n";
			$html .= "\n";
			#
			# "r" describes the response packet. This value
			# may or may not be meaningful depending on the
			# SSH software.
			if ($length1 > $minsize) {
			   $delay .= "r 1\n";
			   $delay .= sprintf("p %.6f\n",$timediff1);
			} else {
			   $delay .= "r 2\n";
			   $delay .= sprintf("p %.6f\n",$timediff2);
			}
			$state = "output";
		}
	   }

	   ### Now output the data in the replay program
	   print REPLAY "print '" . $data . "';\n";

	   ### This causes the replay program to pause
	   print REPLAY "ms($timediff1);\n";
	}
	$duration = 0.01 if $duration == 0;     # avoid divide by 0,
	$speed = sprintf("%.2f",$bytes / (1024 * $duration));
	print REPLAY "print \"\n\n" .
	 "Summary: $duration2 seconds, $bytes bytes, $speed Kb/sec\\n\";";
	close REPLAY;

	### Better make it executable
	chmod (0755, "$filename1");

	#
	#  HTML version of the replay script
	#
	$filename2 = "session_${numtext}.text${service_name}.html";
	open (HTML,">$filename2") ||
	 die "ERROR36: Can't write to file, $filename2 $!\n";
	$html = "<html><head><title>SSH text analysis</title></head>\n" .
	"<body bgcolor=\"white\">" .
	"<H1>$service_name: $session_text</H1>\n" .
	"<H2>File $Arg{infile}, Session $number</H2>\n" .
	"<h3>$duration2 seconds, $bytes bytes, $speed Kb/sec</h3>\n" .
	'"*" is client traffic (including ' .
	'keystrokes), "." is the return ' .
	'text.<br>A number is a multiple of the previous char, eg ".32" ' .
	'is 32 return chars.<br>' .
	"\n<b><pre>$html</pre></b>\n</body>\n</html>\n";
	print HTML $html;
	close HTML;

	#
	#  Text Database of time delays between possible keystrokes
	#
	$filename3 = "session_${numtext}.text${service_name}.keydata";
	open (DELAY,">$filename3") ||
	 die "ERROR37: Can't write keydata file: $filename3 $!\n";
	$delay = "$delay  \n";
	print DELAY $delay;
	close DELAY;

	#
	#  Update Global Vars to remember new filenames
	#
	$Index{HTML}[$number] .= "<li><a href=\"$filename1\">$filename1" .
	 "</a> $duration seconds</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename1","",$duration);
	$Index{HTML}[$number] .= "<li><a href=\"$filename2\">$filename2" .
	 "</a> </li>\n";
	$length = length($html);
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename2","",$length);
	$Index{HTML}[$number] .= "<li><a href=\"$filename3\">$filename3" .
	 "</a> </li>\n";
	$length = length($delay);
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename3","",$length);
}


# Save_Session_Keydata - Save a key delay data file to assist SSH analysis.
#
# This code is intentionally designed to be similar to the SSH processing
# code, so that both their outputs can be compared. As a standalone
# subroutine this wouldn't make too much sense; instead bear in mind that
# I'd like the processing to mimic how SSH was processed. That way we
# run this on plenty of known text (telnet) and become familiar with
# exactly what will happen for the unknown text (SSH).
#
sub Save_Session_Keydata {
	my ($filename1,$filename2,$filename3,$duration,$time,$data,$length,
	 $time0,$time1,$time2,$data0,$data1,$data2,$length0,$length1,$length2,
	 $dir0,$dir1,$dir2,$timediff,$timediff2,$outtime,$outsize);
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $session_text = shift;
	my $numtext = sprintf("%04d",$number);
	my $delay = ""; 		# a text list of key delays
	my $minsize;			# The min client packet size
	my $state = "key";

	### Sort the data by timestamps
	@Times = ();
	%PacketSize = ();
	foreach $time (keys (%{$TCP{id}{$session_id}{time}})) {
		if (length($TCP{id}{$session_id}{time}{$time}{data}) == 0) {
			next;
		}
		push(@Times,$time);
	}
	@Times = sort { $a <=> $b } @Times;
	$outtime = $Times[0];
	$outsize = 0;
	$minsize = 1;				# known for telnet

	### Process data
	for ($i=0; $i <= $#Times; $i++) {		# required

	   ### Calculate time diff if possible
	   $time0 = $Times[$i];
	   $time1 = $Times[$i+1];
	   $time2 = $Times[$i+2];
	   if ($i == $#Times) {
		$timediff1 = 0;
		$timediff2 = 0;
	   } else {
		$timediff1 = $time1 - $time0;
		$timediff2 = $time2 - $time0;
		if ($timediff1 < 0) { $timediff1 = 0; }
	   }

	   ### Fetch data from mem, "0" is this packet...
	   $data0 = $TCP{id}{$session_id}{time}{$time0}{data};
	   $data1 = $TCP{id}{$session_id}{time}{$time1}{data};
	   $data2 = $TCP{id}{$session_id}{time}{$time2}{data};
	   $data0 = "\n" if $data0 eq "\r\n";
	   $data1 = "\n" if $data1 eq "\r\n";
	   $data2 = "\n" if $data2 eq "\r\n";
	   $data0 = "\n" if $data0 =~ /\r./;
	   $data1 = "\n" if $data1 =~ /\r./;
	   $data2 = "\n" if $data2 =~ /\r./;
	   $dir0 = $TCP{id}{$session_id}{time}{$time0}{dir};
	   $dir1 = $TCP{id}{$session_id}{time}{$time1}{dir};
	   $dir2 = $TCP{id}{$session_id}{time}{$time2}{dir};
	   $length0 = length($data0);
	   $length1 = length($data1);
	   $length2 = length($data2);

	   $length = $length0;
	   $data = $data0;

	   #
	   #  Process Data
	   #
	   if ($dir0 eq "A") {
		if ($state eq "output") {
			if ($length0 > $minsize) {
				$length -= $minsize;
				$outsize += $length;
			}

			### Data -> Keystrokes
			if ($dir1 eq "B" && $length1 == $minsize) {
				$delay .= "s $outsize\n";
				$delay .= sprintf("t %.6f\n",$time0 - $outtime);
				$delay .= "  \n";
				$outsize = 0;
				$outtime = $time0;
				$state = "key";
			}
		}
	   } else {
		### Keystroke
		if ($length0 == $minsize) {
			if ($data0 eq "\n") {
				$delay .= "k \\n\n";
			} else {
				$delay .= "k $data0\n";
			}
		}
		### Keystroke -> Keystroke delay
		if ($length0 == $minsize && $dir1 eq "A" && $dir2 eq "B" &&
		 $length2 == $minsize) {
			$timediff2 = $time2 - $time0;
			$delay .= sprintf("d %.6f\n",$timediff2);
			$outsize = 0;
			$outtime = $time0;
		}

		if ($length0 > $minsize) {
			$length -= $minsize;
		}

		### Keystrokes -> Data
		if ( ($length0 == $minsize &&
		 (($length1 + $length2) > ($minsize * 2))) ||
		 ($dir1 eq "A" && $dir2 eq "A") ) {
			if ($length1 > $minsize) {
			   $delay .= "r 1\n";
			   $delay .= sprintf("p %.6f\n",$timediff1);
			} else {
			   $delay .= "r 2\n";
			   $delay .= sprintf("p %.6f\n",$timediff2);
			}
			$state = "output";
		}
	   }
	}

	#
	#  Text Database of time delays between possible keystrokes
	#
	$filename3 = "session_${numtext}.${service_name}.keydata";
	open (DELAY,">$filename3") ||
	 die "ERROR38: A pink jelly hits you. You die. $filename3 $!\n";
	print DELAY "$delay  \n";
	close DELAY;

	#
	#  Update Global Vars to remember new filenames
	#
	$Index{HTML}[$number] .= "<li><a href=\"$filename3\">$filename3" .
	 "</a> </li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s\n",
	 '"' , "   $filename3","","");
}


# Save_Stream_Replay - Save a replay program for this stream. eg, dns.
#
sub Save_Stream_Replay {
	my ($filename,$duration);
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $numtext = sprintf("%04d",$number);

	### Output
	$filename = "stream_${numtext}.${service_name}.replay";
	$duration = ($UDP{id}{$session_id}{EndTime} -
	 $UDP{id}{$session_id}{StartTime});
	$duration = sprintf("%.0f",$duration);
	open (REPLAY,">$filename") ||
	 die "ERROR39: creating $filename $!\n";
	binmode(REPLAY);	# for backward OSs

	#
	#  Create a perl program, that when run itself will print out
	#  the contents of the server 1-way stream, with pauses based on
	#  the packet arrival times (replay the stream in realtime).
	#
	print REPLAY "#!$PERL\n";
	print REPLAY <<'END';
#
# This is a UDP replay program. It will replay a stream using
# the timestamps from the packet log.
#
# USAGE: run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./stream_0002.telnet.replay 2
#
# Auto generated by Chaosreader.
#
$| = 1;
$factor = $ARGV[0] || 1;
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
END

	#
	#  Sort the data on the timestamps, calculating timestamp differences
	#  to record in the replay program.
	#
	@Times = keys (%{$UDP{id}{$session_id}{time}});
	@Times = sort { $a <=> $b } @Times;

	for ($i=0; $i <= $#Times; $i++) {	# required

		### Calculate time diff if possible
		if ($i == $#Times) {
			$timediff = 0;
		} else {
			$timediff = $Times[$i+1] - $Times[$i];
			if ($timediff < 0) { $timediff = 0; }
		}
		$time = $Times[$i];

		### Fetch data from mem
		$data = $UDP{id}{$session_id}{time}{$time};
		delete $UDP{id}{$session_id}{time}{$time};

		#
		#  Clean the data a little (order important)
		#
		$data =~ s/\\/\\\\/g;	# backslash the backslashes
		$data =~ s/'/\\'/g;	# backslash single quotes

		#
		#  Now output the data in the replay program
		#
		print REPLAY "print '" . $data . "';\n";

		#
		#  This causes the replay program to pause
		#
		print REPLAY "ms($timediff);\n";
	}
	close REPLAY;

	### Better make it executable
	chmod (0755, "$filename");

	### Global Vars
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename" .
	 "</a> $duration seconds</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename","",$duration);
}


# Save_Session_XReplay - Save a replay program for this session. eg, X11.
#	This processes far more of the X11 protocol than I was hoping.
#	(xscope and ethereal were used to analyse X11).
#
sub Save_Session_XReplay {
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $numtext = sprintf("%04d",$number);
	my ($filename,$duration,$xcode,$xres_old,$xrest,$xwnum,$xdiff,
	 $xlength,$xmsb,$xstart,$xjunk,$xvalue,$readnow,$data,$newdata,
	 $n,$N,$chars,$y,$timediff,$texttimediff,$checkdepth,$filename2,
	 $x11type);
	my @xWords;

	### Initials
	$xmsb = "";
	$readnow = 0;
	$xres_old = -1;
	$checkdepth = 0;

	#
	#  Output - Main X11 replay program
	#
	$filename = "session_${numtext}.${service_name}.replay";
	$duration = ($TCP{id}{$session_id}{EndTime} -
	 $TCP{id}{$session_id}{StartTime});
	$duration = sprintf("%.0f",$duration);
	open (REPLAY,">$filename") ||
	 die "ERROR40: creating $filename $!\n";
	binmode(REPLAY);	# for backward OSs

	#
	#  Output - Text (keystroke replay)
	#
	$filename2 = "session_${numtext}.text${service_name}.replay";
	open (REPLAY2,">$filename2") ||
	 die "ERROR41: creating $filename2 $!\n";
	binmode(REPLAY2);	# for backward OSs


	#  --- textX11 ---
	#
	#  Create a perl program, that when run itself will print out
	#  the contents of the server 1-way stream, with pauses based on
	#  the packet arrival times (replay the session in realtime).
	#
	print REPLAY2 "#!$PERL\n";
	print REPLAY2 <<'END';
#
# This is an X11 text replay program. It will replay keystrokes and text
# of an X11 session using the timestamps from the packet log.
#
# USAGE: run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./session_0002.textX11.replay 2
#
# Auto generated by Chaosreader.
#
$| = 1;
$factor = $ARGV[0] || 1;
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
END


	#  --- X11 ---
	#
	#  Create a perl program, that when run itself will print out
	#  the contents of the server 1-way stream, with pauses based on
	#  the packet arrival times (replay the session in realtime).
	#
	print REPLAY "#!$PERL\n";
	print REPLAY <<'END';
#
# This is a X11 replay program. It will replay a session using
# the timestamps from the packet log, and transpose the X11 protocol so
# that it can be redisplayed. You must have captured from the start
# of the connection for this to work.
#
# USAGE: ./session_0001.X11.replay [-d destination host] [-p port] factor
#
#	just run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./session_0002.X11.replay 2
#	a different host and port can be specified if needed. eg,
#		./session_0002.X11.replay -d 192.168.1.5 -p 6001
#
# PROBLEMS: you may need to authorise this connection to the X11 server
#	before it works. You could run "xhost +hostname" beforehand.
# 	The playback needs to have captured the start of the connection.
#	Check you support the same colour depth as the playback. And check
#	the playback file simply isn't too big! (more than 500 Kb is
#	currently problematic).
#
#
# Auto generated by Chaosreader.
#

use IO::Socket;
use Getopt::Std;

if ($ARGV[0] =~ /^-h$|^--help$/) { &help(); }

# Try fetching values from $DISPLAY
($hostdef,$portdef) = $ENV{DISPLAY} =~ /([^:]*):(\d*)/;
$hostdef = "127.0.0.1" if $hostdef eq "";
$portdef += 6000;

# Command line options take preference
&getopts('d:p:');
if (defined $opt_d) { $host = $opt_d; } else { $host = $hostdef; }
if (defined $opt_p) { $port = $opt_p; } else { $port = $portdef; }
$factor = $ARGV[0] || 1;
$DEBUG = 0;
$| = 1;

print "Chaosreader X11 Replay (experimental)\n\n";
print "Connecting to $host:$port\n";
print "(problems? try running \"xhost +hostname\" first).\n\n";


# --- Open Socket ---
#
$remote = IO::Socket::INET->new( Proto => "tcp",
				PeerAddr  => $host,
				PeerPort  => $port,
				);
unless ($remote) { die "ERROR42: Can't connect to X11 daemon on $host:$port"; }
$remote->autoflush(1);


# --- Subroutines ---
#

# ms - sleeps for specified milliseconds
#
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
# help - print help
#
sub help {
        open (MYSELF,"$0") || die "ERROR43: I can't see myself: $!\n";
        @Myself = <MYSELF>;
        close MYSELF;
        ### Print comment from top of code
        foreach $line (@Myself) {
                last if $line !~ /^#/;
                next if $line =~ m:^#!/usr/bin/perl:;
                $line =~ s/^#/ /;
                print $line;
        }
        print "\n";
        exit(0);
}
# R - recalculates and prints a resourse setting
#     The single character subroutine name saves on file space below.
#
sub R {
	#$offset = shift;
	#$new = $res + $offset;
	my $rid = shift;
	my $new;

	# final checks
	$diff = $rid - $ridbaseold;
	$diff = -$diff if $diff < 0;
	if ((($rid < $ridbaseold) && ($rid < 8196)) || ($diff > 8196)) {
	   if ($msb) { return pack('N',$rid); }
		else { return pack('V',$rid); }
	}

	$new = $rid & $ridmaskold;
	$new = $new | $ridbase;
	if ($msb) { return pack('N',$new); }
	     else { return pack('V',$new); }
}
# D - prints the new Drawable, usually the rootid.
#
sub D {
	my $rid = shift;

	# final checks
	if ($rid >= $ridbaseold) {
		# return mapped resource id
		return R($rid);
	}
	# return rootid
	if ($msb) { return pack('N',$rootid); }
	     else { return pack('V',$rootid); }
}
# C - prints the new Colour map.
#
sub C {
	my $rid = shift;

	# final checks
	if ($rid >= $ridbaseold) {
		# return mapped resource id
		return R($rid);
	}
	# return colour map
	if ($msb) { return pack('N',$colour); }
	     else { return pack('V',$colour); }
}
# M - Returns a generic mapped id. Can be rootid, colour, or resource.
#     These are used in Xcodes involving a mask.
#
sub M {
	my $rid = shift;

	# final checks
	if ($rid >= $ridbaseold) {
		# return mapped resource id
		return R($rid);
	}
	# return rootid map
	if ($rid == $rootidold) {
	   if ($msb) { return pack('N',$rootid); }
		else { return pack('V',$rootid); }
	}
	# return colour map
	if ($rid == $colourold) {
	   if ($msb) { return pack('N',$colour); }
		else { return pack('V',$colour); }
	}
	# return other
	if ($msb) { return pack('N',$rid); }
		else { return pack('V',$rid); }
}
# P - Check depth pixels, print warning if there is a mismatch.
#
sub P {
	my $depth = shift;
	if (! defined $Depth{$depth}) {
		print "\nWARNING: requested depth $depth may not be ".
		 "supported by the server?\n";
	}
}
# debug - print out a value
#
sub debug {
	my $word = shift;
	my $num = shift;
	my $pack = pack("N",$num);
	print "$word: $num ",
	 sprintf("%2.2x%2.2x%2.2x%2.2x\n",unpack("C*",$pack));
}


# --- MAIN ---
#
print "Sending X11 traffic:";
END
	### Fetch raw data
	$xserver = &TCP_Follow_RawA($session_id);

	#
	#  Determine endian of this transfer. Reading the
	#  second short on MSB gives 11, and on LSB 2816
	#  (at least in testing). We split the difference
	#  on 256 (is case there is a little variation).
	#
	($xjunk,$xvalue,$xjunk) = unpack('nna*',$xserver);
	#
	#  Create aliases for "n" and "N" so I can think
	#  in big endian.
	#
	if ($xvalue < 256) {
		$xmsb = 1;
		$n = "n";
		$N = "N";
	} else {
		$xmsb = 0;
		$n = "v";
		$N = "V";
	}
	my ($success,$major,$minor,$length,$release,$ridbase,
	 $ridmask,$mbsize,$vendor,$reqmax,$roots,$formats,$ibo,
	 $bbo,$bslu,$bslp,$keymin,$keymax,$pad,$rest) =
	 unpack("a2$n$n$n$N$N$N$N$n${n}CCCCCCCC${N}a*",$xserver);

	($x11type,$rest) = unpack("a${vendor}a*",$rest);
	$pad = ((4 - ($vendor % 4)) % 4);
	($junk,$rest) = unpack("a${pad}a*",$rest);

	foreach $i (1..$formats) {
		($junk,$rest) = unpack("a8a*",$rest);
	}
	($rootid,$colour,$junk) = unpack("$N${N}a*",$rest);

	#
	#  Sort the data on the timestamps, calculating timestamp differences
	#  to record in the replay program.
	#
	@Times = ();
	foreach $time (keys (%{$TCP{id}{$session_id}{time}})) {
		if ($TCP{id}{$session_id}{time}{$time}{dir} eq "B") {
			push(@Times,$time)
		}
	}
	@Times = sort { $a <=> $b } @Times;

	#
	# --- Main Loop ---
	#
	# (this needs to be a for loop!)
	for ($i=0; $i <= $#Times; $i++) {

		### Calculate time diff if possible
		if ($i == $#Times) {
			$timediff = 0;
		} else {
			$timediff = $Times[$i+1] - $Times[$i];
			# just in case,
			if ($timediff < 0) { $timediff = 0; }
		}
		$time = $Times[$i];
		$texttimediff += $timediff;

		### Fetch data from mem
		$data = $TCP{id}{$session_id}{time}{$time}{data};

		### If initial request was fetched,
		if ($readnow == 0) {
			### Populate $xstart with initial request
			$xstart .= $data;

			#
			#  This triggers the replay program to ask the X11
			#  server for the connection data - which
			#  needs to be processed so that various
			#  resource offsets can be used later on.
			#
			if (length($xstart) >= 12)  {
			   $readnow = 1;
			}

		} else {
		   #
		   #  Change resource offsets
		   #  (reads $data and writes to $data)
		   #
		   $xrest = $data;
		   $data = "";		# output stream of data & subs

		   #
		   #  Process through X11 codes
		   #
		   while (length($xrest) > 0) {
			($xcode,$xbyte,$xlength,$xrest) =
			 unpack("CC${n}a*",$xrest);

			### Add xcode to output stream $data
			$d = pack("CC${n}",$xcode,$xbyte,$xlength);
			# the unusual seds
			$d =~ s/\\/\\\\/g;
			$d =~ s/'/\\'/g;
			$d =~ s/\015\012/'."\\015\\012".'/gs;
			$data .= $d;

			#
			#  Fetch code values from $xrest, and trim
			#  $xrest. For most requests, the value length
			#  is a field (bytes 3,4) except for XErrors
			#  (code 0) where the total length is always 32.
			#
			if ($xcode == 0) {
				$xlv = 28;
			} else {
				$xlv = ($xlength - 1) * 4;
				$xlv = -$xlv if $xlv < 0;
			}
			while (length($xrest) < $xlv) {
				# some more magic
				$i++;
				last if ($i > $#Times);

				$next = $Times[$i];

				### Fetch data from mem
				$xrest .=
				 $TCP{id}{$session_id}{time}{$next}{data};
			}

			($xvalue,$xrest) = unpack("a${xlv}a*",$xrest);

			#$format = "%2.2x%2.2x " x ($xlv/2);
			#printf("X$xcode: $xbyte,$xlength $format\n",
			# unpack("C*",$xvalue));   ### Debug

			$xwnum = 0;
			@xWords = unpack("${N}*",$xvalue);

			#
			#  If this is a text event, save the text to the
			#  textX11 replay program.
			#
			if (($xcode == 76) || ($xcode == 77)) {

				# Check if this is a xImageText16Req
				if ($xcode == 77) { $xbyte *= 2; }

				($pad,$y,$chars) =
				 unpack("a10${n}a$xbyte",$xvalue);
				if ($yold != $y) { $chars = "\n$chars"; }

				### Clean the data a little (order important)
				$chars =~ s/\\/\\\\/g;
				$chars =~ s/'/\\'/g;
				$chars =~ s/\0//g;

				### Now output the data in the replay program
				print REPLAY2 "print '" . $chars . "';\n";

				### This causes the replay program to pause
				print REPLAY2 "ms($texttimediff);\n"
		 		 unless $texttimediff < 0.002;

				$yold = $y;
				$texttimediff = 0;
			}
			#
			#  Process a text scroll event (by using 62 - copy area)
			#
			if ($xcode == 62) {
				print REPLAY2 "print \"\\n\";\n";
				$chars = "\n";
			}


			#
			#  If this is a create window event, check the depth.
			#
			if (($xcode == 1) && ($checkdepth == 0)) {
				$data .= "',P($xbyte),'";
				$checkdepth = 1;
			}

			#
			#  Print the X11 data with embedded subroutines
			#  to transpose the resource IDs.
			#
			foreach $xw (@xWords) {
				$xwnum++;
				if ($X11_Codes[$xcode][$xwnum] == 1) {
					$data .= "',R($xw),'";
					#print "XCODER: $xcode, $xwnum\n";
				} elsif ($X11_Codes[$xcode][$xwnum] == 2) {
					$data .= "',D($xw),'";
					#print "XCODED: $xcode, $xwnum\n";
				} elsif ($X11_Codes[$xcode][$xwnum] == 3) {
					$data .= "',C($xw),'";
					#print "XCODEC: $xcode, $xwnum\n";
				} elsif ($X11_Codes[$xcode][$xwnum] == 4) {
					$data .= "',M($xw),'";
					#print "XCODEM: $xcode, $xwnum\n";
				} else {
					$d = pack("$N",$xw);
					$d =~ s/\\/\\\\/g;
					$d =~ s/'/\\'/g;
					$d =~ s/\015\012/'."\\015\\012".'/gs;
					$data .= $d;
				}
			}
		   }
		}

		#
		#  Now output the data in the replay program
		#
		print REPLAY "print '.';\n";
		print REPLAY "print \$remote '" . $data . "';\n";

		if ($readnow == 1) {
			$readnow = 2;
			print REPLAY "\$msb = $xmsb;\n";
			print REPLAY "\$ridbaseold = $ridbase;\n";
			print REPLAY "\$ridmaskold = $ridmask;\n";
			print REPLAY "\$rootidold = $rootid;\n";
			print REPLAY "\$colourold = $colour;\n";
			#
			#  The following code implements the client to
			#  server connection - we need to read the
			#  resource and window IDs which are necessary
			#  when transposing the replay traffic to
			#  these new values.
			#
			print REPLAY <<'END';
if ($msb) {
	$n = "n";
	$N = "N";
} else {
	$n = "v";
	$N = "V";
}


read($remote,$in,40);		# (xConnSetup)
($success,$major,$minor,$length,$release,$ridbase,$ridmask,$mbsize,$vendor,
$reqmax,$roots,$formats,$ibo,$bbo,$bslu,$bslp,$keymin,$keymax,$pad) =
unpack("a2$n$n$n$N$N$N$N$n${n}CCCCCCCC${N}a*",$in);

read($remote,$in,$vendor);
print "\nX11 Server Type: $in\n";
read($remote,$in,((4 - ($vendor % 4)) % 4));

foreach $i (1..$formats) {
	read($remote,$in,8);	# (xPixmapFormat)
	($depth,$junk) = unpack("Ca*",$in);
	$Depth{$depth} = 1;
	next if $depth == 1;
	print "X11 server supports $depth bit resolution\n";
}
read($remote,$in,8);	# (xWindowRoot)
($rootid,$colour,$junk) = unpack("$N$N",$in) unless defined $rootid;

if ($DEBUG) {
	debug("Resource ID new: ",$ridbase);
	debug("Resource ID old: ",$ridbaseold);
	debug("Root ID new: ",$rootid);
	debug("Root ID old: ",$rootidold);
	debug("Colour map new: ",$colour);
	debug("Colour map old: ",$colourold);
}
END
		}

		#
		#  This causes the replay program to pause
		#
		print REPLAY "ms($timediff);\n"
		 unless $timediff < 0.002;	# (efficiency).
	}
	print REPLAY "print \"\n\";\n";
	print REPLAY "close \$remote;\n";
	close REPLAY;

	### Better make it executable
	chmod (0755, "$filename");

	close REPLAY2;
	### Better make it executable
	chmod (0755, "$filename2");

	### Global Vars
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename" .
	 "</a> $duration seconds</li>\n";
	$Index{HTML}[$number] .= "<li><a href=\"$filename2\">$filename2" .
	 "</a> $duration seconds</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename","",$duration);
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename2","",$duration);
}



# Save_Session_VNCReplay_andHTML - Save a replay program for this session.
#	This creates a program that is used in conjunction with vncviewer.
#	It also saves the HTML version (it would have been redundant to
# 	create a seperate subroutine for that).
#
sub Save_Session_VNCReplay_andHTML {
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $session_text = shift;
	my $numtext = sprintf("%04d",$number);
	my ($filename,$filename2,$filename3,$duration,$code,$rest,$extra,
	 $length,$start,$junk,$down,$value,$data,$oldtimediff,$printed,$chars,
	 $char,$timediff,$checkdepth,$html);
	my @xWords;

	$oldtimediff = 0;
	$printed = 0;
	$html = "";


	#
	#  Output - Text (keystroke replay)
	#
	$filename2 = "session_${numtext}.text${service_name}.replay";
	open (REPLAY2,">$filename2") ||
	 die "ERROR44: creating $filename2 $!\n";
	binmode(REPLAY2);	# for backward OSs

	#
	#  --- textVNC ---
	#
	#  Create a perl program, that when run itself will print out
	#  the contents of the client 1-way stream, with pauses based on
	#  the packet arrival times (replay the session in realtime).
	#
	print REPLAY2 "#!$PERL\n";
	print REPLAY2 <<'END';
#
# This is an VNC text replay program. It will replay keystrokes from
# a VNC session using the timestamps from the packet log.
#
# USAGE: run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./session_0002.textVNC.replay 2
#
# Auto generated by Chaosreader.
#
$| = 1;
$factor = $ARGV[0] || 1;
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
END

	#
	#  Sort the data on the timestamps, calculating timestamp differences
	#  to record in the replay program.
	#
	@Times = ();
	foreach $time (keys (%{$TCP{id}{$session_id}{time}})) {
		if ($TCP{id}{$session_id}{time}{$time}{dir} eq "B") {
			push(@Times,$time)
		}
	}
	@Times = sort { $a <=> $b } @Times;

	#
	# --- Main Loop ---
	#
	# (this needs to be a for loop!)
	for ($i=0; $i <= $#Times; $i++) {

		### Calculate time diff if possible
		if ($i == $#Times) {
			$timediff = 0;
		} else {
			$timediff = $Times[$i+1] - $Times[$i];
			# just in case,
			if ($timediff < 0) { $timediff = 0; }
		}
		$time = $Times[$i];

		### Fetch data from mem
		$data = $TCP{id}{$session_id}{time}{$time}{data};
		($code) = unpack("C",$data);

		$chars = "";

		# skip code 0's
		if ($code > 0) {
		   #
		   #  Process through VNC client codes
		   #
		   $chars = "";
		   while (length($data) > 0) {
			($code) = unpack("C",$data);
			$length = $VNC_Code_Size{$code};
			$length--;
			last if $length <= 0;

			# Fetch this code only
			($code,$value,$data) = unpack("Ca${length}a*",$data);

			### Process Key Pressed
			if ($code == 4) {
			   ($down,$junk,$extra,$char) = unpack("Ca4Ca",$value);

			   next if $down == 0;		# record key-ups

			   if ($extra == 0) {
				$chars .= $char;
			   } else {
				if (defined $KeyCode{vnc}{0}{$char}) {
				   $chars .= $KeyCode{vnc}{0}{$char};
				}
			   }
			   $html .= $chars;
			}
		   }

		}

		$chars =~ s/\\/\\\\/g;
		$chars =~ s/'/\\'/g;

		### Now output the data in the replay program
		unless (length($chars) == 0) {
			print REPLAY2 "ms($oldtimediff);\n"
	 		 unless $oldtimediff < 0.002;

			### Print the data
			print REPLAY2 "print '" . $chars . "';\n";

			# these counters are for efficiency, otherwise
			# we print too many sequiential sleeps
			$printed = 1;
			$oldtimediff = 0;
		} else {
			$printed = 0;
			$oldtimediff += $timediff;
			next;
		}

		### This causes the replay program to pause
		print REPLAY2 "ms($timediff);\n"
 		 unless $timediff < 0.002;
	}
	close REPLAY2;

	### Better make it executable
	chmod (0755, "$filename2");


	#  --- HTML ---
	#
	#  Create a HTML page showing the keystrokes

	### Clean up html
	$html = &Desex_HTML($html);

	### Output
        $filename3 = "session_${numtext}.text${service_name}${ext}.html";
	open (OUT,">$filename3") ||die "ERROR45: file create, $filename3: $!\n";
	binmode(OUT);
	print OUT "<HTML>\n<BODY bgcolor=\"white\">\n" .
	 "<H1>$service_name: $session_text</H1>\n" .
	 "<H2>File $Arg{infile}, Session $number</H2>\n" .
	 "<PRE WRAP=\"virtual\">\n" .
         "<font color=\"red\">" .$html. "</font></PRE>\n</BODY>\n</HTML>\n";
        close OUT;

	### Global Vars
	$length = length($html);
	$Index{HTML}[$number] .=
	 "<li><a href=\"$filename3\">keystrokes</a></li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename3","",$length);


	#
	#  Output - Main VNC replay program
	#
	$filename = "session_${numtext}.${service_name}.replay";
	$duration = ($TCP{id}{$session_id}{EndTime} -
	 $TCP{id}{$session_id}{StartTime});
	$duration = sprintf("%.0f",$duration);
	open (REPLAY,">$filename") ||
	 die "ERROR46: creating $filename $!\n";
	binmode(REPLAY);	# for backward OSs

	#
	#  --- VNC ---
	#
	#  Create a perl program, that when run itself will create a
	#  playback VNC server that listens on a port. When a vncviewer
	#  connects, the contents of the server 1-way stream arew played back,
	#  with pauses.
	#
	print REPLAY "#!$PERL\n";
	print REPLAY <<'END';
#
# This is a VNC replay program. This runs as a server and listens on a port,
# then vncviewer is run to connect to that port - at which point the playback
# commences.
#
# USAGE: ./session_0001.VNC.replay [-p port] factor
#
#	just run the script as normal. You can provide a factor as an
#	argument, eg "2" to run twice as fast, or "0.5" to run
#	at half time. eg,
# 		./session_0002.VNC.replay 2
#	a different host and port can be specified if needed. eg,
#		./session_0002.VNC.replay -p 5925
#
#	After the script is running, connect using vncviewer. eg,
#		vncviewer -viewonly localhost:25
#
# PROBLEMS: The playback needs to have captured the start of the connection,
# 	you need to be at the same colour depth as the playback (or more may
#	work), and your screen should be at least as big as the playback
#	resolution. Newer versions of vncviewer may be tuned to match the
#	playback (eg "-8bit").
#
# Auto generated by Chaosreader.
#

use IO::Socket;
use Getopt::Std;
use Net::hostent;

$| = 1;

if ($ARGV[0] =~ /^-h$|^--help$/) { &help(); }

# Command line options take preference
&getopts('p:');
if (defined $opt_p) { $port = $opt_p; } else { $port = 5921; }
$vncport = $port - 5900;
if ($vncport < 0) { die "ERROR47: Port $port too low, use at least 5901.\n"; }
$factor = $ARGV[0] || 1;
$DEBUG = 0;

print "Chaosreader VNC Replay (experimental)\n\n";
print "Listening on port $port...\n";


# --- Open Socket ---
#
$server = IO::Socket::INET->new( Proto     => 'tcp',
                                 LocalPort => $port,
                                 Listen    => SOMAXCONN,
                                 Reuse     => 1);

die "can't setup server" unless $server;
unless ($server) {
	die "ERROR48: Can't open port $port. Try a different port.";
}

print <<WELCOME;
Port opened successfully.

Now run vncviewer and connect to this port. eg,
	vncviewer -viewonly localhost:$vncport

If you are prompted for a password, type any character and hit enter.
Waiting for connection...
WELCOME


# --- Subroutines ---
#

# ms - sleeps for specified milliseconds
#
sub ms {
	$ms = shift;
	$ms = $ms / $factor;
	select(undef, undef, undef, $ms);
}
# help - print help
#
sub help {
        open (MYSELF,"$0") || die "ERROR49: I can't see myself: $!\n";
        @Myself = <MYSELF>;
        close MYSELF;
        ### Print comment from top of code
        foreach $line (@Myself) {
                last if $line !~ /^#/;
                next if $line =~ m:^#!/usr/bin/perl:;
                $line =~ s/^#/ /;
                print $line;
        }
        print "\n";
        exit(0);
}


#
# --- MAIN ---
#

### Wait for connection
$client = $server->accept();
$client->autoflush(1);

print "Sending VNC traffic:";

END

	#
	#  Sort the data on the timestamps, calculating timestamp differences
	#  to record in the replay program.
	#
	@Times = ();
	foreach $time (keys (%{$TCP{id}{$session_id}{time}})) {
		if ($TCP{id}{$session_id}{time}{$time}{dir} eq "A") {
			push(@Times,$time)
		}
	}
	@Times = sort { $a <=> $b } @Times;

	#
	# --- Main Loop ---
	#
	# (this needs to be a for loop!)
	for ($i=0; $i <= $#Times; $i++) {

		### Calculate time diff if possible
		if ($i == $#Times) {
			$timediff = 0;
		} else {
			$timediff = $Times[$i+1] - $Times[$i];
			# just in case,
			if ($timediff < 0) { $timediff = 0; }
		}
		$time = $Times[$i];

		### Fetch data from mem
		$data = $TCP{id}{$session_id}{time}{$time}{data};

		$data =~ s/\\/\\\\/g;
		$data =~ s/'/\\'/g;
		$data =~ s/\015\012/'."\\015\\012".'/gs;

		#
		#  Now output the data in the replay program
		#
		print REPLAY "print '.';\n";
		print REPLAY "print \$client '" . $data . "';\n";

		#
		#  This causes the replay program to pause
		#
		print REPLAY "ms($timediff);\n"
		 unless $timediff < 0.002;	# (efficiency).
	}
	print REPLAY "print \"\n\";\n";
	print REPLAY "close \$client;\n";
	close REPLAY;

	### Better make it executable
	chmod (0755, "$filename");

	### Global Vars
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename" .
	 "</a> $duration seconds</li>\n";
	$Index{HTML}[$number] .= "<li><a href=\"$filename2\">$filename2" .
	 "</a> $duration seconds</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename","",$duration);
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s seconds\n",
	 '"' , "   $filename2","",$duration);
}



# Save_SMTP_Emails - Save emails from an SMTP session.
#
sub Save_SMTP_Emails {
	my ($filename);
	my $session_id = shift;
	my $number = shift;
	my $service_name = "smtp";
	my $numtext = sprintf("%04d",$number);


	### Full - Input
	$snmp_data = &TCP_Follow_RawB($session_id);

	### Full - Processing
	@Snmp_parts = split(/\r\n\.\r\n|\n\.\n/,$snmp_data);

	### LOOP
	$partnum = 0;
	foreach $snmp_part (@Snmp_parts) {

		next unless $snmp_part =~ /DATA/;
		$partnum++;
		$parttext = sprintf("%02d",$partnum);

		### Part - Processing
		$snmp_part =~ s/^.*DATA\r?\n//s;	# '/s;' is new perl5,
							# else '/;' with $* = 1

		### Part - Output
		if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
		 else { $ext = ""; }
	        $filename = "session_${numtext}.part_${parttext}." .
		 "${service_name}${ext}.email";
	        open (OUT,">$filename") ||
		 die "ERROR50: file create, $filename: $!\n";
		binmode(OUT);		# for backward OSs
	        print OUT $snmp_part;
	        close OUT;

		### Part - Global Vars
		my $length = length($snmp_part);
		$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename" .
		 "</a> $length bytes</li>\n";
		$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
		 '"' , "   $filename","",$length);
	}
}


# Save_HTTP_Files - Save HTTP components.
#
sub Save_HTTP_Files {
	my ($filename);
	my $session_id = shift;
	my $number = shift;
	my $service_name = shift;
	my $numtext = sprintf("%04d",$number);

	### Full - Input
	$http_session = &TCP_Follow_RawA($session_id);

	### Full - Processing
	@HttpParts = split(/HTTP\/[0-9.]* /,$http_session);

	### LOOP
	$partnum = 0;
	foreach $http_part (@HttpParts) {

	    # JL.  I want to see all parts, in partcular empty ones
		# resulting from 304 (Not Modified).  Thus, the original
		# check below on $http_data is too strong.
		next if $http_part eq "";

		### Part - Processing
		($http_header,$http_data) = split(/\r\n\r\n|\n\n/,$http_part,2);
		# next if $http_data eq "";
		# next if length($http_data) < 8;
		$partnum++;
		$parttext = sprintf("%02d",$partnum);

		### JL: Chunk Check, patch from http://refrequelate.blogspot.com/2008/07/more-de-chunking-chaosreader-patch.html
		if ( $http_header =~ /Transfer-Encoding: chunked/ ) {
		    my $new_http_data="";
		    my $chunksize=-1;
		    my $pos=0;
		    until ($chunksize==0) {
			my $eolpos=index($http_data,"\r\n",$pos);
			$chunksize=hex(substr($http_data,$pos,$eolpos - $pos));
			$pos=($eolpos+2);
			if ($chunksize > 0) {
			    $new_http_data.=substr($http_data,$pos,$chunksize);
			}
			$pos+=($chunksize+2);
		    }
		    $http_data=$new_http_data;
		}

		### Part - Checks
		my $http_type = &File_Type($http_data);
		if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
		 else { $ext = ""; }

		### JL: Check for known MIME type in Content-Type
		my ($content_type) = $http_header =~ /Content-Type:\s+(\S*)/is;
		my $file_extension = "";
		if ($content_type ne "") {
		    for my $pattern ( keys %mime_types ) {
			my $value = $mime_types{$pattern};
			if ( $content_type =~ /$pattern/i ) {
			    $file_extension = $value;
			    last;
			}
		    }
		    if (($file_extension eq "bin")
			or ($file_extension eq "asc")) {
			# Not too specific.  Some HTTP servers return images
			# with Content-Type: application/octet-stream, others
			# with Content-Type: text/plain
			# Prefer http_type then...
			if ($http_type ne "data") {
			    $file_extension = $http_type;
			}
		    }
		    elsif ($file_extension eq "") {
			print "Unkown Content-Type $content_type.";
			print "  May want to extend MIME types.\n";
		    }
		}

		### Part - Output
		# JL: Create filename based on Content-Type
	        my $filename = "session_${numtext}.part_$parttext${ext}";
		if ($file_extension ne "") {
		    $filename .= ".$file_extension";
		}
		if ( ($file_extension eq "") or ($http_type eq "gz") ) {
		    $filename .= ".$http_type";
		}
	        open (OUT,">$filename") ||
		 die "ERROR51: file create, $filename: $!\n";
		binmode(OUT);		# for backward OSs
	        print OUT $http_data;
	        close OUT;

		### JL: gz decompressing
		if ( $http_type eq "gz" ) {
		    my $gunzipped = substr($filename, 0, length($filename) - 3);
		    my $gunzip_failed = 0;
		    gunzip $filename => $gunzipped
			or $gunzip_failed = 1;
		    if ( $gunzip_failed == 0 ) {
				$filename = $gunzipped;
		    }
		}
		# Pex:Deflate
		elsif ( $http_type eq "deflate") {
			#print "inflating " . $http_type ;
			my $inflated = substr($filename, 0, length($filename) - 4) . "inflated.html";
		    my $status = IO::Uncompress::Inflate::inflate($filename, $inflated, Transparent => 0);
		    my $error = $IO::Uncompress::Inflate::InflateError;
		    if ($status) {
				#Succesful inflate
		    	$filename = $inflated;
		    }
		    else {
			    my $status = IO::Uncompress::RawInflate::rawinflate($filename, $inflated);
			    my $error = $IO::Uncompress::RawInflate::RawInflateError;
		 		if ($status) {
		 			#Succesful raw inflate
		 			$filename = $inflated;
		 		}
		 		elsif ($error eq "expected end of file"){
		 			# End of file, might been succesful
		    		$filename = $inflated;
		 		}			
		 		else {
		 			#failed inflate
		 			#print "failed inflate";
		 		}
		    }
		}

		### Part - Global Vars
		my $length = length($http_data);
		$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename" .
		 "</a> $length bytes</li>\n";
		$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
		 '"' , "   $filename","",$length);
		if (&Is_Image($http_type) or
		    &Is_Image($file_extension)) { # JL: Also check file ext.
		    $Image{HTML}[$number]{links} .= "<img src=\"$filename\"> ";
		    $Image{notempty} = 1;
		    # JL: Remember this part as image.
		    $ExtImage{HTML}[$number]{parts}[$partnum] = 1;
		}
	}
}


# Save_NFS_File - Save NFS file. Only works well for some files, if the NFS
#		header can't be processed, a "*.nfs.raw" file is created.
#
sub Save_NFS_File {
	my ($filename);
	my $session_id = shift;
	my $number = shift;
	my $service_name = "nfs";
	my $numtext = sprintf("%04d",$number);

	### Input
	my $nfs_raw = &TCP_Follow_RawB($session_id);

	### Processing
	($nfs_start,$nfs_size,$nfs_end) = unpack('a56a4a*',$nfs_raw);
	$nfs_sizeint = unpack("N",$nfs_size);
	($nfs_start,$nfs_data) = split(/$nfs_size....$nfs_size/,$nfs_end,2);

	### Checks
	if (($nfs_sizeint > 4) && (length($nfs_data) >= $nfs_sizeint)) {
		$nfs_type = &File_Type($nfs_data);
		if ($nfs_sizeint < length($nfs_data)) {
			$nfs_data = unpack("a${nfs_sizeint}a*",$nfs_data);
		}
	} else {
		$nfs_type = "raw";
		$nfs_data = $nfs_raw;
	}
	if ($TCP{id}{$session_id}{Partial}) { $ext = ".partial"; }
	 else { $ext = ""; }

	### Output
        $filename = "session_${numtext}.part_01.${service_name}${ext}.nfs." .
	 "$nfs_type";
	open (OUT,">$filename") || die "ERROR52: file create, $filename: $!\n";
	binmode(OUT);		# for backward OSs
	print OUT $nfs_data;
	close OUT;

	### Global Vars
	my $length = length($nfs_data);
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename</a>" .
	 " $length bytes</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename","",$length);
}


# Save_DNS_File - Save DNS file.
#
sub Save_DNS_File {
	my ($filename);
	my $session_id = shift;
	my $number = shift;
	my $numtext = sprintf("%04d",$number);
	my $text = $UDP{id}{$session_id}{DNS};

	### Output
        $filename = "session_${numtext}.domain.txt";
	open (OUT,">$filename") || die "ERROR52: file create, $filename: $!\n";
	print OUT $text;
	close OUT;

	### Global Vars
	my $length = length($text);
	$Index{HTML}[$number] .= "<li><a href=\"$filename\">$filename</a>" .
	 " $length bytes</li>\n";
	$Index{Text}[$number] .= sprintf("%-4s %-45s %-10s %8s bytes\n",
	 '"' , "   $filename","",$length);
}


# TCP_Follow_RawA - process session by TCP Seq numbers 1-way.
#			(TCP ASSEMBLY)
#
sub TCP_Follow_RawA {
	my $session_id = shift;
	my $raw = "";

	#
	#  Assemble TCP Sessions. Each hash contains session_ids as keys,
	#  and the value points to another hash of sequence numbers and data.
	#  %TCP{id}{}{Aseq} is input, and %TCP{id}{}{RawA} is output.
	#
	@Seqs = keys (%{$TCP{id}{$session_id}{Aseq}});
	foreach $seq (sort { $a <=> $b } @Seqs) {
		$raw .= ${$TCP{id}{$session_id}{Aseq}{$seq}};
	}

	return $raw;
}


# TCP_Follow_RawB - process session by TCP Seq numbers 1-way.
#			(TCP ASSEMBLY)
#
sub TCP_Follow_RawB {
	my $session_id = shift;
	my $raw = "";

	#
	#  Assemble TCP Sessions. Each hash contains session_ids as keys,
	#  and the value points to another hash of sequence numbers and data.
	#  %TCP{id}{}{Aseq} is input, and %TCP{id}{}{RawA} is output.
	#
	@Seqs = keys (%{$TCP{id}{$session_id}{Bseq}});
	foreach $seq (sort { $a <=> $b } @Seqs) {
		$raw .= ${$TCP{id}{$session_id}{Bseq}{$seq}};
	}

	return $raw;
}


# Pick_Service_Port - pick which port is the server. Usually is the lower
#	number, however check if the direction is already known (eg SYN).
#	The port arguments will not often be needed.
#
# NOTE: This code is different to Generate_TCP_IPs - which does the "<->"'s
#
sub Pick_Service_Port {
	my $type = shift;
	my $id = shift;
	my $porta = shift;
	my $portb = shift;
	my $from_server = 0;
	my ($hi,$low);

	# Catch active FTP, etc.
	($low,$hi) = sort { $a <=> $b } ($porta,$portb);
	if ($low < 100) {
		return ($low,$hi);
	}

	if ($type eq "TCP") {
	   if (defined $TCP{id}{$id}{source}) {
		if ($TCP{id}{$id}{source} eq $TCP{id}{$id}{src}) {
		   return ($TCP{id}{$id}{dest_port},$TCP{id}{$id}{src_port});
		} else {
		   return ($TCP{id}{$id}{src_port},$TCP{id}{$id}{dest_port});
		}
	   }
	} elsif ($type eq "UDP") {
	   return ($UDP{id}{$id}{dest_port},$UDP{id}{$id}{src_port});
	}

	# resort to a sort
	return sort { $a <=> $b } ($porta,$portb);
}

# Retrieve DNS name for IP address based on DNS traffic of this capture.
# If possible, retrieve the original name for a CNAME of the IP address.
#
sub Get_Name_For_IP {
    my $ip_addr = shift;
    my $result = $ip_addr;
    if (defined $DNS{$ip_addr}) {
	$result = $DNS{$ip_addr};
	while (defined $DNS{$result}) {
	    $result = $DNS{$result};
	}
    }
    return $result;
}

# Generate_SessionID - input source and dest IPs and ports, and generate
# 	a unique session_id based on them. this is done by sorting on
#	ports and then IPs. Also returns a flag if the packet may be
#	assumed to be from_server - where the lowest port is assumed to
#	be the server (unless TCP SYNs have been observed).
#
sub Generate_SessionID {
	my $ip_src = shift;
	my $tcp_src_port = shift;
	my $ip_dest = shift;
	my $tcp_dest_port = shift;
	my $type = shift;
	my $from_server = 0;
	my $session_id;

	#
	#  Generate session_id string using host:port,host:port sorted on
	#  port (low port last).
	#
	if ($tcp_src_port < $tcp_dest_port) {
		$session_id = "$ip_dest:$tcp_dest_port,$ip_src:$tcp_src_port";
		$from_server = 1;
	} elsif ($tcp_src_port > $tcp_dest_port) {
		$session_id = "$ip_src:$tcp_src_port,$ip_dest:$tcp_dest_port";
		$from_server = 0;
	} else {
		$session_id =join(",",sort("$ip_src:$tcp_src_port",
					"$ip_dest:$tcp_dest_port"));
		$from_server = 1;
	}

	if ($type eq "TCP") {
		if (defined $TCP{id}{$session_id}{source}) {
			if ($TCP{id}{$session_id}{source} eq $ip_dest
			    # JL: Also look at the port as ip_src and ip_dest
			    # may be the same (e.g., 127.0.0.1)
			    # Also in Generate_TCP_IDs below.
			    && $TCP{id}{$session_id}{source_port} eq $tcp_dest_port) {
				$from_server = 1;
			} else {
				$from_server = 0;
			}
		}
	}
	return ($session_id,$from_server);
}



# Generate_TCP_IDs - generate a text and html version of the session ID, that
#		displays direction of the TCP session if SYNs and ACKs were
#		observed, else uses a "<->" symbol to represent unknown
#		direction. TCP only.
#
sub Generate_TCP_IDs {
	my $session_id = shift;
	my ($ip_src,$tcp_src_port,$ip_dest,$tcp_dest_port,$text,$html);

	# try this direction,
	$ip_src = $TCP{id}{$session_id}{src};
	$ip_dest = $TCP{id}{$session_id}{dest};
	$tcp_src_port = $TCP{id}{$session_id}{src_port};
	$tcp_dest_port = $TCP{id}{$session_id}{dest_port};

	if (defined $TCP{id}{$session_id}{source}) {
		if ($TCP{id}{$session_id}{source} eq $ip_dest
		    && $TCP{id}{$session_id}{source_port} eq $tcp_dest_port) {
			# nope, switch ends
			$ip_src = $TCP{id}{$session_id}{dest};
			$ip_dest = $TCP{id}{$session_id}{src};
			$tcp_src_port = $TCP{id}{$session_id}{dest_port};
			$tcp_dest_port = $TCP{id}{$session_id}{src_port};
		}
		if ($Arg{prefer_dns}) {
		    $ip_src = &Get_Name_For_IP($ip_src);
		    $ip_dest = &Get_Name_For_IP($ip_dest);
		}
		$text = "$ip_src:$tcp_src_port -> $ip_dest:$tcp_dest_port";
		$html = "$ip_src:$tcp_src_port -&gt; $ip_dest:$tcp_dest_port";
	} else {
	        if ($Arg{prefer_dns}) {
		    $ip_src = &Get_Name_For_IP($ip_src);
		    $ip_dest = &Get_Name_For_IP($ip_dest);
		}
		$text = "$ip_src:$tcp_src_port <-> $ip_dest:$tcp_dest_port";
		$html = "$ip_src:$tcp_src_port &lt;-&gt; " .
		 "$ip_dest:$tcp_dest_port";
	}

	return ($text,$html);
}



# Generate_IP_ID - input source IP, dest IP and ident, and generate a
#		unique ip_id based on them. This is necessary for IP
#		fragmentation reassembely. Normally we would assume that
#		the IP_ident was unique - however this program could
#		process traffic from many different hosts over a long
#		period of time - idents alone could clash.
#
sub Generate_IP_ID {
	my $ip_src = shift;
	my $ip_dest = shift;
	my $ip_ident = shift;
	my $ip_id;

	#
	#  Generate ip_id string using host:host:ident sorted on IP.
	#
	#
	$ip_id = join(",",sort("$ip_src","$ip_dest")) . ",$ip_ident";

	return $ip_id;
}



# Read_Tcpdump_Record - Read the next tcpdump record, will "last" if
#			there are no more records.
#
sub Read_Tcpdump_Record {
	my $more;

	### Fetch record header
	$length = read(INFILE,$header_rec,($integerSize * 2 + 8));

	### Quit main loop if at end of file
	last if $length < 16;

	### Throw out extra info in tcpdump/modified1 format
	if ($STYLE =~ /^modified/) {
		$length = read(INFILE,$more,8);
	}

	$frame++;

	## Unpack header, endian sensitive
	if ($STYLE =~ /1$/) {
		($tcpdump_seconds,$tcpdump_msecs,$tcpdump_length,
		 $tcpdump_length_orig)
		 = unpack('NNNN',$header_rec);
	} else {
		($tcpdump_seconds,$tcpdump_msecs,$tcpdump_length,
		 $tcpdump_length_orig)
		 = unpack('VVVV',$header_rec);
	}
	$length = read(INFILE,$tcpdump_data,$tcpdump_length);
	$tcpdump_drops = $tcpdump_length_orig - $tcpdump_length;
}


# Read_Snoop_Record - Read the next snoop record, will "last" if
#			there are no more records.
#
sub Read_Snoop_Record {
	### Fetch record header
	$length = read(INFILE,$header_rec,24);

	### Quit main loop if at end of file
	last if $length < 24;

	$frame++;

	### Unpack header
	($snoop_length_orig,$snoop_length_inc,$snoop_length_rec,$snoop_drops,
		$snoop_seconds,$snoop_msecs) = unpack('NNNNNN',$header_rec);
	$length = read(INFILE,$snoop_data,$snoop_length_inc);
	$skip = read(INFILE,$pad,($snoop_length_rec - $snoop_length_inc - 24));
}


# Load_Index_File - Load the master index file "index.file" into @Master
#
sub Load_Index_File {

	my ($path,$dir,$file,$start,$end,$duration,$index);

	#
	#  Load index.file lines into memory
	#
	open (FILES,"index.file") || die "ERROR53: Can't read index.file: $!\n"
	 ."Standalone mode needs to have run recently from this directory.\n\n";

	chomp(@Files = <FILES>);
	close FILES;

	#
	#  Populate @Master
	#
	$index = 0;
	foreach $path (@Files) {
		($dir,$file,$duration,$start,$end) = split(/\t/,$path);
		$Master[$index]{starttime} = $start;
		$Master[$index]{endtime} = $end;
		$Master[$index]{dir} = $dir;
		$Master[$index]{file} = $file;
		$Master[$index]{duration} = $duration;
		$Master[$index]{size} = -s "$dir/$file";
		$index++;
	}
}


# Load_Etc_Services - load /etc/services lookup table into memory,
#			into %Services_TCP and %Services_UDP.
#
sub Load_Etc_Services {
	my ($line,$name,$service);

	### Hardcoded
	%Services_TCP = (20 => "ftp-data",
		         21 => "ftp",
		         23 => "telnet",
		         25 => "smtp",
		         80 => "web",
		         109 => "pop2",
		         110 => "pop3",
		         143 => "imap",
		         513 => "login",
		         514 => "shell",
		         3128 => "web",
		         4110 => "irc4110",
		         5000 => "irc5000",
		         6000 => "X11",
		         6660 => "irc",
		         6665 => "irc",
		         6666 => "irc",
		         6667 => "irc",
		         6668 => "irc",
		         6669 => "irc",
		         7000 => "irc7000",
		         8000 => "irc8000",
		         8080 => "web",
		         9000 => "irc9000");
	# non standard IRC ports include the number in their name

	foreach (@Save_As_X11_Playback_Ports) {
		$Services_TCP{$_} = "X11";
	}

	foreach (@Save_As_VNC_Playback_Ports) {
		$Services_TCP{$_} = "VNC";
	}

	%Services_UDP = (53 => "dns");

	### File input
	open(SERVICES,"/etc/services") || return;
	while ($line = <SERVICES>) {
		next if $line =~ /^#|^\s*$/;    # skip comments, blank lines.
		if ($line =~ /\d\/tcp/) {
			$is_tcp = 1;
		} else {
			$is_tcp = 0;
		}
		$line =~ s:/.*::;
		($name,$port) = split(' ',$line);
		if ($is_tcp) {
			$Services_TCP{$port} = $name;
		} else {
			$Services_UDP{$port} = $name;
		}

	}
	close SERVICES;
}


# Set_IP_Protocols - Set a lookup hash for IP Protocols to names.
# 		RFC790, RFC1700.
#
sub Set_IP_Protocols {
	%IP_Protocols = (0 => "Reserved",
			1 => "ICMP",
			2 => "Unassigned",
			3 => "Gateway-to-Gateway",
			4 => "CCMC Gateway Monitoring Message",
			5 => "ST",
			6 => "TCP",
			7 => "UCL",
			8 => "Unassigned",
			9 => "Secure",
			10 => "BBN RCC Monitoring",
			11 => "NVP",
			12 => "PUP",
			13 => "Pluribus",
			14 => "Telenet",
			15 => "XNET",
			16 => "Chaos",
			17 => "UDP",
			18 => "Multiplexing",
			19 => "DCN",
			20 => "TAC Monitoring",
			37 => "DDP",
			41 => "SIP",
			42 => "SDRP",
			44 => "IPv6 Frag",
			50 => "SIPP-ESP",
			51 => "SIPP-AH",
			53 => "SWIPE",
			50 => "SDRP",
			58 => "ICMPv6",
			88 => "IGRP",
			94 => "IPIP"
	);
}

# Set_ICMP_Types - Set a lookup hash for ICMP Types. RFC792.
#
sub Set_ICMP_Types {
	%ICMP_Types = (0 => "Echo Reply",
			3 => "Destination Unreachable",
			4 => "Source Quench",
			5 => "Redirect",
			8 => "Echo",
			11 => "Time Exceeded",
			12 => "Parameter Problem",
			13 => "Timestamp",
			14 => "Timestamp Reply",
			15 => "Information Request",
			16 => "Information Reply",
			128 => "Echo",
			129 => "Echo Reply",
			135 => "Neighbor solicitation",
			136 => "Neighbor advertisement"
	);
}

# Set_Result_Names - Set a lookup hash for squid result codes.
#		(This needs some fine tuning).
#
sub Set_Result_Names {
	%Result_Names = ("" => "TCP_MISS",
			000 => "TCP_MISS",
			200 => "TCP_HIT",
			302 => "TCP_HIT",
			304 => "TCP_REFRESH_HIT",
			404 => "TCP_NEGATIVE_HIT"
	);
}

# Set_X11_Codes - creates a lookup hash needed for X11 transposing.
#
sub Set_X11_Codes {
	#
	#  This has a row per X11 code, the row describing the 16 bit
	#  words that make up the values. "1" means resource id.
	#  (some values are 8 bit, but are fortunately padded).
	#

	@X11_Codes = (
[ 0 ],	# X_Error entry
[ 0, 2, 2, 0, 0, 0, 1, 0,4,4,4,4,4,4,4,4,4,4,4,4 ], # X_CreateWindow 1
[ 0, 1, 0 ], # X_ChangeWindowAttributes
[ 0, 1 ], # X_GetWindowAttributes
[ 0 ], # X_DestroyWindow?
[ 0 ], # X_DestroySubwindows?
[ 0, 1 ], # X_ChangeSaveSet
[ 0, 1, 1, 0 ], # X_ReparentWindow
[ 0, 1 ], # X_MapWindow
[ 0, 1 ], # X_MapSubwindows
[ 0, 1 ], # X_UnmapWindow 10
[ 0, 1 ], # X_UnmapSubwindows
[ 0, 1, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_ConfigureWindow
[ 0, 1 ], # X_CirculateWindow
[ 0, 2 ], # X_GetGeometry
[ 0, 1 ], # X_QueryTree
[ 0, 1 ], # X_InternAtom (? else 0,0)
[ 0 ], # X_GetAtomName?
[ 0, 1, 0, 0, 1, 0 ], # X_ChangeProperty (? else 0,1,0,0,0,0)
[ 0, 1, 0 ], # X_DeleteProperty
[ 0, 2, 0, 0, 0, 0 ], # X_GetProperty 20
[ 0 ], # X_ListProperties?
[ 0, 1, 0, 0 ], # X_SetSelectionOwner
[ 0 ], # X_GetSelectionOwner
[ 0, 1, 0, 0, 0, 0 ], # X_ConvertSelection
[ 0, 1, 0 ], # X_SendEvent
[ 0, 1, 0, 1, 0, 0 ], # X_GrabPointer
[ 0, 1, 0 ], # X_UngrabPointer?
[ 0, 1, 0, 1, 0, 0 ], # X_GrabButton
[ 0, 1, 0 ], # X_UngrabButton
[ 0, 1, 0, 0 ], # X_ChangeActivePointerGrab 30
[ 0, 1, 0, 0 ], # X_GrabKeyboard
[ 0, 1, 0 ], # X_UngrabKeyboard?
[ 0, 1, 0, 0 ], # X_GrabKey
[ 0, 1, 0 ], # X_UngrabKey
[ 0, 0, 0 ], # X_AllowEvents
[ 0 ], # X_GrabServer?
[ 0 ], # X_UngrabServer?
[ 0 ], # X_QueryPointer?
[ 0, 1, 0, 0 ], # X_GetMotionEvents
[ 0, 1, 1, 0 ], # X_TranslateCoords 40
[ 0, 1, 1, 0, 0, 0 ], # X_WarpPointer
[ 0, 1, 0 ], # X_SetInputFocus
[ 0 ], # X_GetInputFocus?
[ 0 ], # X_QueryKeymap?
[ 0, 1, 0 ], # X_OpenFont
[ 0, 1 ], # X_CloseFont
[ 0, 1 ], # X_QueryFont
[ 0, 1 ], # X_QueryTextExtents
[ 0, 0 ], # X_ListFonts
[ 0, 0 ], # X_ListFontsWithInfo 50
[ 0, 0 ], # X_SetFontPath
[ 0 ], # X_GetFontPath?
[ 0, 1, 2, 0 ], # X_CreatePixmap
[ 0 ], # X_FreePixmap?
[ 0, 1, 2, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_CreateGC ?(else 0,1,1,0)
[ 0, 1, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_ChangeGC
[ 0, 1, 1, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_CopyGC
[ 0, 1, 0 ], # X_SetDashes
[ 0, 1, 0 ], # X_SetClipRectangles
[ 0, 1 ], # X_FreeGC? 60
[ 0, 1, 0, 0 ], # X_ClearArea
[ 0, 2, 2, 1, 0, 0, 0 ], # X_CopyArea
[ 0, 2, 2, 1, 0, 0, 0, 0 ], # X_CopyPlane
[ 0, 2, 1 ], # X_PolyPoint
[ 0, 2, 1 ], # X_PolyLine
[ 0, 2, 1 ], # X_PolySegment
[ 0, 2, 1 ], # X_PolyRectangle
[ 0, 2, 1 ], # X_PolyArc
[ 0, 2, 1, 0 ], # X_FillPoly
[ 0, 2, 1 ], # X_PolyFillRectangle 70
[ 0, 2, 1 ], # X_PolyFillArc
[ 0, 2, 1, 0, 0, 0 ], # X_PutImage
[ 0, 2, 0, 0, 0 ], # X_GetImage
[ 0, 2, 1, 0 ], # X_PolyText8
[ 0, 2, 1, 0 ], # X_PolyText16
[ 0, 2, 1, 0 ], # X_ImageText8
[ 0, 2, 1, 0 ], # X_ImageText16
[ 0, 3, 1, 1 ], # X_CreateColormap
[ 0 ], # X_FreeColormap?
[ 0, 3, 3 ], # X_CopyColormapAndFree 80
[ 0 ], # X_InstallColormap?
[ 0 ], # X_UninstallColormap?
[ 0 ], # X_ListInstalledColormaps?
[ 0, 3, 0, 0 ], # X_AllocColor
[ 0, 3, 0 ], # X_AllocNamedColor
[ 0, 3, 0 ], # X_AllocColorCells
[ 0, 3, 0, 0 ], # X_AllocColorPlanes
[ 0, 3, 0 ], # X_FreeColors
[ 0, 3 ], # X_StoreColors
[ 0, 3, 0, 0 ], # X_StoreNamedColor 90
[ 0, 3 ], # X_QueryColors
[ 0, 3, 0 ], # X_LookupColor
[ 0, 1, 1, 1, 0, 0, 0, 0 ], # X_CreateCursor
[ 0, 1, 1, 1, 0, 0, 0, 0 ], # X_CreateGlyphCursor
[ 0 ], # X_FreeCursor?
[ 0, 1, 0, 0, 0 ], # X_RecolorCursor
[ 0, 2, 0 ], # X_QueryBestSize
[ 0, 1 ], # X_QueryExtension (? else 0,0)
[ 0, 0, 0 ], # X_ListExtensions?
[ 0, 1, 0 ], # X_ChangeKeyboardMapping 100
[ 0, 1, 0 ], # X_GetKeyboardMapping
[ 0, 0, 4,4,4,4,4,4,4,4,4,4,4,4 ], # X_ChangeKeyboardControl
[ 0, 0, 0 ], # X_GetKeyboardControl?
[ 0 ], # X_Bell
[ 0, 0, 0 ], # X_ChangePointerControl
[ 0, 0, 0 ], # X_GetPointerControl?
[ 0, 0, 0 ], # X_SetScreenSaver
[ 0, 0, 0 ], # X_GetScreenSaver?
[ 0, 0 ], # X_ChangeHosts
[ 0 ], # X_ListHosts 110
[ 0 ], # X_SetAccessControl
[ 0 ], # X_SetCloseDownMode
[ 0, 0, 0 ], # X_KillClient?
[ 0, 1, 0 ], # X_RotateProperties
[ 0 ], # X_ForceScreenSaver
[ 0 ], # X_SetPointerMapping
[ 0, 0, 0 ], # X_GetPointerMapping?
[ 0 ], # X_SetModifierMapping
[ 0, 0, 0 ], # X_GetModifierMapping?
[ 0 ], # undef 120
[ 0 ], # undef
[ 0 ], # undef
[ 0 ], # undef
[ 0 ], # undef
[ 0 ], # undef
[ 0 ], # undef
[ 0, 0, 0 ] # X_NoOperation 127
	);

}

# Set_X11_KeyCodes - creates a lookup hash of X11 Key codes needed
#	to generate coloured 2-way HTML X11 reports.
#
sub Set_X11_KeyCodes {
	my ($junk,$code,$char1,$char2,$line,
	 $sun_xmodmap_pke,$linux_xmodmap_pke);
	my %Alias;

	#
	# These are generated using "xmodmap -pke" (and trimmed a little).
	#
	$sun_xmodmap_pke = <<END;
keycode   8 = Control_L
keycode   9 = Control_R
keycode  10 = Shift_L
keycode  11 = Shift_R
keycode  12 = Meta_L
keycode  13 = Meta_R
keycode  14 = Alt_L
keycode  15 = Alt_R
keycode  16 = space
keycode  17 = 0 parenright
keycode  18 = 1 exclam
keycode  19 = 2 at
keycode  20 = 3 numbersign
keycode  21 = 4 dollar
keycode  22 = 5 percent
keycode  23 = 6 asciicircum
keycode  24 = 7 ampersand
keycode  25 = 8 asterisk
keycode  26 = 9 parenleft
keycode  27 = minus underscore
keycode  28 = equal plus
keycode  29 = bracketleft braceleft
keycode  30 = bracketright braceright
keycode  31 = semicolon colon
keycode  32 = apostrophe quotedbl
keycode  33 = grave asciitilde
keycode  34 = comma less
keycode  35 = period greater
keycode  36 = slash question
keycode  37 = backslash bar
keycode  38 = a A
keycode  39 = b B
keycode  40 = c C
keycode  41 = d D
keycode  42 = e E
keycode  43 = f F
keycode  44 = g G
keycode  45 = h H
keycode  46 = i I
keycode  47 = j J
keycode  48 = k K
keycode  49 = l L
keycode  50 = m M
keycode  51 = n N
keycode  52 = o O
keycode  53 = p P
keycode  54 = q Q
keycode  55 = r R
keycode  56 = s S
keycode  57 = t T
keycode  58 = u U
keycode  59 = v V
keycode  60 = w W
keycode  61 = x X
keycode  62 = y Y
keycode  63 = z Z
keycode  64 = BackSpace
keycode  65 = Return
keycode  66 = Tab
keycode  67 = Escape
keycode  68 = Delete
END

	#
	# These are generated using "xmodmap -pke" (and trimmed a little).
	#
	$linux_xmodmap_pke = <<END;
keycode   8 =
keycode   9 = Escape
keycode  10 = 1 exclam
keycode  11 = 2 at
keycode  12 = 3 numbersign
keycode  13 = 4 dollar
keycode  14 = 5 percent
keycode  15 = 6 asciicircum
keycode  16 = 7 ampersand
keycode  17 = 8 asterisk
keycode  18 = 9 parenleft
keycode  19 = 0 parenright
keycode  20 = minus underscore
keycode  21 = equal plus
keycode  22 = BackSpace Terminate_Server
keycode  23 = Tab ISO_Left_Tab
keycode  24 = q Q
keycode  25 = w W
keycode  26 = e E
keycode  27 = r R
keycode  28 = t T
keycode  29 = y Y
keycode  30 = u U
keycode  31 = i I
keycode  32 = o O
keycode  33 = p P
keycode  34 = bracketleft braceleft
keycode  35 = bracketright braceright
keycode  36 = Return
keycode  37 = Control_L
keycode  38 = a A
keycode  39 = s S
keycode  40 = d D
keycode  41 = f F
keycode  42 = g G
keycode  43 = h H
keycode  44 = j J
keycode  45 = k K
keycode  46 = l L
keycode  47 = semicolon colon
keycode  48 = apostrophe quotedbl
keycode  49 = grave asciitilde
keycode  50 = Shift_L
keycode  51 = backslash bar
keycode  52 = z Z
keycode  53 = x X
keycode  54 = c C
keycode  55 = v V
keycode  56 = b B
keycode  57 = n N
keycode  58 = m M
keycode  59 = comma less
keycode  60 = period greater
keycode  61 = slash question
keycode  62 = Shift_R
keycode  64 = Alt_L Meta_L
keycode  65 = space
keycode  94 = less greater
END
	%Alias = qw(exclam ! at @ dollar $ percent %
	 asciicircum ^ ampersand & asterisk * minus - underscore _
	 equal = plus + bracketleft [ bracketright ] braceleft {
	 braceright } semicolon ; colon : apostrophe ' quotedbl "
	 grave ` asciitilde ~ backslash \ bar | less <
	 period . greater > slash / question ?);

	# naughty chatacrers (some of these generate warnings)
	@Alias{"parenleft","parenright","space"} = ("(",")"," ");
	@Alias{"Tab","Return","numbersign","comma"} = ("\t","\n","#",",");


	#
	#  Populate KeyCode aliase
	#
	foreach $line (split(/\n/,$sun_xmodmap_pke)) {
		($junk,$code,$junk,$char1,$char2) = split(' ',$line);
		if (defined $Alias{$char1}) { $char1 = $Alias{$char1}; }
		if (defined $Alias{$char2}) { $char2 = $Alias{$char2}; }
		if (length($char1) > 1) { $char1 = "."; }
		if (length($char2) > 1) { $char2 = "."; }
		$KeyCode{sun}{0}{$code} = $char1;
		$KeyCode{sun}{1}{$code} = $char2;
	}
	foreach $line (split(/\n/,$linux_xmodmap_pke)) {
		($junk,$code,$junk,$char1,$char2) = split(' ',$line);
		if (defined $Alias{$char1}) { $char1 = $Alias{$char1}; }
		if (defined $Alias{$char2}) { $char2 = $Alias{$char2}; }
		if (length($char1) > 1) { $char1 = "."; }
		if (length($char2) > 1) { $char2 = "."; }
		$KeyCode{linux}{0}{$code} = $char1;
		$KeyCode{linux}{1}{$code} = $char2;
	}

}


# Set_VNC_Codes - set globals for VNC.
#
sub Set_VNC_Codes  {

	### set client code to request size hash.
	%VNC_Code_Size = ( 0 => 20,
			   1 => 6,
			   2 => 4,
			   3 => 10,
			   4 => 8,
			   5 => 6,
			   6 => 8 );

	### Some essential keysyms
	$KeyCode{vnc}{0}{"\010"} = "\b";
	$KeyCode{vnc}{0}{"\011"} = "\t";
	$KeyCode{vnc}{0}{"\015"} = "\n";

}


# JL: Initialize Hostnames for IP addresses
sub Set_DNS {
    %DNS = (
	"192.168.178.1" => "fritz.box"
	)
}

# JL: Set_MIME_Types - create hash of MIME types and file extensions.
sub Set_MIME_Types {
    # Initialize with types seen in the wild but not covered below.
    %mime_types = (
	"application/binary" => "binary",
	"application/ocsp-response" => "ocsp",
	"application/pln" => "pln", # bahn.de schedule
	"application/vnd.google.safebrowsing-update" => "safebrowsing-update",
	"application/vnd.google.safebrowsing-chunk" => "safebrowsing-chunk",
	"application/vnd.ms-sync.wbxml" => "mssync", # MS ActiveSync
	"application/x-protobuffer" => "protobuffer", # google
	"application/x-smd" => "smd", # MapDroyd boundary
	"(application|text)/((x-)?javascript|(x-)?js)" => "js",
	"(application|text)/json" => "json",
	"application/x-amf" => ".amf",
	"application/x-zip-compressed" => ".zip",
	"image/bmp" => "bmp",
	"image/vnd.microsoft.icon" => "ico",
	"image/x-gif" => "gif",
	"(image/x-)?jp(e)?g" => "jpeg",
	"image/x-png" => "png",
	"text/xml" => "xml",
	"application/x-bzip2" => "bz2",
	"application/x-css" => "css",
	"font/woff" => "woff",
	"application/font-woff" => "woff");
    # Following created with:
    # grep -o -P "^(text|application|audio|image|video).*\t+([a-z0-9]+)" /etc/mime.types > mime.types
    $raw_mime_types = <<END;
application/andrew-inset			ez
application/annodex				anx
application/atom+xml				atom
application/atomcat+xml				atomcat
application/atomserv+xml			atomsrv
application/bbolin				lin
application/cap					cap
application/cu-seeme				cu
application/davmount+xml			davmount
application/dsptype				tsp
application/ecmascript				es
application/futuresplash			spl
application/hta					hta
application/java-archive			jar
application/java-serialized-object		ser
application/java-vm				class
application/javascript				js
application/m3g					m3g
application/mac-binhex40			hqx
application/mac-compactpro			cpt
application/mathematica				nb
application/msaccess				mdb
application/msword				doc
application/mxf					mxf
application/octet-stream			bin
application/oda					oda
application/ogg					ogx
application/pdf					pdf
application/pgp-keys				key
application/pgp-signature			pgp
application/pics-rules				prf
application/postscript				ps
application/rar					rar
application/rdf+xml				rdf
application/rss+xml				rss
application/rtf					rtf
application/smil				smi
application/xhtml+xml				xhtml
application/xml					xml
application/xspf+xml				xspf
application/zip					zip
application/vnd.android.package-archive		apk
application/vnd.cinderella			cdy
application/vnd.google-earth.kml+xml		kml
application/vnd.google-earth.kmz		kmz
application/vnd.mozilla.xul+xml			xul
application/vnd.ms-excel			xls
application/vnd.ms-pki.seccat			cat
application/vnd.ms-pki.stl			stl
application/vnd.ms-powerpoint			ppt
application/vnd.oasis.opendocument.chart			odc
application/vnd.oasis.opendocument.database			odb
application/vnd.oasis.opendocument.formula			odf
application/vnd.oasis.opendocument.graphics			odg
application/vnd.oasis.opendocument.graphics-template		otg
application/vnd.oasis.opendocument.image			odi
application/vnd.oasis.opendocument.presentation			odp
application/vnd.oasis.opendocument.presentation-template	otp
application/vnd.oasis.opendocument.spreadsheet			ods
application/vnd.oasis.opendocument.spreadsheet-template		ots
application/vnd.oasis.opendocument.text				odt
application/vnd.oasis.opendocument.text-master			odm
application/vnd.oasis.opendocument.text-template		ott
application/vnd.oasis.opendocument.text-web			oth
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet		xlsx
application/vnd.openxmlformats-officedocument.spreadsheetml.template		xltx
application/vnd.openxmlformats-officedocument.presentationml.presentation	pptx
application/vnd.openxmlformats-officedocument.presentationml.slideshow		ppsx
application/vnd.openxmlformats-officedocument.presentationml.template		potx
application/vnd.openxmlformats-officedocument.wordprocessingml.document		docx
application/vnd.openxmlformats-officedocument.wordprocessingml.template		dotx
application/vnd.rim.cod				cod
application/vnd.smaf				mmf
application/vnd.stardivision.calc		sdc
application/vnd.stardivision.chart		sds
application/vnd.stardivision.draw		sda
application/vnd.stardivision.impress		sdd
application/vnd.stardivision.math		sdf
application/vnd.stardivision.writer		sdw
application/vnd.stardivision.writer-global	sgl
application/vnd.sun.xml.calc			sxc
application/vnd.sun.xml.calc.template		stc
application/vnd.sun.xml.draw			sxd
application/vnd.sun.xml.draw.template		std
application/vnd.sun.xml.impress			sxi
application/vnd.sun.xml.impress.template	sti
application/vnd.sun.xml.math			sxm
application/vnd.sun.xml.writer			sxw
application/vnd.sun.xml.writer.global		sxg
application/vnd.sun.xml.writer.template		stw
application/vnd.symbian.install			sis
application/vnd.visio				vsd
application/vnd.wap.wbxml			wbxml
application/vnd.wap.wmlc			wmlc
application/vnd.wap.wmlscriptc			wmlsc
application/vnd.wordperfect			wpd
application/vnd.wordperfect5.1			wp5
application/x-123				wk
application/x-7z-compressed			7z
application/x-abiword				abw
application/x-apple-diskimage			dmg
application/x-bcpio				bcpio
application/x-bittorrent			torrent
application/x-cab				cab
application/x-cbr				cbr
application/x-cbz				cbz
application/x-cdf				cdf
application/x-cdlink				vcd
application/x-chess-pgn				pgn
application/x-cpio				cpio
application/x-csh				csh
application/x-debian-package			deb
application/x-director				dcr
application/x-dms				dms
application/x-doom				wad
application/x-dvi				dvi
application/x-httpd-eruby			rhtml
application/x-font				pfa
application/x-freemind				mm
application/x-futuresplash			spl
application/x-gnumeric				gnumeric
application/x-go-sgf				sgf
application/x-graphing-calculator		gcf
application/x-gtar				gtar
application/x-hdf				hdf
application/x-httpd-php				phtml
application/x-httpd-php-source			phps
application/x-httpd-php3			php3
application/x-httpd-php3-preprocessed		php3p
application/x-httpd-php4			php4
application/x-httpd-php5			php5
application/x-ica				ica
application/x-info				info
application/x-internet-signup			ins
application/x-iphone				iii
application/x-iso9660-image			iso
application/x-jam				jam
application/x-java-jnlp-file			jnlp
application/x-jmol				jmz
application/x-kchart				chrt
application/x-killustrator			kil
application/x-koan				skp
application/x-kpresenter			kpr
application/x-kspread				ksp
application/x-kword				kwd
application/x-latex				latex
application/x-lha				lha
application/x-lyx				lyx
application/x-lzh				lzh
application/x-lzx				lzx
application/x-maker				frm
application/x-mif				mif
application/x-ms-wmd				wmd
application/x-ms-wmz				wmz
application/x-msdos-program			com
application/x-msi				msi
application/x-netcdf				nc
application/x-ns-proxy-autoconfig		pac
application/x-nwc				nwc
application/x-object				o
application/x-oz-application			oza
application/x-pkcs7-certreqresp			p7r
application/x-pkcs7-crl				crl
application/x-python-code			pyc
application/x-qgis				qgs
application/x-quicktimeplayer			qtl
application/x-redhat-package-manager		rpm
application/x-ruby				rb
application/x-sh				sh
application/x-shar				shar
application/x-shockwave-flash			swf
application/x-silverlight			scr
application/x-stuffit				sit
application/x-sv4cpio				sv4cpio
application/x-sv4crc				sv4crc
application/x-tar				tar
application/x-tcl				tcl
application/x-tex-gf				gf
application/x-tex-pk				pk
application/x-texinfo				texinfo
application/x-troff				t
application/x-troff-man				man
application/x-troff-me				me
application/x-troff-ms				ms
application/x-ustar				ustar
application/x-wais-source			src
application/x-wingz				wz
application/x-x509-ca-cert			crt
application/x-xcf				xcf
application/x-xfig				fig
application/x-xpinstall				xpi
audio/amr					amr
audio/amr-wb					awb
audio/amr					amr
audio/amr-wb					awb
audio/annodex					axa
audio/basic					au
audio/flac					flac
audio/midi					mid
audio/mpeg					mpga
audio/mpegurl					m3u
audio/ogg					oga
audio/prs.sid					sid
audio/x-aiff					aif
audio/x-gsm					gsm
audio/x-mpegurl					m3u
audio/x-ms-wma					wma
audio/x-ms-wax					wax
audio/x-pn-realaudio				ra
audio/x-realaudio				ra
audio/x-scpls					pls
audio/x-sd2					sd2
audio/x-wav					wav
image/gif					gif
image/ief					ief
image/jpeg					jpeg
image/pcx					pcx
image/png					png
image/svg+xml					svg
image/tiff					tiff
image/vnd.djvu					djvu
image/vnd.wap.wbmp				wbmp
image/x-canon-cr2				cr2
image/x-canon-crw				crw
image/x-cmu-raster				ras
image/x-coreldraw				cdr
image/x-coreldrawpattern			pat
image/x-coreldrawtemplate			cdt
image/x-corelphotopaint				cpt
image/x-epson-erf				erf
image/x-icon					ico
image/x-jg					art
image/x-jng					jng
image/x-ms-bmp					bmp
image/x-nikon-nef				nef
image/x-olympus-orf				orf
image/x-photoshop				psd
image/x-portable-anymap				pnm
image/x-portable-bitmap				pbm
image/x-portable-graymap			pgm
image/x-portable-pixmap				ppm
image/x-rgb					rgb
image/x-xbitmap					xbm
image/x-xpixmap					xpm
image/x-xwindowdump				xwd
text/cache-manifest				manifest
text/calendar					ics
text/css					css
text/csv					csv
text/h323					323
text/html					html
text/iuls					uls
text/mathml					mml
text/plain					asc
text/richtext					rtx
text/scriptlet					sct
text/texmacs					tm
text/tab-separated-values			tsv
text/vnd.sun.j2me.app-descriptor		jad
text/vnd.wap.wml				wml
text/vnd.wap.wmlscript				wmls
text/x-bibtex					bib
text/x-boo					boo
text/x-c++hdr					h
text/x-c++src					c
text/x-chdr					h
text/x-component				htc
text/x-csh					csh
text/x-csrc					c
text/x-dsrc					d
text/x-diff					diff
text/x-haskell					hs
text/x-java					java
text/x-literate-haskell				lhs
text/x-moc					moc
text/x-pascal					p
text/x-pcs-gcd					gcd
text/x-perl					pl
text/x-python					py
text/x-scala					scala
text/x-setext					etx
text/x-sh					sh
text/x-tcl					tcl
text/x-tex					tex
text/x-vcalendar				vcs
text/x-vcard					vcf
video/3gpp					3gp
video/annodex					axv
video/dl					dl
video/dv					dif
video/fli					fli
video/gl					gl
video/mpeg					mpeg
video/mp4					mp4
video/quicktime					qt
video/ogg					ogv
video/vnd.mpegurl				mxu
video/x-flv					flv
video/x-la-asf					lsf
video/x-mng					mng
video/x-ms-asf					asf
video/x-ms-wm					wm
video/x-ms-wmv					wmv
video/x-ms-wmx					wmx
video/x-ms-wvx					wvx
video/x-msvideo					avi
video/x-sgi-movie				movie
video/x-matroska				mpv
END
    %ext_types = ();
    foreach $line (split(/\n/, $raw_mime_types)) {
	my ($mime_type, $extension) = split(/\t+/, $line);
	# Beware.  "+" needs to be escaped in patterns.
	$mime_type =~ s/\+/\\\+/g;
	$mime_types{$mime_type} = $extension;
	my ($type, $sub_type) = split(/\//, $mime_type);
	# We check whether we already have a value for this extension.
	# We don't care if application is overwritten.
	if ((defined $ext_types{$extension}) &&
	    ($ext_types{$extension} ne $type) &&
	    ($ext_types{$extension} ne "application")) {
	    print "$extension already has $ext_types{$extension}.";
	    print "Should now become $type.  Fix mime.types?\n"
	}
	else {
	    $ext_types{$extension} = $type;
	}
    }
}



# Touch_Vars - This is stops perl -w warnings about vars used only once.
#		Part of my todo list is to cull this list.
#
#
sub Touch_Vars {
	#
	#  Perl < 5.6 code
	#
	#use vars qw($ip_ttl $udp_checksum $ip_ident $tcp_length_data
	#$ip_tos $tcp_options $opt_A $opt_D $tcp_header_rest $opt_J
	#$opt_P $opt_U $opt_X $opt_e $opt_h $opt_i $pad $opt_j
	#$snoop_length_orig $http_header $opt_p $opt_q $opt_r
	#$header_rest $tcp_ack $ether_dest $ether_src $skip
	#$ip_length $udp_length $ip_options $ip_checksum
	#$opt_b $opt_B $opt_l $opt_L $ip_rest $ip_hop $ip_reserved
	#$ip_flow $icmp_rest $opt_f $opt_z);
	#
	#  Perl 5.6 code
	#
	#our ($ip_ttl,$udp_checksum,$ip_ident,$tcp_length_data,
	#$ip_tos,$tcp_options,$opt_A,$opt_D,$tcp_header_rest,$opt_J,
	#$opt_P,$opt_U,$opt_X,$opt_e,$opt_h,$opt_i,$pad,$opt_j,
	#$snoop_length_orig,$http_header,$opt_p,$opt_q,$opt_r,
	#$header_rest,$tcp_ack,$ether_dest,$ether_src,$skip,
	#$ip_length,$udp_length,$ip_options,$ip_checksum,
	#$opt_b,$opt_B,$opt_l,$opt_L,$ip_rest,$ip_hop,$ip_reserved,
	#$ip_flow,$icmp_rest,$opt_f,$opt_z);
	#
	#  Perl < 5.6 and 5.6 code (but not elegant)
	#
	@Once_is_okay = ($ip_ttl,$udp_checksum,$ip_ident,$tcp_length_data,
	$ip_tos,$tcp_options,$opt_A,$opt_D,$tcp_header_rest,$opt_J,
	$opt_P,$opt_U,$opt_X,$opt_e,$opt_h,$opt_i,$pad,$opt_j,
	$snoop_length_orig,$http_header,$opt_p,$opt_q,$opt_r,
	$header_rest,$tcp_ack,$ether_dest,$ether_src,$skip,
	$ip_length,$udp_length,$ip_options,$ip_checksum,$tcp_rst,$tcp_fin,
	$opt_b,$opt_B,$opt_l,$opt_L,$ip_rest,$ip_hop,$ip_reserved,
	$ip_flow,$icmp_rest,$opt_f,$opt_z,$junk1,$opt_H,$opt_I,$opt_R);
}


# Check_Command - check which is the network sniffing command and save
#		it to $command.
#
sub Check_Command {

	#
	#  Check which OS we are on, die if it looks incompatible
	#
	if ($^O eq "linux") {
		#
		#  The "-s9999" tells tcpdump to keep a packet up to this
		#  size, otherwise the default is 68 bytes. Some versions of
		#  tcpdump allow using "-s0" for unlimited.
		#
		$command = "tcpdump -s9999 -w";
	} elsif ($^O eq "solaris") {
		$command = "snoop -o";
	} elsif ($^O eq "darwin") {
		$command = "tcpdump -s0 -w";
	} else {
		die "ERROR54: Can't find the sniffer command for \"$^O\".\n" .
		 "\t Please use log mode instead.\n";
	}

	#
	#  Check username
	#
	if ($ENV{LOGNAME} ne "root") {
		print STDERR "WARNING: Are you root? If not, this probably "
		 . "won't work. Trying anyway...\n";
	}
}


# Process_Command_Line_Arguments - this process the command line arguments
#	and sets various globals which are kept in %Arg. It also prints
#	usage and exists if need be.
#
sub Process_Command_Line_Arguments {
	my $result;

	#
	#  Process Global Defaults into %Arg
	#
	foreach (@Save_As_HTML_TCP_Ports) {
		$Arg{Save_As_TCP_HTML}{$_} = 1;
	}
	foreach (@Save_As_HTML_UDP_Ports) {
		$Arg{Save_As_UDP_HTML}{$_} = 1;
	}
	foreach (@Save_As_TCP_Playback_Ports) {
		$Arg{Save_As_TCP_Playback}{$_} = 1;
	}
	foreach (@Save_As_UDP_Playback_Ports) {
		$Arg{Save_As_UDP_Playback}{$_} = 1;
	}
	foreach (@Save_As_X11_Playback_Ports) {
		$Arg{Save_As_X11_Playback}{$_} = 1;
	}
	foreach (@Save_As_HTML_X11_Ports) {
		$Arg{Save_As_X11_HTML}{$_} = 1;
	}
	foreach (@Save_As_VNC_Playback_Ports) {
		$Arg{Save_As_VNC_Playback}{$_} = 1;
	}

	if (defined $ARGV[0]) {
		### Dump full help if asked
		&Usage_Full if $ARGV[0] eq "--help";

		### Dump massive help if asked
		&Usage_Massive if $ARGV[0] eq "--help2";
	}

	#
	#  Command Line Defaults
	#
	$Arg{output_raw} = 0;
	$Arg{output_hex} = 0;
	$Arg{output_UDP} = 1;
	$Arg{output_TCP} = 1;
	$Arg{output_ICMP} = 1;
	$Arg{output_info} = 0;
	$Arg{output_apps} = 1;
	$Arg{output_index} = 1;
	$Arg{prefer_dns} = 0; # JL: Prefer DNS names over IP addresses?
	$Arg{httplog_html} = 0; # JL: Should we create HTTPlog in HTML?
	$Arg{httplog_name} = "httplog.text"; # JL: Old default as variable
	$Arg{httplog_txt} = "httplog.txt";   # JL: New text format
	$Arg{keydata} = 0;
	$Arg{debug} = 0;

	#
	#  Check correct switches were used
	#
	Getopt::Long::Configure ("bundling");
	$result = GetOptions (
				"application!" => \$opt_a,
				"a" => \$opt_a,
				"d|preferdns" => \$opt_d, # JL: new option
				"e|everything" => \$opt_e,
				"h" => \$opt_h,
				"info!" => \$opt_i,
				"i" => \$opt_i,
				"n|names" => \$opt_n, # JL: new option
				"q|quiet" => \$opt_q,
				"raw!" => \$opt_r,
				"r" => \$opt_r,
				"v|verbose" => \$opt_v,
				"index!" => \$opt_x,
				"x" => \$opt_x,
				"A" => \$opt_A,
				"H|hex" => \$opt_H,
				"I" => \$opt_I,
				"R" => \$opt_R,
				"U|noudp" => \$opt_U,
				"T|notcp" => \$opt_T,
				"Y|noicmp" => \$opt_Y,
				"X" => \$opt_X,
				"D|dir=s" => \$opt_D,
				"b|playtcp=s" => \$opt_b,
				"B|playudp=s" => \$opt_B,
				"l|htmltcp=s" => \$opt_l,
				"L|htmludp=s" => \$opt_L,
				"m|min=s" => \$opt_m,
				"M|max=s" => \$opt_M,
				"o|sort=s" => \$opt_o,
				"p|port=s" => \$opt_p,
				"P|noport=s" => \$opt_P,
				"j|ipaddr=s" => \$opt_j,
				"J|noipaddr=s" => \$opt_J,
				"s|runonce=s" => \$opt_s,
				"S|runmany=s" => \$opt_S,
				"z|runredo" => \$opt_z,
				"f|filter=s" => \$opt_f,
				"k|keydata" => \$opt_k,
				"debug" => \$opt_debug,
				"bench" => \$opt_bench
	 );

	#
	#  Process switches
	#
	&Usage() if ($opt_h || ! $result);
	$Arg{output_raw} = 1 if $opt_r or $opt_v;
	$Arg{output_hex} = 1 if $opt_H or $opt_e;
	$Arg{output_info} = 1 if $opt_i or $opt_v;
	$Arg{quiet} = 1 if $opt_q;
	$Arg{output_UDP} = 0 if $opt_U;
	$Arg{output_TCP} = 0 if $opt_T;
	$Arg{output_ICMP} = 0 if $opt_Y;
	$Arg{output_apps} = 0 if ($opt_A || (defined $opt_a && $opt_a eq "0"));
	$Arg{output_index} = 0 if ($opt_X || (defined $opt_x && $opt_x eq "0"));
	$Arg{output_allhtml} = 1 if $opt_e;
	$Arg{prefer_dns} = 1 if $opt_d;
	$Arg{httplog_html} = 1 if $opt_n;
	$Arg{httplog_name} = "httplog.html" if $opt_n;
	my $extra_TCPplayback = $opt_b;
	my $extra_UDPplayback = $opt_B;
	my $extra_TCPhtml = $opt_l;
	my $extra_UDPhtml = $opt_L;
	my $ports_accepted = $opt_p;
	my $ports_rejected = $opt_P;
	my $ips_accepted = $opt_j;
	my $ips_rejected = $opt_J;
	$Arg{output_dir} = $opt_D;
	$Arg{filter} = $opt_f || "";
	$Arg{minbytes} = 0;
	$Arg{maxbytes} = 0;
	$Arg{sort} = "time";
	$Arg{keydata} = 1 if $opt_k;
	$Arg{debug} = 1 if $opt_debug;
	$Arg{bench} = 1 if $opt_bench;

	mkdir $Arg{output_dir};

	#
	#  Check for min/max bytes
	#
	if (defined $opt_m) {
		if ($opt_m =~ /k$/) {
			$opt_m =~ s/k$//;
			$opt_m *= 1024;
		}
		$Arg{minbytes} = $opt_m;
	}
	if (defined $opt_M) {
		if ($opt_M =~ /k$/) {
			$opt_M =~ s/k$//;
			$opt_M *= 1024;
		}
		$Arg{maxbytes} = $opt_M;
	}

	#
	#  Check for sort option
	#
	if (defined $opt_o) {
		if ($opt_o !~ /^(time|size|type|ip)$/) {
			print STDERR "ERROR55: Sort must be \"time\", " .
			 "\"size\", \"type\" or \"ip\".\n";
			&Usage();
		}
		$Arg{sort} = $opt_o;
	}

	#
	#  Check for standalone redo mode
	#
	if (defined $opt_z) {
		$Arg{redo} = 1;
		if (defined $Arg{output_dir}) {
			# bad luck
			die "ERROR56: Can't use an output dir "
			 . "$Arg{output_dir} in redo mode.\n\n";
		}
	}

	#
	#  Check for standalone mode
	#
	elsif (defined $opt_s || defined $opt_S) {
		$Arg{standalone} = 1;
		if (defined $opt_s) {
			if ($opt_s =~ /,/) {
				die "ERROR57: Unexpected comma found in " .
				 "\"-s$opt_s\" (did you mean \"-S$opt_s\"?)\n";
			}
			$Arg{mins} = $opt_s;
			$Arg{count} = 1;
		} elsif (defined $opt_S) {
			my ($mins,$count) = split(/,/,$opt_S);
			$Arg{mins} = $mins;
			### -1 means endless
			$Arg{count} = $count || -1;
		}
	}

	#
	#  This is normal mode
	#
	else {
		$Arg{normal} = 1;
	}

	#
	#  Build accepted or rejected port list as %Arg{Port_Accepted},...
	#
	if (defined $ports_accepted) {
		$Arg{port_accept} = 1;
		foreach $port (split(/,/,$ports_accepted)) {
			$Arg{Port_Accepted}{$port} = 1;
		}
	}
	if (defined $ports_rejected) {
		$Arg{port_reject} = 1;
		foreach $port (split(/,/,$ports_rejected)) {
			$Arg{Port_Rejected}{$port} = 1;
		}
	}

	#
	#  Build accepted or rejected IP list as %Arg{IP_Accepted},...
	#
	if (defined $ips_accepted) {
		$Arg{ip_accept} = 1;
		foreach $ip (split(/,/,$ips_accepted)) {
			$Arg{IP_Accepted}{$ip} = 1;
		}
	}
	if (defined $ips_rejected) {
		$Arg{ip_reject} = 1;
		foreach $ip (split(/,/,$ips_rejected)) {
			$Arg{IP_Rejected}{$ip} = 1;
		}
	}

	#
	#  Add extra ports to playback or HTML
	#
	if (defined $extra_TCPplayback) {
		foreach $port (split(/,/,$extra_TCPplayback)) {
			$Arg{Save_As_TCP_Playback}{$port} = 1;
		}
	}
	if (defined $extra_UDPplayback) {
		foreach $port (split(/,/,$extra_UDPplayback)) {
			$Arg{Save_As_UDP_Playback}{$port} = 1;
		}
	}
	if (defined $extra_TCPhtml) {
		foreach $port (split(/,/,$extra_TCPhtml)) {
			$Arg{Save_As_TCP_HTML}{$port} = 1;
		}
	}
	if (defined $extra_UDPhtml) {
		foreach $port (split(/,/,$extra_UDPhtml)) {
			$Arg{Save_As_UDP_HTML}{$port} = 1;
		}
	}

	#
	#  Check infile was provided, or print usage
	#
	if (! defined $ARGV[0] && ! ($Arg{standalone} || $Arg{redo})) {
		&Usage();
	}
	@{$Arg{infiles}} = @ARGV;
}


# Usage - print command usage and exit.
#
sub Usage {
        print "USAGE: chaosreader [-adehiknqrvxAEHIRTUXY] [-D dir]
	           [-b port[,...]] [-B port[,...]]
	           [-j IPaddr[,...]] [-J IPaddr[,...]]
 	           [-l port[,...]] [-L port[,...]] [-m bytes[k]]
	           [-M bytes[k]] [-o \"time\"|\"size\"|\"type\"|\"ip\"]
                   [-p port[,...]] [-P port[,...]]
	           infile [infile2 ...]
       chaosreader -s [mins] | -S [mins[,count]]
	           [-z] [-f 'filter']
   eg, chaosreader infile      # Create application session files, indexes
       chaosreader -v infile   # Verbose - Create ALL files
       chaosreader -i infile   # Create info files
       chaosreader -r infile   # Create raw files
       chaosreader -S 2,5      # Standalone - sniff network 5 times by 2 mins.
       chaosreader -h          # Print a brief help (this)
       chaosreader --help      # Print verbose help and version
       chaosreader --help2     # Print massive help\n\n";
        exit(0);
}


# Usage Full - print command usage and exit.
#
sub Usage_Full {
        print "Version 0.95i, 14-Apr-2014

USAGE: chaosreader [-adehiknqrvxAHIRTUXY] [-D dir]
                   [-b port[,...]] [-B port[,...]]
                   [-j IPaddr[,...]] [-J IPaddr[,...]]
                   [-l port[,...]] [-L port[,...]] [-m bytes[k]]
                   [-M bytes[k]] [-o \"time\"|\"size\"|\"type\"|\"ip\"]
                   [-p port[,...]] [-P port[,...]]
                   infile [infile2 ...]

       chaosreader -s [mins] | -S [mins[,count]]
	           [-z] [-f 'filter']

   chaosreader           # Create application session files, indexes

   -a, --application     # Create application session files (default)
   -d, --preferdns       # Show DNS names instead of IP addresses
   -e, --everything      # Create HTML 2-way & hex files for everything
   -h                    # Print a brief help
   --help                # Print verbose help (this) and version
   --help2               # Print massive help
   -i, --info            # Create info file
   -q, --quiet           # Quiet, no output to screen
   -r, --raw             # Create raw files
   -v, --verbose         # Verbose - Create ALL files .. (except -e)
   -x, --index           # Create index files (default)
   -A, --noapplication   # Exclude application session files
   -H, --hex             # Include hex dumps (slow)
   -I, --noinfo          # Exclude info files
   -R, --noraw           # Exclude raw files
   -T, --notcp           # Exclude TCP traffic
   -U, --noudp           # Exclude UDP traffic
   -Y, --noicmp          # Exclude ICMP traffic
   -X, --noindex         # Exclude index files
   -k, --keydata         # Create extra files for keystroke analysis
   -n, --names           # Include hostnames in hyperlinked HTTPlog (HTML)
   -D dir    --dir dir        # Output all files to this directory
   -b 25,79  --playtcp 25,79  # replay these TCP ports as well (playback)
   -B 36,42  --playudp 36,42  # replay these UDP ports as well (playback)
   -l 7,79   --htmltcp 7,79   # Create HTML for these TCP ports as well
   -L 7,123  --htmludp 7,123  # Create HTML for these UDP ports as well
   -m 1k     --min 1k         # Min size of connection to save (\"k\" for Kb)
   -M 1024k  --max 1k         # Max size of connection to save (\"k\" for Kb)
   -o size   --sort size      # sort Order: time/size/type/ip (Default time)
   -p 21,23  --port 21,23     # Only examine these ports (TCP & UDP)
   -P 80,81  --noport 80,81   # Exclude these ports (TCP & UDP)
   -s 5      --runonce 5      # Standalone. Run tcpdump/snoop for 5 mins.
   -S 5,10   --runmany 5,10   # Standalone, many. 10 samples of 5 mins each.
   -S 5      --runmany 5      # Standalone, endless. 5 min samples forever.
   -z        --runredo        # Standalone, redo. Rereads last run's logs.
   -j 10.1.2.1  --ipaddr 10.1.2.1    # Only examine these IPs
   -J 10.1.2.1  --noipaddr 10.1.2.1  # Exclude these IPs
   -f 'port 7'  --filter 'port 7'    # With standalone, use this dump filter.

eg1,
     tcpdump -s9000 -w output1          # create tcpdump capture file
     chaosreader output1                # extract recognised sessions, or,
     chaosreader -ve output1            # gimme everything, or,
     chaosreader -p 20,21,23 output1    # only ftp and telnet...
eg2,
     snoop -o output1                   # create snoop capture file instead
     chaosreader output1                # extract recognised sessions...
eg3,
     chaosreader -S 2,5		# Standalone, sniff network 5 times for 2 mins
				# each. View index.html for progress (or .text)
";
        exit(0);
}


# Usage_Massive - print massive help. Actually strip it from the comments
#		at the top of the code.
#
sub Usage_Massive {
	open (MYSELF,"$0") || die "ERROR58: I can't see myself: $!\n";
	@Myself = <MYSELF>;
	close MYSELF;

	### Print comment from top of code
	foreach $line (@Myself) {
		last if $line !~ /^#/;
		last if $line =~ /^# Todo:/;
		next if $line =~ m:^#!/usr/bin/perl:;
		$line =~ s/^#/ /;
		print $line;
	}
	print "\n";

        exit(0);
}



__END__

Reminders for myself
====================
/s for multiline match


Comments style:

# Micro comment

### Tiny Comment

#
#  Small comment
#

#
# --- Meduim Comment ---
#

#########################
# --- Large Comment ---
#

########################
# --- Huge Comment --- #
########################


Error message style
===================

die "ERROR#: message: $!\n";



Data types,
===========
	%Arg
		-> @infiles
		-> output_raw
		-> output_hex
		-> output_UDP
		-> output_info
		-> output_apps
		-> output_index
		-> output_allhtml
		-> Save_As_TCP_HTML
			-> $port
		-> Save_As_UDP_HTML
			-> $port
		-> Save_As_TCP_Playback
			-> $port
		-> Save_As_UDP_Playback
			-> $port
		-> Port_Accepted
			-> $port
		-> Port_Rejected
			-> $port
		-> ip_accept
		-> ip_reject
		-> IP_Accepted
			-> $ip
		-> IP_Rejected
			-> $ip
		-> debug
		-> standalone
		-> redo
		-> normal
		-> mins
		-> count
		-> output_dir
		-> quiet
		-> infile
		-> minbytes
		-> maxbytes

	%IP
		-> time
			-> $packet_time
				-> ver
				-> src
				-> dest
				-> protocol
				-> frag
					-> $ip_frag
				-> fragged
				-> drops
		-> id
			-> $ip_id
				-> StartTime

	%TCP
		-> id
			-> $session_id
				-> src
				-> dest
				-> source	# SYN seen
				-> src_port
				-> dest_port
				-> Aseq
					-> $$tcp_seq
				-> Bseq
					-> $$tcp_seq
				-> time
					-> $time
						-> dir
						-> data
				-> BothHTML
				-> StartTime
				-> EndTime
				-> size
				-> knowndir

	%UDP
		-> id
			-> $session_id
				-> src
				-> dest
				-> src_port
				-> dest_port
				-> RawA
				-> RawB
				-> time
					-> $time
				-> BothHTML
				-> StartTime
				-> EndTime
				-> size

	%ICMP
		-> time
			-> type
			-> code
			-> src
			-> dest
			-> Partial
			-> ver
			-> size

	%Count
		-> IP
		-> IPprotocols
		-> TCPports
		-> UDPports
		-> EtherType

	%CountMaster
		(as above)

	%Index
		-> @HTML
		-> @Text
		-> Time_Order
			-> $session_timeid
		-> Sort_Lookup
			-> $session_timeid

	%Image
		-> @HTML
			-> links
			-> info
		-> notempty

	%GETPOST
		-> @HTML
			-> query
			-> info
		-> notempty

	%Hex
		-> $type
			-> $session_id
				-> [ $from_server, data ]
	%Filenames
		-> $time
			-> filename
			-> service
			-> session_id

	@Master
		-> starttime
		-> endtime
		-> duration
		-> size
		-> dir
		-> file
