#!/usr/bin/perl
#
# Snare Audit Dispatcher for Linux
# (c) Copyright 2006 InterSect Alliance Pty Ltd
#
# This application will integrate into the native linux audit subsystem,
# and translate linux auditd log format data into something that can be
# parsed appropriately by apps that prefer one-line-per-event, such as the
# Snare audit daemon (for delivery to the Snare Server), or logwatch.
#
# INSTALLATION:
#
#	* Run the MakeTranslationTable program
#	  (optional, but highly recommended)
#         It will create /etc/snare-xlate.conf
#	* Save this file to /usr/sbin/SnareDispatcher.pl
#	* Add the following line to your /etc/auditd.conf file:
#		dispatcher = /usr/sbin/SnareDispatcher.pl
#
# Notes:
# Although the items=x field should provide us with a reasonable guestimate
# of how many lines to expect for a particular syscall event, items=x isn't
# available for all events. As such, as well as a items=x test, the code uses
# a timeout to determine when to send an event, as we can't easily establish
# exactly how many event-lines might potentially contribute to a single event
# without items=x (even then, we are assuming that the PATH line will be the
# last to be sent). Yuck.
#
# Issues:
#	* a0, a1, a2, a3 are practically useless, as string arguments are not
#	  supported. execve's are a particular problem.
#	* It is programatically difficult to determine how many 'lines' make
#         up an audit event. Some lines can be repeated, with slightly
#         different values.
#       * You can have multiple, identical tokens for an event (eg: two path=)
#	  we handle this by appending a count (after a colon).
#	* Event lines may be interleaved (ie: you might get two lines from
#	  event # 1000, then one line from event # 1001, then another line
#         from event # 1000).
#	* Some filename characters are translated into their HEX equivalents
#         which will make matching filenames difficult.
#           if (char == '"' || char < 0x21 || char > 0x7f)
#

use POSIX qw(strftime);
use Socket;
use Sys::Syslog qw ( :DEFAULT setlogsock );

$DEBUG=0;

$SUCCESS=1;
$FAILURE=2;

############# User Configurable options.

$WEBSERVER="/usr/sbin/SnareWebServer.pl";
$CONFIGFILE="/etc/snare.conf";

# Note: These two values can be configured using the config file.

# Should we care about the criticality?
# If not, no worries - we can return as soon as we find a single match,
# which will speed things up a little.
$USECRITICALITY=1;

# Should we manage the audit configuration? Or do we leave it to the native audit subsystem?
$MANAGEAUDIT=1;

# Should we use regular expressions instead of wildcards?
$REGEX=0;

# How often should we flush our buffer?
$FLUSHINTERVAL=10;	# in seconds

$WEBSERVERALLOW=0;
$WEBSERVERPORT=6161;
$ACCESSKEY="";
$RESTRICT_IP="";

# Where are we sending events?
$SYSLOGDEST=0;

############# End user configurable options.

$VERSION="1.2";

$FLUSH=0;
$FLUSHNOW=-1;
$CONTINUE=1;

# Open our translation table.
$rc=open(XLATE,"/etc/snare-xlate.conf");
if($rc) {
	while($line=<XLATE>) {
		chomp($line);
		($num,$scall)=split(/:/,$line);
		$syscall{$num}=$scall;
		$nsyscall{$call}=$num;
	}
	close(XLATE);
}


# Declare a UID/GID cache so we don't wind up getting into recursive
# audit event generation.
my %uidcache=();
my %gidcache=();

# Global event cache
%event=();

# Global filters array
%Filters=();
$eventsent=0;
$lasteventnum=0;

$timecheck=time();

$FQDN=`hostname --fqdn`;
chomp($FQDN);

# Set up some select() related variables.
$rin = '';
vec($rin,fileno(STDIN),1) = 1;

LoadConfig() or exit;

# Start up our web server (if allowed)
StartWeb();

# Set up our audit configuration based on our objectives.
SetAudit();

# Trap a few signals
$SIG{USR1}='Restart';
$SIG{USR2}='DumpEvents';
$SIG{PIPE}='Handler';
$SIG{HUP}='Handler';
$SIG{INT}='Handler';
$SIG{QUIT}='IGNORE';
$SIG{TERM}='Handler';

LogMsg("Snare for Linux $VERSION: Started and active.");

# Main reader loop.
while($CONTINUE==1) {
	$now=time();
	# Every 10 seconds
	if($now-$timecheck > $FLUSHINTERVAL) {
		# Flush any old data
		SendEvents($FLUSH);
		$timecheck=$now;
	}

	$nfound=select($rout=$rin,undef,undef,1);
	if($nfound > 0) {
		# Grab the version number
		$bytes=sysread(STDIN,$tversion,4);
		if($bytes==0) {
			$CONTINUE=0;
			last;
		} elsif ($bytes==undef) {
			# Probably interrupted by a signal
			next;
		}
		sysread(STDIN,$theadersize,4);
		($version)=unpack("I",$tversion);
		($headersize)=unpack("I",$theadersize);

		# Eventtype 1302 = PATH
		# Eventtype 1300 = SYSCALL
		# Eventtype 1307 = CWD
		# 8k sanity check. Hopefully, we'll resynchronise.
		if($headersize > 0 && $headersize < 8192) {
			# Read the header, minus the 8 bytes we've already grabbed.
			sysread(STDIN,$temp,$headersize-8);
			($eventtype,$contentsize)=unpack("II",$temp);
			if($contentsize > 0 && $contentsize < 8192) {
				sysread(STDIN,$line,$contentsize);
			} else {
				if($DEBUG) { LogMsg("Content is $contentsize! Not reading"); }
				next;
			}
		} else {
			next;
		}
	} else {
		if($DEBUG>1) { LogMsg("NO DATA (timout)!"); }
		next;
	}

	chomp($line);
	# Kill off internal newlines.
	$line=~s/\n//g;

	if($DEBUG>2) { LogMsg("\nLINE: $line"); }

	$eventnum=0;
	# Pull out the date/time and eventID
	($null,$head,$datetime,$eventnum,$tail)=split(/^([a-zA-Z0-9]+)\(([0-9\.]+):([0-9]+)\): (.*)/,$line);

	if($DEBUG>1) { LogMsg("Head: $head datetime: $datetime eventnum: $eventnum tail: $tail"); }

	if($head ne "audit" || $datetime==0 || !$tail || !$eventnum) {
		# Not interested
		next;
	}
	$line=$tail;

	if($DEBUG>1) { LogMsg("DATE TIME = $datetime EVENTID = $eventnum eventtype = $eventtype"); }


	# Lets split this line up into element/content pairs.
	# First, break apart by spaces that aren't inside inverted commas.
	@elements=split(/\s(?=(?:[^"]*"[^"]*")*[^"]*\z)/,$line);  # comment helper """
	%data=();
	if($DEBUG>2) { LogMsg("Elements contains " . @elements . " elements"); }
	foreach $element (@elements) {
		if($element=~/=/) {
			# Then, by the equals sign.
			($key,$val)=split(/=/,$element,2);
			$val =~ s/^"//;		# Make gedit pretty: "
			$val =~ s/"$//;		# Make gedit pretty: "
			$data{$key}=$val;
		}
	}

	# Ok, we now have an array of key/value pairs.

	$eventsent=0;

	if(!$event{$eventnum}{"datetime"}) {
		@ltime=localtime($datetime);
		$sdatetime=strftime("%Y%m%d %T",@ltime);
		$event{$eventnum}{"datetime"}=$sdatetime;
	}
	# Unix time..
	$event{$eventnum}{"utime"}=$datetime;

	foreach $key (keys %data) {
		$count=0;
		while(defined($event{$eventnum}{$key . ($count==0?"":":$count")})) {
			$count++;
		}

		$event{$eventnum}{$key . ($count==0?"":":$count")}=$data{$key};
	}

	# Eventtype 1302 = "PATH"
	# This should tell us when the end of a record comes through.
	if($eventtype == 1302) {
		$items=$event{$eventnum}{"items"};
		if($items > 0) {
			$pathcount=$event{$eventnum}{"pathcount"};
			$pathcount++;
			if($pathcount == $items) {
				# We have all the data we need! Send this event out the door.
				# Clear our counter first.
				delete $event{$eventnum}{"pathcount"};
				if($DEBUG>1) { LogMsg("Sending out Event $eventnum - we've had $pathcount items ($items)!"); }
				SendEvents($eventnum);
			} else {
				if($DEBUG>1) { LogMsg("Found another PATH component for event $eventnum."); }
				$event{$eventnum}{"pathcount"}++;
			}
		}
	}
}

if ($DEBUG) { LogMsg("DEBUG: Exit requested\n"); }

if($webpid) {
	# send sigusr1, which tells the web server thread to terminate cleanly.
	LogMsg("Sending signal 3 (Quit) to webpid $webpid\n");
	kill(3,$webpid);
}

# Flush our buffers
SendEvents($FLUSHNOW);
CloseOutputs();

exit;

sub SendEvents {
	($eventnumber)=@_;

	@sendevents=();

	$now=time();
	# If we are sent a particular event number, flush it out.
	if($eventnumber != $FLUSH && $eventnumber != $FLUSHNOW) {
		@sendevents=($eventnumber);
	} else {
		# if eventnumber is zero, flush any events that are older than 10 seconds.
		# otherwise, if eventnumber is <0, flush all events we have - We're going down.
		foreach $eventnum (sort keys %event) {
			$utime=$event{$eventnum}{"utime"};
			if(($now-$utime < $FLUSHINTERVAL) && $eventnumber==$FLUSH) {
				# Since we're sorted, we can break out as soon as we find a event in the last 5 secs.
				last;
			}
			delete $event{$eventnum}{"utime"};
			push(@sendevents,$eventnum);
		}
	}

	# Ok, we now have an array of events to send out.
	foreach $eventnum (@sendevents) {
		#if($DEBUG) { LogMsg("DEBUG: Eventnum is $eventnum"); }

		# If the event doesn't satisfy the basic requirements, dump it
		if(!defined($event{$eventnum}{"syscall"}) ||
		   !defined($event{$eventnum}{"datetime"}) ||
		   !defined($event{$eventnum}{"uid"}) ||
		   !defined($event{$eventnum}{"gid"}) ||
		   !defined($event{$eventnum}{"pid"}) ||
		   !defined($event{$eventnum}{"comm"}) ||
		   !defined($event{$eventnum}{"exit"}) ||
		   !defined($event{$eventnum}{"success"})) {
			if($DEBUG) {
				LogMsg(":::::::: Event $eventnum does not contain all required data");
				@elements=keys %{$event{$eventnum}};
				@values=values %{$event{$eventnum}};
				LogMsg(":::::::: [@elements] [@values]");
			}

			delete $event{$eventnum};

			next;
		}
	

		# Lets construct a token/data array in the order we wish to send things out, with
		# appropriate data extrapolated.
		my %tokens=();
		my @tokenlist=();

		$syscallname=$syscall{$event{$eventnum}{"syscall"}};
		if(!$syscallname) {
			# Fall back to the number.
			$syscallname=$event{$eventnum}{"syscall"};
		}
		delete $event{$eventnum}{"syscall"};
		$tokens{"event"}="$syscallname," . $event{$eventnum}{"datetime"};
		push(@tokenlist,"event");
		delete $event{$eventnum}{"datetime"};
		$uidname=getuid($event{$eventnum}{"uid"});
		$tokens{"uid"}=$event{$eventnum}{"uid"} . ($uidname?",$uidname":"");
		push(@tokenlist,"uid");
		delete $event{$eventnum}{"uid"};
		$gidname=getgid($event{$eventnum}{"gid"});
		$tokens{"gid"}=$event{$eventnum}{"gid"} . ($uidname?",$gidname":"");
		push(@tokenlist,"gid");
		delete $event{$eventnum}{"gid"};
		$euidname=getuid($event{$eventnum}{"euid"});
		$tokens{"euid"}=$event{$eventnum}{"euid"} . ($uidname?",$euidname":"");
		push(@tokenlist,"euid");
		delete $event{$eventnum}{"euid"};
		$egidname=getgid($event{$eventnum}{"egid"});
		$tokens{"egid"}=$event{$eventnum}{"egid"} . ($uidname?",$egidname":"");
		push(@tokenlist,"egid");
		delete $event{$eventnum}{"egid"};
		$tokens{"process"}=$event{$eventnum}{"pid"} . "," . $event{$eventnum}{"comm"};
		push(@tokenlist,"process");
		delete $event{$eventnum}{"pid"};
		delete $event{$eventnum}{"comm"};
		$tokens{"return"}=$event{$eventnum}{"exit"} . "," . $event{$eventnum}{"success"};
		push(@tokenlist,"return");
		delete $event{$eventnum}{"exit"};
		delete $event{$eventnum}{"success"};


		foreach $key (sort keys %{$event{$eventnum}} ) {
			# Resolve names
			if($key =~ /^[eosa]uid(:[0-9]+)*$/ || $key =~ /^fsuid(:[0-9]+)*$/) {
				$name=getuid($event{$eventnum}{$key});
				if($name) {
					$eventstring = $event{$eventnum}{$key} . ",$name";
				}
			} elsif($key =~ /^[eosa]gid(:[0-9]+)*$/ || $key =~ /^fsgid(:[0-9]+)*$/) {
				$name=getgid($event{$eventnum}{$key});
				if($name) {
					$eventstring = $event{$eventnum}{$key} . ",$name";
				}
			} elsif($key =~ /^name(:[0-9]+)*$/ || $key =~ /^exe(:[0-9]+)*$/) {
				$tpath=$event{$eventnum}{$key};
				if($tpath =~ /^\// || !$event{$eventnum}{"cwd"}) {
					$path = resolve_path($tpath);
				} else {
					$path = resolve_path($event{$eventnum}{"cwd"} . "/" . $tpath);
				}
				$eventstring = $path;
			} else {
				$eventstring = $event{$eventnum}{$key};
			}
			$tokens{$key}=$eventstring;
			push(@tokenlist,$key);
		}

		# Ok, we have a range of token/value pairs to use for event checks.
		delete $event{$eventnum};

		# Format of the filters:
		#$Filters{$ObjectiveCount}{$key}{$keymatch}{$ElementCount}{$AlternativeCount}=$alternative;
		#$Filters{0}{event}{0}{0}{0}="execve";
		$EventMatched=0;
		$EventCrit=0;
		foreach $objectivecount (keys(%Filters)) {
			$ObjectiveMatch=0;
			foreach $key (keys(%{$Filters{$objectivecount}})) {
				if($tokens{$key}) {
					foreach $keymatch (keys(%{$Filters{$objectivecount}{$key}})) {
						foreach $elementcount (keys(%{$Filters{$objectivecount}{$key}{$keymatch}})) {
							@elements=split(/,/,$tokens{$key});
							if($DEBUG>3) { LogMsg("Key: $key - ELEMENTS: @elements\n"); }
							$result=0;
							foreach $alternativecount (keys(%{$Filters{$objectivecount}{$key}{$keymatch}{$elementcount}})) {
								$match=$Filters{$objectivecount}{$key}{$keymatch}{$elementcount}{$alternativecount};
								$element=$elements[$elementcount];
								$negate=$FilterTypes{$objectivecount}{$key}{$keymatch};
								if($DEBUG>2) { LogMsg("Match element $element against $match (negate is $negate)\n"); }

								# Special case:
								if($match eq "*") {
									$result=1;
								} else {
									@strings=split(/,/,$element);
									@matches=split(/,/,$match);
									$count=0;
									foreach $tmatch (@matches) {
										if ($REGEX == 1) {
											$regex=$tmatch;
											$regex =~ s/[^\\]\//\\\//g;
										} else {
											#$regex=wildcard($tmatch);
											my $re = quotemeta $tmatch;
											$re =~ s/\\\*/(.*)/g;
											$regex = "^" . $re . "\$";
										}
										if($DEBUG>1) { LogMsg("Match: $regex against " . $strings[$count]); }
										if($strings[$count] =~ /$regex/) {
											$result=!$negate;
										} else {
											$result=$negate;
										}
										if(!$result) {
											if($DEBUG > 1) { LogMsg("Match: No Match! (negate is $negate)"); }
											break;
										}
										$count++;
									}
									#$result=Match($element,$match,$negate);
								}
								#if($result && !$negate || !$result && $negate) {}
								if($result) {
									# Yay - one of the alternatives matched
									if($DEBUG > 1) { LogMsg("BREAKING out (result is $result, negate is $negate)"); }
									last;
								}
							}
							if($result) {
								$ObjectiveMatch=1;
							} else {
								$ObjectiveMatch=0;
								last;
							}
						}
						if(!$ObjectiveMatch) {
							last;
						}
					}
				} else {
					# No token in this event that matches? Drop it.
					$ObjectiveMatch=0;
				}

				if(!$ObjectiveMatch) {
					last;
				}
			}
			if($ObjectiveMatch) {
				$EventMatched=1;
				if($USECRITICALITY) {
					$tcrit=$Criticality[$objectivecount];
					if($tcrit > $EventCrit) {
						$EventCrit=$tcrit;
					}
				} else {
					$EventCrit=$Criticality[$objectivecount];
					last;
				}
			}
		}

		if(!$EventMatched) {
			# Ok, go to the next objective.
			# We're not interested in this one.
			next;
		}

		# Craft our tokens into a single string.
		$sendstring="criticality,$EventCrit";

		foreach $key (@tokenlist) {
			if($sendstring) { $sendstring .= "	"; }
			$sendstring .= $key ."," . $tokens{$key};
		}

		# Send this out.
		if($DEBUG) { LogMsg("OUTPUT: " . $sendstring); }

		SendEvent("$FQDN	LinuxKAudit","$sendstring\n");
	}
}

sub SendEvent() {
	my($prefix,$string)=@_;

	if(@OUTPUTFILES) {
		foreach $fp (@OUTPUTFILES) {
			print $fp "$prefix\t$string";
		}
	}
	if($OUTPUTNET{"SocketCount"}>0) {
		for($i=0;$i<$OUTPUTNET{"SocketCount"};$i++) {
			if($OUTPUTNET{$i}{"Port"} == 514) {
				# Syslog = special
				# Prepend some syslog gumf. REDRED TODO
				$sdate=strftime("%b %e %H:%M:%S",localtime);
				$sprefix="<$SYSLOGDEST> $sdate $FQDN LinuxKAudit";
				#$sprefix="<$SYSLOGDEST> LinuxKAudit";
				send($OUTPUTNET{$i}{"Socket"}, "$sprefix\t$string", 0, $OUTPUTNET{$i}{"PortAddr"});
			} else {
				send($OUTPUTNET{$i}{"Socket"}, "$prefix\t$string", 0, $OUTPUTNET{$i}{"PortAddr"});
			}
		}
	}

	if($webpid) {
		# Cache the last 100 events, for the web.
		$CacheSize=@EventCache;
		if($CacheSize > 99) {
			shift(@EventCache)
		}
		if($string ne "") {
			push(@EventCache,"$prefix\t$string");
		}
	}
}

sub CloseOutputs() {
	if(@OUTPUTFILES) {
		foreach $fp (@OUTPUTFILES) {
			close($fp);
		}
	}

	if($OUTPUTNET{"SocketCount"}>0) {
		for($i=0;$i<$OUTPUTNET{"SocketCount"};$i++) {
			close($OUTPUTNET{$i}{"Socket"});
		}
	}
}

sub LoadConfig() {
	$OUTPUTNET{"SocketCount"}=0;
	@Criticality=();
	@Filters=();
	@FilterTypes=();
	@Output=();

	$rc=open(CONFIG,"$CONFIGFILE");
	if(!$rc) {
		LogMsg("Cannot open Snare Configuration File! Exiting.");
		return(0);
	}
	$section="Unknown";
	$ObjectiveCount=0;

	while($line=<CONFIG>) {
		chomp($line);
		if($line =~ /^[ \t]*#/ || $line =~ /^[ \t#]*$/) {
			# Ignore
			next;
		} elsif ($line =~ /^[ \t]*\[[a-zA-Z]+\]/) {
			($null,$section)=split(/[\[\]]/,$line);
			$section =~ tr/a-z/A-Z/;
		} else {
			$line =~ s/^[ \t]+//;
			$line =~ s/[ \t]+/\t/g;
			$line =~ s/[ \t]+$//;

			if($DEBUG) { LogMsg("Config File: Loaded line $line\n"); }

			if($section eq "REMOTE") {
				($key,$val)=split(/=/,$line);
				$key =~ tr/a-z/A-Z/;
				if($key eq "ALLOW") {
					$WEBSERVERALLOW=$val;
				} elsif($key eq "LISTEN_PORT") {
					$WEBSERVERPORT=$val;
				} elsif($key eq "ACCESSKEY") {
					$ACCESSKEY=$val;
				} elsif($key eq "RESTRICT_IP") {
					$RESTRICT_IP=$val;
				}
			} elsif($section eq "OUTPUT") {
				# Add this into our output file array
				$rc=OpenOutput($line);
			} elsif($section eq "CONFIG") {
				($key,$val)=split(/=/,$line);
				$key =~ tr/a-z/A-Z/;
				if($key eq "USE_CRITICALITY") {
					$USECRITICALITY=$val;
				} elsif($key eq "USE_REGEX") {
					$REGEX=$val;
				} elsif($key eq "CLIENTNAME") {
					$FQDN=$val;
				} elsif($key eq "SET_AUDIT") {
					$MANAGEAUDIT=$val;
				} elsif($key eq "SYSLOG_FACILITY") {
					$SFACILITY=$val;
					$SFACILITY =~ tr/a-z/A-Z/;
				} elsif($key eq "SYSLOG_PRIORITY") {
					$SPRIORITY=$val;
					$SPRIORITY =~ tr/a-z/A-Z/;
				}
			} elsif($section eq "OBJECTIVES") {
				# Default value.
				$TEMPObjectiveCount = $ObjectiveCount;
				$Criticality[$TEMPObjectiveCount]=0;

				@elements=split(/\t/,$line);
				%Config=();
				
				foreach $element (@elements) {
					($key,$check,$val)=split(/(\!*=)/,$element);
					if($key eq "criticality") {
						$Criticality[$TEMPObjectiveCount]=$val;
						next;
					}

					@components=();
					@tcomponents=split(/,/,$val);
					while(@tcomponents) {
						$component=shift(@tcomponents);
						if($component =~ /^\(/) {
							while($component !~ /\)$/) {
								$component .= "," . shift(@tcomponents);
							}
							$component =~ s/^\(//;
							$component =~ s/\)$//;
						}
						push(@components,$component);
					}
					
					$ElementCount=0;
					$keymatch=keys(%{$Filters{$TEMPObjectiveCount}{$key}});
					foreach $component (@components) {
						$AlternativeCount=0;
						foreach $alternative (split(/,/,$component)) {
							#LogMsg("Adding $alternative to $TEMPObjectiveCount:$key:$keymatch:$ElementCount:$AlternativeCount");
							$Filters{$TEMPObjectiveCount}{$key}{$keymatch}{$ElementCount}{$AlternativeCount}=$alternative;
							if($check eq "=") {
								# If the user has specified a non-negate match, and
								# Has identified an explicit event, then mark it to be turned on.
								if($key eq "event") {
									# put the events in a temporary list
									$temp_EventsON{$alternative}=$SUCCESS + $FAILURE;
								} elsif($key eq "return") {
									# if we are only looking for a specific return code,
									# only enable the necessary auditing
									if ($alternative eq "no") {
										foreach $eventid (keys(%temp_EventsON)) {
											$temp_EventsON{$eventid}=$FAILURE;
										}
									} elsif ($alternative eq "yes") {
										foreach $eventid (keys(%temp_EventsON)) {
											$temp_EventsON{$eventid}=$SUCCESS;
										}
									}
								}
								$FilterTypes{$TEMPObjectiveCount}{$key}{$keymatch}=0;
							} else {
								# Negate this match
								$FilterTypes{$TEMPObjectiveCount}{$key}{$keymatch}=1;
							}
							$AlternativeCount++;
						}
						$ElementCount++;
					}
				}
				foreach $eventid (keys(%temp_EventsON)) {
					$EventsON{$eventid} = $temp_EventsON{$eventid};
					delete $temp_EventsON{$eventid};
				}

				$ObjectiveCount++;
			}
		}
	}
	# Work out syslog facility/priority number:
	# $SFACILITY $SPRIORITY (all upper)
	$sfac{"KERNEL"}=0;	$sfac{"USER"}=1;	$sfac{"MAIL"}=2;
	$sfac{"DAEMON"}=3;	$sfac{"AUTH"}=4;	$sfac{"SYSLOG"}=5;
	$sfac{"LPR"}=6;		$sfac{"NEWS"}=7;	$sfac{"UUCP"}=8;
	$sfac{"CRON"}=9;	$sfac{"AUTHPRIV"}=10;	$sfac{"FTP"}=11;
	$sfac{"LOCAL0"}=16;	$sfac{"LOCAL1"}=17;	$sfac{"LOCAL2"}=18;
	$sfac{"LOCAL3"}=19;	$sfac{"LOCAL4"}=20;	$sfac{"LOCAL5"}=21;
	$sfac{"LOCAL6"}=22;
	$spri{"EMERGENCY"}=0;	$spri{"ALERT"}=1;	$spri{"CRITICAL"}=2;
	$spri{"ERROR"}=3;	$spri{"WARNING"}=4;	$spri{"NOTICE"}=5;
	$spri{"INFORMATION"}=6;	$spri{"DEBUG"}=7;
	
	$SYSLOGDEST=($sfac{$SFACILITY} << 3) | $spri{$SPRIORITY};

	return(1);
}

sub SetAudit() {
	if(%EventsON && $MANAGEAUDIT == 1) {
		# Clear all audit events
		`/sbin/auditctl -D`;
		# Make sure audit is enabled
		`/sbin/auditctl -e 1`;

		foreach $eventid (keys(%EventsON)) {
			if ($EventsON{$eventid} == ($SUCCESS + $FAILURE)) {
				if($DEBUG) { LogMsg("Turning on event $eventid"); }
				`/sbin/auditctl -a entry,always -S $eventid`;
			} elsif ($EventsON{$eventid} == $SUCCESS) {
				if($DEBUG) { LogMsg("Turning on event $eventid (SUCCESS only)"); }
				`/sbin/auditctl -a exit,always -S $eventid -F success=1`;
			} elsif ($EventsON{$eventid} == $FAILURE) {
				if($DEBUG) { LogMsg("Turning on event $eventid (FAILURE only)"); }
				`/sbin/auditctl -a exit,always -S $eventid -F success=0`;
			} else {
				if($DEBUG) { LogMsg("fail $eventid"); }
			}
		}
	}
}

sub StartWeb() {
	$webpid=0;
	if($WEBSERVERALLOW) {
		if(-f $WEBSERVER) {
			require $WEBSERVER;

			$webpid=open(WEBPROCESS,"|-");
			if($webpid==0) {
				# Child process
				WebServerSetup($WEBSERVERPORT,$ACCESSKEY,$RESTRICT_IP);
				exit;
			}
			# No buffering!
			$old_fh = select(WEBPROCESS);
			$| = 1;
			select($old_fh);
			LogMsg("Snare Configuration Web Server active and running.");
		}
	}
}

sub Match {
	my($string,$match,$negate)=@_;

	# Commas are special.
	@strings=split(/,/,$string);
	@matches=split(/,/,$match);
	$count=0;
	foreach $tmatch (@matches) {
		if ($REGEX == 1) {
			$regex=$tmatch;
			$regex =~ s/[^\\]\//\\\//g;
		} else {
			$regex=wildcard($tmatch);
		}
		if($DEBUG>1) { LogMsg("Match: $regex against " . $strings[$count]); }
		if($strings[$count] =~ /$regex/) {
			$match=!$negate;
		} else {
			$match=$negate;
		}
		if(!$match) {
			if($DEBUG > 1) { LogMsg("Match: No Match! (negate is $negate)"); }
			break;
		}
		$count++;
	}
	return($match);
}

sub OpenOutput() {
	my $line = shift;
	($type,$destination,$opt)=split(/[=:]/,$line);
	if($type eq "file") {
		if($destination eq "stdout") {
			open($fp,">-");
		} else {
			open($fp,">>$destination");
		}
		if($fp) {
			push(@OUTPUTFILES,$fp);
			return(1);
			# return($fp);
		}
		return(0);
	} elsif($type eq "network") {
		# Destination will be an ip or dns name
		# opt will be the port.
		$Port=6161;
		if($opt > 0 && $opt < 65536) {
			$Port=$opt;
		}

		socket($tsocket, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or return(0);
		if($destination =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
			# IP Address
			$ipaddr = inet_aton($destination);
		} else {
			$ipaddr = gethostbyname($destination);
		}
		if(!$ipaddr) {
			return(0);
		}
		$portaddr = sockaddr_in($Port, $ipaddr);
		if(!$portaddr) {
			return(0);
		}
		
		$OUTPUTNET{$OUTPUTNET{"SocketCount"}}{"Socket"}=$tsocket;
		$OUTPUTNET{$OUTPUTNET{"SocketCount"}}{"PortAddr"}=$portaddr;
		$OUTPUTNET{$OUTPUTNET{"SocketCount"}}{"Port"}=$Port;
		$OUTPUTNET{"SocketCount"}++;
	}
	return(0);
}

#
# wildcard to regex conversion
#
sub wildcard ($) {
	my $pat = shift or return(".*");
	my $re = quotemeta $pat;
	$re =~ s/\\\*/(.*)/g;
	return("^" . $re . "\$");
}

sub resolve_path {
	my($path)=@_;
	my(@pathparts)=split(/\//,$path);
	my(@newpath)=();

	foreach $part (@pathparts) {
		if($part eq "..") {
			if(@newpath) {
				pop(@newpath);
			}
		} elsif($part eq ".") {
			# Do nothing
		} elsif($part eq "") {
			# Do nothing
		} else {
			push(@newpath,$part);
		}
	}
	$resolvedpath="";
	if($path =~ /^\//) {
		$resolvedpath = "/";
	}
	$resolvedpath .= join("/",@newpath);

	return($resolvedpath);
}


sub getuid {
	my($id)=@_;
	if($uidcache{"$id"} eq "-") {
		return(0);
	} elsif($uidcache{"$id"}) {
		return($uidcache{"$id"});
	} else {
		$name=getpwuid($id);
		if($name) {
			$uidcache{"$id"}=$name;
			return($name);
		} else {
			$uidcache{"$id"}="-";
			return(0);
		}
	}
}

sub getgid {
	my($id)=@_;
	if($gidcache{"$id"} eq "-") {
		return(0);
	} elsif($gidcache{"$id"}) {
		return($gidcache{"$id"});
	} else {
		$name=getgrgid($id);
		if($name) {
			$gidcache{"$id"}=$name;
			return($name);
		} else {
			$gidcache{"$id"}="-";
			return(0);
		}
	}
}

sub Handler {
	local($sig)=@_;
	if($DEBUG) { LogMsg("SIG$sig Signal Received"); }
	$CONTINUE=0;
}

sub Restart {
	local($sig)=@_;
	if($DEBUG) { LogMsg("SIG$sig Signal Received in restart routine"); }
	#$CONTINUE=0;
	#if($webpid) {
	#	# send sigusr1, which tells the web server thread to terminate cleanly.
	#	kill(10,$webpid);
	#}

	# Flush our buffers
	SendEvents($FLUSHNOW);
	CloseOutputs();
	sleep(1);

	%event=();
	%Filters=();
	$eventsent=0;
	$lasteventnum=0;

	$timecheck=time();

	LoadConfig();
	StartWeb();
	# Set up our audit configuration based on our objectives.
	SetAudit();
	# Good to go.
}

sub DumpEvents {
	local($sig)=@_;
	if($webpid) {
		$EventCount=@EventCache;
		if($EventCount) {
			foreach $event (reverse(@EventCache)) {
				chomp($event);
				print WEBPROCESS "$event\n";
			}
			print WEBPROCESS "END\n";
		}
	}
}


sub LogMsg {
	my($string)=@_;
	if(!$string) {
		return;
	}
	setlogsock('unix');
	$rc=openlog("SnareDispatcher",'','user');
	if($rc) {
		syslog('info',$string);
	}
	closelog();
	print "DEBUG: $string\n";
}

