diff --git a/config/backup/backup.pl b/config/backup/backup.pl index afd8d1663..63004491c 100644 --- a/config/backup/backup.pl +++ b/config/backup/backup.pl @@ -159,6 +159,12 @@ restore_backup() { rm -rf "/var/ipfire/snort" fi + # IDS multiple providers converter. + if [ -e "/var/ipfire/suricata/rules-settings" ]; then + # Run the converter + convert-ids-multiple-providers + fi + # Convert DNS settings convert-dns-settings diff --git a/config/backup/include b/config/backup/include index 5db452cda..3b96b1d62 100644 --- a/config/backup/include +++ b/config/backup/include @@ -49,7 +49,7 @@ /var/ipfire/qos/bin/qos.sh /var/ipfire/suricata/*.conf /var/ipfire/suricata/*.yaml -/var/ipfire/suricata/rules-settings +/var/ipfire/suricata/providers-settings /var/ipfire/*/settings /var/ipfire/time/ /var/ipfire/urlfilter @@ -59,4 +59,5 @@ /var/log/rrd/* /var/log/rrd/collectd /var/log/vnstat -/var/tmp/idsrules.tar.gz +/var/tmp/idsrules-*.tar.gz +/var/tmp/idsrules-*.rules diff --git a/config/cfgroot/ids-functions.pl b/config/cfgroot/ids-functions.pl index 0e397ca19..74d55def6 100644 --- a/config/cfgroot/ids-functions.pl +++ b/config/cfgroot/ids-functions.pl @@ -21,16 +21,22 @@ # # ############################################################################ +use strict; + package IDS; require '/var/ipfire/general-functions.pl'; require "${General::swroot}/network-functions.pl"; +require "${General::swroot}/suricata/ruleset-sources"; # Location where all config and settings files are stored. our $settingsdir = "${General::swroot}/suricata"; -# File where the used rulefiles are stored. -our $used_rulefiles_file = "$settingsdir/suricata-used-rulefiles.yaml"; +# File where the main file for providers ruleset inclusion exists. +our $suricata_used_providers_file = "$settingsdir/suricata-used-providers.yaml"; + +# File for static ruleset inclusions. +our $suricata_default_rulefiles_file = "$settingsdir/suricata-default-rules.yaml"; # File where the addresses of the homenet are stored. our $homenet_file = "$settingsdir/suricata-homenet.yaml"; @@ -41,11 +47,8 @@ our $dns_servers_file = "$settingsdir/suricata-dns-servers.yaml"; # File where the HTTP ports definition is stored. our $http_ports_file = "$settingsdir/suricata-http-ports.yaml"; -# File which contains the enabled sids. -our $enabled_sids_file = "$settingsdir/oinkmaster-enabled-sids.conf"; - -# File which contains the disabled sids. -our $disabled_sids_file = "$settingsdir/oinkmaster-disabled-sids.conf"; +# File which contains includes for provider specific rule modifications. +our $oinkmaster_provider_includes_file = "$settingsdir/oinkmaster-provider-includes.conf"; # File which contains wheater the rules should be changed. our $modify_sids_file = "$settingsdir/oinkmaster-modify-sids.conf"; @@ -53,14 +56,14 @@ our $modify_sids_file = "$settingsdir/oinkmaster-modify-sids.conf"; # File which stores the configured IPS settings. our $ids_settings_file = "$settingsdir/settings"; -# File which stores the configured rules-settings. -our $rules_settings_file = "$settingsdir/rules-settings"; +# File which stores the used and configured ruleset providers. +our $providers_settings_file = "$settingsdir/providers-settings"; # File which stores the configured settings for whitelisted addresses. our $ignored_file = "$settingsdir/ignored"; -# Location and name of the tarball which contains the ruleset. -our $rulestarball = "/var/tmp/idsrules.tar.gz"; +# Location where the downloaded rulesets are stored. +our $dl_rules_path = "/var/tmp"; # File to store any errors, which also will be read and displayed by the wui. our $storederrorfile = "/tmp/ids_storederror"; @@ -71,6 +74,18 @@ our $ids_page_lock_file = "/tmp/ids_page_locked"; # Location where the rulefiles are stored. our $rulespath = "/var/lib/suricata"; +# Location where the default rulefils are stored. +our $default_rulespath = "/usr/share/suricata/rules"; + +# Location where the addition config files are stored. +our $configspath = "/usr/share/suricata"; + +# Location of the classification file. +our $classification_file = "$configspath/classification.config"; + +# Location of the sid to msg mappings file. +our $sid_msg_file = "$rulespath/sid-msg.map"; + # Location to store local rules. This file will not be touched. our $local_rules_file = "$rulespath/local.rules"; @@ -87,6 +102,18 @@ our $idspidfile = "/var/run/suricata.pid"; # Location of suricatactrl. my $suricatactrl = "/usr/local/bin/suricatactrl"; +# Prefix for each downloaded ruleset. +my $dl_rulesfile_prefix = "idsrules"; + +# Temporary directory where the rulesets will be extracted. +my $tmp_directory = "/tmp/ids_tmp"; + +# Temporary directory where the extracted rules files will be stored. +my $tmp_rules_directory = "$tmp_directory/rules"; + +# Temporary directory where the extracted additional config files will be stored. +my $tmp_conf_directory = "$tmp_directory/conf"; + # Array with allowed commands of suricatactrl. my @suricatactrl_cmds = ( 'start', 'stop', 'restart', 'reload', 'fix-rules-dir', 'cron' ); @@ -97,21 +124,87 @@ my @cron_intervals = ('off', 'daily', 'weekly' ); # http_ports_file. my @http_ports = ('80', '81'); +# Array which contains a list of rulefiles which always will be included if they exist. +my @static_included_rulefiles = ('local.rules', 'whitelist.rules'); + +# Array which contains a list of allways enabled application layer protocols. +my @static_enabled_app_layer_protos = ('app-layer', 'decoder', 'files', 'stream'); + +# Hash which allows to convert the download type (dl_type) to a file suffix. +my %dl_type_to_suffix = ( + "archive" => ".tar.gz", + "plain" => ".rules", +); + +# Hash to translate an application layer protocol to the application name. +my %tr_app_layer_proto = ( + "ikev2" => "ipsec", + "krb5" => "kerberos", +); + # ## Function to check and create all IDS related files, if the does not exist. # sub check_and_create_filelayout() { # Check if the files exist and if not, create them. - unless (-f "$enabled_sids_file") { &create_empty_file($enabled_sids_file); } - unless (-f "$disabled_sids_file") { &create_empty_file($disabled_sids_file); } + unless (-f "$oinkmaster_provider_includes_file") { &create_empty_file($oinkmaster_provider_includes_file); } unless (-f "$modify_sids_file") { &create_empty_file($modify_sids_file); } - unless (-f "$used_rulefiles_file") { &create_empty_file($used_rulefiles_file); } + unless (-f "$suricata_used_providers_file") { &create_empty_file($suricata_used_providers_file); } + unless (-f "$suricata_default_rulefiles_file") { &create_empty_file($suricata_default_rulefiles_file); } unless (-f "$ids_settings_file") { &create_empty_file($ids_settings_file); } - unless (-f "$rules_settings_file") { &create_empty_file($rules_settings_file); } + unless (-f "$providers_settings_file") { &create_empty_file($providers_settings_file); } unless (-f "$ignored_file") { &create_empty_file($ignored_file); } unless (-f "$whitelist_file" ) { &create_empty_file($whitelist_file); } } +# +## Function to get a list of all available ruleset providers. +## +## They will be returned as a sorted array. +# +sub get_ruleset_providers() { + my @providers; + + # Loop through the hash of providers. + foreach my $provider ( keys %IDS::Ruleset::Providers ) { + # Add the provider to the array. + push(@providers, $provider); + } + + # Sort and return the array. + return sort(@providers); +} + +# +## Function to get a list of all enabled ruleset providers. +## +## They will be returned as an array. +# +sub get_enabled_providers () { + my %used_providers = (); + + # Array to store the enabled providers. + my @enabled_providers = (); + + # Read-in the providers config file. + &General::readhasharray("$providers_settings_file", \%used_providers); + + # Loop through the hash of used_providers. + foreach my $id (keys %used_providers) { + # Skip disabled providers. + next unless ($used_providers{$id}[3] eq "enabled"); + + # Grab the provider handle. + my $provider = "$used_providers{$id}[0]"; + + # Add the provider to the array of enabled providers. + push(@enabled_providers, $provider); + } + + # Return the array. + return @enabled_providers; +} + # ## Function for checking if at least 300MB of free disk space are available ## on the "/var" partition. @@ -147,32 +240,39 @@ sub checkdiskspace () { } # -## This function is responsible for downloading the configured IDS ruleset. +## This function is responsible for downloading the configured IDS rulesets or if no one is specified +## all configured rulesets will be downloaded. ## -## * At first it obtains from the stored rules settings which ruleset should be downloaded. -## * The next step is to get the download locations for all available rulesets. -## * After that, the function will check if an upstream proxy should be used and grab the settings. -## * The last step will be to generate the final download url, by obtaining the URL for the desired -## ruleset, add the settings for the upstream proxy and final grab the rules tarball from the server. +## * At first it gathers all configured ruleset providers, initialize the downloader and sets an +## upstream proxy if configured. +## * After that, the given ruleset or in case all rulesets should be downloaded, it will determine wether it +## is enabled or not. +## * The next step will be to generate the final download url, by obtaining the URL for the desired +## ruleset, add the settings for the upstream proxy. +## * Finally the function will grab all the rules files or tarballs from the servers. # -sub downloadruleset { - # Get rules settings. - my %rulessettings=(); - &General::readhash("$rules_settings_file", \%rulessettings); +sub downloadruleset ($) { + my ($provider) = @_; + + # If no provider is given default to "all". + $provider //= 'all'; + + # Hash to store the providers and access id's, for which rules should be downloaded. + my %sheduled_providers = (); + + # Get used provider settings. + my %used_providers = (); + &General::readhasharray("$providers_settings_file", \%used_providers); # Check if a ruleset has been configured. - unless($rulessettings{'RULES'}) { + unless(%used_providers) { # Log that no ruleset has been configured and abort. - &_log_to_syslog("No ruleset source has been configured."); + &_log_to_syslog("No ruleset provider has been configured."); # Return "1". return 1; } - # Get all available ruleset locations. - my %rulesetsources=(); - &General::readhash($rulesetsourcesfile, \%rulesetsources); - # Read proxysettings. my %proxysettings=(); &General::readhash("${General::swroot}/proxy/settings", \%proxysettings); @@ -204,40 +304,103 @@ sub downloadruleset { $downloader->proxy(['http', 'https'], $proxy_url); } - # Grab the right url based on the configured vendor. - my $url = $rulesetsources{$rulessettings{'RULES'}}; + # Loop through the hash of configured providers. + foreach my $id ( keys %used_providers ) { + # Skip providers which are not enabled. + next if ($used_providers{$id}[3] ne "enabled"); - # Check if the vendor requires an oinkcode and add it if needed. - $url =~ s/\/$rulessettings{'OINKCODE'}/g; + # Obtain the provider handle. + my $provider_handle = $used_providers{$id}[0]; - # Abort if no url could be determined for the vendor. - unless ($url) { - # Log error and abort. - &_log_to_syslog("Unable to gather a download URL for the selected ruleset."); - return 1; + # Handle update off all providers. + if (($provider eq "all") || ($provider_handle eq "$provider")) { + # Add provider handle and it's id to the hash of sheduled providers. + $sheduled_providers{$provider_handle} = $id; + } } - # Variable to store the filesize of the remote object. - my $remote_filesize; + # Loop through the hash of sheduled providers. + foreach my $provider ( keys %sheduled_providers) { + # Log download/update of the ruleset. + &_log_to_syslog("Downloading ruleset for provider: $provider."); - # The sourcfire (snort rules) does not allow to send "HEAD" requests, so skip this check - # for this webserver. - # - # Check if the ruleset source contains "snort.org". - unless ($url =~ /\.snort\.org/) { - # Pass the requrested url to the downloader. - my $request = HTTP::Request->new(HEAD => $url); + # Grab the download url for the provider. + my $url = $IDS::Ruleset::Providers{$provider}{'dl_url'}; - # Accept the html header. - $request->header('Accept' => 'text/html'); + # Check if the provider requires a subscription. + if ($IDS::Ruleset::Providers{$provider}{'requires_subscription'} eq "True") { + # Grab the previously stored access id for the provider from hash. + my $id = $sheduled_providers{$provider}; - # Perform the request and fetch the html header. - my $response = $downloader->request($request); + # Grab the subscription code. + my $subscription_code = $used_providers{$id}[1]; + + # Add the subscription code to the download url. + $url =~ s/\/$subscription_code/g; + + } + + # Abort if no url could be determined for the provider. + unless ($url) { + # Log error and abort. + &_log_to_syslog("Unable to gather a download URL for the selected ruleset provider."); + return 1; + } + + # Variable to store the filesize of the remote object. + my $remote_filesize; + + # The sourcfire (snort rules) does not allow to send "HEAD" requests, so skip this check + # for this webserver. + # + # Check if the ruleset source contains "snort.org". + unless ($url =~ /\.snort\.org/) { + # Pass the requrested url to the downloader. + my $request = HTTP::Request->new(HEAD => $url); + + # Accept the html header. + $request->header('Accept' => 'text/html'); + + # Perform the request and fetch the html header. + my $response = $downloader->request($request); + + # Check if there was any error. + unless ($response->is_success) { + # Obtain error. + my $error = $response->status_line(); + + # Log error message. + &_log_to_syslog("Unable to download the ruleset. \($error\)"); + + # Return "1" - false. + return 1; + } + + # Assign the fetched header object. + my $header = $response->headers(); + + # Grab the remote file size from the object and store it in the + # variable. + $remote_filesize = $header->content_length; + } + + # Load perl module to deal with temporary files. + use File::Temp; + + # Generate temporary file name, located in "/var/tmp" and with a suffix of ".tmp". + my $tmp = File::Temp->new( SUFFIX => ".tmp", DIR => "/var/tmp/", UNLINK => 0 ); + my $tmpfile = $tmp->filename(); + + # Pass the requested url to the downloader. + my $request = HTTP::Request->new(GET => $url); + + # Perform the request and save the output into the tmpfile. + my $response = $downloader->request($request, $tmpfile); # Check if there was any error. unless ($response->is_success) { # Obtain error. - my $error = $response->status_line(); + my $error = $response->content; # Log error message. &_log_to_syslog("Unable to download the ruleset. \($error\)"); @@ -246,84 +409,195 @@ sub downloadruleset { return 1; } - # Assign the fetched header object. - my $header = $response->headers(); + # Load perl stat module. + use File::stat; - # Grab the remote file size from the object and store it in the - # variable. - $remote_filesize = $header->content_length; - } + # Perform stat on the tmpfile. + my $stat = stat($tmpfile); - # Load perl module to deal with temporary files. - use File::Temp; + # Grab the local filesize of the downloaded tarball. + my $local_filesize = $stat->size; - # Generate temporary file name, located in "/var/tmp" and with a suffix of ".tar.gz". - my $tmp = File::Temp->new( SUFFIX => ".tar.gz", DIR => "/var/tmp/", UNLINK => 0 ); - my $tmpfile = $tmp->filename(); + # Check if both file sizes match. + if (($remote_filesize) && ($remote_filesize ne $local_filesize)) { + # Log error message. + &_log_to_syslog("Unable to completely download the ruleset. "); + &_log_to_syslog("Only got $local_filesize Bytes instead of $remote_filesize Bytes. "); - # Pass the requested url to the downloader. - my $request = HTTP::Request->new(GET => $url); + # Delete temporary file. + unlink("$tmpfile"); - # Perform the request and save the output into the tmpfile. - my $response = $downloader->request($request, $tmpfile); + # Return "1" - false. + return 1; + } - # Check if there was any error. - unless ($response->is_success) { - # Obtain error. - my $error = $response->content; + # Genarate and assign file name and path to store the downloaded rules file. + my $dl_rulesfile = &_get_dl_rulesfile($provider); - # Log error message. - &_log_to_syslog("Unable to download the ruleset. \($error\)"); + # Check if a file name could be obtained. + unless ($dl_rulesfile) { + # Log error message. + &_log_to_syslog("Unable to store the downloaded rules file. "); - # Return "1" - false. - return 1; - } + # Delete downloaded temporary file. + unlink("$tmpfile"); - # Load perl stat module. - use File::stat; + # Return "1" - false. + return 1; + } - # Perform stat on the tmpfile. - my $stat = stat($tmpfile); + # Load file copy module, which contains the move() function. + use File::Copy; - # Grab the local filesize of the downloaded tarball. - my $local_filesize = $stat->size; - - # Check if both file sizes match. - if (($remote_filesize) && ($remote_filesize ne $local_filesize)) { - # Log error message. - &_log_to_syslog("Unable to completely download the ruleset. "); - &_log_to_syslog("Only got $local_filesize Bytes instead of $remote_filesize Bytes. "); + # Overwrite the may existing rulefile or tarball with the downloaded one. + move("$tmpfile", "$dl_rulesfile"); # Delete temporary file. unlink("$tmpfile"); - # Return "1" - false. - return 1; + # Set correct ownership for the tarball. + set_ownership("$dl_rulesfile"); } - # Load file copy module, which contains the move() function. - use File::Copy; - - # Overwrite existing rules tarball with the new downloaded one. - move("$tmpfile", "$rulestarball"); - - # Set correct ownership for the rulesdir and files. - set_ownership("$rulestarball"); - # If we got here, everything worked fine. Return nothing. return; } # -## A tiny wrapper function to call the oinkmaster script. +## Function to extract a given ruleset. +## +## In case the ruleset provider offers a plain file, it simply will +## be copied. +# +sub extractruleset ($) { + my ($provider) = @_; + + # Load perl module to deal with archives. + use Archive::Tar; + + # Load perl module to deal with files and path. + use File::Basename; + + # Load perl module for file copying. + use File::Copy; + + # Get full path and downloaded rulesfile for the given provider. + my $tarball = &_get_dl_rulesfile($provider); + + # Check if the file exists. + unless (-f $tarball) { + &_log_to_syslog("Could not find ruleset file: $tarball"); + + # Return nothing. + return; + } + + # Check if the temporary directories exist, otherwise create them. + mkdir("$tmp_directory") unless (-d "$tmp_directory"); + mkdir("$tmp_rules_directory") unless (-d "$tmp_rules_directory"); + mkdir("$tmp_conf_directory") unless (-d "$tmp_conf_directory"); + + # Omit the type (dl_type) of the stored ruleset. + my $type = $IDS::Ruleset::Providers{$provider}{'dl_type'}; + + # Handle the different ruleset types. + if ($type eq "plain") { + # Generate destination filename an full path. + my $destination = "$tmp_rules_directory/$provider\-ruleset.rules"; + + # Copy the file into the temporary rules directory. + copy($tarball, $destination); + + } elsif ( $type eq "archive") { + # Initialize the tar module. + my $tar = Archive::Tar->new($tarball); + + # Get the filelist inside the tarball. + my @packed_files = $tar->list_files; + + # Loop through the filelist. + foreach my $packed_file (@packed_files) { + my $destination; + + # Splitt the packed file into chunks. + my $file = fileparse($packed_file); + + # Handle msg-id.map file. + if ("$file" eq "sid-msg.map") { + # Set extract destination to temporary config_dir. + $destination = "$tmp_conf_directory/$provider\-sid-msg.map"; + + # Handle classification.conf + } elsif ("$file" eq "classification.config") { + # Set extract destination to temporary config_dir. + $destination = "$tmp_conf_directory/$provider\-classification.config"; + + # Handle rules files. + } elsif ($file =~ m/\.rules$/) { + my $rulesfilename; + + # Splitt the filename into chunks. + my @filename = split("-", $file); + + # Reverse the array. + @filename = reverse(@filename); + + # Get the amount of elements in the array. + my $elements = @filename; + + # Remove last element of the hash. + # It contains the vendor name, which will be replaced. + if ($elements >= 3) { + # Remove last element from hash. + pop(@filename); + } + + # Check if the last element of the filename does not + # contain the providers name. + if ($filename[-1] ne "$provider") { + # Add provider name as last element. + push(@filename, $provider); + } + + # Reverse the array back. + @filename = reverse(@filename); + + # Generate the name for the rulesfile. + $rulesfilename = join("-", @filename); + + # Set extract destination to temporaray rules_dir. + $destination = "$tmp_rules_directory/$rulesfilename"; + } else { + # Skip all other files. + next; + } + + # Extract the file to the temporary directory. + $tar->extract_file("$packed_file", "$destination"); + } + } +} + +# +## A wrapper function to call the oinkmaster script, setup the rules structues and +## call the functions to merge the additional config files. (classification, sid-msg, etc.). # sub oinkmaster () { # Check if the files in rulesdir have the correct permissions. &_check_rulesdir_permissions(); - # Cleanup the rules directory before filling it with the new rulest. + # Cleanup the rules directory before filling it with the new rulests. &_cleanup_rulesdir(); + # Get all enabled providers. + my @enabled_providers = &get_enabled_providers(); + + # Loop through the array of enabled providers. + foreach my $provider (@enabled_providers) { + # Call the extractruleset function. + &extractruleset($provider); + } + # Load perl module to talk to the kernel syslog. use Sys::Syslog qw(:DEFAULT setlogsock); @@ -331,7 +605,7 @@ sub oinkmaster () { openlog('oinkmaster', 'cons,pid', 'user'); # Call oinkmaster to generate ruleset. - open(OINKMASTER, "/usr/local/bin/oinkmaster.pl -s -u file://$rulestarball -C $settingsdir/oinkmaster.conf -o $rulespath 2>&1 |") or die "Could not execute oinkmaster $!\n"; + open(OINKMASTER, "/usr/local/bin/oinkmaster.pl -s -u dir://$tmp_rules_directory -C $settingsdir/oinkmaster.conf -o $rulespath 2>&1 |") or die "Could not execute oinkmaster $!\n"; # Log output of oinkmaster to syslog. while() { @@ -348,6 +622,181 @@ sub oinkmaster () { # Close the log handle. closelog(); + + # Call function to merge the classification files. + &merge_classifications(@enabled_providers); + + # Call function to merge the sid to message mapping files. + &merge_sid_msg(@enabled_providers); + + # Cleanup temporary directory. + &cleanup_tmp_directory(); +} + +# +## Function to merge the classifications for a given amount of providers and write them +## to the classifications file. +# +sub merge_classifications(@) { + my @providers = @_; + + # Hash to store all collected classifications. + my %classifications = (); + + # Loop through the given array of providers. + foreach my $provider (@providers) { + # Generate full path to classification file. + my $classification_file = "$tmp_conf_directory/$provider\-classification.config"; + + # Skip provider if no classification file exists. + next unless (-f "$classification_file"); + + # Open the classification file. + open(CLASSIFICATION, $classification_file) or die "Could not open file $classification_file. $!\n"; + + # Loop through the file content. + while() { + # Parse the file and grab the classification details. + if ($_ =~/.*config classification\: (.*)/) { + # Split the grabbed details. + my ($short_name, $short_desc, $priority) = split("\,", $1); + + # Check if the grabbed classification is allready known and the priority value is greater + # than the stored one (which causes less priority in the IDS). + if (($classifications{$short_name}) && ($classifications{$short_name}[1] >= $priority)) { + #Change the priority value to the stricter one. + $classifications{$short_name} = [ "$classifications{$short_name}[0]", "$priority" ]; + } else { + # Add the classification to the hash. + $classifications{$short_name} = [ "$short_desc", "$priority" ]; + } + } + } + + # Close the file. + close(CLASSIFICATION); + } + + # Open classification file for writing. + open(FILE, ">", "$classification_file") or die "Could not write to $classification_file. $!\n"; + + # Print notice about autogenerated file. + print FILE "#Autogenerated file. Any custom changes will be overwritten!\n\n"; + + # Sort and loop through the hash of classifications. + foreach my $key (sort keys %classifications) { + # Assign some nice variable names for the items. + my $short_name = $key; + my $short_desc = $classifications{$key}[0]; + my $priority = $classifications{$key}[1]; + + # Write the classification to the file. + print FILE "config classification: $short_name,$short_desc,$priority\n"; + } + + # Close file handle. + close(FILE); +} + +# +## Function to merge the "sid to message mapping" files of various given providers. +# +sub merge_sid_msg (@) { + my @providers = @_; + + # Hash which contains all the sid to message mappings. + my %mappings = (); + + # Loop through the array of given providers. + foreach my $provider (@providers) { + # Generate full path and filename. + my $sid_msg_file = "$tmp_conf_directory/$provider\-sid-msg.map"; + + # Skip provider if no sid to msg mapping file for this provider exists. + next unless (-f $sid_msg_file); + + # Open the file. + open(MAPPING, $sid_msg_file) or die "Could not open $sid_msg_file. $!\n"; + + # Loop through the file content. + while () { + # Remove newlines. + chomp($_); + + # Skip lines which do not start with a number, + next unless ($_ =~ /^\d+/); + + # Split line content and assign it to an array. + my @line = split(/ \|\| /, $_); + + # Grab the first element (and remove it) from the line array. + # It contains the sid. + my $sid = shift(@line); + + # Store the grabbed sid and the remain array as hash value. + # It still contains the messages, references etc. + $mappings{$sid} = [@line]; + } + + # Close file handle. + close(MAPPING); + } + + # Open mappings file for writing. + open(FILE, ">", $sid_msg_file) or die "Could not write $sid_msg_file. $!\n"; + + # Write notice about autogenerated file. + print FILE "#Autogenerated file. Any custom changes will be overwritten!\n\n"; + + # Loop through the hash of mappings. + foreach my $sid ( sort keys %mappings) { + # Grab data for the sid. + my @data = @{$mappings{$sid}}; + + # Add the sid to the data array. + unshift(@data, $sid); + + # Generate line. + my $line = join(" \|\| ", @data); + + print FILE "$line\n"; + + } + + # Close file handle. + close(FILE); +} + +# +## A very tiny function to move an extracted ruleset from the temporary directory into +## the rules directory. +# +sub move_tmp_ruleset() { + # Load perl module. + use File::Copy; + + # Do a directory listing of the temporary directory. + opendir DH, $tmp_rules_directory; + + # Loop over all files. + while(my $file = readdir DH) { + # Move them to the rules directory. + move "$tmp_rules_directory/$file" , "$rulespath/$file"; + } + + # Close directory handle. + closedir DH; +} + +# +## Function to cleanup the temporary IDS directroy. +# +sub cleanup_tmp_directory () { + # Load rmtree() function from file path perl module. + use File::Path 'rmtree'; + + # Delete temporary directory and all containing files. + rmtree([ "$tmp_directory" ]); } # @@ -411,6 +860,157 @@ sub _store_error_message ($) { &set_ownership("$storederrorfile"); } +# +## Private function to get the path and filename for a downloaded ruleset by a given provider. +# +sub _get_dl_rulesfile($) { + my ($provider) = @_; + + # Gather the download type for the given provider. + my $dl_type = $IDS::Ruleset::Providers{$provider}{'dl_type'}; + + # Obtain the file suffix for the download file type. + my $suffix = $dl_type_to_suffix{$dl_type}; + + # Check if a suffix has been found. + unless ($suffix) { + # Abort return - nothing. + return; + } + + # Generate the full filename and path for the stored rules file. + my $rulesfile = "$dl_rules_path/$dl_rulesfile_prefix-$provider$suffix"; + + # Return the generated filename. + return $rulesfile; +} + +# +## Tiny function to delete the stored ruleset file or tarball for a given provider. +# +sub drop_dl_rulesfile ($) { + my ($provider) = @_; + + # Gather the full path and name of the stored rulesfile. + my $rulesfile = &_get_dl_rulesfile($provider); + + # Check if the given rulesfile exists. + if (-f $rulesfile) { + # Delete the stored rulesfile. + unlink($rulesfile) or die "Could not delete $rulesfile. $!\n"; + } +} + +# +## Tiny function to get/generate the full path and filename for the providers oinkmaster +## modified sids file. +# +sub get_oinkmaster_provider_modified_sids_file ($) { + my ($provider) = @_; + + # Generate the filename. + my $filename = "$settingsdir/oinkmaster-$provider-modified-sids.conf"; + + # Return the filename. + return $filename; +} + +# +## Function to directly altering the oinkmaster provider includes file. +## +## Requires tha acition "remove" or "add" and a provider handle. +# +sub alter_oinkmaster_provider_includes_file ($$) { + my ($action, $provider) = @_; + + # Call function to get the path and name for the given providers + # oinkmaster modified sids file. + my $provider_modified_sids_file = &get_oinkmaster_provider_modified_sids_file($provider); + + # Open the file for reading.. + open (FILE, $oinkmaster_provider_includes_file) or die "Could not read $oinkmaster_provider_includes_file. $!\n"; + + # Read-in file content. + my @lines = ; + + # Close file after reading. + close(FILE); + + # Re-open the file for writing. + open(FILE, ">", $oinkmaster_provider_includes_file) or die "Could not write to $oinkmaster_provider_includes_file. $!\n"; + + # Loop through the file content. + foreach my $line (@lines) { + # Remove newlines. + chomp($line); + + # Skip line if we found our given provider and the action should be remove. + next if (($line =~ /$provider/) && ($action eq "remove")); + + # Write the read-in line back to the file. + print FILE "$line\n"; + } + + # Check if the file exists and add the provider if requested. + if ((-f $provider_modified_sids_file) && ($action eq "add")) { + print FILE "include $provider_modified_sids_file\n"; + } + + # Close file handle. + close(FILE); +} + +# +## 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; +} + # ## Function to check if the IDS is running. # @@ -550,9 +1150,6 @@ sub _cleanup_rulesdir() { # We only want files. next unless (-f "$rulespath/$file"); - # Skip element if it has config as file extension. - next if ($file =~ m/\.config$/); - # Skip rules file for whitelisted hosts. next if ("$rulespath/$file" eq $whitelist_file); @@ -755,13 +1352,18 @@ sub generate_http_ports_file() { } # -## Function to generate and write the file for used rulefiles. +## Function to generate and write the file for used rulefiles file for a given provider. +## +## The function requires as first argument a provider handle, and as second an array with files. # -sub write_used_rulefiles_file(@) { - my @files = @_; +sub write_used_provider_rulefiles_file($@) { + my ($provider, @files) = @_; + + # Get the path and file for the provider specific used rulefiles file. + my $used_provider_rulesfile_file = &get_used_provider_rulesfile_file($provider); # Open file for used rulefiles. - open (FILE, ">$used_rulefiles_file") or die "Could not write to $used_rulefiles_file. $!\n"; + open (FILE, ">", "$used_provider_rulesfile_file") or die "Could not write to $used_provider_rulesfile_file. $!\n"; # Write yaml header to the file. print FILE "%YAML 1.1\n"; @@ -770,9 +1372,6 @@ sub write_used_rulefiles_file(@) { # Write header to file. print FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; - # Allways use the whitelist. - print FILE " - whitelist.rules\n"; - # Loop through the array of given files. foreach my $file (@files) { # Check if the given filename exists and write it to the file of used rulefiles. @@ -785,18 +1384,117 @@ sub write_used_rulefiles_file(@) { close(FILE); } +# +## Function to write the main file for provider rulesfiles inclusions. +## +## This function requires an array of provider handles. +# +sub write_main_used_rulefiles_file (@) { + my (@providers) = @_; + + # Call function to write the static rulefiles file. + &_write_default_rulefiles_file(); + + # Open file for used rulefils inclusion. + open (FILE, ">", "$suricata_used_providers_file") or die "Could not write to $suricata_used_providers_file. $!\n"; + + # Write yaml header to the file. + print FILE "%YAML 1.1\n"; + print FILE "---\n\n"; + + # Write header to file. + print FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + + # Loop through the list of given providers. + foreach my $provider (@providers) { + # Call function to get the providers used rulefiles file. + my $filename = &get_used_provider_rulesfile_file($provider); + + # Check if the file exists and write it into the used rulefiles file. + if (-f $filename) { + # Print the provider to the file. + print FILE "include\: $filename\n"; + } + } + + # Close the filehandle after writing. + close(FILE); +} + +sub _write_default_rulefiles_file () { + # Get enabled application layer protocols. + my @enabled_app_layer_protos = &get_suricata_enabled_app_layer_protos(); + + # Open file. + open (FILE, ">", $suricata_default_rulefiles_file) or die "Could not write to $suricata_default_rulefiles_file. $!\n"; + + # Write yaml header to the file. + print FILE "%YAML 1.1\n"; + print FILE "---\n\n"; + + # Write notice about autogenerated file. + print FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + + # Loop through the array of static included rulesfiles. + foreach my $file (@static_included_rulefiles) { + # Check if the file exists. + if (-f "$rulespath/$file") { + # Write the rulesfile name to the file. + print FILE " - $rulespath/$file\n"; + } + } + + print FILE "\n#Default rules for used application layer protocols.\n"; + foreach my $enabled_app_layer_proto (@enabled_app_layer_protos) { + # Check if the current processed app layer proto needs to be translated + # into an application name. + if (exists($tr_app_layer_proto{$enabled_app_layer_proto})) { + # Obtain the translated application name for this protocol. + $enabled_app_layer_proto = $tr_app_layer_proto{$enabled_app_layer_proto}; + } + + # Generate filename. + my $rulesfile = "$default_rulespath/$enabled_app_layer_proto\.rules"; + + # Check if such a file exists. + if (-f "$rulesfile") { + # Write the rulesfile name to the file. + print FILE " - $rulesfile\n"; + } + + # Generate filename with "events" in filename. + $rulesfile = "$default_rulespath/$enabled_app_layer_proto\-events.rules"; + + # Check if this file exists. + if (-f "$rulesfile" ) { + # Write the rulesfile name to the file. + print FILE " - $rulesfile\n"; + } + } + + # Close the file handle + close(FILE); +} + +# +## Tiny function to generate the full path and name for the used_provider_rulesfile file of a given provider. +# +sub get_used_provider_rulesfile_file ($) { + my ($provider) = @_; + + my $filename = "$settingsdir/suricata\-$provider\-used\-rulefiles.yaml"; + + # Return the gernerated file. + return $filename; +} + # ## Function to generate and write the file for modify the ruleset. # sub write_modify_sids_file() { # Get configured settings. my %idssettings=(); - my %rulessettings=(); &General::readhash("$ids_settings_file", \%idssettings); - &General::readhash("$rules_settings_file", \%rulessettings); - - # Gather the configured ruleset. - my $ruleset = $rulessettings{'RULES'}; # Open modify sid's file for writing. open(FILE, ">$modify_sids_file") or die "Could not write to $modify_sids_file. $!\n"; @@ -813,38 +1511,69 @@ sub write_modify_sids_file() { # malware in that file. Rules which fall into the first category should stay as # alert since not all flows of that type contain malware. - if($ruleset eq 'registered' or $ruleset eq 'subscripted' or $ruleset eq 'community') { - # These types of rulesfiles contain meta-data which gives the action that should - # be used when in IPS mode. Do the following: - # - # 1. Disable all rules and set the action to 'drop' - # 2. Set the action back to 'alert' if the rule contains 'flowbits:noalert;' - # This should give rules not in the policy a reasonable default if the user - # manually enables them. - # 3. Enable rules and set actions according to the meta-data strings. + # These types of rulesfiles contain meta-data which gives the action that should + # be used when in IPS mode. Do the following: + # + # 1. Disable all rules and set the action to 'drop' + # 2. Set the action back to 'alert' if the rule contains 'flowbits:noalert;' + # This should give rules not in the policy a reasonable default if the user + # manually enables them. + # 3. Enable rules and set actions according to the meta-data strings. - my $policy = 'balanced'; # Placeholder to allow policy to be changed. + my $policy = 'balanced'; # Placeholder to allow policy to be changed. print FILE <mtime; + } + + # Check if the timestamp has not been grabbed. + unless ($mtime) { + # Return N/A for Not available. + return "N/A"; + } + + # Convert into human read-able format. + $date = strftime('%Y-%m-%d %H:%M:%S', localtime($mtime)); + + # Return the date. + return $date; +} + # ## Function to gather the version of suricata. # @@ -882,6 +1611,48 @@ sub get_suricata_version($) { } } +# +## Function to get the enabled application layer protocols. +# +sub get_suricata_enabled_app_layer_protos() { + # Array to store and return the enabled app layer protos. + my @enabled_app_layer_protos = (); + + # Execute piped suricata command and return the list of + # enabled application layer protocols. + open(SURICATA, "suricata --list-app-layer-protos |") or die "Could not execute program: $!"; + + # Grab and store the list of enabled application layer protocols. + my @output = ; + + # Close pipe. + close(SURICATA); + + # Merge allways enabled static application layers protocols array. + @enabled_app_layer_protos = @static_enabled_app_layer_protos; + + # Loop through the array which contains the output of suricata. + foreach my $line (@output) { + # Skip header line which starts with "===". + next if ($line =~ /^\s*=/); + + # Skip info or warning lines. + next if ($line =~ /\s*--/); + + # Remove newlines. + chomp($line); + + # Add enabled app layer proto to the array. + push(@enabled_app_layer_protos, $line); + } + + # Sort the array. + @enabled_app_layer_protos = sort(@enabled_app_layer_protos); + + # Return the array. + return @enabled_app_layer_protos; +} + # ## Function to generate the rules file with whitelisted addresses. # @@ -918,7 +1689,7 @@ sub generate_ignore_file() { # Check if the address/network is valid. if ((&General::validip($address)) || (&General::validipandmask($address))) { # Write rule line to the file to pass any traffic from this IP - print FILE "pass ip $address any -> any any (msg:\"pass all traffic from/to $address\"\; sid:$sid\;)\n"; + print FILE "pass ip $address any -> any any (msg:\"pass all traffic from/to $address\"\; bypass; sid:$sid\;)\n"; # Increment sid. $sid++; @@ -1051,6 +1822,53 @@ sub get_red_address() { return; } +# +## Function to get the used rules files of a given provider. +# +sub read_used_provider_rulesfiles($) { + my ($provider) = @_; + + # Array to store the used rulefiles. + my @used_rulesfiles = (); + + # Get the used rulesefile file for the provider. + my $rulesfile_file = &get_used_provider_rulesfile_file($provider); + + # Check if the a used rulesfile exists for this provider. + if (-f $rulesfile_file) { + # Open the file or used rulefiles and read-in content. + open(FILE, $rulesfile_file) or die "Could not open $rulesfile_file. $!\n"; + + while () { + # Assign the current line to a nice variable. + my $line = $_; + + # Remove newlines. + chomp($line); + + # Skip comments. + next if ($line =~ /\#/); + + # Skip blank lines. + next if ($line =~ /^\s*$/); + + # Gather the rulefile. + if ($line =~ /.*- (.*)/) { + my $rulefile = $1; + + # Add the rulefile to the array of used rulesfiles. + push(@used_rulesfiles, $rulefile); + } + } + + # Close the file. + close(FILE); + } + + # Return the array of used rulesfiles. + return @used_rulesfiles; +} + # ## Function to write the lock file for locking the WUI, while ## the autoupdate script runs. diff --git a/config/oinkmaster/oinkmaster.conf b/config/oinkmaster/oinkmaster.conf index 57c328139..4d4ee40ef 100644 --- a/config/oinkmaster/oinkmaster.conf +++ b/config/oinkmaster/oinkmaster.conf @@ -182,11 +182,8 @@ update_files = \.rules$|\.config$|\.conf$|\.txt$|\.map$ # files from included files. Example to load stuff from "/etc/foo.conf". # include /etc/foo.conf -# Include file for enabled sids. -include /var/ipfire/suricata/oinkmaster-enabled-sids.conf - -# Include file for disabled sids. -include /var/ipfire/suricata/oinkmaster-disabled-sids.conf +# Include file for provider specific includes. +include /var/ipfire/suricata/oinkmaster-provider-includes.conf # Include file which defines the runmode of suricata. include /var/ipfire/suricata/oinkmaster-modify-sids.conf diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot index 2dfc8ae1f..904c718c3 100644 --- a/config/rootfiles/common/configroot +++ b/config/rootfiles/common/configroot @@ -4,6 +4,7 @@ usr/sbin/convert-portfw usr/sbin/convert-snort usr/sbin/convert-xtaccess usr/sbin/convert-ids-modifysids-file +usr/sbin/convert-ids-multiple-providers usr/sbin/firewall-policy #var/ipfire var/ipfire/addon-lang diff --git a/config/suricata/convert-ids-multiple-providers b/config/suricata/convert-ids-multiple-providers new file mode 100644 index 000000000..a08250841 --- /dev/null +++ b/config/suricata/convert-ids-multiple-providers @@ -0,0 +1,284 @@ +#!/usr/bin/perl +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2021 IPFire Development 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 # +# 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 . # +# # +############################################################################### + +use strict; + +require '/var/ipfire/general-functions.pl'; +require "${General::swroot}/ids-functions.pl"; + +# Old file declarations +my $old_rules_settings_file = "$IDS::settingsdir/rules-settings"; +my $old_used_rulefiles_file = "$IDS::settingsdir/suricata-used-rulefiles.yaml"; +my $old_enabled_sids_file = "$IDS::settingsdir/oinkmaster-enabled-sids.conf"; +my $old_disabled_sids_file = "$IDS::settingsdir/oinkmaster-disabled-sids.conf"; +my $old_rules_tarball = "/var/tmp/idsrules.tar.gz"; + +# Script wide variable to store the used ruleset provider. +my $ruleset_provider; + +# Hashes to store the old and new settings. +my %old_rules_settings = (); +my %idssettings = (); +my %providers_settings = (); + +exit unless(-f $IDS::ids_settings_file and -f $old_rules_settings_file); + +# Read-in all settings. +&General::readhash($old_rules_settings_file, \%old_rules_settings); +&General::readhash($IDS::ids_settings_file, \%idssettings); + +# +## Step 1: Create new file layout +# +&IDS::check_and_create_filelayout(); + +# +## Step 2: Migrate automatic update interval. +# + +# Get old configured autoupdate interval. +my $autoupdate_interval = $old_rules_settings{'AUTOUPDATE_INTERVAL'}; + +# Check for valid intervals. +if ($autoupdate_interval eq "off" || $autoupdate_interval eq "daily" || $autoupdate_interval eq "weekly") { + # Put the setting to the new configuration location. + $idssettings{'AUTOUPDATE_INTERVAL'} = $autoupdate_interval; +} else { + # Swith to default which should be weekly. + $idssettings{'AUTOUPDATE_INTERVAL'} = "weekly"; +} + +# Store the updated idssettings file. +&General::writehash($IDS::ids_settings_file, \%idssettings); + +# +## Step 3: Migrate the providers settings. +# + +# Try to get the previously configured provider. +$ruleset_provider = $old_rules_settings{'RULES'}; + +# Exit the script if no ruleset provider has configured. +exit unless ($ruleset_provider); + +# Defaults. +my $id = "1"; +my $enabled = "enabled"; +my $autoupdate_status = "enabled"; + +# Try to get a configured subscription code. +my $subscription_code = $old_rules_settings{'OINKCODE'}; + +# Check if the autoupdate should be disabled. +if ($idssettings{'AUTOUPDATE_INTERVAL'} eq "off") { + # Set the autoupdate for the provider to disabled. + $autoupdate_status = "disabled"; +} + +# Create and assign the provider structure to the providers hash. +$providers_settings{$id} = [ "$ruleset_provider", "$subscription_code", "$autoupdate_status", "$enabled" ]; + +# Write the converted provider settings to the new providers-settings file. +&General::writehasharray($IDS::providers_settings_file, \%providers_settings); + +# Set correct ownership. +&IDS::set_ownership("$IDS::providers_settings_file"); + +# Remove old rules settings file. +unlink($old_rules_settings_file); + +# +## Step 4: Rename downloaded rulestarball to new name sheme. +# + +# Check if a rulestarball exists. +if (-f $old_rules_tarball) { + # Load perl module which contains the move command. + use File::Copy; + + # Call function to generate the path and filename for the new rules tarball name. + my $new_rules_tarball = &IDS::_get_dl_rulesfile($ruleset_provider); + + # Move the rulestarball to the new location. + move($old_rules_tarball, $new_rules_tarball); + + # Set correct ownership. + &IDS::set_ownership("$new_rules_tarball"); +} + +# +## Step 5: Migrate oinkmaster configuration files for enabled and disabled rules. +# + +# Read-in old enabled / disabled sids files. +my %enabled_disabled_sids = ( + &IDS::read_enabled_disabled_sids_file($old_enabled_sids_file), + &IDS::read_enabled_disabled_sids_file($old_disabled_sids_file) +); + +# Check if any modifications have been done. +if (%enabled_disabled_sids) { + # Get path and filename for new file. + my $oinkmaster_provider_modified_sids_file = &IDS::get_oinkmaster_provider_modified_sids_file($ruleset_provider); + + # Open the new file for writing. + open (FILE, ">", $oinkmaster_provider_modified_sids_file) or die "Could not write to $oinkmaster_provider_modified_sids_file. $!\n"; + + # Write header to the files. + print PROVIDER_MOD_FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + + # 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 as enabled to the file. + print FILE "enablesid $sid\n"; + # Check if the sid is disabled. + } elsif ($enabled_disabled_sids{$sid} eq "disabled") { + # Print the sid as disabled to the file. + print FILE "disablesid $sid\n"; + # Something strange happende - skip the current sid. + } else { + next; + } + } + + # Close the file handle. + close(FILE); + + # Add the provider modifications file to the oinkmaster provider includes file. + &IDS::alter_oinkmaster_provider_includes_file("add", "$ruleset_provider"); + + # Set correct ownership for the new generated file. + &IDS::set_ownership("$oinkmaster_provider_modified_sids_file"); +} + +# Set correct ownership for the main file. +&IDS::set_ownership("$IDS::oinkmaster_provider_includes_file"); + +# Remove old files. +unlink($old_enabled_sids_file); +unlink($old_disabled_sids_file); + +# +## Step 6: Call oinkmaster and regenerate the ruleset structures. +# +&IDS::oinkmaster(); + +# Set correct ownerships. +&IDS::set_ownership("$IDS::rulespath"); + +# +## Step 7: Migrate used rulefiles into new format. +# + +# Check if the a used rulesfile exists. +if (-f $old_used_rulefiles_file) { + # Array to collect the used rulefiles. + my @used_rulefiles = (); + + # Open the file or used rulefiles and read-in content. + open(FILE, $old_used_rulefiles_file) or die "Could not open $old_used_rulefiles_file. $!\n"; + + while () { + # Assign the current line to a nice variable. + my $line = $_; + + # Remove newlines. + chomp($line); + + # Skip comments. + next if ($line =~ /\#/); + + # Skip blank lines. + next if ($line =~ /^\s*$/); + + # Gather the rulefile. + if ($line =~ /.*- (.*)/) { + my $rulefile = $1; + + # Skip whitelist.rules and local.rules + next if ($rulefile eq "whitelist.rules" || $rulefile eq "local.rules"); + + # Splitt the filename into chunks. + my @filename = split("-", $rulefile); + + # Reverse the array. + @filename = reverse(@filename); + + # Get the amount of elements in the array. + my $elements = @filename; + + # Remove last element of the hash. + # It contains the vendor name, which will be replaced. + if ($elements >= 3) { + # Remove last element from hash. + pop(@filename); + } + + # Check if the last element of the filename does not + # contain the providers name. + if ($filename[-1] ne "$ruleset_provider") { + # Add provider name as last element. + push(@filename, $ruleset_provider); + } + + # Reverse the array back. + @filename = reverse(@filename); + + # Generate the name for the rulesfile. + $rulefile = join("-", @filename); + + # Add the rulefile to the array of used rulesfiles. + push(@used_rulefiles, $rulefile); + } + } + + # Close the file. + close(FILE); + + # Write the new provider exclusive used rulesfiles file. + &IDS::write_used_provider_rulefiles_file($ruleset_provider, @used_rulefiles); + + # Write main used rulefiles file. + &IDS::write_main_used_rulefiles_file("$ruleset_provider"); + + # Get the provider specific used rulefiles file name. + my $provider_used_rulefiles_file = &IDS::get_used_provider_rulesfile_file($ruleset_provider); + + # Set correct ownerships. + &IDS::set_ownership("$provider_used_rulefiles_file"); + &IDS::set_ownership("$IDS::suricata_used_providers_file"); + &IDS::set_ownership("$IDS::suricata_default_rulefiles_file"); +} + +# Remove old used rulefiles file. +unlink($old_used_rulefiles_file); + +# +## Step 8: Reload the IDS ruleset if running. +# + +# Check if the IDS is running. +if(&IDS::ids_is_running()) { + # Call suricatactrl to restart it. + &IDS::call_suricatactrl("restart"); +} diff --git a/config/suricata/convert-snort b/config/suricata/convert-snort index 7d75233b4..dc068eb2f 100644 --- a/config/suricata/convert-snort +++ b/config/suricata/convert-snort @@ -118,14 +118,10 @@ my %snortsettings; # # Add default value for MONITOR_TRAFFIC_ONLY which will be "on" # when migrating from snort to the new IDS. -my %idssettings = ( - "MONITOR_TRAFFIC_ONLY" => "on", -); - -# Hash which contains the RULES settings. # # Set default value for UPDATE_INTERVAL to weekly. -my %rulessettings = ( +my %idssettings = ( + "MONITOR_TRAFFIC_ONLY" => "on", "AUTOUPDATE_INTERVAL" => "weekly", ); @@ -159,17 +155,27 @@ foreach my $zone (@network_zones) { } } -# Grab the choosen ruleset from snort settings hash and store it in the rules -# settings hash. -$rulessettings{"RULES"} = $snortsettings{"RULES"}; +# Hash to store the provider settings. +my %providersettings = (); + +# Default ID. +$id = "1"; + +# Grab the choosen ruleset from snort settings hash. +my $provider = $snortsettings{"RULES"}; +my $subscription_code; # Check if an oinkcode has been provided. if($snortsettings{"OINKCODE"}) { - # Take the oinkcode from snort settings hash and store it in the rules - # settings hash. - $rulessettings{"OINKCODE"} = $snortsettings{"OINKCODE"}; + # Take the oinkcode from snort settings hash. + $subscription_code = $snortsettings{"OINKCODE"}; } +# Generate providers config line and add it to the provider settings hash. +# +# Enabled automatic ruleste updates and the usage of the provider. +$providersettings{$id} = [ "$provider", "$subscription_code", "enabled", "enabled" ]; + # ## Step 4: Import guardian settings and whitelist if the addon is installed. # @@ -225,8 +231,8 @@ if (-f $guardian_meta) { # Write IDS settings. &General::writehash("$IDS::ids_settings_file", \%idssettings); -# Write rules settings. -&General::writehash("$IDS::rules_settings_file", \%rulessettings); +# Write provider settings. +&General::writehash("$IDS::providers_settings_file", \%providersettings); # ## Step 6: Generate and write the file to modify the ruleset. @@ -242,16 +248,19 @@ if (-f $guardian_meta) { ## Step 7: Move rulestarball to its new location. # +# Grab file and path to store the provider rules tarball. +my $rulestarball = &IDS::_get_dl_rulesfile($provider); + # Check if a rulestarball has been downloaded yet. if (-f $snort_rules_tarball) { # Load perl module which contains the move command. use File::Copy; # Move the rulestarball to the new location. - move($snort_rules_tarball, $IDS::rulestarball); + move($snort_rules_tarball, $rulestarball); # Set correct ownership. - &IDS::set_ownership("$IDS::rulestarball"); + &IDS::set_ownership("$rulestarball"); # In case no tarball is present, try to download the ruleset. } else { @@ -270,7 +279,7 @@ if (-f $snort_rules_tarball) { # # Check if a rulestarball is present. -if (-f $IDS::rulestarball) { +if (-f $rulestarball) { # Launch oinkmaster by calling the subfunction. &IDS::oinkmaster(); @@ -312,10 +321,10 @@ if (-f $IDS::rulestarball) { ## Step 12: Setup automatic ruleset updates. # -# Check if a ruleset is configured. -if($rulessettings{"RULES"}) { +# Check if a provider is configured. +if(%providersettings) { # Call suricatactrl and setup the periodic update mechanism. - &IDS::call_suricatactrl("cron", $rulessettings{'AUTOUPDATE_INTERVAL'}); + &IDS::call_suricatactrl("cron", $idssettings{'AUTOUPDATE_INTERVAL'}); } # @@ -362,7 +371,16 @@ while (my $line = ) { close(SNORTCONF); # Pass the array of enabled rule files to the subfunction and write the file. -&IDS::write_used_rulefiles_file(@enabled_rule_files); +&IDS::write_used_provider_rulefiles_file("$provider", @enabled_rule_files); +&IDS::write_main_used_rulefiles_file("$provider"); + +# Grab the used provider rulesfile file path and name. +my $used_provider_rulesfile_file = &IDS::get_used_provider_rulesfile_file("$provider"); + +# Set correct ownership for new files. +&IDS::set_ownership("$suricata_used_providers_file"); +&IDS::set_ownership("$suricata_static_rulefiles_file"); +&IDS::set_ownership("$used_provider_rulesfile_file"); # ## Step 14: Start the IDS if enabled. diff --git a/config/suricata/ruleset-sources b/config/suricata/ruleset-sources index a00cef945..7da1ecc1d 100644 --- a/config/suricata/ruleset-sources +++ b/config/suricata/ruleset-sources @@ -1,15 +1,169 @@ -# Ruleset for registered sourcefire users. -registered = https://www.snort.org/rules/snortrules-snapshot-29161.tar.gz?oinkcode= +package IDS::Ruleset; -# Ruleset for registered sourcefire users with valid subscription. -subscripted = https://www.snort.org/rules/snortrules-snapshot-29161.tar.gz?oinkcode= +# This file contains the supported ruleset providers. +# +# Each one is defined as a hash in the main hash. +# It's name acts as handle/key and the key/value pair acts as data part. +# So the structure is like the following: +# +# handle => { +# summary => A short summary of the service. This also will be shown if no translation string is available for the WUI. +# website => The website of the ruleset provider. +# tr_string => The translation string which is used by the WUI and part of the language files. +# requires_subscription => "True/False" - If some kind of registration code is required in order to download the ruleset. +# dl_url => The download URL to grab the ruleset. +# dl_type => "archive/plain" - To specify, if the downloaded file is a packed archive or a plain text file. +# }, -# Community rules from sourcefire. -community = https://www.snort.org/rules/community +# Hash which contains the supported ruleset providers. +our %Providers = ( + # Ruleset for registered sourcefire users. + registered => { + summary => "Talos VRT rules for registered users", + website => "https://www.snort.org", + tr_string => "registered user rules", + requires_subscription => "True", + dl_url => "https://www.snort.org/rules/snortrules-snapshot-29190.tar.gz?oinkcode=", + dl_type => "archive", + }, -# Emerging threads community rules. -emerging = https://rules.emergingthreats.net/open/suricata-5.0/emerging.rules.tar.gz + # Ruleset for registered sourcefire users with a valid subsription. + subscripted => { + summary => "Talos VRT rules with subscription", + website => "https://www.snort.org", + tr_string => "subscripted user rules", + requires_subscription => "True", + dl_url => "https://www.snort.org/rules/snortrules-snapshot-29190.tar.gz?oinkcode=", + dl_type => "archive", + }, -# Emerging threads pro rules. -emerging_pro = https://rules.emergingthreatspro.com//suricata-5.0/etpro.rules.tar.gz + # Community rules from sourcefire. + community => { + summary => "Snort/VRT GPLv2 Community Rules", + website => "https://www.snort.org", + tr_string => "community rules", + requires_subscription => "False", + dl_url => "https://www.snort.org/rules/community", + dl_type => "archive", + }, + # Emerging threads community rules. + emerging => { + summary => "Emergingthreats.net Community Rules", + website => "https://emergingthreats.net/", + tr_string => "emerging rules", + requires_subscription => "False", + dl_url => "https://rules.emergingthreats.net/open/suricata-5.0/emerging.rules.tar.gz", + dl_type => "archive", + }, + + # Emerging threads Pro rules. + emerging_pro => { + summary => "Emergingthreats.net Pro Rules", + website => "https://emergingthreats.net/", + tr_string => "emerging pro rules", + requires_subscription => "True", + dl_url => "https://rules.emergingthreatspro.com//suricata-5.0/etpro.rules.tar.gz", + dl_type => "archive", + }, + + # Abuse.ch SSLBL JA3 fingerprint rules. + sslbl_ja3 => { + summary => "Abuse.ch SSLBL JA3 Rules", + website => "https://sslbl.abuse.ch/", + tr_string => "sslbl ja3 fingerprint rules", + requires_subscription => "False", + dl_url => "https://sslbl.abuse.ch/blacklist/ja3_fingerprints.rules", + dl_type => "plain", + }, + + # Abuse.ch SSLBL Blacklist rules. + sslbl_blacklist => { + summary => "Abuse.ch SSLBL Blacklist Rules", + website => "https://sslbl.abuse.ch/", + tr_string => "sslbl blacklist rules", + requires_subscription => "False", + dl_url => "https://sslbl.abuse.ch/blacklist/sslblacklist.rules", + dl_type => "plain", + }, + + # Abuse.ch URLhaus Blacklist rules. + urlhaus => { + summary => "Abuse.ch URLhaus Blacklist Rules", + website => "https://urlhaus.abuse.ch/", + tr_string => "urlhaus blacklist rules", + requires_subscription => "False", + dl_url => "https://urlhaus.abuse.ch/downloads/urlhaus_suricata.tar.gz", + dl_type => "archive", + }, + + # Etnetera Aggressive Blacklist. + etnetera_aggresive => { + summary => "Etnetera Aggressive Blacklist Rules", + website => "https://security.etnetera.cz/", + tr_string => "etnetera aggressive blacklist rules", + requires_subscription => "False", + dl_url => "https://security.etnetera.cz/feeds/etn_aggressive.rules", + dl_type => "plain", + }, + + # OISF Traffic ID rules. + oisf_trafficid => { + summary => "OISF Traffic ID Rules", + website => "https://www.openinfosecfoundation.org/", + tr_string => "oisf traffic id rules", + requires_subscription => "False", + dl_url => "https://openinfosecfoundation.org/rules/trafficid/trafficid.rules", + dl_type => "plain", + }, + + # Positive Technologies Attack Detection Team rules. + attack_detection => { + summary => "PT Attack Detection Team Rules", + website => "https://github.com/ptresearch/AttackDetection", + tr_string => "attack detection team rules", + requires_subscription => "False", + dl_url => "https://raw.githubusercontent.com/ptresearch/AttackDetection/master/pt.rules.tar.gz", + dl_type => "archive", + }, + + # Secureworks Security rules. + secureworks_security => { + summary => "Secureworks Security Ruleset", + website => "https://www.secureworks.com", + tr_string => "secureworks security ruleset", + requires_subscription => "True", + dl_url => "https://ws.secureworks.com/ti/ruleset//Suricata_suricata-security_latest.tgz", + dl_type => "archive", + }, + + # Secureworks Malware rules. + secureworks_malware => { + summary => "Secureworks Malware Ruleset", + website => "https://www.secureworks.com", + tr_string => "secureworks malware ruleset", + requires_subscription => "True", + dl_url => "https://ws.secureworks.com/ti/ruleset//Suricata_suricata-malware_latest.tgz", + dl_type => "archive", + }, + + # Secureworks Enhanced rules. + secureworks_enhanced => { + summary => "Secureworks Enhanced Ruleset", + website => "https://www.secureworks.com", + tr_string => "secureworks enhanced ruleset", + requires_subscription => "True", + dl_url => "https://ws.secureworks.com/ti/ruleset//Suricata_suricata-enhanced_latest.tgz", + dl_type => "archive", + }, + + # Travis B. Green hunting rules. + tgreen => { + summary => "Travis Green - Hunting rules", + website => "https://github.com/travisbgreen/hunting-rules", + tr_string => "travis green hunting rules", + requires_subscription => "False", + dl_url => "https://raw.githubusercontent.com/travisbgreen/hunting-rules/master/hunting.rules", + dl_type => "plain", + }, +); diff --git a/config/suricata/suricata.yaml b/config/suricata/suricata.yaml index b4a188d40..6fbc7b3ee 100644 --- a/config/suricata/suricata.yaml +++ b/config/suricata/suricata.yaml @@ -46,16 +46,15 @@ vars: ## default-rule-path: /var/lib/suricata rule-files: - # Include enabled ruleset files from external file - include: /var/ipfire/suricata/suricata-used-rulefiles.yaml + # Include enabled ruleset files from external file. + include: /var/ipfire/suricata/suricata-used-providers.yaml # Include default rules. include: /var/ipfire/suricata/suricata-default-rules.yaml -classification-file: /var/lib/suricata/classification.config -reference-config-file: /var/lib/suricata/reference.config -threshold-file: /var/lib/suricata/threshold.config - +classification-file: /usr/share/suricata/classification.config +reference-config-file: /usr/share/suricata/reference.config +threshold-file: /usr/share/suricata/threshold.config ## ## Logging options. @@ -64,7 +63,7 @@ default-log-dir: /var/log/suricata/ # global stats configuration stats: - enabled: yes + enabled: no # The interval field (in seconds) controls at what interval # the loggers are invoked. interval: 8 @@ -318,7 +317,7 @@ logging: # compiled with the --enable-debug configure option. # # This value is overriden by the SC_LOG_LEVEL env var. - default-log-level: notice + default-log-level: Info # A regex to filter output. Can be overridden in an output section. # Defaults to empty (no filter). @@ -522,6 +521,41 @@ app-layer: double-decode-path: no double-decode-query: no + # Note: Modbus probe parser is minimalist due to the poor significant field + # Only Modbus message length (greater than Modbus header length) + # And Protocol ID (equal to 0) are checked in probing parser + # It is important to enable detection port and define Modbus port + # to avoid false positive + modbus: + # How many unreplied Modbus requests are considered a flood. + # If the limit is reached, app-layer-event:modbus.flooded; will match. + #request-flood: 500 + + enabled: no + detection-ports: + dp: 502 + # According to MODBUS Messaging on TCP/IP Implementation Guide V1.0b, it + # is recommended to keep the TCP connection opened with a remote device + # and not to open and close it for each MODBUS/TCP transaction. In that + # case, it is important to set the depth of the stream reassembling as + # unlimited (stream.reassembly.depth: 0) + + # Stream reassembly size for modbus. By default track it completely. + stream-depth: 0 + + # DNP3 + dnp3: + enabled: no + detection-ports: + dp: 20000 + + # SCADA EtherNet/IP and CIP protocol support + enip: + enabled: no + detection-ports: + dp: 44818 + sp: 44818 + ntp: enabled: yes dhcp: diff --git a/html/cgi-bin/ids.cgi b/html/cgi-bin/ids.cgi index 4e8b28fd8..161464d0d 100644 --- a/html/cgi-bin/ids.cgi +++ b/html/cgi-bin/ids.cgi @@ -20,6 +20,7 @@ ############################################################################### use strict; +use experimental 'smartmatch'; # enable only the following on debugging purpose #use warnings; @@ -31,12 +32,14 @@ require "${General::swroot}/header.pl"; require "${General::swroot}/ids-functions.pl"; require "${General::swroot}/network-functions.pl"; +# Import ruleset providers file. +require "$IDS::rulesetsourcesfile"; + my %color = (); my %mainsettings = (); my %idsrules = (); my %idssettings=(); -my %rulessettings=(); -my %rulesetsources = (); +my %used_providers=(); my %cgiparams=(); my %checked=(); my %selected=(); @@ -248,63 +251,57 @@ if (-e $IDS::storederrorfile) { unlink($IDS::storederrorfile); } -## Grab all available rules and store them in the idsrules hash. -# -# Open rules directory and do a directory listing. -opendir(DIR, $IDS::rulespath) or die $!; - # Loop through the direcory. - while (my $file = readdir(DIR)) { +# Gather ruleset details. +if ($cgiparams{'RULESET'}) { + ## Grab all available rules and store them in the idsrules hash. + # - # We only want files. - next unless (-f "$IDS::rulespath/$file"); + # Get enabled providers. + my @enabled_providers = &IDS::get_enabled_providers(); - # Ignore empty files. - next if (-z "$IDS::rulespath/$file"); + # Open rules directory and do a directory listing. + opendir(DIR, $IDS::rulespath) or die $!; + # Loop through the direcory. + while (my $file = readdir(DIR)) { - # Use a regular expression to find files ending in .rules - next unless ($file =~ m/\.rules$/); + # We only want files. + next unless (-f "$IDS::rulespath/$file"); - # Ignore files which are not read-able. - next unless (-R "$IDS::rulespath/$file"); + # Ignore empty files. + next if (-z "$IDS::rulespath/$file"); - # Skip whitelist rules file. - next if( $file eq "whitelist.rules"); + # Use a regular expression to find files ending in .rules + next unless ($file =~ m/\.rules$/); - # Call subfunction to read-in rulefile and add rules to - # the idsrules hash. - &readrulesfile("$file"); - } + # Ignore files which are not read-able. + next unless (-R "$IDS::rulespath/$file"); -closedir(DIR); + # Skip whitelist rules file. + next if( $file eq "whitelist.rules"); -# Gather used rulefiles. -# -# Check if the file for activated rulefiles is not empty. -if(-f $IDS::used_rulefiles_file) { - # Open the file for used rulefile and read-in content. - open(FILE, $IDS::used_rulefiles_file) or die "Could not open $IDS::used_rulefiles_file. $!\n"; + # Splitt vendor from filename. + my @filename_parts = split(/-/, $file); - # Read-in content. - my @lines = ; + # Assign vendor name for easy processing. + my $vendor = @filename_parts[0]; - # Close file. - close(FILE); + # Skip rulefile if the provider is disabled. + next unless ($vendor ~~ @enabled_providers); - # Loop through the array. - foreach my $line (@lines) { - # Remove newlines. - chomp($line); + # Call subfunction to read-in rulefile and add rules to + # the idsrules hash. + &readrulesfile("$file"); + } - # Skip comments. - next if ($line =~ /\#/); + closedir(DIR); - # Skip blank lines. - next if ($line =~ /^\s*$/); - - # Gather rule sid and message from the ruleline. - if ($line =~ /.*- (.*)/) { - my $rulefile = $1; + # Loop through the array of used providers. + foreach my $provider (@enabled_providers) { + # Gather used rulefiles. + my @used_rulesfiles = &IDS::read_used_provider_rulesfiles($provider); + # Loop through the array of used rulesfiles. + foreach my $rulefile (@used_rulesfiles) { # Check if the current rulefile exists in the %idsrules hash. # If not, the file probably does not exist anymore or contains # no rules. @@ -316,103 +313,11 @@ if(-f $IDS::used_rulefiles_file) { } } -# 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 enough 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'}"); - - # Write the modify sid's file and pass the taken ruleaction. - &IDS::write_modify_sids_file(); - - # 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); - } 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'}) { +if ($cgiparams{'RULESET'} eq $Lang::tr{'ids apply'}) { # Arrays to store which rulefiles have been enabled and will be used. my @enabled_rulefiles; - # Hash to store the user-enabled and disabled sids. - my %enabled_disabled_sids; - # Store if a restart of suricata is required. my $suricata_restart_required; @@ -434,86 +339,138 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { } } - # 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)); + # Open oinkmaster main include file for provider modifications. + open(OINKM_INCL_FILE, ">", "$IDS::oinkmaster_provider_includes_file") or die "Could not open $IDS::oinkmaster_provider_includes_file. $!\n"; - # Loop through the hash of idsrules. - foreach my $rulefile (keys %idsrules) { - # Loop through the single rules of the rulefile. - foreach my $sid (keys %{$idsrules{$rulefile}}) { - # Skip the current sid if it is not numeric. - next unless ($sid =~ /\d+/ ); + # Print file header and notice about autogenerated file. + print OINKM_INCL_FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; - # Check if there exists a key in the cgiparams hash for this sid. - if (exists($cgiparams{$sid})) { - # Look if the rule is disabled. - if ($idsrules{$rulefile}{$sid}{'State'} eq "off") { - # Check if the state has been set to 'on'. - if ($cgiparams{$sid} eq "on") { - # Add/Modify the sid to/in the enabled_disabled_sids hash. - $enabled_disabled_sids{$sid} = "enabled"; + # Get enabled providers. + my @enabled_providers = &IDS::get_enabled_providers(); + + # Loop through the array of enabled providers. + foreach my $provider (@enabled_providers) { + # Hash to store the used-enabled and disabled sids. + my %enabled_disabled_sids; + + # Generate modified sids file name for the current processed provider. + my $providers_modified_sids_file = &IDS::get_oinkmaster_provider_modified_sids_file($provider); + + # Check if a modified sids file for this provider exists. + if (-f $providers_modified_sids_file) { + # Read-in the file for enabled/disabled sids. + %enabled_disabled_sids = &IDS::read_enabled_disabled_sids_file($providers_modified_sids_file); + } + + # Loop through the hash of idsrules. + foreach my $rulefile (keys %idsrules) { + # Split the rulefile to get the vendor. + my @filename_parts = split(/-/, $rulefile); + + # Assign rulefile vendor. + my $rulefile_vendor = @filename_parts[0]; + + # Skip the rulefile if the vendor is not our current processed provider. + next unless ($rulefile_vendor eq $provider); + + # Loop through the single rules of the 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 ($idsrules{$rulefile}{$sid}{'State'} eq "off") { + # Check if the state has been set to 'on'. + if ($cgiparams{$sid} eq "on") { + # 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}; + } + } + } else { + # Look if the rule is enabled. + 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/Modify it to/in the enabled_disabled_sids hash. + $enabled_disabled_sids{$sid} = "disabled"; # Drop item from cgiparams hash. delete $cgiparams{$rulefile}{$sid}; } } - } else { - # Look if the rule is enabled. - 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/Modify it to/in the enabled_disabled_sids hash. - $enabled_disabled_sids{$sid} = "disabled"; + } + } - # Drop item from cgiparams hash. - delete $cgiparams{$rulefile}{$sid}; + # Check if the hash for enabled/disabled sids contains any entries. + if (%enabled_disabled_sids) { + # Open providers modified sids file for writing. + open(PROVIDER_MOD_FILE, ">$providers_modified_sids_file") or die "Could not write to $providers_modified_sids_file. $!\n"; + + # Write header to the files. + print PROVIDER_MOD_FILE "#Autogenerated file. Any custom changes will be overwritten!\n"; + + # 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 PROVIDER_MOD_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 PROVIDER_MOD_FILE "disablesid $sid\n"; + # Something strange happende - skip the current sid. + } else { + next; } } + + # Close file handle for the providers modified sids file. + close(PROVIDER_MOD_FILE); + + # Add the file to the oinkmasters include file. + print OINKM_INCL_FILE "include $providers_modified_sids_file\n"; } } - # Open enabled sid's file for writing. - open(ENABLED_FILE, ">$IDS::enabled_sids_file") or die "Could not write to $IDS::enabled_sids_file. $!\n"; + # Close the file handle after writing. + close(OINKM_INCL_FILE); - # 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"; + # Handle enabled / disabled rulefiles. + # - # 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"; + # Loop through the array of enabled providers. + foreach my $provider(@enabled_providers) { + # Array to store the rulefiles which belong to the current processed provider. + my @provider_rulefiles = (); - # 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; + # Loop through the array of enabled rulefiles. + foreach my $rulesfile (@enabled_rulefiles) { + # Split the rulefile name. + my @filename_parts = split(/-/, "$rulesfile"); + + # Assign vendor name for easy processings. + my $vendor = @filename_parts[0]; + + # Check if the rulesvendor is our current processed enabled provider. + if ("$vendor" eq "$provider") { + # Add the rulesfile to the array of provider rulesfiles. + push(@provider_rulefiles, $rulesfile); } + + # Call function and write the providers used rulesfile file. + &IDS::write_used_provider_rulefiles_file($provider, @provider_rulefiles); } } - # Close file for enabled_sids after writing. - close(ENABLED_FILE); - - # Close file for disabled_sids after writing. - close(DISABLED_FILE); - # Call function to generate and write the used rulefiles file. - &IDS::write_used_rulefiles_file(@enabled_rulefiles); + &IDS::write_main_used_rulefiles_file(@enabled_providers); # Lock the webpage and print message. &working_notice("$Lang::tr{'ids apply ruleset changes'}"); @@ -537,7 +494,10 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { &reload(); # Download new ruleset. -} elsif ($cgiparams{'RULESET'} eq $Lang::tr{'update ruleset'}) { +} elsif ($cgiparams{'PROVIDERS'} eq $Lang::tr{'ids force ruleset update'}) { + # Assign given provider handle. + my $provider = $cgiparams{'PROVIDER'}; + # 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'}"; @@ -555,8 +515,8 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { &working_notice("$Lang::tr{'ids download new ruleset'}"); # Call subfunction to download the ruleset. - if(&IDS::downloadruleset()) { - $errormessage = $Lang::tr{'could not download latest updates'}; + if(&IDS::downloadruleset($provider)) { + $errormessage = "$provider - $Lang::tr{'could not download latest updates'}"; # Call function to store the errormessage. &IDS::_store_error_message($errormessage); @@ -577,6 +537,61 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { &reload(); } } + +# Reset a provider to it's defaults. +} elsif ($cgiparams{'PROVIDERS'} eq "$Lang::tr{'ids reset provider'}") { + # Grab provider handle from cgihash. + my $provider = $cgiparams{'PROVIDER'}; + + # Lock the webpage and print message. + &working_notice("$Lang::tr{'ids apply ruleset changes'}"); + + # Create new empty file for used rulefiles + # for this provider. + &IDS::write_used_provider_rulefiles_file($provider); + + # Call function to get the path and name for the given providers + # oinkmaster modified sids file. + my $provider_modified_sids_file = &IDS::get_oinkmaster_provider_modified_sids_file($provider); + + # Check if the file exists. + if (-f $provider_modified_sids_file) { + # Remove the file, as requested. + unlink("$provider_modified_sids_file"); + } + + # Alter the oinkmaster provider includes file and remove the provider. + &IDS::alter_oinkmaster_provider_includes_file("remove", $provider); + + # Regenerate ruleset. + &IDS::oinkmaster(); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Get enabled providers. + my @enabled_providers = &IDS::get_enabled_providers(); + + # Get amount of enabled providers. + my $amount = @enabled_providers; + + # Check if at least one enabled provider remains. + if ($amount >= 1) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("restart"); + + # Stop suricata if no enabled provider remains. + } else { + # Call suricatactrel to perform the stop. + &IDS::call_suricatactrl("stop"); + } + } + + # Undefine providers flag. + undef($cgiparams{'PROVIDERS'}); + + # Reload page. + &reload(); + # Save IDS settings. } elsif ($cgiparams{'IDS'} eq $Lang::tr{'save'}) { my %oldidssettings; @@ -586,14 +601,17 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { # Read-in current (old) IDS settings. &General::readhash("$IDS::ids_settings_file", \%oldidssettings); + # Get enabled providers. + my @enabled_providers = &IDS::get_enabled_providers(); + # 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'}; + # Check if at least one provider is enabled. Otherwise abort and display an error. + unless(@enabled_providers) { + $errormessage = $Lang::tr{'ids no enabled ruleset provider'}; } # Loop through the array of available interfaces. @@ -620,6 +638,12 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { &General::writehash("$IDS::ids_settings_file", \%cgiparams); } + # Check if the the automatic rule update hass been touched. + if($cgiparams{'AUTOUPDATE_INTERVAL'} ne $oldidssettings{'AUTOUPDATE_INTERVAL'}) { + # Call suricatactrl to set the new interval. + &IDS::call_suricatactrl("cron", $cgiparams{'AUTOUPDATE_INTERVAL'}); + } + # Generate file to store the home net. &IDS::generate_home_net_file(); @@ -634,8 +658,8 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { # 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) { + # Check if at least one provider is enabled. + if (@enabled_providers) { # Lock the webpage and print message. &working_notice("$Lang::tr{'ids working'}"); @@ -667,295 +691,698 @@ if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { # Perform a reload of the page. &reload(); } + +# Toggle Enable/Disable autoupdate for a provider +} elsif ($cgiparams{'AUTOUPDATE'} eq $Lang::tr{'toggle enable disable'}) { + my %used_providers = (); + + # 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 providers settings file. + &General::readhasharray($IDS::providers_settings_file, \%used_providers); + + # Grab the configured status of the corresponding entry. + my $status_autoupdate = $used_providers{$id}[2]; + + # Switch the status. + if ($status_autoupdate eq "disabled") { + $status_autoupdate = "enabled"; + } else { + $status_autoupdate = "disabled"; + } + + # Modify the status of the existing entry. + $used_providers{$id} = ["$used_providers{$id}[0]", "$used_providers{$id}[1]", "$status_autoupdate", "$used_providers{$id}[3]"]; + + # Write the changed hash to the providers settings file. + &General::writehasharray($IDS::providers_settings_file, \%used_providers); + } + +# Add/Edit a provider to the list of used providers. +# +} elsif (($cgiparams{'PROVIDERS'} eq "$Lang::tr{'add'}") || ($cgiparams{'PROVIDERS'} eq "$Lang::tr{'update'}")) { + my %used_providers = (); + + # Read-in providers settings file. + &General::readhasharray("$IDS::providers_settings_file", \%used_providers); + + # Assign some nice human-readable values. + my $provider = $cgiparams{'PROVIDER'}; + my $subscription_code = $cgiparams{'SUBSCRIPTION_CODE'}; + my $status_autoupdate; + + # Handle autoupdate checkbox. + if ($cgiparams{'ENABLE_AUTOUPDATE'} eq "on") { + $status_autoupdate = "enabled"; + } else { + $status_autoupdate = "disabled"; + } + + # Check if we are going to add a new provider. + if ($cgiparams{'PROVIDERS'} eq "$Lang::tr{'add'}") { + # Loop through the hash of used providers. + foreach my $id ( keys %used_providers) { + # Check if the choosen provider is already in use. + if ($used_providers{$id}[0] eq "$provider") { + # Assign error message. + $errormessage = "$Lang::tr{'ids the choosen provider is already in use'}"; + } + } + } + + # Check if the provider requires a subscription code. + if ($IDS::Ruleset::Providers{$provider}{'requires_subscription'} eq "True") { + # Check if an subscription code has been provided. + if ($subscription_code) { + # Check if the code contains unallowed chars. + unless ($subscription_code =~ /^[a-z0-9]+$/) { + $errormessage = $Lang::tr{'invalid input for subscription code'}; + } + } else { + # Print an error message, that an subsription code is required for this + # provider. + $errormessage = $Lang::tr{'ids subscription code required'}; + } + } + + # Go further if there was no error. + if ($errormessage eq '') { + my $id; + my $status; + + # Check if we should edit an existing entry and got an ID. + if (($cgiparams{'PROVIDERS'} 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 = $used_providers{$id}[3]; + } 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 %used_providers; + + # 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 used providers hash.. + $used_providers{$id} = ["$provider", "$subscription_code", "$status_autoupdate", "$status"]; + + # Write the changed hash to the providers settings file. + &General::writehasharray($IDS::providers_settings_file, \%used_providers); + + # Check if a new provider will be added. + if ($cgiparams{'PROVIDERS'} eq $Lang::tr{'add'}) { + # Check if the red device is active. + unless (-e "${General::swroot}/red/active") { + $errormessage = "$Lang::tr{'ids could not add provider'} - $Lang::tr{'system is offline'}"; + } + + # Check if enough free disk space is availabe. + if(&IDS::checkdiskspace()) { + $errormessage = "$Lang::tr{'ids could not add provider'} - $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'}"); + + # Download the ruleset. + if(&IDS::downloadruleset($provider)) { + $errormessage = "$Lang::tr{'ids could not add provider'} - $Lang::tr{'ids unable to download the ruleset'}"; + + # Call function to store the errormessage. + &IDS::_store_error_message($errormessage); + + # Remove the configured provider again. + &remove_provider($id); + } else { + # Extract the ruleset + &IDS::extractruleset($provider); + + # Move the ruleset. + &IDS::move_tmp_ruleset(); + + # Cleanup temporary directory. + &IDS::cleanup_tmp_directory(); + + # Create new empty file for used rulefiles + # for this provider. + &IDS::write_used_provider_rulefiles_file($provider); + } + + # Perform a reload of the page. + &reload(); + } + } + + } + + # Undefine providers flag. + undef($cgiparams{'PROVIDERS'}); + +## Toggle Enabled/Disabled for an existing provider. +# +} elsif ($cgiparams{'PROVIDERS'} eq $Lang::tr{'toggle enable disable'}) { + my %used_providers = (); + my $provider_includes_action; + + # Value if oinkmaster has to be executed. + my $oinkmaster = "False"; + + # 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 file which contains the provider settings. + &General::readhasharray($IDS::providers_settings_file, \%used_providers); + + # Grab the configured status of the corresponding entry. + my $status = $used_providers{$id}[3]; + + # Grab the provider handle. + my $provider_handle = $used_providers{$id}[0]; + + # Switch the status. + if ($status eq "enabled") { + $status = "disabled"; + + # Set the provider includes action to "remove" for removing the entry. + $provider_includes_action = "remove"; + } else { + $status = "enabled"; + + # Set the provider includes action to "add". + $provider_includes_action = "add"; + + # This operation requires to launch oinkmaster. + $oinkmaster = "True"; + } + + # Modify the status of the existing entry. + $used_providers{$id} = ["$used_providers{$id}[0]", "$used_providers{$id}[1]", "$used_providers{$id}[2]", "$status"]; + + # Write the changed hash to the providers settings file. + &General::writehasharray($IDS::providers_settings_file, \%used_providers); + + # Get all enabled providers. + my @enabled_providers = &IDS::get_enabled_providers(); + + # Write the main providers include file. + &IDS::write_main_used_rulefiles_file(@enabled_providers); + + # Call function to alter the oinkmasters provider includes file and + # add or remove the provider. + &IDS::alter_oinkmaster_provider_includes_file($provider_includes_action, $provider_handle); + + # Check if oinkmaster has to be executed. + if ($oinkmaster eq "True") { + # Lock the webpage and print message. + &working_notice("$Lang::tr{'ids apply ruleset changes'}"); + + # Launch oinkmaster. + &IDS::oinkmaster(); + } + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Gather the amount of enabled providers (elements in the array). + my $amount = @enabled_providers; + + # Check if there are still enabled ruleset providers. + if ($amount >= 1) { + # Call suricatactrl to perform a restart. + &IDS::call_suricatactrl("restart"); + + # No active ruleset provider, suricata has to be stopped. + } else { + # Stop suricata. + &IDS::call_suricatactrl("stop"); + } + } + + # Undefine providers flag. + undef($cgiparams{'PROVIDERS'}); + + # Reload page. + &reload(); + } + +## Remove provider from the list of used providers. +# +} elsif ($cgiparams{'PROVIDERS'} eq $Lang::tr{'remove'}) { + # Assign a nice human-readable variable. + my $id = $cgiparams{'ID'}; + + # Grab the provider name bevore deleting. + my $provider = &get_provider_handle($id); + + # Remove the provider. + &remove_provider($id); + + # Undef the given ID. + undef($cgiparams{'ID'}); + + # Lock the webpage and print message. + &working_notice("$Lang::tr{'ids apply ruleset changes'}"); + + # Drop the stored ruleset file. + &IDS::drop_dl_rulesfile($provider); + + # Get the name of the provider rulessets include file. + my $provider_used_rulefile = &IDS::get_used_provider_rulesfile_file($provider); + + # Drop the file, it is not longer needed. + unlink("$provider_used_rulefile"); + + # Call function to get the path and name for the given providers + # oinkmaster modified sids file. + my $provider_modified_sids_file = &IDS::get_oinkmaster_provider_modified_sids_file($provider); + + # Check if the file exists. + if (-f $provider_modified_sids_file) { + # Remove the file, which is not longer needed. + unlink("$provider_modified_sids_file"); + } + + # Alter the oinkmaster provider includes file and remove the provider. + &IDS::alter_oinkmaster_provider_includes_file("remove", $provider); + + # Regenerate ruleset. + &IDS::oinkmaster(); + + # Gather all enabled providers. + my @enabled_providers = &IDS::get_enabled_providers(); + + # Regenerate main providers include file. + &IDS::write_main_used_rulefiles_file(@enabled_providers); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Get amount of enabled providers. + my $amount = @enabled_providers; + + # Check if at least one enabled provider remains. + if ($amount >= 1) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("restart"); + + # Stop suricata if no enabled provider remains. + } else { + # Call suricatactrel to perform the stop. + &IDS::call_suricatactrl("stop"); + } + } + + # Undefine providers flag. + undef($cgiparams{'PROVIDERS'}); + + # Reload page. + &reload(); } -# 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'}{$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 -; - &Header::openbigbox('100%', 'left', '', $errormessage); -if ($errormessage) { - &Header::openbox('100%', 'left', $Lang::tr{'error messages'}); - print "$errormessage\n"; - print " \n"; - &Header::closebox(); -} +&show_display_error_message(); -# 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 +if ($cgiparams{'RULESET'} eq "$Lang::tr{'ids customize ruleset'}" ) { + &show_customize_ruleset(); +} elsif ($cgiparams{'PROVIDERS'} ne "") { + &show_add_provider(); } else { - # Otherwise display a hint that the service is not launched. - print < - - $Lang::tr{'intrusion detection'} - - - - $Lang::tr{'guardian daemon'} - $Lang::tr{'stopped'} - - -END + &show_mainpage(); } -# Only show this area, if a ruleset is present. -if (%idsrules) { +&Header::closebigbox(); +&Header::closepage(); - print <$errormessage\n"; + print " \n"; + &Header::closebox(); + } +} -

$Lang::tr{'settings'}

+# +## Function to display the main IDS page. +# +sub show_mainpage() { + # Read-in idssettings and provider settings. + &General::readhash("$IDS::ids_settings_file", \%idssettings); + &General::readhasharray("$IDS::providers_settings_file", \%used_providers); -
- - - - - - - - - - - - - - - - - - - -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"; + # If no autoupdate intervall has been configured yet, set default value. + unless(exists($idssettings{'AUTOUPDATE_INTERVAL'})) { + # Set default to "weekly". + $idssettings{'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{'AUTOUPDATE_INTERVAL'}{'off'} = ''; + $selected{'AUTOUPDATE_INTERVAL'}{'daily'} = ''; + $selected{'AUTOUPDATE_INTERVAL'}{'weekly'} = ''; + $selected{'AUTOUPDATE_INTERVAL'}{$idssettings{'AUTOUPDATE_INTERVAL'}} = "selected='selected'"; + + # 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{'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 "
$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 at least one ruleset provider is configured. + if (%used_providers) { + 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; -&Header::closebox(); + # Convert current zone name to upper case. + my $zone_upper = uc($zone); -# Draw elements for ruleset configuration. -&Header::openbox('100%', 'center', $Lang::tr{'ids ruleset settings'}); + # Set zone name. + my $zone_name = $zone; -print < -
+  $Lang::tr{'ids enable'} + +  $Lang::tr{'ids monitor traffic only'} +








$Lang::tr{'ids monitored interfaces'}
- - - - + # 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'"; + } - - - - - - - - - - - - - - - \n"; } -print < - - -
$Lang::tr{'ids rules update'}$Lang::tr{'ids automatic rules update'}
- - -


  -END -; - # 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 "\n"; + print "\n"; + print " $Lang::tr{'enabled on'} $Lang::tr{$zone_name}\n"; + print "
-
+print < + + +

+

+

+

+ + + + $Lang::tr{'ids automatic rules update'} + + + + + + + + + +

+ + + + + +
+ END ; -&Header::closebox(); + } -# -# Whitelist / Ignorelist -# -&Header::openbox('100%', 'center', $Lang::tr{'ids ignored hosts'}); + &Header::closebox(); + + # + # Used Ruleset Providers section. + # + &Header::openbox('100%', 'center', $Lang::tr{'ids ruleset settings'}); print < + + $Lang::tr{'ids provider'} + $Lang::tr{'date'} + $Lang::tr{'ids autoupdates'} + + + +END + my $line = 1; + + # Check if some providers has been configured. + if (keys (%used_providers)) { + my $col = ""; + + # Loop through all entries of the hash. + foreach my $id (sort keys(%used_providers)) { + # Assign data array positions to some nice variable names. + my $provider = $used_providers{$id}[0]; + my $provider_name = &get_provider_name($provider); + my $rulesetdate = &IDS::get_ruleset_date($provider); + + my $subscription_code = $used_providers{$id}[1]; + my $autoupdate_status = $used_providers{$id}[2]; + my $status = $used_providers{$id}[3]; + + # Check if the item number is even or not. + if ($line % 2) { + $col="bgcolor='$color{'color22'}'"; + } else { + $col="bgcolor='$color{'color20'}'"; + } + + # Choose icons for the checkboxes. + my $status_gif; + my $status_gdesc; + my $autoupdate_status_gif; + my $autoupdate_status_gdesc; + + # Check if the status is enabled and select the correct image and description. + if ($status eq 'enabled' ) { + $status_gif = 'on.gif'; + $status_gdesc = $Lang::tr{'click to disable'}; + } else { + $status_gif = 'off.gif'; + $status_gdesc = $Lang::tr{'click to enable'}; + } + + # Check if the autoupdate status is enabled and select the correct image and description. + if ($autoupdate_status eq 'enabled') { + $autoupdate_status_gif = 'on.gif'; + $autoupdate_status_gdesc = $Lang::tr{'click to disable'}; + } else { + $autoupdate_status_gif = 'off.gif'; + $autoupdate_status_gdesc = $Lang::tr{'click to enable'}; + } + +print < + $provider_name + $rulesetdate + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + +END + # Increment lines value. + $line++; + + } + + } else { + # Print notice that currently no hosts are ignored. + print "\n"; + print "$Lang::tr{'guardian no entries'}\n"; + print "\n"; + } + + print "\n"; + + # Section to add new elements or edit existing ones. +print < +
+
+ +
+ + + +END + + # Only show this button if a ruleset provider is configured. + if (%used_providers) { + print "\n"; + } +print < + + +
+
+END + + &Header::closebox(); + + # + # Whitelist / Ignorelist + # + &Header::openbox('100%', 'center', $Lang::tr{'ids ignored hosts'}); + + print < $Lang::tr{'ip address'} @@ -1003,17 +1430,17 @@ print <
- - - + + +
- - - + + +
@@ -1023,83 +1450,96 @@ print < - - + + END + } + } else { + # Print notice that currently no hosts are ignored. + print "\n"; + print "$Lang::tr{'guardian no entries'}\n"; + print "\n"; } - } else { - # Print notice that currently no hosts are ignored. - print "\n"; - print "$Lang::tr{'guardian no entries'}\n"; - print "\n"; - } - print "\n"; + print "\n"; - # Section to add new elements or edit existing ones. + # Section to add new elements or edit existing ones. print < -
-
- -
- +
+
+
+ +
+
END - # Assign correct headline and button text. - my $buttontext; - my $entry_address; - my $entry_remark; + # Assign correct headline and button text. + my $buttontext; + my $entry_address; + my $entry_remark; - # 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 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"; - # 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"; - } + # 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"; + } print < - - - - + + + + + - - - - - -
$Lang::tr{'update'}
$Lang::tr{'update'}
$Lang::tr{'dnsforward add a new entry'}
$Lang::tr{'dnsforward add a new entry'}
$Lang::tr{'ip address'}:
$Lang::tr{'ip address'}: $Lang::tr{'remark'}:
-
+ $Lang::tr{'remark'}: + + + + + + END -&Header::closebox(); + &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 ); +# +## Function to show the customize ruleset section. +# +sub show_customize_ruleset() { + ### Java Script ### + print" +END +; + &Header::openbox('100%', 'LEFT', "$Lang::tr{'intrusion detection system rules'}" ); print"
\n"; # Output display table for rule files @@ -1193,7 +1633,10 @@ if (%idsrules) { print < - + + + + @@ -1203,8 +1646,270 @@ END } } -&Header::closebigbox(); -&Header::closepage(); +# +## Function to show section for add/edit a provider. +# +sub show_add_provider() { + my %used_providers = (); + my @subscription_providers; + + # Read -in providers settings file. + &General::readhasharray("$IDS::providers_settings_file", \%used_providers); + + # Get all supported ruleset providers. + my @ruleset_providers = &IDS::get_ruleset_providers(); + + ### Java Script ### + print " +END +; + + # Check if an existing provider should be edited. + if($cgiparams{'PROVIDERS'} eq "$Lang::tr{'edit'}") { + # Check if autoupdate is enabled for this provider. + if ($used_providers{$cgiparams{'ID'}}[2] eq "enabled") { + # Set the checkbox to be checked. + $checked{'ENABLE_AUTOUPDATE'} = "checked='checked'"; + } + + # Display section to force an rules update and to reset the provider. + &show_additional_provider_actions(); + + } elsif ($cgiparams{'PROVIDERS'} eq "$Lang::tr{'ids add provider'}") { + # Set the autoupdate to true as default. + $checked{'ENABLE_AUTOUPDATE'} = "checked='checked'"; + } + + &Header::openbox('100%', 'center', $Lang::tr{'ids provider settings'}); + +print < + + + + + + + + + + + + + + + + + + + + + + + + + +
$Lang::tr{'ids provider'}
+ +END +; + # Value to allow disabling the dropdown menu. + my $disabled; + + # Check if we are in edit mode. + if ($cgiparams{'PROVIDERS'} eq "$Lang::tr{'edit'}") { + $disabled = "disabled"; + + # Add hidden input with the provider because the disable select does not provider + # this. + print "\n"; + } + + print " + $Lang::tr{'ids visit provider website'} +


+  $Lang::tr{'ids enable automatic updates'} +
+ +END +; + # Check if a provider should be added or edited. + if ($cgiparams{'PROVIDERS'} eq "$Lang::tr{'edit'}") { + # Display button for updating the existing provider. + print "\n"; + } else { + # Display button to add the new provider. + print "\n"; + } +print < +
+ +END +; + &Header::closebox(); +} + +# +## Function to show the area where additional provider actions can be done. +# +sub show_additional_provider_actions() { + my $disabled; + my %used_providers = (); + + # Read-in providers settings file. + &General::readhasharray("$IDS::providers_settings_file", \%used_providers); + + # Assign variable for provider handle. + my $provider = "$used_providers{$cgiparams{'ID'}}[0]"; + + # Call function to get the path and name for the given providers + # oinkmaster modified sids file. + my $provider_modified_sids_file = &IDS::get_oinkmaster_provider_modified_sids_file($provider); + + # Disable the reset provider button if no provider modified sids file exists. + unless (-f $provider_modified_sids_file) { + $disabled = "disabled"; + } + + &Header::openbox('100%', 'center', ""); + print < + + + + +
+ + + +
+ +END +; + &Header::closebox(); +} # ## A function to display a notice, to lock the webpage and @@ -1270,7 +1975,7 @@ sub readrulesfile ($) { my $msg; # Gather rule sid and message from the ruleline. - if ($line =~ m/.*msg:\"(.*?)\"\; .* sid:(.*?); /) { + if ($line =~ m/.*msg:\s*\"(.*?)\"\;.*sid:\s*(.*?); /) { $msg = $1; $sid = $2; @@ -1332,54 +2037,62 @@ sub get_memory_usage($) { } # -## Function to read-in the given enabled or disables sids file. +## Function to get the provider handle by a given ID. # -sub read_enabled_disabled_sids_file($) { - my ($file) = @_; +sub get_provider_handle($) { + my ($id) = @_; - # Temporary hash to store the sids and their state. It will be - # returned at the end of this function. - my %temphash; + my %used_providers = (); - # Open the given filename. - open(FILE, "$file") or die "Could not open $file. $!\n"; + # Read-in provider settings file. + &General::readhasharray($IDS::providers_settings_file, \%used_providers); - # Loop through the file. - while() { - # Remove newlines. - chomp $_; + # Obtain the provider handle for the given ID. + my $provider_handle = $used_providers{$cgiparams{'ID'}}[0]; - # Skip blank lines. - next if ($_ =~ /^\s*$/); + # Return the handle. + return $provider_handle; +} - # Skip coments. - next if ($_ =~ /^\#/); +# +## Function to get the provider name from the language file or providers file for a given handle. +# +sub get_provider_name($) { + my ($handle) = @_; + my $provider_name; - # Splitt line into sid and state part. - my ($state, $sid) = split(" ", $_); + # Get the required translation string for the given provider handle. + my $tr_string = $IDS::Ruleset::Providers{$handle}{'tr_string'}; - # 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; - } + # Check if the translation string is available in the language files. + if ($Lang::tr{$tr_string}) { + # Use the translated string from the language file. + $provider_name = $Lang::tr{$tr_string}; + } else { + # Fallback and use the provider summary from the providers file. + $provider_name = $IDS::Ruleset::Providers{$handle}{'summary'}; } - # Close filehandle. - close(FILE); + # Return the obtained provider name. + return $provider_name; +} - # Return the hash. - return %temphash; +# +## Function to remove a provider by a given ID. +# +sub remove_provider($) { + my ($id) = @_; + + my %used_providers = (); + + # Read-in provider settings file. + &General::readhasharray($IDS::providers_settings_file, \%used_providers); + + # Drop entry from the hash. + delete($used_providers{$id}); + + # Write the changed hash to the provider settings file. + &General::writehasharray($IDS::providers_settings_file, \%used_providers); } # diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index c81b28fea..bb2e8f8e2 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1372,11 +1372,17 @@ 'idle' => 'Leerlauf', 'idle timeout' => 'Leerlaufwartezeit in Minuten (0 zum Deaktivieren):', 'idle timeout not set' => 'Leerlaufwartezeit nicht angegeben.', +'ids add provider' => 'Provider hinzufügen', 'ids apply' => 'Übernehmen', 'ids apply ruleset changes' => 'Regeländerungen werden übernommen. Bitte warten Sie, bis dieser Vorgang erfolgreich beendet wurde...', +'ids autoupdates' => 'Automatische Updates', 'ids automatic rules update' => 'Automatische Regelaktualisierung', -'ids download new ruleset' => 'Das neue Regelsatz wird heruntergeladen und entpackt. Bitte warten Sie, bis dieser Vorgang erfolgreich beendet wurde...', +'ids could not add provider' => 'Provider konnte nicht hinzugefügt werden', +'ids customize ruleset' => 'Regelset anpassen', +'ids download new ruleset' => 'Das neue Regelset wird heruntergeladen und entpackt. Bitte warten Sie, bis dieser Vorgang erfolgreich beendet wurde...', 'ids enable' => 'Einbruchsverhinderungssystem aktivieren', +'ids enable automatic updates' => 'Automatische Updates aktivieren', +'ids force ruleset update' => 'Regelset jetzt aktualisieren', 'ids hide' => 'Verstecken', 'ids ignored hosts' => 'Ausnahmeliste', 'ids log hits' => 'Gesamtanzahl der Regeltreffer für', @@ -1385,12 +1391,18 @@ 'ids monitor traffic only' => 'Netzwerkpakete nur überprüfen (nicht verwerfen)', 'ids monitored interfaces' => 'Überwachte Netzwerkzonen', 'ids no network zone' => 'Bitte wählen Sie mindestens eine zu überwachende Netzwerkzone aus', -'ids no ruleset available' => 'Es ist kein Regelsatz verfügbar. Bitte laden Sie einen Regelsatz herunter.', +'ids no enabled ruleset provider' => 'Es ist kein aktivierter Provider verfügbar. Bitte aktivieren Sie einen oder fügen Sie einen Provider hinzu.', 'ids oinkcode required' => 'Für den ausgewählten Regelsatz wird ein Abonnement oder ein Oinkcode benötigt', +'ids provider' => 'Regelset-Anbieter', +'ids provider settings' => 'Regelset-Anbieter-Einstellungen', +'ids reset provider' => 'Providereinstellungen zurücksetzen', 'ids rules update' => 'Regelsatz', 'ids ruleset autoupdate in progress' => 'Der Regelsatz wird gerade aktualisiert. Bitte warten Sie, bis dieser Vorgang erfolgreich beendet wurde...', 'ids ruleset settings' => 'Regelsatzeinstellungen', 'ids show' => 'Anzeigen', +'ids the choosen provider is already in use' => 'Der gewhählte Provider wird bereits verwendet.', +'ids unable to download the ruleset' => 'Das Regelset konnte nicht heruntergeladen werden.', +'ids visit provider website' => 'Anbieter-Webseite besuchen', 'ids working' => 'Änderungen werden übernommen. Bitte warten Sie, bis dieser Vorgang erfolgreich beendet wurde.', 'iface' => 'Iface', 'ignore filter' => '"Ignorieren"-Filter', diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index 2f7038fb1..978e3176e 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -1401,11 +1401,17 @@ 'idle' => 'Idle', 'idle timeout' => 'Idle timeout (mins; 0 to disable):', 'idle timeout not set' => 'Idle timeout not set.', +'ids add provider' => 'Add provider', 'ids apply' => 'Apply', 'ids apply ruleset changes' => 'The ruleset changes are being applied. Please wait until all operations have completed successfully...', +'ids autoupdates' => 'Automatic updates', 'ids automatic rules update' => 'Automatic Rule Update', +'ids could not add provider' => 'Could not add provider', +'ids customize ruleset' => 'Customize ruleset', 'ids download new ruleset' => 'Downloading and unpacking new ruleset. Please wait until all operations have completed successfully...', 'ids enable' => 'Enable Intrusion Prevention System', +'ids enable automatic updates' => 'Enable automatic updates', +'ids force ruleset update' => 'Force ruleset update', 'ids hide' => 'Hide', 'ids ignored hosts' => 'Whitelisted Hosts', 'ids log hits' => 'Total of number of activated rules for', @@ -1414,12 +1420,18 @@ 'ids monitor traffic only' => 'Monitor traffic only', 'ids monitored interfaces' => 'Monitored Interfaces', 'ids no network zone' => 'Please select at least one network zone to be monitored', -'ids no ruleset available' => 'No ruleset is available. Please download one first', -'ids oinkcode required' => 'The selected ruleset requires a subscription or an Oinkcode', +'ids no enabled ruleset provider' => 'No enabled ruleset is available. Please activate or add one first.', +'ids subscription code required' => 'The selected ruleset requires a subscription code', +'ids provider' => 'Provider', +'ids provider settings' => 'Provider settings', +'ids reset provider' => 'Reset provider', 'ids rules update' => 'Ruleset', 'ids ruleset autoupdate in progress' => 'Ruleset update in progress. Please wait until all operations have completed successfully...', 'ids ruleset settings' => 'Ruleset Settings', 'ids show' => 'Show', +'ids the choosen provider is already in use' => 'The choosen provider is already in use.', +'ids unable to download the ruleset' => 'Unable to download the ruleset', +'ids visit provider website' => 'Visit provider website', 'ids working' => 'Changes are being applied. Please wait until all operations have completed successfully...', 'iface' => 'Iface', 'ignore filter' => 'Ignore filter', @@ -1497,7 +1509,7 @@ 'invalid input for max clients' => 'Invalid input for Max Clients. The maximum of 1024 clients has been exceeded', 'invalid input for mode' => 'Invalid input for mode', 'invalid input for name' => 'Invalid input for user\'s full name or system hostname', -'invalid input for oink code' => 'Invalid input for Oink code', +'invalid input for subscription code' => 'Invalid input for subscription code', 'invalid input for organization' => 'Invalid input for organization', 'invalid input for remote host/ip' => 'Invalid input for remote host/ip.', 'invalid input for state or province' => 'Invalid input for state or province.', @@ -2371,6 +2383,7 @@ 'subnet is invalid' => 'Netmask is invalid', 'subnet mask' => 'Subnet Mask', 'subscripted user rules' => 'Talos VRT rules with subscription', +'subscription code' => 'Subscription code', 'successfully refreshed updates list' => 'Successfully refreshed updates list.', 'summaries kept' => 'Keep summaries for', 'sunday' => 'Sunday', diff --git a/lfs/configroot b/lfs/configroot index e0156c746..60dc55bfd 100644 --- a/lfs/configroot +++ b/lfs/configroot @@ -138,6 +138,7 @@ $(TARGET) : # Install snort to suricata converter. cp $(DIR_SRC)/config/suricata/convert-snort /usr/sbin/convert-snort cp $(DIR_SRC)/config/suricata/convert-ids-modifysids-file /usr/sbin/convert-ids-modifysids-file + cp $(DIR_SRC)/config/suricata/convert-ids-multiple-providers /usr/sbin/convert-ids-multiple-providers # set converters executable chmod 755 /usr/sbin/convert-* diff --git a/lfs/suricata b/lfs/suricata index 6a24a02ab..a870e3668 100644 --- a/lfs/suricata +++ b/lfs/suricata @@ -100,15 +100,19 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) # Install yaml file for loading default rules. install -m 0664 $(DIR_SRC)/config/suricata/suricata-default-rules.yaml /var/ipfire/suricata + # Set correct ownership for the default rules file. + chown nobody:nobody /var/ipfire/suricata/suricata-default-rules.yaml + # Create emtpy rules directory. -mkdir -p /var/lib/suricata # Move config files for references, threshold and classification # to the rules directory. - mv /etc/suricata/*.config /var/lib/suricata + rm -rfv /etc/suricata/*.config - # Set correct permissions for the files. - chmod 644 /var/lib/suricata/*.config + # Set correct ownership for the classifiction config file. + # (File has to be writeable for the nobody user) + chown nobody:nobody /usr/share/suricata/classification.config # Set correct ownership for /var/lib/suricata and the # contained files diff --git a/src/scripts/update-ids-ruleset b/src/scripts/update-ids-ruleset index dbe5b6849..10a270907 100644 --- a/src/scripts/update-ids-ruleset +++ b/src/scripts/update-ids-ruleset @@ -2,7 +2,7 @@ ############################################################################### # # # IPFire.org - A linux based firewall # -# Copyright (C) 2018 IPFire Team # +# Copyright (C) 2018-2021 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 # @@ -26,6 +26,9 @@ require '/var/ipfire/general-functions.pl'; require "${General::swroot}/ids-functions.pl"; require "${General::swroot}/lang.pl"; +# Hash to store the configured providers. +my %providers = (); + # The user and group name as which this script should be run. my $run_as = 'nobody'; @@ -39,6 +42,17 @@ if ( $> == 0 ) { POSIX::setuid( $uid ); } +# Check if the IDS lock file exists. +# In this case the WUI or another instance currently is altering the +# ruleset. +if (-f "$IDS::ids_page_lock_file") { + # Store notice to the syslog. + &IDS::_log_to_syslog("Another process currently is altering the IDS ruleset."); + + # Exit. + exit 0; +} + # Check if the red device is active. unless (-e "${General::swroot}/red/active") { # Store notice in the syslog. @@ -63,21 +77,37 @@ if(&IDS::checkdiskspace()) { # Lock the IDS page. &IDS::lock_ids_page(); -# Call the download function and gather the new ruleset. -if(&IDS::downloadruleset()) { - # Store error message for displaying in the WUI. - &IDS::_store_error_message("$Lang::tr{'could not download latest updates'}"); +# Grab the configured providers. +&General::readhasharray("$IDS::providers_settings_file", \%providers); - # Unlock the IDS page. - &IDS::unlock_ids_page(); +# Loop through the array of available providers. +foreach my $id (keys %providers) { + # Assign some nice variabled. + my $provider = $providers{$id}[0]; + my $autoupdate_status = $providers{$id}[3]; - # Exit. - exit 0; + # Skip the provider if autoupdate is not enabled. + next unless($autoupdate_status eq "enabled"); + + # Call the download function and gather the new ruleset for the current processed provider. + if(&IDS::downloadruleset($provider)) { + # Store error message for displaying in the WUI. + &IDS::_store_error_message("$provider: $Lang::tr{'could not download latest updates'}"); + + # Unlock the IDS page. + &IDS::unlock_ids_page(); + + # Exit. + exit 0; + } + + # Get path and name of the stored rules file or archive. + my $stored_file = &IDS::_get_dl_rulesfile($provider); + + # Set correct ownership for the downloaded tarball. + &IDS::set_ownership("$stored_file"); } -# Set correct ownership for the downloaded tarball. -&IDS::set_ownership("$IDS::rulestarball"); - # Call oinkmaster to alter the ruleset. &IDS::oinkmaster();