]> git.ipfire.org Git - people/mfischer/ipfire-2.x.git/commitdiff
Merge branch 'master-IDSv3' into temp-c164-development
authorPeter Müller <peter.mueller@ipfire.org>
Fri, 14 Jan 2022 14:05:10 +0000 (14:05 +0000)
committerPeter Müller <peter.mueller@ipfire.org>
Fri, 14 Jan 2022 14:05:10 +0000 (14:05 +0000)
15 files changed:
config/backup/backup.pl
config/backup/include
config/cfgroot/ids-functions.pl
config/oinkmaster/oinkmaster.conf
config/rootfiles/common/configroot
config/suricata/convert-ids-multiple-providers [new file with mode: 0644]
config/suricata/convert-snort
config/suricata/ruleset-sources
config/suricata/suricata.yaml
html/cgi-bin/ids.cgi
langs/de/cgi-bin/de.pl
langs/en/cgi-bin/en.pl
lfs/configroot
lfs/suricata
src/scripts/update-ids-ruleset

index afd8d1663df650aeebafa39147adcc4fd0e30f37..63004491c9257edbb23798e0304fe0219dc8e1f8 100644 (file)
@@ -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
 
index 5db452cda05907d7168b9f8deab54a87d220292b..3b96b1d626962171302380481ce6beee0d495d29 100644 (file)
@@ -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
index 0e397ca192182a1dd9b61604c04ed09b2cbf1184..74d55def6c986ca96b3104f24eb8c865978cd071 100644 (file)
 #                                                                          #
 ############################################################################
 
+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/\<oinkcode\>/$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'};
+
+               # 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};
 
-               # Accept the html header.
-               $request->header('Accept' => 'text/html');
+                       # Grab the subscription code.
+                       my $subscription_code = $used_providers{$id}[1];
 
-               # Perform the request and fetch the html header.
-               my $response = $downloader->request($request);
+                       # Add the subscription code to the download url.
+                       $url =~ s/\<subscription_code\>/$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();
-
-               # Grab the remote file size from the object and store it in the
-               # variable.
-               $remote_filesize = $header->content_length;
-       }
+               # Load perl stat module.
+               use File::stat;
 
-       # Load perl module to deal with temporary files.
-       use File::Temp;
+               # Perform stat on the tmpfile.
+               my $stat = stat($tmpfile);
 
-       # 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();
+               # Grab the local filesize of the downloaded tarball.
+               my $local_filesize = $stat->size;
 
-       # Pass the requested url to the downloader.
-       my $request = HTTP::Request->new(GET => $url);
+               # 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. ");
 
-       # Perform the request and save the output into the tmpfile.
-       my $response = $downloader->request($request, $tmpfile);
+                       # Delete temporary file.
+                       unlink("$tmpfile");
 
-       # Check if there was any error.
-       unless ($response->is_success) {
-               # Obtain error.
-               my $error = $response->content;
+                       # Return "1" - false.
+                       return 1;
+               }
 
-               # Log error message.
-               &_log_to_syslog("Unable to download the ruleset. \($error\)");
+               # Genarate and assign file name and path to store the downloaded rules file.
+               my $dl_rulesfile = &_get_dl_rulesfile($provider);
 
-               # Return "1" - false.
-               return 1;
-       }
+               # Check if a file name could be obtained.
+               unless ($dl_rulesfile) {
+                       # Log error message.
+                       &_log_to_syslog("Unable to store the downloaded rules file. ");
 
-       # Load perl stat module.
-       use File::stat;
+                       # Delete downloaded temporary file.
+                       unlink("$tmpfile");
 
-       # Perform stat on the tmpfile.
-       my $stat = stat($tmpfile);
+                       # Return "1" - false.
+                       return 1;
+               }
 
-       # Grab the local filesize of the downloaded tarball.
-       my $local_filesize = $stat->size;
+               # Load file copy module, which contains the move() function.
+               use File::Copy;
 
-       # 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.
+       # If we got here, everything worked fine. Return nothing.
+       return;
+}
+
+#
+## 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;
 
-       # Overwrite existing rules tarball with the new downloaded one.
-       move("$tmpfile", "$rulestarball");
+       # Get full path and downloaded rulesfile for the given provider.
+       my $tarball = &_get_dl_rulesfile($provider);
 
-       # Set correct ownership for the rulesdir and files.
-       set_ownership("$rulestarball");
+       # Check if the file exists.
+       unless (-f $tarball) {
+               &_log_to_syslog("Could not find ruleset file: $tarball");
 
-       # If we got here, everything worked fine. Return nothing.
-       return;
+               # 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 tiny wrapper function to call the oinkmaster script.
+## 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(<OINKMASTER>) {
@@ -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(<CLASSIFICATION>) {
+                       # 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 (<MAPPING>) {
+                       # 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 = <FILE>;
+
+       # 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(<FILE>) {
+               # 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 <<END;
-modifysid * "^#?(?:alert|drop)" | "#drop"
-modifysid * "^#drop(.+flowbits:noalert;)" | "#alert\${1}"
 modifysid * "^#(?:alert|drop)(.+policy $policy-ips alert)" | "alert\${1}"
 modifysid * "^#(?:alert|drop)(.+policy $policy-ips drop)" | "drop\${1}"
-END
-               } else {
-                       # These rulefiles don't have the metadata, so set rules to 'drop' unless they
-                       # contain the string 'flowbits:noalert;'.
-                       print FILE <<END;
 modifysid * "^(#?)(?:alert|drop)" | "\${1}drop"
 modifysid * "^(#?)drop(.+flowbits:noalert;)" | "\${1}alert\${2}"
 END
                }
-       }
 
        # Close file handle.
        close(FILE);
 }
 
+#
+## Function to get the ruleset date for a given provider.
+##
+## The function simply return the creation date in a human read-able format
+## of the stored providers rulesfile.
+#
+sub get_ruleset_date($) {
+       my ($provider) = @_;
+       my $date;
+       my $mtime;
+
+       # Load neccessary perl modules for file stat and to format the timestamp.
+       use File::stat;
+       use POSIX qw( strftime );
+
+       # Get the stored rulesfile for this provider.
+       my $stored_rulesfile = &_get_dl_rulesfile($provider);
+
+       # Check if we got a file.
+       if (-f $stored_rulesfile) {
+               # Call stat on the rulestarball.
+               my $stat = stat("$stored_rulesfile");
+
+               # Get timestamp the file creation.
+               $mtime = $stat->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 = <SURICATA>;
+
+       # 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 (<FILE>) {
+                       # 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.
index 57c3281395d1c7f00522a138a0ea8d8fc62c12f1..4d4ee40efdc9fee026401403db571f1d3eaaf8a8 100644 (file)
@@ -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
index 2dfc8ae1ffc96b9e984690fef04d0137e048668d..904c718c33efe037712b7b62b07c46f4e61f5621 100644 (file)
@@ -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 (file)
index 0000000..a082508
--- /dev/null
@@ -0,0 +1,284 @@
+#!/usr/bin/perl
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2021 IPFire Development Team <info@ipfire.org>                #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+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 (<FILE>) {
+               # 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");
+}
index 7d75233b4fa29e5b40b8c468d1f7a6d982df36f2..dc068eb2f6c6893cab2c87ddb61da4c68c65e0a9 100644 (file)
@@ -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 = <SNORTCONF>) {
 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.
index a00cef94548d47834754c194d41db4c26f1d7931..7da1ecc1d61d4f8e1e731c56870c7d8185ba247d 100644 (file)
-# Ruleset for registered sourcefire users.
-registered = https://www.snort.org/rules/snortrules-snapshot-29161.tar.gz?oinkcode=<oinkcode>
+package IDS::Ruleset;
 
-# Ruleset for registered sourcefire users with valid subscription.
-subscripted = https://www.snort.org/rules/snortrules-snapshot-29161.tar.gz?oinkcode=<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=<subscription_code>",
+               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=<subscription_code>",
+               dl_type => "archive",
+       },
 
-# Emerging threads pro rules.
-emerging_pro = https://rules.emergingthreatspro.com/<oinkcode>/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/<subscription_code>/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/<subscription_code>/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/<subscription_code>/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/<subscription_code>/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",
+       },
+);
index b4a188d4045287b3e46ebc7eda6c7ef6a2174101..6fbc7b3ee1bacbf2ab17815230205d1db0f3a551 100644 (file)
@@ -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:
index 4e8b28fd84caabdaa1f934e2bcc25ab9d93bd694..161464d0d9990998ad23bcd67c826611a3d5065f 100644 (file)
@@ -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)) {
-
-               # We only want files.
-               next unless (-f "$IDS::rulespath/$file");
+# Gather ruleset details.
+if ($cgiparams{'RULESET'}) {
+       ## Grab all available rules and store them in the idsrules hash.
+       #
 
-               # Ignore empty files.
-               next if (-z "$IDS::rulespath/$file");
+       # Get enabled providers.
+       my @enabled_providers = &IDS::get_enabled_providers();
 
-               # Use a regular expression to find files ending in .rules
-               next unless ($file =~ m/\.rules$/);
+       # Open rules directory and do a directory listing.
+       opendir(DIR, $IDS::rulespath) or die $!;
+               # Loop through the direcory.
+               while (my $file = readdir(DIR)) {
 
-               # Ignore files which are not read-able.
-               next unless (-R "$IDS::rulespath/$file");
+                       # We only want files.
+                       next unless (-f "$IDS::rulespath/$file");
 
-               # Skip whitelist rules file.
-               next if( $file eq "whitelist.rules");
+                       # Ignore empty files.
+                       next if (-z "$IDS::rulespath/$file");
 
-               # Call subfunction to read-in rulefile and add rules to
-               # the idsrules hash.
-               &readrulesfile("$file");
-       }
+                       # Use a regular expression to find files ending in .rules
+                       next unless ($file =~ m/\.rules$/);
 
-closedir(DIR);
+                       # Ignore files which are not read-able.
+                       next unless (-R "$IDS::rulespath/$file");
 
-# 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";
+                       # Skip whitelist rules file.
+                       next if( $file eq "whitelist.rules");
 
-       # Read-in content.
-       my @lines = <FILE>;
+                       # Splitt vendor from filename.
+                       my @filename_parts = split(/-/, $file);
 
-       # Close file.
-       close(FILE);
+                       # Assign vendor name for easy processing.
+                       my $vendor = @filename_parts[0];
 
-       # Loop through the array.
-       foreach my $line (@lines) {
-               # Remove newlines.
-               chomp($line);
+                       # Skip rulefile if the provider is disabled.
+                       next unless ($vendor ~~ @enabled_providers);
 
-               # Skip comments.
-               next if ($line =~ /\#/);
+                       # Call subfunction to read-in rulefile and add rules to
+                       # the idsrules hash.
+                       &readrulesfile("$file");
+               }
 
-               # Skip blank  lines.
-               next if ($line =~ /^\s*$/);
+       closedir(DIR);
 
-               # 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 =~ /\<oinkcode\>/ ) {
-               # 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+/ );
-
-                       # 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";
+       # Print file header and notice about autogenerated file.
+       print OINKM_INCL_FILE "#Autogenerated file. Any custom changes will be overwritten!\n";
+
+       # 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};
-                               }
                        }
                }
-       }
 
-       # 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";
-
-       # Open disabled sid's file for writing.
-       open(DISABLED_FILE, ">$IDS::disabled_sids_file") or die "Could not write to $IDS::disabled_sids_file. $!\n";
-
-       # Write header to the files.
-       print ENABLED_FILE "#Autogenerated file. Any custom changes will be overwritten!\n";
-       print DISABLED_FILE "#Autogenerated file. Any custom changes will be overwritten!\n";
-
-       # Check if the hash for enabled/disabled files contains any entries.
-       if (%enabled_disabled_sids) {
-               # Loop through the hash.
-               foreach my $sid (keys %enabled_disabled_sids) {
-                       # Check if the sid is enabled.
-                       if ($enabled_disabled_sids{$sid} eq "enabled") {
-                               # Print the sid to the enabled_sids file.
-                               print ENABLED_FILE "enablesid $sid\n";
-                       # Check if the sid is disabled.
-                       } elsif ($enabled_disabled_sids{$sid} eq "disabled") {
-                               # Print the sid to the disabled_sids file.
-                               print DISABLED_FILE "disablesid $sid\n";
-                       # Something strange happende - skip the current sid.
-                       } else {
-                               next;
+               # 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";
                }
        }
 
-       # Close file for enabled_sids after writing.
-       close(ENABLED_FILE);
+       # Close the file handle after writing.
+       close(OINKM_INCL_FILE);
+
+       # Handle enabled / disabled rulefiles.
+       #
+
+       # 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 = ();
+
+               # 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);
+                       }
 
-       # Close file for disabled_sids after writing.
-       close(DISABLED_FILE);
+                       # Call function and write the providers used rulesfile file.
+                       &IDS::write_used_provider_rulefiles_file($provider, @provider_rulefiles);
+               }
+       }
 
        # 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();
        }
-}
-
-# 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';
-}
+# Toggle Enable/Disable autoupdate for a provider
+} elsif ($cgiparams{'AUTOUPDATE'} eq $Lang::tr{'toggle enable disable'}) {
+       my %used_providers = ();
 
-# 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'";
+       # Only go further, if an ID has been passed.
+       if ($cgiparams{'ID'}) {
+               # Assign the given ID.
+               my $id = $cgiparams{'ID'};
 
-&Header::openpage($Lang::tr{'intrusion detection system'}, 1, '');
+               # Undef the given ID.
+               undef($cgiparams{'ID'});
 
-### Java Script ###
-print"<script>\n";
+               # Read-in providers settings file.
+               &General::readhasharray($IDS::providers_settings_file, \%used_providers);
 
-# Java script variable declaration for show and hide.
-print"var show = \"$Lang::tr{'ids show'}\"\;\n";
-print"var hide = \"$Lang::tr{'ids hide'}\"\;\n";
+               # Grab the configured status of the corresponding entry.
+               my $status_autoupdate = $used_providers{$id}[2];
 
-print <<END
-       // Java Script function to show/hide the text input field for
-       // Oinkcode/Subscription code.
-       var update_code = function() {
-               if(\$('#RULES').val() == 'registered') {
-                       \$('#code').show();
-               } else if(\$('#RULES').val() == 'subscripted') {
-                       \$('#code').show();
-               } else if(\$('#RULES').val() == 'emerging_pro') {
-                       \$('#code').show();
+               # Switch the status.
+               if ($status_autoupdate eq "disabled") {
+                       $status_autoupdate = "enabled";
                } else {
-                       \$('#code').hide();
+                       $status_autoupdate = "disabled";
                }
-       };
 
-       // JQuery function to call corresponding function when
-       // the ruleset is changed or the page is loaded for showing/hiding
-       // the code area.
-       \$(document).ready(function() {
-               \$('#RULES').change(update_code);
-               update_code();
-       });
+               # Modify the status of the existing entry.
+               $used_providers{$id} = ["$used_providers{$id}[0]", "$used_providers{$id}[1]", "$status_autoupdate", "$used_providers{$id}[3]"];
 
-       // Tiny java script function to show/hide the rules
-       // of a given category.
-       function showhide(tblname) {
-               \$("#" + tblname).toggle();
+               # Write the changed hash to the providers settings file.
+               &General::writehasharray($IDS::providers_settings_file, \%used_providers);
+       }
 
-               // Get current content of the span element.
-               var content = document.getElementById("span_" + tblname);
+# 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 = ();
 
-               if (content.innerHTML === show) {
-                       content.innerHTML = hide;
-               } else {
-                       content.innerHTML = show;
-               }
-       }
-</script>
-END
-;
+       # Read-in providers settings file.
+       &General::readhasharray("$IDS::providers_settings_file", \%used_providers);
 
-&Header::openbigbox('100%', 'left', '', $errormessage);
+       # Assign some nice human-readable values.
+       my $provider = $cgiparams{'PROVIDER'};
+       my $subscription_code = $cgiparams{'SUBSCRIPTION_CODE'};
+       my $status_autoupdate;
 
-if ($errormessage) {
-       &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
-       print "<class name='base'>$errormessage\n";
-       print "&nbsp;</class>\n";
-       &Header::closebox();
-}
+       # Handle autoupdate checkbox.
+       if ($cgiparams{'ENABLE_AUTOUPDATE'} eq "on") {
+               $status_autoupdate = "enabled";
+       } else {
+               $status_autoupdate = "disabled";
+       }
 
-# Draw current state of the IDS
-&Header::openbox('100%', 'left', $Lang::tr{'intrusion detection system'});
+       # 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 IDS is running and obtain the process-id.
-my $pid = &IDS::ids_is_running();
+       # 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'};
+               }
+       }
 
-# Display some useful information, if suricata daemon is running.
-if ($pid) {
-       # Gather used memory.
-       my $memory = &get_memory_usage($pid);
+       # Go further if there was no error.
+       if ($errormessage eq '') {
+               my $id;
+               my $status;
 
-       print <<END;
-               <table width='95%' cellspacing='0' class='tbl'>
-                       <tr>
-                               <th bgcolor='$color{'color20'}' colspan='3' align='left'><strong>$Lang::tr{'intrusion detection'}</strong></th>
-                       </tr>
+               # 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'};
 
-                       <tr>
-                               <td class='base'>$Lang::tr{'guardian daemon'}</td>
-                               <td align='center' colspan='2' width='75%' bgcolor='${Header::colourgreen}'><font color='white'><strong>$Lang::tr{'running'}</strong></font></td>
-                       </tr>
+                       # Undef the given ID.
+                       undef($cgiparams{'ID'});
 
-                       <tr>
-                               <td class='base'></td>
-                               <td bgcolor='$color{'color20'}' align='center'><strong>PID</strong></td>
-                               <td bgcolor='$color{'color20'}' align='center'><strong>$Lang::tr{'memory'}</strong></td>
-                       </tr>
+                       # Grab the configured status of the corresponding entry.
+                       $status = $used_providers{$id}[3];
+               } else {
+                       # Each newly added entry automatically should be enabled.
+                       $status = "enabled";
 
-                       <tr>
-                               <td class='base'></td>
-                               <td bgcolor='$color{'color22'}' align='center'>$pid</td>
-                               <td bgcolor='$color{'color22'}' align='center'>$memory KB</td>
-                       </tr>
-               </table>
-END
-} else {
-       # Otherwise display a hint that the service is not launched.
-       print <<END;
-               <table width='95%' cellspacing='0' class='tbl'>
-                       <tr>
-                               <th bgcolor='$color{'color20'}' colspan='3' align='left'><strong>$Lang::tr{'intrusion detection'}</strong></th>
-                       </tr>
+                       # 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;
 
-                       <tr>
-                               <td class='base'>$Lang::tr{'guardian daemon'}</td>
-                               <td align='center' width='75%' bgcolor='${Header::colourred}'><font color='white'><strong>$Lang::tr{'stopped'}</strong></font></td>
-                       </tr>
-               </table>
-END
-}
+                       # Reverse the key array.
+                       my @reversed = reverse(@keys);
 
-# Only show this area, if a ruleset is present.
-if (%idsrules) {
+                       # Obtain the last used id.
+                       my $last_id = @reversed[0];
 
-       print <<END
+                       # Increase the last id by one and use it as id for the new entry.
+                       $id = ++$last_id;
+               }
 
-       <br><br><h2>$Lang::tr{'settings'}</h2>
+               # Add/Modify the entry to/in the used providers hash..
+               $used_providers{$id} = ["$provider", "$subscription_code", "$status_autoupdate", "$status"];
 
-       <form method='post' action='$ENV{'SCRIPT_NAME'}'>
-               <table width='100%' border='0'>
-                       <tr>
-                               <td class='base' colspan='2'>
-                                       <input type='checkbox' name='ENABLE_IDS' $checked{'ENABLE_IDS'}{'on'}>&nbsp;$Lang::tr{'ids enable'}
-                               </td>
+               # Write the changed hash to the providers settings file.
+               &General::writehasharray($IDS::providers_settings_file, \%used_providers);
 
-                               <td class='base' colspan='2'>
-                                       <input type='checkbox' name='MONITOR_TRAFFIC_ONLY' $checked{'MONITOR_TRAFFIC_ONLY'}{'on'}>&nbsp;$Lang::tr{'ids monitor traffic only'}
-                       </td>
-                       </tr>
+               # 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'}";
+                       }
 
-                       <tr>
-                               <td><br><br></td>
-                               <td><br><br></td>
-                               <td><br><br></td>
-                               <td><br><br></td>
-                       </tr>
+                       # 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'}";
+                       }
 
-                       <tr>
-                               <td colspan='4'><b>$Lang::tr{'ids monitored interfaces'}</b><br></td>
-                       </tr>
+                       # Check if any errors happend.
+                       unless ($errormessage) {
+                               # Lock the webpage and print notice about downloading
+                               # a new ruleset.
+                               &working_notice("$Lang::tr{'ids working'}");
 
-                       <tr>
-END
-;
+                               # 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);
 
-       # Loop through the array of available networks and print config options.
-       foreach my $zone (@network_zones) {
-               my $checked_input;
-               my $checked_forward;
+                                       # Remove the configured provider again.
+                                       &remove_provider($id);
+                               } else {
+                                       # Extract the ruleset
+                                       &IDS::extractruleset($provider);
 
-               # Convert current zone name to upper case.
-               my $zone_upper = uc($zone);
+                                       # Move the ruleset.
+                                       &IDS::move_tmp_ruleset();
 
-               # Set zone name.
-               my $zone_name = $zone;
+                                       # Cleanup temporary directory.
+                                       &IDS::cleanup_tmp_directory();
 
-               # Dirty hack to get the correct language string for the red zone.
-               if ($zone eq "red") {
-                       $zone_name = "red1";
-               }
+                                       # Create new empty file for used rulefiles
+                                       # for this provider.
+                                       &IDS::write_used_provider_rulefiles_file($provider);
+                               }
 
-               # Grab checkbox status from settings hash.
-               if ($idssettings{"ENABLE_IDS_$zone_upper"} eq "on") {
-                       $checked_input = "checked = 'checked'";
+                               # Perform a reload of the page.
+                               &reload();
+                       }
                }
 
-               print "<td class='base' width='20%'>\n";
-               print "<input type='checkbox' name='ENABLE_IDS_$zone_upper' $checked_input>\n";
-               print "&nbsp;$Lang::tr{'enabled on'}<font color='$colourhash{$zone}'> $Lang::tr{$zone_name}</font>\n";
-               print "</td>\n";
        }
 
-print <<END
-                       </tr>
-               </table>
+       # Undefine providers flag.
+       undef($cgiparams{'PROVIDERS'});
 
-               <br><br>
+## Toggle Enabled/Disabled for an existing provider.
+#
+} elsif ($cgiparams{'PROVIDERS'} eq $Lang::tr{'toggle enable disable'}) {
+       my %used_providers = ();
+       my $provider_includes_action;
 
-               <table width='100%'>
-                       <tr>
-                               <td align='right'><input type='submit' name='IDS' value='$Lang::tr{'save'}' /></td>
-                       </tr>
-               </table>
-       </form>
-END
-;
+       # 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'};
 
-&Header::closebox();
+               # Undef the given ID.
+               undef($cgiparams{'ID'});
 
-# Draw elements for ruleset configuration.
-&Header::openbox('100%', 'center', $Lang::tr{'ids ruleset settings'});
+               # Read-in file which contains the provider settings.
+               &General::readhasharray($IDS::providers_settings_file, \%used_providers);
 
-print <<END
-<form method='post' action='$ENV{'SCRIPT_NAME'}'>
-        <table width='100%' border='0'>
-               <tr>
-                       <td><b>$Lang::tr{'ids rules update'}</b></td>
-                       <td><b>$Lang::tr{'ids automatic rules update'}</b></td>
-               </tr>
+               # Grab the configured status of the corresponding entry.
+               my $status = $used_providers{$id}[3];
 
-               <tr>
-                       <td><select name='RULES' id='RULES'>
-                               <option value='emerging' $selected{'RULES'}{'emerging'} >$Lang::tr{'emerging rules'}</option>
-                               <option value='emerging_pro' $selected{'RULES'}{'emerging_pro'} >$Lang::tr{'emerging pro rules'}</option>
-                               <option value='community' $selected{'RULES'}{'community'} >$Lang::tr{'community rules'}</option>
-                               <option value='registered' $selected{'RULES'}{'registered'} >$Lang::tr{'registered user rules'}</option>
-                               <option value='subscripted' $selected{'RULES'}{'subscripted'} >$Lang::tr{'subscripted user rules'}</option>
-                       </select>
-                       </td>
-
-                       <td>
-                               <select name='AUTOUPDATE_INTERVAL'>
-                                       <option value='off' $selected{'AUTOUPDATE_INTERVAL'}{'off'} >- $Lang::tr{'Disabled'} -</option>
-                                       <option value='daily' $selected{'AUTOUPDATE_INTERVAL'}{'daily'} >$Lang::tr{'Daily'}</option>
-                                       <option value='weekly' $selected{'AUTOUPDATE_INTERVAL'}{'weekly'} >$Lang::tr{'Weekly'}</option>
-                               </select>
-                       </td>
-               </tr>
+               # Grab the provider handle.
+               my $provider_handle = $used_providers{$id}[0];
 
-               <tr>
-                       <td colspan='2'><br><br></td>
-               </tr>
+               # Switch the status.
+               if ($status eq "enabled") {
+                       $status = "disabled";
 
-               <tr style='display:none' id='code'>
-                       <td colspan='2'>Oinkcode:&nbsp;<input type='text' size='40' name='OINKCODE' value='$rulessettings{'OINKCODE'}'></td>
-               </tr>
+                       # Set the provider includes action to "remove" for removing the entry.
+                       $provider_includes_action = "remove";
+               } else {
+                       $status = "enabled";
 
-               <tr>
-                       <td>&nbsp;</td>
+                       # 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();
+}
+
+&Header::openpage($Lang::tr{'intrusion detection system'}, 1, '');
+
+&Header::openbigbox('100%', 'left', '', $errormessage);
+
+&show_display_error_message();
+
+if ($cgiparams{'RULESET'} eq "$Lang::tr{'ids customize ruleset'}" ) {
+       &show_customize_ruleset();
+} elsif ($cgiparams{'PROVIDERS'} ne "") {
+       &show_add_provider();
+} else {
+       &show_mainpage();
+}
+
+&Header::closebigbox();
+&Header::closepage();
+
+#
+## Tiny function to show if a error message happened.
+#
+sub show_display_error_message() {
+       if ($errormessage) {
+               &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
+                       print "<class name='base'>$errormessage\n";
+                       print "&nbsp;</class>\n";
+               &Header::closebox();
+       }
+}
+
+#
+## 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);
+
+       # 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 <<END;
+                       <table width='95%' cellspacing='0' class='tbl'>
+                               <tr>
+                                       <th bgcolor='$color{'color20'}' colspan='3' align='left'><strong>$Lang::tr{'intrusion detection'}</strong></th>
+                               </tr>
+
+                               <tr>
+                                       <td class='base'>$Lang::tr{'guardian daemon'}</td>
+                                       <td align='center' colspan='2' width='75%' bgcolor='${Header::colourgreen}'><font color='white'><strong>$Lang::tr{'running'}</strong></font></td>
+                               </tr>
+
+                               <tr>
+                                       <td class='base'></td>
+                                       <td bgcolor='$color{'color20'}' align='center'><strong>PID</strong></td>
+                                       <td bgcolor='$color{'color20'}' align='center'><strong>$Lang::tr{'memory'}</strong></td>
+                               </tr>
+
+                               <tr>
+                                       <td class='base'></td>
+                                       <td bgcolor='$color{'color22'}' align='center'>$pid</td>
+                                       <td bgcolor='$color{'color22'}' align='center'>$memory KB</td>
+                               </tr>
+                       </table>
+END
+       } else {
+               # Otherwise display a hint that the service is not launched.
+               print <<END;
+                       <table width='95%' cellspacing='0' class='tbl'>
+                               <tr>
+                                       <th bgcolor='$color{'color20'}' colspan='3' align='left'><strong>$Lang::tr{'intrusion detection'}</strong></th>
+                               </tr>
 
-                       <td align='right'>
+                               <tr>
+                                       <td class='base'>$Lang::tr{'guardian daemon'}</td>
+                                       <td align='center' width='75%' bgcolor='${Header::colourred}'><font color='white'><strong>$Lang::tr{'stopped'}</strong></font></td>
+                               </tr>
+                       </table>
+END
+       }
+
+       # Only show this area, if at least one ruleset provider is configured.
+       if (%used_providers) {
+
+print <<END
+
+               <br><br><h2>$Lang::tr{'settings'}</h2>
+
+               <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                       <table width='100%' border='0'>
+                               <tr>
+                                       <td class='base' colspan='2'>
+                                               <input type='checkbox' name='ENABLE_IDS' $checked{'ENABLE_IDS'}{'on'}>&nbsp;$Lang::tr{'ids enable'}
+                                       </td>
+
+                                       <td class='base' colspan='2'>
+                                               <input type='checkbox' name='MONITOR_TRAFFIC_ONLY' $checked{'MONITOR_TRAFFIC_ONLY'}{'on'}>&nbsp;$Lang::tr{'ids monitor traffic only'}
+                               </td>
+                               </tr>
+
+                               <tr>
+                                       <td><br><br></td>
+                                       <td><br><br></td>
+                                       <td><br><br></td>
+                                       <td><br><br></td>
+                               </tr>
+
+                               <tr>
+                                       <td colspan='4'><b>$Lang::tr{'ids monitored interfaces'}</b><br></td>
+                               </tr>
+
+                               <tr>
 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"<input type='submit' name='RULESET' value='$Lang::tr{'update ruleset'}'>\n";
+
+               # 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 "<td class='base' width='20%'>\n";
+                       print "<input type='checkbox' name='ENABLE_IDS_$zone_upper' $checked_input>\n";
+                       print "&nbsp;$Lang::tr{'enabled on'}<font color='$colourhash{$zone}'> $Lang::tr{$zone_name}</font>\n";
+                       print "</td>\n";
                }
-print <<END;
-                               <input type='submit' name='RULESET' value='$Lang::tr{'save'}'>
-                       </td>
 
-               </tr>
-       </table>
-</form>
+print <<END
+                               </tr>
+
+                               <tr>
+                                       <td><br><br></td>
+                                       <td><br><br></td>
+                                       <td><br><br></td>
+                                       <td><br><br></td>
+                               </tr>
+
+                               <tr>
+                                       <td colspan='4'><b>$Lang::tr{'ids automatic rules update'}</b></td>
+                               </tr>
+
+                               <tr>
+                                       <td>
+                                               <select name='AUTOUPDATE_INTERVAL'>
+                                                       <option value='off' $selected{'AUTOUPDATE_INTERVAL'}{'off'} >- $Lang::tr{'Disabled'} -</option>
+                                                       <option value='daily' $selected{'AUTOUPDATE_INTERVAL'}{'daily'} >$Lang::tr{'Daily'}</option>
+                                                       <option value='weekly' $selected{'AUTOUPDATE_INTERVAL'}{'weekly'} >$Lang::tr{'Weekly'}</option>
+                                               </select>
+                                       </td>
+                               </tr>
+                       </table>
+
+                       <br><br>
+
+                       <table width='100%'>
+                               <tr>
+                                       <td align='right'><input type='submit' name='IDS' value='$Lang::tr{'save'}' /></td>
+                               </tr>
+                       </table>
+               </form>
 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 <<END;
+       <table width='100%' border='0'>
+               <tr>
+                       <td class='base' bgcolor='$color{'color20'}'><b>$Lang::tr{'ids provider'}</b></td>
+                       <td class='base' bgcolor='$color{'color20'}'><b>$Lang::tr{'date'}</b></td>
+                       <td class='base' bgcolor='$color{'color20'}' align='center'><b>$Lang::tr{'ids autoupdates'}</b></td>
+                       <td class='base' bgcolor='$color{'color20'}'></td>
+                       <td class='base' colspan='3' bgcolor='$color{'color20'}'></td>
+               </tr>
+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 <<END;
+                               <tr>
+                                       <td width='33%' class='base' $col>$provider_name</td>
+                                       <td width='30%' class='base' $col>$rulesetdate</td>
+
+                                       <td align='center' $col>
+                                               <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                                                       <input type='hidden' name='AUTOUPDATE' value='$Lang::tr{'toggle enable disable'}' />
+                                                       <input type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$autoupdate_status_gif' alt='$autoupdate_status_gdesc' title='$autoupdate_status_gdesc' />
+                                                       <input type='hidden' name='ID' value='$id' />
+                                               </form>
+                                       </td>
+
+                                       <td align='center' $col>
+                                               <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                                                       <input type='hidden' name='PROVIDERS' value='$Lang::tr{'toggle enable disable'}'>
+                                                       <input type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$status_gif' alt='$status_gdesc' title='$status_gdesc'>
+                                                       <input type='hidden' name='ID' value='$id'>
+                                               </form>
+                                       </td>
+
+                                       <td align='center' $col>
+                                               <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                                                       <input type='hidden' name='PROVIDERS' value='$Lang::tr{'edit'}'>
+                                                       <input type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}'>
+                                                       <input type='hidden' name='ID' value='$id'>
+                                               </form>
+                                       </td>
+
+                                       <td align='center' $col>
+                                               <form method='post' name='$provider' action='$ENV{'SCRIPT_NAME'}'>
+                                                       <input type='image' name='$Lang::tr{'remove'}' src='/images/delete.gif' title='$Lang::tr{'remove'}' alt='$Lang::tr{'remove'}'>
+                                                       <input type='hidden' name='ID' value='$id'>
+                                                       <input type='hidden' name='PROVIDERS' value='$Lang::tr{'remove'}'>
+                                               </form>
+                                       </td>
+                               </tr>
+END
+                       # Increment lines value.
+                       $line++;
+
+                       }
+
+               } else {
+                       # Print notice that currently no hosts are ignored.
+                       print "<tr>\n";
+                       print "<td class='base' colspan='2'>$Lang::tr{'guardian no entries'}</td>\n";
+                       print "</tr>\n";
+               }
+
+       print "</table>\n";
+
+       # Section to add new elements or edit existing ones.
+print <<END;
+       <br>
+       <hr>
+       <br>
+
+       <div align='right'>
+               <table width='100%'>
+                       <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                               <tr>
+END
+
+                                       # Only show this button if a ruleset provider is configured.
+                                       if (%used_providers) {
+                                               print "<input type='submit' name='RULESET' value='$Lang::tr{'ids customize ruleset'}'>\n";
+                                       }
+print <<END;
+                                       <input type='submit' name='PROVIDERS' value='$Lang::tr{'ids add provider'}'>
+                               </tr>
+                       </form>
+               </table>
+       </div>
+END
+
+       &Header::closebox();
+
+       #
+       # Whitelist / Ignorelist
+       #
+       &Header::openbox('100%', 'center', $Lang::tr{'ids ignored hosts'});
+
+       print <<END;
        <table width='100%'>
                <tr>
                        <td class='base' bgcolor='$color{'color20'}'><b>$Lang::tr{'ip address'}</b></td>
@@ -1003,17 +1430,17 @@ print <<END;
 
                                        <td align='center' $col>
                                                <form method='post' action='$ENV{'SCRIPT_NAME'}'>
-                                                       <input type='hidden' name='WHITELIST' value='$Lang::tr{'toggle enable disable'}' />
-                                                       <input type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$gif' alt='$gdesc' title='$gdesc' />
-                                                       <input type='hidden' name='ID' value='$key' />
+                                                       <input type='hidden' name='WHITELIST' value='$Lang::tr{'toggle enable disable'}'>
+                                                       <input type='image' name='$Lang::tr{'toggle enable disable'}' src='/images/$gif' alt='$gdesc' title='$gdesc'>
+                                                       <input type='hidden' name='ID' value='$key'>
                                                </form>
                                        </td>
 
                                        <td align='center' $col>
                                                <form method='post' action='$ENV{'SCRIPT_NAME'}'>
-                                                       <input type='hidden' name='WHITELIST' value='$Lang::tr{'edit'}' />
-                                                       <input type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}' />
-                                                       <input type='hidden' name='ID' value='$key' />
+                                                       <input type='hidden' name='WHITELIST' value='$Lang::tr{'edit'}'>
+                                                       <input type='image' name='$Lang::tr{'edit'}' src='/images/edit.gif' alt='$Lang::tr{'edit'}' title='$Lang::tr{'edit'}'>
+                                                       <input type='hidden' name='ID' value='$key'>
                                                </form>
                                        </td>
 
@@ -1023,83 +1450,96 @@ print <<END;
                                                        <input type='hidden' name='ID' value='$key'>
                                                        <input type='hidden' name='WHITELIST' value='$Lang::tr{'remove'}'>
                                                </form>
-                                       </td>
-                               </tr>
+                                               </td>
+                                       </tr>
 END
+                               }
+                       } else {
+                               # Print notice that currently no hosts are ignored.
+                               print "<tr>\n";
+                               print "<td class='base' colspan='2'>$Lang::tr{'guardian no entries'}</td>\n";
+                               print "</tr>\n";
                        }
-               } else {
-                       # Print notice that currently no hosts are ignored.
-                       print "<tr>\n";
-                       print "<td class='base' colspan='2'>$Lang::tr{'guardian no entries'}</td>\n";
-                       print "</tr>\n";
-               }
 
-       print "</table>\n";
+               print "</table>\n";
 
-       # Section to add new elements or edit existing ones.
+               # Section to add new elements or edit existing ones.
 print <<END;
-       <br>
-       <hr>
-       <br>
-
-       <div align='center'>
-               <table width='100%'>
+               <br>
+               <hr>
+               <br>
+       
+               <div align='center'>
+                       <table width='100%'>
 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 "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'update'}</b></td></tr>\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 "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'update'}</b></td></tr>\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 "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'dnsforward add a new entry'}</b></td></tr>\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 "<tr><td class='boldbase' colspan='3'><b>$Lang::tr{'dnsforward add a new entry'}</b></td></tr>\n";
+                       }
 
 print <<END;
-                       <form method='post' action='$ENV{'SCRIPT_NAME'}'>
-                       <input type='hidden' name='ID' value='$cgiparams{'ID'}'>
-                       <tr>
-                               <td width='30%'>$Lang::tr{'ip address'}: </td>
-                               <td width='50%'><input type='text' name='IGNORE_ENTRY_ADDRESS' value='$entry_address' size='24' /></td>
+                               <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                               <input type='hidden' name='ID' value='$cgiparams{'ID'}'>
+                               <tr>
+                                       <td width='30%'>$Lang::tr{'ip address'}: </td>
+                                       <td width='50%'><input type='text' name='IGNORE_ENTRY_ADDRESS' value='$entry_address' size='24' /></td>
 
-                               <td width='30%'>$Lang::tr{'remark'}: </td>
-                               <td wicth='50%'><input type='text' name=IGNORE_ENTRY_REMARK value='$entry_remark' size='24' /></td>
-                               <td align='center' width='20%'><input type='submit' name='WHITELIST' value='$buttontext' /></td>
-                       </tr>
-                       </form>
-               </table>
-       </div>
+                                       <td width='30%'>$Lang::tr{'remark'}: </td>
+                                       <td wicth='50%'><input type='text' name=IGNORE_ENTRY_REMARK value='$entry_remark' size='24' /></td>
+                                       <td align='center' width='20%'><input type='submit' name='WHITELIST' value='$buttontext' /></td>
+                               </tr>
+                               </form>
+                       </table>
+               </div>
 END
 
-&Header::closebox();
-
-# Only show the section for configuring the ruleset if one is present.
-if (%idsrules) {
-       # Load neccessary perl modules for file stat and to format the timestamp.
-       use File::stat;
-       use POSIX qw( strftime );
+       &Header::closebox();
+}
 
-       # Call stat on the rulestarball.
-       my $stat = stat("$IDS::rulestarball");
+#
+## Function to show the customize ruleset section.
+#
+sub show_customize_ruleset() {
+       ### Java Script ###
+       print"<script>\n";
 
-       if (defined $stat) {
-               # Get timestamp the file creation.
-               my $mtime = $stat->mtime;
+       # Java script variable declaration for show and hide.
+       print"var show = \"$Lang::tr{'ids show'}\"\;\n";
+       print"var hide = \"$Lang::tr{'ids hide'}\"\;\n";
 
-               # Convert into human read-able format.
-               my $rulesdate = strftime('%Y-%m-%d %H:%M:%S', localtime($mtime));
+print <<END
+       // Tiny java script function to show/hide the rules
+       // of a given category.
+       function showhide(tblname) {
+               \$("#" + tblname).toggle();
 
-               &Header::openbox('100%', 'LEFT', "$Lang::tr{'intrusion detection system rules'} ($rulesdate)" );
+               // Get current content of the span element.
+               var content = document.getElementById("span_" + tblname);
 
+               if (content.innerHTML === show) {
+                       content.innerHTML = hide;
+               } else {
+                       content.innerHTML = show;
+               }
+       }
+       </script>
+END
+;
+       &Header::openbox('100%', 'LEFT', "$Lang::tr{'intrusion detection system rules'}" );
                print"<form method='POST' action='$ENV{'SCRIPT_NAME'}'>\n";
 
                # Output display table for rule files
@@ -1193,7 +1633,10 @@ if (%idsrules) {
                print <<END
 <table width='100%'>
 <tr>
-       <td width='100%' align='right'><input type='submit' name='RULESET' value='$Lang::tr{'ids apply'}'></td>
+       <td width='100%' align='right'>
+               <input type='submit' value='$Lang::tr{'fwhost back'}'>
+               <input type='submit' name='RULESET' value='$Lang::tr{'ids apply'}'>
+       </td>
 </tr>
 </table>
 </form>
@@ -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 "<script>\n";
+
+       # Generate Java Script Object which contains the URL of the providers.
+       print "\t// Object, which contains the webpages of the ruleset providers.\n";
+       print "\tvar url = {\n";
+
+       # Loop through the array of supported providers.
+       foreach my $provider (@ruleset_providers) {
+               # Check if the provider requires a subscription.
+               if ($IDS::Ruleset::Providers{$provider}{'requires_subscription'} eq "True") {
+                       # Add the provider to the array of subscription_providers.
+                       push(@subscription_providers, $provider);
+               }
+
+               # Grab the URL for the provider.
+               my $url = $IDS::Ruleset::Providers{$provider}{'website'};
+
+               # Print the URL to the Java Script Object.
+               print "\t\t$provider: \"$url\"\,\n";
+       }
+
+       # Close the Java Script Object declaration.
+       print "\t}\;\n\n";
+
+       # Generate Java Script Array which contains the provider that requires a subscription.
+       my $line = "";
+       $line = join("', '", @subscription_providers);
+
+       print "\t// Array which contains the providers that requires a subscription.\n";
+       print "\tsubscription_provider = ['$line']\;\n\n";
+
+print <<END
+       // Java Script function to swap the text input field for
+       // entering a subscription code.
+       var update_provider = function() {
+               if(inArray(\$('#PROVIDER').val(), subscription_provider)) {
+                       \$('.subscription_code').show();
+               } else {
+                       \$('.subscription_code').hide();
+               }
+
+               // Call function to change the website url.
+               change_url(\$('#PROVIDER').val());
+       };
+
+       // Java Script function to check if a given value is part of
+       // an array.
+       function inArray(value,array) {
+               var count=array.length;
+
+               for(var i=0;i<count;i++) {
+                       if(array[i]===value){
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       // Tiny function to change the website url based on the selected element in the "PROVIDERS"
+       // dropdown menu.
+       function change_url(provider) {
+               // Get and change the href to the corresponding url.
+               document.getElementById("website").href = url[provider];
+       }
+
+       // JQuery function to call corresponding function when
+       // the ruleset provider is changed or the page is loaded for showing/hiding
+       // the subscription_code area.
+       \$(document).ready(function() {
+               \$('#PROVIDER').change(update_provider);
+                       update_provider();
+       });
+
+       </script>
+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 <<END
+       <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+               <table width='100%' border='0'>
+                       <tr>
+                               <td colspan='2'><b>$Lang::tr{'ids provider'}</b></td>
+                       </tr>
+
+                       <tr>
+                               <td width='40%'>
+                                       <input type='hidden' name='ID' value='$cgiparams{'ID'}'>
+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 "<input type='hidden' name='PROVIDER' value='$used_providers{$cgiparams{'ID'}}[0]'>\n";
+                                       }
+
+                                       print "<select name='PROVIDER' id='PROVIDER' $disabled>\n";
+                                               # Temporary hash to store the provier names and their handles.
+                                               my %tmphash = ();
+
+                                               # Loop through the array of ruleset providers.
+                                               foreach my $handle (@ruleset_providers) {
+                                                       # Get the provider name.
+                                                       my $name = &get_provider_name($handle);
+
+                                                       # Add the grabbed provider  name and handle to the
+                                                       # temporary hash.
+                                                       $tmphash{$name} = "$handle";
+                                               }
+
+                                               # Sort and loop through the temporary hash.
+                                               foreach my $provider_name ( sort keys %tmphash ) {
+                                                       # Grab the provider handle.
+                                                       my $provider = $tmphash{$provider_name};
+
+                                                       # Pre-select the provider if one is given.
+                                                       if (($used_providers{$cgiparams{'ID'}}[0] eq "$provider") || ($cgiparams{'PROVIDER'} eq "$provider")) {
+                                                               $selected{$provider} = "selected='selected'";
+                                                       }
+
+                                                       # Add the provider to the dropdown menu.
+                                                       print "<option value='$provider' $selected{$provider}>$provider_name</option>\n";
+                                               }
+print <<END
+                                       </select>
+                               </td>
+
+                               <td width='60%'>
+                                       <b><a id="website" target="_blank" href="#">$Lang::tr{'ids visit provider website'}</a></b>
+                               </td>
+                       </tr>
+
+                       <tr>
+                               <td colspan='2'><br><br></td>
+                       </tr>
+
+                       <tr class='subscription_code' style='display:none' id='subscription_code'>
+                               <td colspan='2'>
+                                       <table border='0'>
+                                               <tr>
+                                                       <td>
+                                                               <b>$Lang::tr{'subscription code'}</b>
+                                                       </td>
+                                               </tr>
+
+                                               <tr>
+                                                       <td>
+                                                               <input type='text' size='40' name='SUBSCRIPTION_CODE' value='$used_providers{$cgiparams{'ID'}}[1]'>
+                                                       </td>
+                                               </tr>
+
+                                               <tr>
+                                                       <td><br><br></td>
+                                               </tr>
+                                       </table>
+                               </td>
+                       </tr>
+
+                       <tr>
+                               <td colspan='2'>
+                                       <input type='checkbox' name='ENABLE_AUTOUPDATE' $checked{'ENABLE_AUTOUPDATE'}>&nbsp;$Lang::tr{'ids enable automatic updates'}
+                               </td>
+                       </tr>
+
+                       <tr>
+                               <td colspan='2' align='right'>
+                                       <input type='submit' value='$Lang::tr{'back'}'>
+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 "<input type='submit' name='PROVIDERS' value='$Lang::tr{'update'}'>\n";
+                               } else {
+                                       # Display button to add the new provider.
+                                       print "<input type='submit' name='PROVIDERS' value='$Lang::tr{'add'}'>\n";
+                               }
+print <<END
+                               </td>
+                       </tr>
+               </table>
+       </form>
+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
+               <form method='post' action='$ENV{'SCRIPT_NAME'}'>
+                       <table width='100%' border="0">
+                               <tr>
+                                       <td align='center'>
+                                               <input type='hidden' name='PROVIDER' value='$provider'>
+                                               <input type='submit' name='PROVIDERS' value='$Lang::tr{'ids reset provider'}' $disabled>
+                                               <input type='submit' name='PROVIDERS' value='$Lang::tr{'ids force ruleset update'}'>
+                                       </td>
+                               </tr>
+                       </table>
+               </form>                 
+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) = @_;
-
-       # 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(<FILE>) {
-               # 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;
-               }
+sub get_provider_handle($) {
+       my ($id) = @_;
+
+       my %used_providers = ();
+
+       # Read-in provider settings file.
+       &General::readhasharray($IDS::providers_settings_file, \%used_providers);
+
+       # Obtain the provider handle for the given ID.
+       my $provider_handle = $used_providers{$cgiparams{'ID'}}[0];
+
+       # Return the handle.
+       return $provider_handle;
+}
+
+#
+## 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;
+
+       # Get the required translation string for the given provider handle.
+       my $tr_string = $IDS::Ruleset::Providers{$handle}{'tr_string'};
+
+       # 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;
+}
+
+#
+## 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});
 
-       # Return the hash.
-       return %temphash;
+       # Write the changed hash to the provider settings file.
+       &General::writehasharray($IDS::providers_settings_file, \%used_providers);
 }
 
 #
index c81b28fea62ff2b657ab5ce82098214f81bb85e5..bb2e8f8e2a1a3806c61b002d2a04ee34fc417e48 100644 (file)
 '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',
 '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' => '&quot;Ignorieren&quot;-Filter',
index 2f7038fb1f85310ff9566737633e845117263e9f..978e3176ea72396cd8aae5f90e2c77e291430569 100644 (file)
 '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',
 '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',
 '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.',
 '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',
index e0156c7467519a111d20bbec9f4e3f2bf233a24f..60dc55bfd2f6afadd5140ccb22c8c85818f1c169 100644 (file)
@@ -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-*
index 6a24a02ab8194cbee7143f427a8f8c6f7680d9a7..a870e3668c63216096e02dc9a6221c146d9ce10a 100644 (file)
@@ -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
index dbe5b6849c3da172c83f4c42c69057cd34151863..10a2709074dfecc3575834d9a7970269b6501b65 100644 (file)
@@ -2,7 +2,7 @@
 ###############################################################################
 #                                                                             #
 # IPFire.org - A linux based firewall                                         #
-# Copyright (C) 2018 IPFire Team  <info@ipfire.org>                           #
+# Copyright (C) 2018-2021 IPFire Team  <info@ipfire.org>                      #
 #                                                                             #
 # 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,20 +77,36 @@ 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();
 
-# Set correct ownership for the downloaded tarball.
-&IDS::set_ownership("$IDS::rulestarball");
+               # 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");
+}
 
 # Call oinkmaster to alter the ruleset.
 &IDS::oinkmaster();