#!/usr/bin/perl -wT
use lib '/usr/share/autopsy/';
use lib '/usr/share/autopsy/lib/';
#
# autopsy gui server
# Autopsy Forensic Browser
#
#
# This file requires The Sleuth Kit 
#    www.sleuthkit.org
#
# version 1.71+
# Brian Carrier [carrier@sleuthkit.org]
# Copyright (c) 2003-2004 by Brian Carrier.  All rights reserved
#
# version 1.5, 1.6, 1.7
# Copyright (c) 2001-2003 by Brian Carrier, @stake Inc.  All rights reserved
#
# version 1.0
# Brian Carrier [carrier@cerias.purdue.edu]
# Copyright (c) 2001 by Brian Carrier.  All rights reserved
#
# THIS SOFTWARE IS NOT AFFILIATED WITH PURDUE UNIVERSITY OR THE CENTER FOR
# EDUCATION IN INFORMATION ASSURANCE AND SECURITY (CERIAS) AND THEY BEAR
# NO RESPONSIBILITY FOR ITS USE OR MISUSE.
#
#   
#
# This file is part of the Autopsy Forensic Browser (Autopsy)
#
# Autopsy is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Autopsy 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 Autopsy; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE.
# IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, LOSS OF USE, DATA, OR PROFITS OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

#
# refer to Security Considerations in README for a description of the 
# cookie authentication
#

require 5.002;

use strict;
use Socket;

use Main;
use Print;
require Fs;
require Caseman;

require 'conf.pl';
require 'lib/define.pl';

# Import variables from conf.pl
use vars '$LOCKDIR', '$INSTALLDIR', '$PICTDIR';
use vars '$SANITIZE_TAG', '$SANITIZE_PICT';
use vars '$USE_STIMEOUT', '$STIMEOUT', '$CTIMEOUT';
use vars '$SAVE_COOKIE', '$STRINGS_EXE', '$GREP_EXE';
use vars '$NSRLDB';

# Default port
my $port = 9999;

# Default 'remote' host
my $rema = 'localhost';


# remove environment stuff that we don't need and that could be insecure
$ENV{PATH} = '';
delete @ENV{ 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
$| = 1;


$::LIVE = 0;
$::USE_NOTES = 1;
$::USE_LOG = 1;

sub usage {
    print "\n\nusage: $0 [-c] [-C] [-d evid_locker] [-p port] [remoteaddr]\n";
	print "  -c: force a cookie in the URL\n";
	print "  -C: force NO cookie in the URL\n";
	print "  -d dir: specify the evidence locker directory\n";
	print "  -i device filesystem mnt: Specify info for live analysis\n";
	print "  -p port: specify the server port (default: $port)\n";
	print "  remoteaddr: specify the host with the browser (default: $rema)\n";
	exit 1;
}

my $cook_force = 0;


# Were options given?
while ((scalar (@ARGV) > 0) && ($ARGV[0] =~ /^-/)) {
	my $f = shift;

	# Evidence Locker
	if ($f eq '-d') {
		if (scalar (@ARGV) == 0) {
			print "Missing Directory\n";
			usage();
		}

		my $d = shift;
		# We need to do this for the tainting
		# We don't need to check for special characters in this case because
		# all commands will be run with the same permissions as the
		# original user.  We will check for the obvious ';' though
		if ($d =~ /;/) {
			print "Illegal argument\n";
			exit (1);
		}

		# If the path is relative, autopsyfunc will get screwed up when
		# this is run from a directory other than where autopsyfunc is
		# so force full paths
		elsif ($d !~ /^\//) {
			print "The evidence locker must be full path (i.e. begin with /)\n";
			exit(1);
		}
		elsif ($d =~ /(.*)/) {
			$LOCKDIR = $1;
		}
	}

	# Force no cookie
	elsif ($f eq '-C') {
		$::USE_COOKIE = 0;
		$cook_force = 1;
	}

	# force a cookie 
	elsif ($f eq '-c') {
		$::USE_COOKIE = 1;
		$cook_force = 1;
	}

	elsif ($f eq '-i') {
		$::LIVE = 1;
		$::USE_LOG = 0;
		$::USE_NOTES = 0;
		$::SAVE_COOKIE = 0;

		if (scalar (@ARGV) < 3) {
			print "Missing device, file system, and mount point arguments\n";
			usage();
		}

		my $dev = shift;
		if ($dev =~ /($::REG_IMG_PATH)/) {
			$dev = $1;
		}
		else {
			print "invalid device: $dev\n";
			usage();
		}

		unless ((-e "$dev") || (-l "$dev")) {
			print "Device ($dev) not found\n";
			usage();
		}

		my $fs = shift;
		if ($fs =~ /($::REG_FTYPE)/) {
			$fs = $1;
		}
		else {
			print "invalid file system: $fs\n";
			usage();
		}
		unless ((exists $Fs::root_meta{$fs}) && 
		  (defined $Fs::root_meta{$fs})) {
			print "File system not supported: $fs\n";
			usage();
		}
		$Caseman::img2ftype{$dev} = "$fs";

		my $mnt = shift;
		if ($mnt =~ /($::REG_MNT)/) {
			$mnt = $1;
		}
		else {
			print "invalid mount point: $mnt\n";
			usage();
		}
		$Caseman::img2mnt{$dev} = "$mnt";
	
	}

	# Specify a different port
	elsif ($f eq '-p') {
		if (scalar (@ARGV) == 0) {
			print "Missing port argument\n";
			usage();
		}

		my $p = shift;
		if ($p =~ /(\d+)/) {
			$p = $1;
		}
		else {
			print "invalid port: $p\n";
			usage();
		}
		if (($p < 1) || ($p > 65535)) {
			print "invalid port: $port\n";
			usage();
		}
		$port = $p;
	}

	else {
		print "Invalid flag: $f\n";
		usage();
	}
}



# remote address
if (scalar (@ARGV) > 0) {
	$rema = shift;
}


# Get remote address
my @acl_addr;		# Array of host addresses
my $hn;				# Host name
my $tmp;
if ($rema =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/) {
	$acl_addr[0] = pack('C4', ($1, $2, $3, $4));
	$hn = $rema;
}
else {
	($hn, $tmp, $tmp, $tmp, @acl_addr) = gethostbyname($rema);
	unless (defined $tmp) {
		print "Host not found: $rema\n";
		usage();
	}
}

# Determine the address that will be used to access this server
my $lclhost;
my @ta = unpack('C4', $acl_addr[0]);

# If we are being accessed by localhost, we need that and not the hostname
if (($ta[0] == 127) && ($ta[1] == 0) &&
  ($ta[2] == 0) && ($ta[3] == 1)) {
	$lclhost = "localhost";

	# Force no cookie to be used unless we already set this value
	# with arguments
	$::USE_COOKIE = 0 unless ($cook_force == 1);
}
else {
	$lclhost = `/bin/hostname`;
	chop $lclhost;

	# Force a cookie to be used unless we already set this value
	# with arguments
	$::USE_COOKIE = 1 unless ($cook_force == 1);
}


# Verify the variables defined in the configuration files
check_vars();


# Make sure TSKDIR ends with '/'
if ($::TSKDIR !~ /.*?\/$/) {
    $::TSKDIR .= '/';
}      

#
# Verify that all of the required executables exist
#
check_tools();

my $date = localtime;

if ($::LIVE == 0) {
	# Make sure LOCKDIR ends with '/'
	if ($LOCKDIR !~ /.*?\/$/) {
		$LOCKDIR .= '/';     
	}
}



# Setup socket
my $proto = getprotobyname('tcp');
socket (Server, PF_INET, SOCK_STREAM, $proto) 
  or die "Error creating network socket: $!";

setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, 1) 
  or die "Error setting network socket options (reuse): $!";

setsockopt(Server, SOL_SOCKET, SO_KEEPALIVE, 1) 
  or die "Error setting network socket options (keep alive): $!";

bind(Server, sockaddr_in($port, INADDR_ANY)) 
  or die "Error binding to port $port (is Autopsy already running?): $!";

listen(Server, SOMAXCONN) 
  or die "Error listening to socket for connections: $!";


my $magic;		# magic authentication cookie
my $cook_file;
my $cookie_url = "";

if  ($::USE_COOKIE == 1) {

	# Try for a real random device, or use rand if all else fails
	if (-e "/dev/urandom") {
		my $r;
		open RAND, "</dev/urandom" or die "can not open /dev/urandom";
		read RAND, $r, 4;
		$magic = unpack "I", $r;
		read RAND, $r, 4;
		$magic .= unpack "I", $r;
		close RAND;
	}
	else {
		$magic = int(rand 0xffffffff).int(rand 0xffffffff);
	}

	$cookie_url = "$magic/";

	# Save to file in case the stdout gets overwritten
	if ($SAVE_COOKIE == 1) {
		$cook_file = "$LOCKDIR/.$port.cookie";
		if (open COOK, ">$cook_file") {
			chmod 0600, "$cook_file";
			print COOK "$magic\n";
			close COOK;
		}
		else {
			print "WARNING: Cannot open file to save cookie in ($cook_file)";
		}
	}
} 


print <<EOF;

============================================================================

                       Autopsy Forensic Browser 
                  http://www.sleuthkit.org/autopsy/
                             ver $::VER 

============================================================================
EOF


if ($::LIVE == 0) {
	print "Evidence Locker: $LOCKDIR\n";
} else {
	print "Live Analysis Mode\n";
}

print <<EOF2;
Start Time: $date
Remote Host: $rema
Local Port: $port

Open an HTML browser on the remote host and paste this URL in it:

    http://$lclhost:${port}/${cookie_url}$::PROGNAME

Keep this process running and use <ctrl-c> to exit
EOF2

Print::log_session_info("Starting session on port $port and $hn\n");


# Set the server alarm
$SIG{ALRM} = \&SIG_ALARM_SERVER;
$SIG{INT} = \&SIG_CLOSE;

# setting this to ignore will automatically wait for children
$SIG{CHLD} = 'IGNORE';


# Wait for Connections
while (1) {

	alarm ($STIMEOUT) if ($USE_STIMEOUT == 1);

	my $raddr = accept(CLIENT, Server); 
	next unless ($raddr);
	my ($rport, $riaddr) = sockaddr_in($raddr);

	die "Error creating child" unless (defined (my $pid = fork()));

	if (0 == $pid) {
		open(STDOUT, ">&CLIENT") or die "Can't dup client to stdout";
		# open(STDERR, ">&CLIENT") or die "Can't dup client to stdout";
		open(STDIN, "<&CLIENT") or die "Can't dup client to stdin";
		$| = 1;

		my @rip = unpack('C4', $riaddr);

		# Check ACL
		foreach $tmp (@acl_addr) {
			if ($tmp eq $riaddr) {
				spawn_cli($riaddr);
				close CLIENT;
				exit 0;
			}
		}

		forbid ("$rip[0].$rip[1].$rip[2].$rip[3]");
		Print::log_session_info("ERROR: Unauthorized Connection from: ".
		  "$rip[0].$rip[1].$rip[2].$rip[3]\n");

		close CLIENT;
		exit 1;
	}
	else {
		close CLIENT;
	}
}


# Error messages
sub forbid 
{
	my $ip = shift;

	print "HTTP/1.0 403 Forbidden$::HTTP_NL".
	  "Content-type: text/html$::HTTP_NL$::HTTP_NL".
	  "<html><center>\n".
	  "<h2>Access Denied</h2>\n".
	  "<h3>Your connection from: $ip has been logged</h3>\n".
	  "</center></html>$::HTTP_NL$::HTTP_NL$::HTTP_NL";

	return;
}

sub bad_req 
{
	print "HTTP/1.0 404 Bad Request$::HTTP_NL".
	  "Content-type: text/html$::HTTP_NL$::HTTP_NL".
	  "<html><body><center>\n".
	  "<h2>Invalid URL<br><tt>".shift()."</tt></h2>\n".
	  "</center></body></html>".
	  "$::HTTP_NL$::HTTP_NL$::HTTP_NL";

	return;
}



# Alarm Functions
sub SIG_ALARM_CLIENT 
{
	Print::log_session_info("Connection timed out\n");
	close CLIENT;
	exit 1;
}

sub SIG_ALARM_SERVER 
{
	print "Server Timeout ($STIMEOUT seconds), Exiting\n";
	Print::log_session_info("Server Timeout ($STIMEOUT seconds), Exiting\n");
	exit 0;
}

# Close the system down when Control-C is given
sub SIG_CLOSE
{
	# delete the cookie file
	if (($::USE_COOKIE == 1) && ($SAVE_COOKIE == 1)) {
		unlink "$cook_file";
	}

	print "End Time: ".localtime()."\n";
	Print::log_session_info("Ending session on port $port and $hn\n");
	exit 0;
}

# Pass the remote IP address as the argument for logging
sub spawn_cli 
{
	# Set timeout for 10 seconds if we dont get any input
	alarm ($CTIMEOUT);
	$SIG{ALRM} = \&SIG_ALARM_CLIENT;

	while (<STDIN>) {

		if (/^GET \/+(\S*)\s?HTTP/) {
			my $url = $1;
			my $script;
			my $args;

			if (/\x0d\x0a$/) {
				$::HTTP_NL = "\x0d\x0a";
			}
			else {
				$::HTTP_NL = "\x0a";
			}

			# Magic Cookie 
			# If we are using cookies, then the url should be:
			# cookie/autopsy?var=val ...
            if ($::USE_COOKIE == 1) {

				if (($url =~ /^(\d+)\/+([\w\.\/]+)(?:\?(.*))?$/) && 
				  ($1 == $magic)) {
					$script = $2;  
					$args = $3;
				} else {
					my @rip = unpack('C4', shift());
					Print::log_session_info("ERROR: Incorrect Cookie from: ".
					  "$rip[0].$rip[1].$rip[2].$rip[3]\n");
					forbid("$rip[0].$rip[1].$rip[2].$rip[3]");
					return 1;
				}
			}

			# if we aren't using cookies, then it should be:
			# autopsy?var=val ...
			else {
				if ($url =~ /^\/?([\w\.\/]+)(?:\?(.*))?$/) {
					$script = $1;
					$args = $2;
				}
				else {
					bad_req($url);
					return 1;
				}
			}

			if ($script eq $::PROGNAME) {
				$args = "" unless (defined $args);

				# Turn timer off
				alarm (0);

				# Print status
				print "HTTP/1.0 200 OK$::HTTP_NL";
				::main($args);
			}
			# Display the sanitized picture or reference error
			elsif ($script eq $::SANITIZE_TAG) {
				Appview::sanitize_pict($args);
				return 1;
			}
			# Display a picture or help file
			elsif ( ($script =~ /^(pict\/[\w\.\/]+)/)  ||
			  ($script =~ /^(help\/[\w\.\/]+)/) ) {
				show_file ($1);
			}
			elsif ($script eq 'about') {
				about();
			}
			else {
				bad_req($url);
				Print::log_session_info("Unknown function: $script\n");
				return 1;
			}
			return 0;
		}
	} # end of while (<>)

} # end of spawn_cli


# Print the contents of a local picture or help file
sub show_file
{
	my $file = "$INSTALLDIR/".shift;

	if (-e "$file") {
		print "HTTP/1.0 200 OK$::HTTP_NL";

		open FILE, "<$file" or
		  die "can not open $file";

		if ($file =~ /\.jpg$/i) {
			print "Content-type: image/jpeg$::HTTP_NL$::HTTP_NL";
		} elsif ($file =~ /\.gif$/i) {
			print "Content-type: image/gif$::HTTP_NL$::HTTP_NL";
		} elsif ($file =~ /\.ico$/i) {
			print "Content-type: image/ico$::HTTP_NL$::HTTP_NL";
		} elsif ($file =~ /\.html$/i) {
		  	print "Content-type: text/html$::HTTP_NL$::HTTP_NL";
		} else {
			print "HTTP/1.0 404 Bad Request$::HTTP_NL".
				"Content-type: text/html$::HTTP_NL$::HTTP_NL".  
				"<html>\n".
				"<head><title>Error</title></head>\n".
				"<h2><center>Unknown Extension</h2>\n".
		  		"</center></html>$::HTTP_NL$::HTTP_NL$::HTTP_NL";
			exit(1);
		}

		while (<FILE>) {
			print "$_";
		}
		close (FILE);

		print "$::HTTP_NL$::HTTP_NL";
	}
	else {
		print "HTTP/1.0 404 Bad Request$::HTTP_NL".
		  "Content-type: text/html$::HTTP_NL$::HTTP_NL".
		  "<html>\n".
		  "<head><title>Error</title></head>\n".
		  "<h2><center>File Not Found</h2>".
		  "</center></html>$::HTTP_NL$::HTTP_NL$::HTTP_NL";
		exit(1);
	}

	return;
}

sub about 
{

	print "HTTP/1.0 200 OK$::HTTP_NL".
	  "Content-type: text/html$::HTTP_NL$::HTTP_NL";

	my $tskver = ::get_tskver();

print <<EOF;

<html>
<head><title>About Autopsy</title></head>

<body BGCOLOR=#CCCC99>

<center><h2>About Autopsy</h2>
  <br>
  <img src=\"pict/logo.jpg\" alt=\"Logo\">
  <br><br>
  <b>Version</b>: $::VER
  <br>
  <tt><a href=$::AUT_HOME_PAGE>$::AUT_HOME_PAGE</a></tt>
  <br>
</center>


<h3>Credits</h3>
<UL>
  <LI>Code Development: Brian Carrier (carrier at sleuthkit dot org)
  <LI>Interface Assistance: Samir Kapuria
  <LI>Mascot: Hash the Hound
</UL>

<h3>Configuration</h3>
<b>The Sleuth Kit</b>:<br> 
&nbsp;&nbsp;URL: <a href="http://www.sleuthkit.org/sleuthkit/">
  <tt>http://www.sleuthkit.org/sleuthkit/</tt></a><br> 
&nbsp;&nbsp;Installation Location: <tt>$::TSKDIR</tt><br>
&nbsp;&nbsp;Version: $tskver<br>
<b>Evidence Locker</b>: <tt>$LOCKDIR</tt><br>
<b>strings</b>: <tt>$STRINGS_EXE</tt><br>
<b>grep</b>: <tt>$GREP_EXE</tt><br>
<b><a href="http://www.nsrl.nist.gov/">NIST NSRL</a></b>: <tt>$NSRLDB</tt><br>

</body></html>

EOF
	return 0;
}


### Check that the required tools are there
sub check_tools
{
	# Sleuth Kit execs
	unless (-x $::TSKDIR."icat") {
		print "ERROR: Sleuth Kit icat executable missing: $::TSKDIR\n";
		exit (1);
	}
	unless (-x $::TSKDIR."istat") {
		print "ERROR: Sleuth Kit istat executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."ifind") {
		print "ERROR: Sleuth Kit ifind executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."ils") {
		print "ERROR: Sleuth Kit ils executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."fls") {
		print "ERROR: Sleuth Kit fls executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."ffind") {
		print "ERROR: Sleuth Kit ffind executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."dcat") {
		print "ERROR: Sleuth Kit dcat executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."dcalc") {
		print "ERROR: Sleuth Kit dcalc executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."dls") {
		print "ERROR: Sleuth Kit dls executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."file") {
		print "ERROR: Sleuth Kit file executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."fsstat") {
		print "ERROR: Sleuth Kit fsstat executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."md5") {
		print "ERROR: Sleuth Kit md5 executable missing\n";
		exit (1);
	}

	if ($::LIVE == 0) {
	unless (-x $::TSKDIR."sorter") {
		print "ERROR: Sleuth Kit sorter executable missing\n";
		exit (1);
	}
	unless (-x $::TSKDIR."hfind") {
		print "ERROR: Sleuth Kit hfind executable missing\n";
		print "  You likely have an old version of The Sleuth Kit or TASK\n";
		exit (1);
	}
	}

	unless (-x "$STRINGS_EXE") {
		print "ERROR: strings executable missing\n";
		exit (1);
	}

	unless (-x "$GREP_EXE") {
		print "ERROR: grep executable missing\n";
		exit (1);
	}
}

# check values that should be defined in the configuration files
# This will show incomplete installations
sub check_vars
{
	unless ( (defined $::TSKDIR) && ($::TSKDIR ne "") ) {
		print "ERROR: TSKDIR variable not set in configuration file\n";
		print "  This could been caused by an incomplete installation\n";
		exit (1);
	}

	unless (-d "$::TSKDIR") {
		print "Invalid Sleuth Kit binary directory: $::TSKDIR\n";
		exit (1);
	}

	return if ($::LIVE == 1);

	# Verify The evidence locker directory
	unless ( (defined $LOCKDIR) && ($LOCKDIR ne "") ) {
		print "ERROR: LOCKDIR variable not set in configuration file\n";
		print "  This could been caused by an incomplete installation\n";
		exit (1);
	}

	unless (-d "$LOCKDIR") {
		print "Invalid evidence locker directory: $LOCKDIR\n";
		exit (1);
	}
}
