X-Git-Url: http://git.ipfire.org/?p=ipfire-2.x.git;a=blobdiff_plain;f=html%2Fcgi-bin%2Fids.cgi;h=313714486553d5debd53ec67477eb91912370009;hp=2b9b7c0b164c331598696b8900d8df7a2448d210;hb=5fbd7b29829caf0bcadcccd6f56ead51e2fb812e;hpb=ef5171ab7175d381a11f196de4e18b7e8af769e2 diff --git a/html/cgi-bin/ids.cgi b/html/cgi-bin/ids.cgi index 2b9b7c0b16..3137144865 100644 --- a/html/cgi-bin/ids.cgi +++ b/html/cgi-bin/ids.cgi @@ -2,7 +2,7 @@ ############################################################################### # # # IPFire.org - A linux based firewall # -# Copyright (C) 2007-2015 IPFire Team # +# Copyright (C) 2007-2018 IPFire Team # # # # 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 # @@ -24,75 +24,247 @@ use strict; # enable only the following on debugging purpose #use warnings; #use CGI::Carp 'fatalsToBrowser'; -use File::Copy; require '/var/ipfire/general-functions.pl'; require "${General::swroot}/lang.pl"; require "${General::swroot}/header.pl"; +require "${General::swroot}/ids-functions.pl"; my %color = (); my %mainsettings = (); -my %netsettings = (); -my %snortrules = (); -my %snortsettings=(); +my %idsrules = (); +my %idssettings=(); +my %rulessettings=(); +my %rulesetsources = (); my %cgiparams=(); my %checked=(); my %selected=(); +my %ignored=(); # Read-in main settings, for language, theme and colors. &General::readhash("${General::swroot}/main/settings", \%mainsettings); &General::readhash("/srv/web/ipfire/html/themes/".$mainsettings{'THEME'}."/include/colors.txt", \%color); -# Get netsettings. -&General::readhash("${General::swroot}/ethernet/settings", \%netsettings); +# Get the available network zones, based on the config type of the system and store +# the list of zones in an array. +my @network_zones = &IDS::get_available_network_zones(); -&Header::showhttpheaders(); +my $errormessage; + +# Create files if they does not exist yet. +&IDS::check_and_create_filelayout(); -# Default settings for snort. -$snortsettings{'ENABLE_SNORT'} = 'off'; -$snortsettings{'ENABLE_SNORT_GREEN'} = 'off'; -$snortsettings{'ENABLE_SNORT_BLUE'} = 'off'; -$snortsettings{'ENABLE_SNORT_ORANGE'} = 'off'; -$snortsettings{'RULES'} = ''; -$snortsettings{'OINKCODE'} = ''; -$snortsettings{'INSTALLDATE'} = ''; +# Hash which contains the colour code of a network zone. +my %colourhash = ( + 'red' => $Header::colourred, + 'green' => $Header::colourgreen, + 'blue' => $Header::colourblue, + 'orange' => $Header::colourorange +); + +&Header::showhttpheaders(); #Get GUI values &Header::getcgihash(\%cgiparams); -my $snortrulepath = "/etc/snort/rules"; -my $snortusedrulefilesfile = "${General::swroot}/snort/snort-used-rulefiles.conf"; -my $errormessage; +## Add/edit an entry to the ignore file. +# +if (($cgiparams{'WHITELIST'} eq $Lang::tr{'add'}) || ($cgiparams{'WHITELIST'} eq $Lang::tr{'update'})) { + + # Check if any input has been performed. + if ($cgiparams{'IGNORE_ENTRY_ADDRESS'} ne '') { + + # Check if the given input is no valid IP-address or IP-address with subnet, display an error message. + if ((!&General::validip($cgiparams{'IGNORE_ENTRY_ADDRESS'})) && (!&General::validipandmask($cgiparams{'IGNORE_ENTRY_ADDRESS'}))) { + $errormessage = "$Lang::tr{'guardian invalid address or subnet'}"; + } + } else { + $errormessage = "$Lang::tr{'guardian empty input'}"; + } + + # Go further if there was no error. + if ($errormessage eq '') { + my %ignored = (); + my $id; + my $status; + + # Assign hash values. + my $new_entry_address = $cgiparams{'IGNORE_ENTRY_ADDRESS'}; + my $new_entry_remark = $cgiparams{'IGNORE_ENTRY_REMARK'}; + + # Read-in ignoredfile. + &General::readhasharray($IDS::ignored_file, \%ignored); + + # Check if we should edit an existing entry and got an ID. + if (($cgiparams{'WHITELIST'} eq $Lang::tr{'update'}) && ($cgiparams{'ID'})) { + # Assin the provided id. + $id = $cgiparams{'ID'}; + + # Undef the given ID. + undef($cgiparams{'ID'}); + + # Grab the configured status of the corresponding entry. + $status = $ignored{$id}[2]; + } else { + # Each newly added entry automatically should be enabled. + $status = "enabled"; + + # Generate the ID for the new entry. + # + # Sort the keys by their ID and store them in an array. + my @keys = sort { $a <=> $b } keys %ignored; + + # Reverse the key array. + my @reversed = reverse(@keys); + + # Obtain the last used id. + my $last_id = @reversed[0]; + + # Increase the last id by one and use it as id for the new entry. + $id = ++$last_id; + } + + # Add/Modify the entry to/in the ignored hash. + $ignored{$id} = ["$new_entry_address", "$new_entry_remark", "$status"]; + + # Write the changed ignored hash to the ignored file. + &General::writehasharray($IDS::ignored_file, \%ignored); + + # Regenerate the ignore file. + &IDS::generate_ignore_file(); + } + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } + +## Toggle Enabled/Disabled for an existing entry on the ignore list. +# + +} elsif ($cgiparams{'WHITELIST'} eq $Lang::tr{'toggle enable disable'}) { + my %ignored = (); + + # Only go further, if an ID has been passed. + if ($cgiparams{'ID'}) { + # Assign the given ID. + my $id = $cgiparams{'ID'}; + + # Undef the given ID. + undef($cgiparams{'ID'}); + + # Read-in ignoredfile. + &General::readhasharray($IDS::ignored_file, \%ignored); + + # Grab the configured status of the corresponding entry. + my $status = $ignored{$id}[2]; + + # Switch the status. + if ($status eq "disabled") { + $status = "enabled"; + } else { + $status = "disabled"; + } + + # Modify the status of the existing entry. + $ignored{$id} = ["$ignored{$id}[0]", "$ignored{$id}[1]", "$status"]; + + # Write the changed ignored hash to the ignored file. + &General::writehasharray($IDS::ignored_file, \%ignored); -# Try to determine if oinkmaster is running. -my $oinkmaster_pid = `pidof oinkmaster.pl -x`; + # Regenerate the ignore file. + &IDS::generate_ignore_file(); -# If oinkmaster is running display output. -if ($oinkmaster_pid) { - &working("$Lang::tr{'snort working'}"); + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } + } + +## Remove entry from ignore list. +# +} elsif ($cgiparams{'WHITELIST'} eq $Lang::tr{'remove'}) { + my %ignored = (); + + # Read-in ignoredfile. + &General::readhasharray($IDS::ignored_file, \%ignored); + + # Drop entry from the hash. + delete($ignored{$cgiparams{'ID'}}); + + # Undef the given ID. + undef($cgiparams{'ID'}); + + # Write the changed ignored hash to the ignored file. + &General::writehasharray($IDS::ignored_file, \%ignored); + + # Regenerate the ignore file. + &IDS::generate_ignore_file(); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } +} + +# Check if the page is locked, in this case, the ids_page_lock_file exists. +if (-e $IDS::ids_page_lock_file) { + # Lock the webpage and print notice about autoupgrade of the ruleset + # is in progess. + &working_notice("$Lang::tr{'ids ruleset autoupdate in progress'}"); + + # Loop and check if the file still exists. + while(-e $IDS::ids_page_lock_file) { + # Sleep for a second and re-check. + sleep 1; + } + + # Page has been unlocked, perform a reload. + &reload(); +} + +# Check if any error has been stored. +if (-e $IDS::storederrorfile) { + # Open file to read in the stored error message. + open(FILE, "<$IDS::storederrorfile") or die "Could not open $IDS::storederrorfile. $!\n"; + + # Read the stored error message. + $errormessage = ; + + # Close file. + close (FILE); + + # Delete the file, which is now not longer required. + unlink($IDS::storederrorfile); } -## Grab all available snort rules and store them in the snortrules hash. +## Grab all available rules and store them in the idsrules hash. # -# Open snort rules directory and do a directory listing. -opendir(DIR, $snortrulepath) or die $!; +# Open rules directory and do a directory listing. +opendir(DIR, $IDS::rulespath) or die $!; # Loop through the direcory. while (my $file = readdir(DIR)) { # We only want files. - next unless (-f "$snortrulepath/$file"); + next unless (-f "$IDS::rulespath/$file"); # Ignore empty files. - next if (-z "$snortrulepath/$file"); + next if (-z "$IDS::rulespath/$file"); # Use a regular expression to find files ending in .rules next unless ($file =~ m/\.rules$/); # Ignore files which are not read-able. - next unless (-R "$snortrulepath/$file"); + next unless (-R "$IDS::rulespath/$file"); + + # Skip whitelist rules file. + next if( $file eq "whitelist.rules"); # Call subfunction to read-in rulefile and add rules to - # the snortrules hash. + # the idsrules hash. &readrulesfile("$file"); } @@ -101,9 +273,9 @@ closedir(DIR); # Gather used rulefiles. # # Check if the file for activated rulefiles is not empty. -if(-f $snortusedrulefilesfile) { +if(-f $IDS::used_rulefiles_file) { # Open the file for used rulefile and read-in content. - open(FILE, $snortusedrulefilesfile) or die "Could not open $snortusedrulefilesfile. $!\n"; + open(FILE, $IDS::used_rulefiles_file) or die "Could not open $IDS::used_rulefiles_file. $!\n"; # Read-in content. my @lines = ; @@ -123,27 +295,116 @@ if(-f $snortusedrulefilesfile) { next if ($line =~ /^\s*$/); # Gather rule sid and message from the ruleline. - if ($line =~ /.*include \$RULE_PATH\/(.*)/) { + if ($line =~ /.*- (.*)/) { my $rulefile = $1; - # Add the rulefile to the %snortrules hash. - $snortrules{$rulefile}{'Rulefile'}{'State'} = "on"; + # Check if the current rulefile exists in the %idsrules hash. + # If not, the file probably does not exist anymore or contains + # no rules. + if($idsrules{$rulefile}) { + # Add the rulefile state to the %idsrules hash. + $idsrules{$rulefile}{'Rulefile'}{'State'} = "on"; + } } } } -# Save ruleset. -if ($cgiparams{'RULESET'} eq $Lang::tr{'update'}) { - my $enabled_sids_file = "${General::swroot}/snort/oinkmaster-enabled-sids.conf"; - my $disabled_sids_file = "${General::swroot}/snort/oinkmaster-disabled-sids.conf"; +# Save ruleset configuration. +if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { + my %oldsettings; + my %rulesetsources; + + # Read-in current (old) IDS settings. + &General::readhash("$IDS::rules_settings_file", \%oldsettings); + + # Get all available ruleset locations. + &General::readhash("$IDS::rulesetsourcesfile", \%rulesetsources); + + # Prevent form name from been stored in conf file. + delete $cgiparams{'RULESET'}; + + # Grab the URL based on the choosen vendor. + my $url = $rulesetsources{$cgiparams{'RULES'}}; + + # Check if the choosen vendor (URL) requires an subscription/oinkcode. + if ($url =~ /\/ ) { + # Check if an subscription/oinkcode has been provided. + if ($cgiparams{'OINKCODE'}) { + # Check if the oinkcode contains unallowed chars. + unless ($cgiparams{'OINKCODE'} =~ /^[a-z0-9]+$/) { + $errormessage = $Lang::tr{'invalid input for oink code'}; + } + } else { + # Print an error message, that an subsription/oinkcode is required for this + # vendor. + $errormessage = $Lang::tr{'ids oinkcode required'}; + } + } + + # Go on if there are no error messages. + if (!$errormessage) { + # Store settings into settings file. + &General::writehash("$IDS::rules_settings_file", \%cgiparams); + + # Check if the the automatic rule update hass been touched. + if($cgiparams{'AUTOUPDATE_INTERVAL'} ne $oldsettings{'AUTOUPDATE_INTERVAL'}) { + # Call suricatactrl to set the new interval. + &IDS::call_suricatactrl("cron", $cgiparams{'AUTOUPDATE_INTERVAL'}); + } + + # Check if a ruleset is present - if not or the source has been changed download it. + if((! %idsrules) || ($oldsettings{'RULES'} ne $cgiparams{'RULES'})) { + # Check if the red device is active. + unless (-e "${General::swroot}/red/active") { + $errormessage = "$Lang::tr{'could not download latest updates'} - $Lang::tr{'system is offline'}"; + } + + # Check if enought free disk space is availabe. + if(&IDS::checkdiskspace()) { + $errormessage = "$Lang::tr{'not enough disk space'}"; + } + + # Check if any errors happend. + unless ($errormessage) { + # Lock the webpage and print notice about downloading + # a new ruleset. + &working_notice("$Lang::tr{'ids working'}"); + + # Call subfunction to download the ruleset. + if(&IDS::downloadruleset()) { + $errormessage = $Lang::tr{'could not download latest updates'}; - # Arrays to store sid which should be added to the corresponding files. - my @enabled_sids; - my @disabled_sids; + # Call function to store the errormessage. + &IDS::_store_error_message($errormessage); + } else { + # Call subfunction to launch oinkmaster. + &IDS::oinkmaster(); + } + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to stop the IDS - because of the changed + # ruleset - the use has to configure it before suricata can be + # used again. + &IDS::call_suricatactrl("stop"); + } + + # Perform a reload of the page. + &reload(); + } + } + } + +# Save ruleset. +} elsif ($cgiparams{'RULESET'} eq $Lang::tr{'ids apply'}) { + # Arrays to store which rulefiles have been enabled and will be used. my @enabled_rulefiles; - # Loop through the hash of snortrules. - foreach my $rulefile(keys %snortrules) { + # Hash to store the user-enabled and disabled sids. + my %enabled_disabled_sids; + + # Loop through the hash of idsrules. + foreach my $rulefile(keys %idsrules) { # Check if the rulefile is enabled. if ($cgiparams{$rulefile} eq "on") { # Add rulefile to the array of enabled rulefiles. @@ -154,21 +415,28 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'update'}) { } } - # Loop through the hash of snortrules. - foreach my $rulefile (keys %snortrules) { + # Read-in the files for enabled/disabled sids. + # This will be done by calling the read_enabled_disabled_sids_file function two times + # and merge the returned hashes together into the enabled_disabled_sids hash. + %enabled_disabled_sids = ( + &read_enabled_disabled_sids_file($IDS::disabled_sids_file), + &read_enabled_disabled_sids_file($IDS::enabled_sids_file)); + + # Loop through the hash of idsrules. + foreach my $rulefile (keys %idsrules) { # Loop through the single rules of the rulefile. - foreach my $sid (keys %{$snortrules{$rulefile}}) { + foreach my $sid (keys %{$idsrules{$rulefile}}) { # Skip the current sid if it is not numeric. next unless ($sid =~ /\d+/ ); # Check if there exists a key in the cgiparams hash for this sid. if (exists($cgiparams{$sid})) { # Look if the rule is disabled. - if ($snortrules{$rulefile}{$sid}{'State'} eq "off") { + if ($idsrules{$rulefile}{$sid}{'State'} eq "off") { # Check if the state has been set to 'on'. if ($cgiparams{$sid} eq "on") { - # Add the sid to the enabled_sids array. - push(@enabled_sids, $sid); + # Add/Modify the sid to/in the enabled_disabled_sids hash. + $enabled_disabled_sids{$sid} = "enabled"; # Drop item from cgiparams hash. delete $cgiparams{$rulefile}{$sid}; @@ -176,12 +444,12 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'update'}) { } } else { # Look if the rule is enabled. - if ($snortrules{$rulefile}{$sid}{'State'} eq "on") { + if ($idsrules{$rulefile}{$sid}{'State'} eq "on") { # Check if the state is 'on' and should be disabled. # In this case there is no entry # for the sid in the cgiparams hash. - # Add it to the disabled_sids array. - push(@disabled_sids, $sid); + # Add/Modify it to/in the enabled_disabled_sids hash. + $enabled_disabled_sids{$sid} = "disabled"; # Drop item from cgiparams hash. delete $cgiparams{$rulefile}{$sid}; @@ -191,194 +459,265 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'update'}) { } # Open enabled sid's file for writing. - open(FILE, ">$enabled_sids_file") or die "Could not write to $enabled_sids_file. $!\n"; + open(ENABLED_FILE, ">$IDS::enabled_sids_file") or die "Could not write to $IDS::enabled_sids_file. $!\n"; - # Write header to file. - print FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; - - # Check if the enabled_sids array contains any sid's. - if (@enabled_sids) { - # Loop through the array of enabled sids and write them to the file. - foreach my $sid (@enabled_sids) { - print FILE "enablesid $sid\n"; + # Open disabled sid's file for writing. + open(DISABLED_FILE, ">$IDS::disabled_sids_file") or die "Could not write to $IDS::disabled_sids_file. $!\n"; + + # Write header to the files. + print ENABLED_FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + print DISABLED_FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + + # Check if the hash for enabled/disabled files contains any entries. + if (%enabled_disabled_sids) { + # Loop through the hash. + foreach my $sid (keys %enabled_disabled_sids) { + # Check if the sid is enabled. + if ($enabled_disabled_sids{$sid} eq "enabled") { + # Print the sid to the enabled_sids file. + print ENABLED_FILE "enablesid $sid\n"; + # Check if the sid is disabled. + } elsif ($enabled_disabled_sids{$sid} eq "disabled") { + # Print the sid to the disabled_sids file. + print DISABLED_FILE "disablesid $sid\n"; + # Something strange happende - skip the current sid. + } else { + next; + } } } - # Close file after writing. - close(FILE); + # Close file for enabled_sids after writing. + close(ENABLED_FILE); - # Open disabled sid's file for writing. - open(FILE, ">$disabled_sids_file") or die "Could not write to $disabled_sids_file. $!\n"; + # Close file for disabled_sids after writing. + close(DISABLED_FILE); - # Write header to file. - print FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + # Call function to generate and write the used rulefiles file. + &IDS::write_used_rulefiles_file(@enabled_rulefiles); - # Check if the enabled_sids array contains any sid's. - if (@disabled_sids) { - # Loop through the array of disabled sids and write them to the file. - foreach my $sid (@disabled_sids) { - print FILE "disablesid $sid\n"; - } - } + # Lock the webpage and print message. + &working_notice("$Lang::tr{'ids apply ruleset changes'}"); - # Close file after writing. - close(FILE); + # Call oinkmaster to alter the ruleset. + &IDS::oinkmaster(); - # Open file for used rulefiles. - open (FILE, ">$snortusedrulefilesfile") or die "Could not write to $snortusedrulefilesfile. $!\n"; + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } - # Write header to file. - print FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + # Reload page. + &reload(); - # Check if the enabled_rulefiles array contains any entries. - if (@enabled_rulefiles) { - # Loop through the array of rulefiles which should be loaded and write the to the file. - foreach my $file (@enabled_rulefiles) { - print FILE "include \$RULE_PATH/$file\n"; - } +# Download new ruleset. +} elsif ($cgiparams{'RULESET'} eq $Lang::tr{'update ruleset'}) { + # Check if the red device is active. + unless (-e "${General::swroot}/red/active") { + $errormessage = "$Lang::tr{'could not download latest updates'} - $Lang::tr{'system is offline'}"; } - # Close file after writing. - close(FILE); + # Check if enought free disk space is availabe. + if(&IDS::checkdiskspace()) { + $errormessage = "$Lang::tr{'not enough disk space'}"; + } - # Call oinkmaster to alter the ruleset. - &oinkmaster(); + # Check if any errors happend. + unless ($errormessage) { + # Lock the webpage and print notice about downloading + # a new ruleset. + &working_notice("$Lang::tr{'ids download new ruleset'}"); -# Download new ruleset. -} elsif ($cgiparams{'RULESET'} eq $Lang::tr{'download new ruleset'}) { - # Local var. - my $return; - - # Call diskfree to gather the free disk space of /var. - my @df = `/bin/df -B M /var`; - - # Loop through the output. - foreach my $line (@df) { - # Ignore header line. - next if $line =~ m/^Filesystem/; - - # Search for a line with the device information. - if ($line =~ m/dev/ ) { - # Split the line into single pieces. - my @values = split(' ', $line); - my ($filesystem, $blocks, $used, $available, $used_perenctage, $mounted_on) = @values; - - # Check if the available disk space is more than 300MB. - if ($available < 300) { - # If there is not enough space, print out an error message. - $errormessage = "$Lang::tr{'not enough disk space'} < 300MB, /var $1MB"; - } else { - # Call subfunction to download the ruleset. - &downloadrulesfile(); + # Call subfunction to download the ruleset. + if(&IDS::downloadruleset()) { + $errormessage = $Lang::tr{'could not download latest updates'}; + + # Call function to store the errormessage. + &IDS::_store_error_message($errormessage); - # Sleep for 3 seconds. - sleep(3); + # Preform a reload of the page. + &reload(); + } else { + # Call subfunction to launch oinkmaster. + &IDS::oinkmaster(); - # Gather return of the external wget. - $return = `cat /var/tmp/log 2>/dev/null`; + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); } - # Check if there was an error. - if ($return =~ "ERROR") { - # Store error message for display. - $errormessage = "
".$return."
"; - } else { - # Remove logfile. - unlink("/var/tmp/log"); + # Perform a reload of the page. + &reload(); + } + } +# Save IDS settings. +} elsif ($cgiparams{'IDS'} eq $Lang::tr{'save'}) { + my %oldidssettings; + my $reload_page; + my $monitored_zones = 0; + + # Read-in current (old) IDS settings. + &General::readhash("$IDS::ids_settings_file", \%oldidssettings); - # Call subfunction to launch oinkmaster. - &oinkmaster(); + # Prevent form name from been stored in conf file. + delete $cgiparams{'IDS'}; + + # Check if the IDS should be enabled. + if ($cgiparams{'ENABLE_IDS'} eq "on") { + # Check if any ruleset is available. Otherwise abort and display an error. + unless(%idsrules) { + $errormessage = $Lang::tr{'ids no ruleset available'}; + } - # Sleep for 2 seconds. - sleep(2); + # Loop through the array of available interfaces. + foreach my $zone (@network_zones) { + # Convert interface name into upper case. + my $zone_upper = uc($zone); + + # Check if the IDS is enabled for this interaces. + if ($cgiparams{"ENABLE_IDS_$zone_upper"}) { + # Increase count. + $monitored_zones++; } } - } -# Save snort settings. -} elsif ($cgiparams{'SNORT'} eq $Lang::tr{'save'}) { - # Prevent form name from been stored in conf file. - delete $cgiparams{'SNORT'}; - # Check if an oinkcode has been provided. - if ($cgiparams{'OINKCODE'}) { - # Check if the oinkcode contains unallowed chars. - unless ($cgiparams{'OINKCODE'} =~ /^[a-z0-9]+$/) { - $errormessage = $Lang::tr{'invalid input for oink code'}; + # Check if at least one zone should be monitored, or show an error. + unless ($monitored_zones >= 1) { + $errormessage = $Lang::tr{'ids no network zone'}; } } # Go on if there are no error messages. if (!$errormessage) { # Store settings into settings file. - &General::writehash("${General::swroot}/snort/settings", \%cgiparams); + &General::writehash("$IDS::ids_settings_file", \%cgiparams); + } - # Create/Remove control files for snort. - if ($snortsettings{'ENABLE_SNORT'} eq 'on') { - system ('/usr/bin/touch', "${General::swroot}/snort/enable"); - } else { - unlink "${General::swroot}/snort/enable"; - } + # Generate file to store the home net. + &IDS::generate_home_net_file(); - if ($snortsettings{'ENABLE_SNORT_GREEN'} eq 'on') { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_green"); - } else { - unlink "${General::swroot}/snort/enable_green"; - } + # Temporary variable to set the ruleaction. + # Default is "drop" to use suricata as IPS. + my $ruleaction="drop"; - if ($snortsettings{'ENABLE_SNORT_BLUE'} eq 'on') { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_blue"); - } else { - unlink "${General::swroot}/snort/enable_blue"; - } + # Check if the traffic only should be monitored. + if($cgiparams{'MONITOR_TRAFFIC_ONLY'} eq 'on') { + # Switch the ruleaction to "alert". + # Suricata acts as an IDS only. + $ruleaction="alert"; + } - if ($snortsettings{'ENABLE_SNORT_ORANGE'} eq 'on') { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_orange"); - } else { - unlink "${General::swroot}/snort/enable_orange"; + # Write the modify sid's file and pass the taken ruleaction. + &IDS::write_modify_sids_file($ruleaction); + + # Check if "MONITOR_TRAFFIC_ONLY" has been changed. + if($cgiparams{'MONITOR_TRAFFIC_ONLY'} ne $oldidssettings{'MONITOR_TRAFFIC_ONLY'}) { + # Check if a ruleset exists. + if (%idsrules) { + # Lock the webpage and print message. + &working_notice("$Lang::tr{'ids working'}"); + + # Call oinkmaster to alter the ruleset. + &IDS::oinkmaster(); + + # Set reload_page to "True". + $reload_page="True"; } + } - if ($snortsettings{'ENABLE_PREPROCESSOR_HTTP_INSPECT'} eq 'on') { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_preprocessor_http_inspect"); + # Check if the IDS currently is running. + if(&IDS::ids_is_running()) { + # Check if ENABLE_IDS is set to on. + if($cgiparams{'ENABLE_IDS'} eq "on") { + # Call suricatactrl to perform a reload of suricata. + &IDS::call_suricatactrl("reload"); } else { - unlink "${General::swroot}/snort/enable_preprocessor_http_inspect"; + # Call suricatactrl to stop suricata. + &IDS::call_suricatactrl("stop"); } + } else { + # Call suricatactrl to start suricata. + &IDS::call_suricatactrl("start"); + } - # Call snortctrl to restart snort - system('/usr/local/bin/snortctrl restart >/dev/null'); + # Check if the page should be reloaded. + if ($reload_page) { + # Perform a reload of the page. + &reload(); } } -# Read-in snortsettings -&General::readhash("${General::swroot}/snort/settings", \%snortsettings); - -$checked{'ENABLE_SNORT'}{'off'} = ''; -$checked{'ENABLE_SNORT'}{'on'} = ''; -$checked{'ENABLE_SNORT'}{$snortsettings{'ENABLE_SNORT'}} = "checked='checked'"; -$checked{'ENABLE_SNORT_GREEN'}{'off'} = ''; -$checked{'ENABLE_SNORT_GREEN'}{'on'} = ''; -$checked{'ENABLE_SNORT_GREEN'}{$snortsettings{'ENABLE_SNORT_GREEN'}} = "checked='checked'"; -$checked{'ENABLE_SNORT_BLUE'}{'off'} = ''; -$checked{'ENABLE_SNORT_BLUE'}{'on'} = ''; -$checked{'ENABLE_SNORT_BLUE'}{$snortsettings{'ENABLE_SNORT_BLUE'}} = "checked='checked'"; -$checked{'ENABLE_SNORT_ORANGE'}{'off'} = ''; -$checked{'ENABLE_SNORT_ORANGE'}{'on'} = ''; -$checked{'ENABLE_SNORT_ORANGE'}{$snortsettings{'ENABLE_SNORT_ORANGE'}} = "checked='checked'"; +# Read-in idssettings and rulesetsettings +&General::readhash("$IDS::ids_settings_file", \%idssettings); +&General::readhash("$IDS::rules_settings_file", \%rulessettings); + +# If no autoupdate intervall has been configured yet, set default value. +unless(exists($rulessettings{'AUTOUPDATE_INTERVAL'})) { + # Set default to "weekly". + $rulessettings{'AUTOUPDATE_INTERVAL'} = 'weekly'; +} + +# Read-in ignored hosts. +&General::readhasharray("$IDS::settingsdir/ignored", \%ignored); + +$checked{'ENABLE_IDS'}{'off'} = ''; +$checked{'ENABLE_IDS'}{'on'} = ''; +$checked{'ENABLE_IDS'}{$idssettings{'ENABLE_IDS'}} = "checked='checked'"; +$checked{'MONITOR_TRAFFIC_ONLY'}{'off'} = ''; +$checked{'MONITOR_TRAFFIC_ONLY'}{'on'} = ''; +$checked{'MONITOR_TRAFFIC_ONLY'}{$idssettings{'MONITOR_TRAFFIC_ONLY'}} = "checked='checked'"; $selected{'RULES'}{'nothing'} = ''; $selected{'RULES'}{'community'} = ''; $selected{'RULES'}{'emerging'} = ''; $selected{'RULES'}{'registered'} = ''; $selected{'RULES'}{'subscripted'} = ''; -$selected{'RULES'}{$snortsettings{'RULES'}} = "selected='selected'"; +$selected{'RULES'}{$rulessettings{'RULES'}} = "selected='selected'"; +$selected{'AUTOUPDATE_INTERVAL'}{'off'} = ''; +$selected{'AUTOUPDATE_INTERVAL'}{'daily'} = ''; +$selected{'AUTOUPDATE_INTERVAL'}{'weekly'} = ''; +$selected{'AUTOUPDATE_INTERVAL'}{$rulessettings{'AUTOUPDATE_INTERVAL'}} = "selected='selected'"; &Header::openpage($Lang::tr{'intrusion detection system'}, 1, ''); ### Java Script ### +print" END @@ -393,263 +732,496 @@ if ($errormessage) { &Header::closebox(); } +# Draw current state of the IDS &Header::openbox('100%', 'left', $Lang::tr{'intrusion detection system'}); + +# Check if the IDS is running and obtain the process-id. +my $pid = &IDS::ids_is_running(); + +# Display some useful information, if suricata daemon is running. +if ($pid) { + # Gather used memory. + my $memory = &get_memory_usage($pid); + + print < + + $Lang::tr{'intrusion detection'} + + + + $Lang::tr{'guardian daemon'} + $Lang::tr{'running'} + + + + + PID + $Lang::tr{'memory'} + + + + + $pid + $memory KB + + +END +} else { + # Otherwise display a hint that the service is not launched. + print < + + $Lang::tr{'intrusion detection'} + + + + $Lang::tr{'guardian daemon'} + $Lang::tr{'stopped'} + + +END +} + +# Only show this area, if a ruleset is present. +if (%idsrules) { + + print <

$Lang::tr{'settings'}

+ +
+ + + + + + + + + + + + + + + + + + + +END +; + + # Loop through the array of available networks and print config options. + foreach my $zone (@network_zones) { + my $checked_input; + my $checked_forward; + + # Convert current zone name to upper case. + my $zone_upper = uc($zone); + + # Set zone name. + my $zone_name = $zone; + + # Dirty hack to get the correct language string for the red zone. + if ($zone eq "red") { + $zone_name = "red1"; + } + + # Grab checkbox status from settings hash. + if ($idssettings{"ENABLE_IDS_$zone_upper"} eq "on") { + $checked_input = "checked = 'checked'"; + } + + print "\n"; + } + print <
+  $Lang::tr{'ids enable'} + +  $Lang::tr{'ids monitor traffic only'} +








$Lang::tr{'ids monitored interfaces'}
\n"; + print "\n"; + print " $Lang::tr{'enabled on'} $Lang::tr{$zone_name}\n"; + print "
- +
GREEN Snort +
+ +

+ + + + + +
+
END ; -if ($netsettings{'BLUE_DEV'} ne '') { - print "       BLUE Snort"; -} -if ($netsettings{'ORANGE_DEV'} ne '') { - print "       ORANGE Snort"; + } - print "       RED Snort"; + +&Header::closebox(); + +# Draw elements for ruleset configuration. +&Header::openbox('100%', 'center', $Lang::tr{'ids ruleset settings'}); print < - -

- - - $Lang::tr{'ids rules update'} - - - + - - - -
- $Lang::tr{'ids rules license'} www.snort.org$Lang::tr{'ids rules license1'}

- $Lang::tr{'ids rules license2'} Get an Oinkcode, $Lang::tr{'ids rules license3'} - - - - Oinkcode:  - - -
+ + + + + + + + +

+ + + + Oinkcode:  + + + +   + + END ; -if ( -e "/var/tmp/snortrules.tar.gz"){ - my @Info = stat("/var/tmp/snortrules.tar.gz"); - $snortsettings{'INSTALLDATE'} = localtime($Info[9]); -} -print " $Lang::tr{'updates installed'}: $snortsettings{'INSTALLDATE'}"; + # Show the "Update Ruleset"-Button only if a ruleset has been downloaded yet and automatic updates are disabled. + if ((%idsrules) && ($rulessettings{'AUTOUPDATE_INTERVAL'} eq "off")) { + # Display button to update the ruleset. + print"\n"; + } +print < + -print < - -

- - - - -
+ + END ; &Header::closebox(); -&Header::openbox('100%', 'LEFT', $Lang::tr{'intrusion detection system rules'}); - print"
\n"; +# +# Whitelist / Ignorelist +# +&Header::openbox('100%', 'center', $Lang::tr{'guardian ignored hosts'}); + +print < + + $Lang::tr{'ip address'} + $Lang::tr{'remark'} + + +END + # Check if some hosts have been added to be ignored. + if (keys (%ignored)) { + my $col = ""; + + # Loop through all entries of the hash. + while( (my $key) = each %ignored) { + # Assign data array positions to some nice variable names. + my $address = $ignored{$key}[0]; + my $remark = $ignored{$key}[1]; + my $status = $ignored{$key}[2]; + + # Check if the key (id) number is even or not. + if ($cgiparams{'ID'} eq $key) { + $col="bgcolor='${Header::colouryellow}'"; + } elsif ($key % 2) { + $col="bgcolor='$color{'color22'}'"; + } else { + $col="bgcolor='$color{'color20'}'"; + } + + # Choose icon for the checkbox. + my $gif; + my $gdesc; + + # Check if the status is enabled and select the correct image and description. + if ($status eq 'enabled' ) { + $gif = 'on.gif'; + $gdesc = $Lang::tr{'click to disable'}; + } else { + $gif = 'off.gif'; + $gdesc = $Lang::tr{'click to enable'}; + } + +print < + $address + $remark + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + +END + } + } else { + # Print notice that currently no hosts are ignored. + print "\n"; + print "$Lang::tr{'guardian no entries'}\n"; + print "\n"; + } + + print "\n"; - # Output display table for rule files - print "\n"; + # Section to add new elements or edit existing ones. +print < +
+
- # Local variable required for java script to show/hide - # rules of a rulefile. - my $rulesetcount = 1; +
+
+END + + # Assign correct headline and button text. + my $buttontext; + my $entry_address; + my $entry_remark; - # Loop over each rule file - foreach my $rulefile (sort keys(%snortrules)) { - my $rulechecked = ''; + # Check if an ID (key) has been given, in this case an existing entry should be edited. + if ($cgiparams{'ID'} ne '') { + $buttontext = $Lang::tr{'update'}; + print "\n"; - # Check if rule file is enabled - if ($snortrules{$rulefile}{'Rulefile'}{'State'} eq 'on') { - $rulechecked = 'CHECKED'; + # Grab address and remark for the given key. + $entry_address = $ignored{$cgiparams{'ID'}}[0]; + $entry_remark = $ignored{$cgiparams{'ID'}}[1]; + } else { + $buttontext = $Lang::tr{'add'}; + print "\n"; } - # Table and rows for the rule files. - print"\n"; - print"\n"; - print"\n"; - print"\n"; - print"\n"; - - # Rows which will be hidden per default and will contain the single rules. - print"\n"; - print" + + + + + + + + +
$Lang::tr{'update'}
$Lang::tr{'dnsforward add a new entry'}
\n"; - print"\n"; - print"$rulefile\n"; - print"SHOW\n"; - print"
$Lang::tr{'ip address'}: $Lang::tr{'remark'}:
+ +END + +&Header::closebox(); + +# Only show the section for configuring the ruleset if one is present. +if (%idsrules) { + # Load neccessary perl modules for file stat and to format the timestamp. + use File::stat; + use POSIX qw( strftime ); + + # Call stat on the rulestarball. + my $stat = stat("$IDS::rulestarball"); + + # Get timestamp the file creation. + my $mtime = $stat->mtime; + + # Convert into human read-able format. + my $rulesdate = strftime('%Y-%m-%d %H:%M:%S', localtime($mtime)); + + &Header::openbox('100%', 'LEFT', "$Lang::tr{'intrusion detection system rules'} ($rulesdate)" ); + + print"
\n"; + + # Output display table for rule files print "\n"; - # Loop over rule file rules - foreach my $sid (sort {$a <=> $b} keys(%{$snortrules{$rulefile}})) { + # Loop over each rule file + foreach my $rulefile (sort keys(%idsrules)) { + my $rulechecked = ''; + + # Check if rule file is enabled + if ($idsrules{$rulefile}{'Rulefile'}{'State'} eq 'on') { + $rulechecked = 'CHECKED'; + } + + # Convert rulefile name into category name. + my $categoryname = &_rulefile_to_category($rulefile); + + # Table and rows for the rule files. + print"\n"; + print"\n"; + print"\n"; + print"\n"; + print"\n"; + + # Rows which will be hidden per default and will contain the single rules. + print"\n"; + print""; + # Increment rule count + $lines++; + } - # Finished whith the rule file, increase count. - $rulesetcount++; - } + # If do not have a second rule for row, create empty cell + if (($lines % 2) != 0) { + print ""; + } - # Close display table - print "
\n"; + print"\n"; + print"$rulefile\n"; + print"$Lang::tr{'ids show'}\n"; + print"
"; + # Close display table + print ""; + } + + # Close display table + print ""; print < - -   - + END ; -&Header::closebox(); + &Header::closebox(); +} + &Header::closebigbox(); &Header::closepage(); -sub working ($) { - my $message = $_[0]; - - &Header::openpage($Lang::tr{'intrusion detection system'}, 1, ''); - &Header::openbigbox('100%', 'left', '', $errormessage); - &Header::openbox( 'Waiting', 1, "" ); - print < - - $Lang::tr{ - $message - - - -
- -
- - +# +## A function to display a notice, to lock the webpage and +## tell the user which action currently will be performed. +# +sub working_notice ($) { + my ($message) = @_; + + &Header::openpage($Lang::tr{'intrusion detection system'}, 1, ''); + &Header::openbigbox('100%', 'left', '', $errormessage); + &Header::openbox( 'Waiting', 1,); + print < + + $Lang::tr{ + $message + + END - &Header::closebox(); - &Header::closebigbox(); - &Header::closepage(); - exit; + &Header::closebox(); + &Header::closebigbox(); + &Header::closepage(); } -sub downloadrulesfile { - my $peer; - my $peerport; - - unlink("/var/tmp/log"); - - unless (-e "${General::swroot}/red/active") { - $errormessage = $Lang::tr{'could not download latest updates'}; - return undef; - } - - # Gather snort settings. - my %snortsettings = (); - &General::readhash("${General::swroot}/snort/settings", \%snortsettings); - - # Get all available ruleset locations. - my %urls=(); - &General::readhash("${General::swroot}/snort/ruleset-sources.list", \%urls); - - # Grab the right url based on the configured vendor. - my $url = $urls{$snortsettings{'RULES'}}; - - # Check and pass oinkcode if the vendor requires one. - $url =~ s/\/$snortsettings{'OINKCODE'}/g; - - # Abort if no url could be determined for the vendor. - unless($url) { - $errormessage = $Lang::tr{'could not download latest updates'}; - return undef; - } - - my %proxysettings=(); - &General::readhash("${General::swroot}/proxy/settings", \%proxysettings); - - if ($_=$proxysettings{'UPSTREAM_PROXY'}) { - ($peer, $peerport) = (/^(?:[a-zA-Z ]+\:\/\/)?(?:[A-Za-z0-9\_\.\-]*?(?:\:[A-Za-z0-9\_\.\-]*?)?\@)?([a-zA-Z0-9\.\_\-]*?)(?:\:([0-9]{1,5}))?(?:\/.*?)?$/); - } - - if ($peer) { - system("wget -r --proxy=on --proxy-user=$proxysettings{'UPSTREAM_USER'} --proxy-passwd=$proxysettings{'UPSTREAM_PASSWORD'} -e http_proxy=http://$peer:$peerport/ -o /var/tmp/log --output-document=/var/tmp/snortrules.tar.gz $url"); - } else { - system("wget -r -o /var/tmp/log --output-document=/var/tmp/snortrules.tar.gz $url"); - } -} +# +## A tiny function to perform a reload of the webpage after one second. +# +sub reload () { + print "\n"; -sub oinkmaster () { - # Call oinkmaster to generate ruleset. - system("/usr/local/bin/oinkmaster.pl -v -s -u file:///var/tmp/snortrules.tar.gz -C /var/ipfire/snort/oinkmaster.conf -o /etc/snort/rules 2>&1 &"); + # Stop the script. + exit; } +# +## Private function to read-in and parse rules of a given rulefile. +# +## The given file will be read, parsed and all valid rules will be stored by ID, +## message/description and it's state in the idsrules hash. +# sub readrulesfile ($) { my $rulefile = shift; # Open rule file and read in contents - open(RULEFILE, "$snortrulepath/$rulefile") or die "Unable to read $rulefile!"; + open(RULEFILE, "$IDS::rulespath/$rulefile") or die "Unable to read $rulefile!"; # Store file content in an array. my @lines = ; @@ -676,18 +1248,132 @@ sub readrulesfile ($) { # Check if a rule has been found. if ($sid && $msg) { - # Add rule to the snortrules hash. - $snortrules{$rulefile}{$sid}{'Description'} = $msg; + # Add rule to the idsrules hash. + $idsrules{$rulefile}{$sid}{'Description'} = $msg; # Grab status of the rule. Check if ruleline starts with a "dash". if ($line =~ /^\#/) { # If yes, the rule is disabled. - $snortrules{$rulefile}{$sid}{'State'} = "off"; + $idsrules{$rulefile}{$sid}{'State'} = "off"; } else { # Otherwise the rule is enabled. - $snortrules{$rulefile}{$sid}{'State'} = "on"; + $idsrules{$rulefile}{$sid}{'State'} = "on"; } } } - } + } +} + +# +## Function to get the used memory of a given process-id. +# +sub get_memory_usage($) { + my ($pid) = @_; + + my $memory = 0; + + # Try to open the status file for the given process-id on the pseudo + # file system proc. + if (open(FILE, "/proc/$pid/status")) { + # Loop through the entire file. + while () { + # Splitt current line content and store them into variables. + my ($key, $value) = split(":", $_, 2); + + # Check if the current key is the one which contains the memory usage. + # The wanted one is VmRSS which contains the Real-memory (resident set) + # of the entire process. + if ($key eq "VmRSS") { + # Found the memory usage add it to the memory variable. + $memory += $value; + + # Break the loop. + last; + } + } + + # Close file handle. + close(FILE); + + # Return memory usage. + return $memory; + } + + # If the file could not be open, return nothing. + return; +} + +# +## Function to read-in the given enabled or disables sids file. +# +sub read_enabled_disabled_sids_file($) { + my ($file) = @_; + + # Temporary hash to store the sids and their state. It will be + # returned at the end of this function. + my %temphash; + + # Open the given filename. + open(FILE, "$file") or die "Could not open $file. $!\n"; + + # Loop through the file. + while() { + # Remove newlines. + chomp $_; + + # Skip blank lines. + next if ($_ =~ /^\s*$/); + + # Skip coments. + next if ($_ =~ /^\#/); + + # Splitt line into sid and state part. + my ($state, $sid) = split(" ", $_); + + # Skip line if the sid is not numeric. + next unless ($sid =~ /\d+/ ); + + # Check if the sid was enabled. + if ($state eq "enablesid") { + # Add the sid and its state as enabled to the temporary hash. + $temphash{$sid} = "enabled"; + # Check if the sid was disabled. + } elsif ($state eq "disablesid") { + # Add the sid and its state as disabled to the temporary hash. + $temphash{$sid} = "disabled"; + # Invalid state - skip the current sid and state. + } else { + next; + } + } + + # Close filehandle. + close(FILE); + + # Return the hash. + return %temphash; +} + +# +## Private function to convert a given rulefile to a category name. +## ( No file extension anymore and if the name contained a dot, it +## would be replaced by a underline sign.) +# +sub _rulefile_to_category($) { + my ($filename) = @_; + + # Splitt the filename into single chunks and store them in a + # temorary array. + my @parts = split(/\./, $filename); + + # Return / Remove last element of the temporary array. + # This removes the file extension. + pop @parts; + + # Join together the single elements of the temporary array. + # If these are more than one, use a "underline" for joining. + my $category = join '_', @parts; + + # Return the converted filename. + return $category; }