X-Git-Url: http://git.ipfire.org/?p=ipfire-2.x.git;a=blobdiff_plain;f=html%2Fcgi-bin%2Fids.cgi;h=604d216c8938fea9aee7055cb95f399b4bf89866;hp=9863251e2d8817f74211383b844f170a200af636;hb=cf02bf2f7d23f9755a6e08383dd46fa9033d924b;hpb=1f606aefce745eca789014668c2fb3691f29dbc8 diff --git a/html/cgi-bin/ids.cgi b/html/cgi-bin/ids.cgi index 9863251e2d..604d216c89 100644 --- a/html/cgi-bin/ids.cgi +++ b/html/cgi-bin/ids.cgi @@ -2,7 +2,7 @@ ############################################################################### # # # IPFire.org - A linux based firewall # -# Copyright (C) 2007-2015 IPFire Team # +# Copyright (C) 2007-2018 IPFire Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # @@ -24,390 +24,655 @@ use strict; # enable only the following on debugging purpose #use warnings; #use CGI::Carp 'fatalsToBrowser'; -use File::Copy; require '/var/ipfire/general-functions.pl'; require "${General::swroot}/lang.pl"; require "${General::swroot}/header.pl"; - -sub refreshpage{&Header::openbox( 'Waiting', 1, "" );print "

$Lang::tr{'pagerefresh'}
";&Header::closebox();} - -$a = new CGI; +require "${General::swroot}/ids-functions.pl"; my %color = (); my %mainsettings = (); +my %idsrules = (); +my %idssettings=(); +my %rulessettings=(); +my %rulesetsources = (); +my %cgiparams=(); +my %checked=(); +my %selected=(); +my %ignored=(); + +# Read-in main settings, for language, theme and colors. &General::readhash("${General::swroot}/main/settings", \%mainsettings); &General::readhash("/srv/web/ipfire/html/themes/".$mainsettings{'THEME'}."/include/colors.txt", \%color); -my %snortsettings=(); -my %checked=(); -my %selected=(); -my %netsettings=(); -our $errormessage = ''; -our $results = ''; -our $tempdir = ''; -our $url=''; -&General::readhash("${General::swroot}/ethernet/settings", \%netsettings); +# Get the available network zones, based on the config type of the system and store +# the list of zones in an array. +my @network_zones = &IDS::get_available_network_zones(); + +my $errormessage; + +# Create files if they does not exist yet. +&IDS::check_and_create_filelayout(); + +# Hash which contains the colour code of a network zone. +my %colourhash = ( + 'red' => $Header::colourred, + 'green' => $Header::colourgreen, + 'blue' => $Header::colourblue, + 'orange' => $Header::colourorange +); &Header::showhttpheaders(); -$snortsettings{'ENABLE_SNORT'} = 'off'; -$snortsettings{'ENABLE_SNORT_GREEN'} = 'off'; -$snortsettings{'ENABLE_SNORT_BLUE'} = 'off'; -$snortsettings{'ENABLE_SNORT_ORANGE'} = 'off'; -$snortsettings{'ACTION'} = ''; -$snortsettings{'RULES'} = ''; -$snortsettings{'OINKCODE'} = ''; -$snortsettings{'INSTALLDATE'} = ''; -$snortsettings{'FILE'} = ''; -$snortsettings{'UPLOAD'} = ''; - -&Header::getcgihash(\%snortsettings, {'wantfile' => 1, 'filevar' => 'FH'}); - -####################### Added for snort rules control ################################# -my $snortrulepath; # change to "/etc/snort/rules" - maniac -my @snortconfig; -my $restartsnortrequired = 0; -my %snortrules; -my $rule = ''; -my $table1colour = ''; -my $table2colour = ''; -my $var = ''; -my $value = ''; -my $tmp = ''; -my $linkedrulefile = ''; -my $border = ''; -my $checkboxname = ''; - -if (-e "/etc/snort/snort.conf") { - - - # Open snort.conf file, read it in, close it, and re-open for writing - open(FILE, "/etc/snort/snort.conf") or die 'Unable to read snort config file.'; - @snortconfig = ; - close(FILE); - open(FILE, ">/etc/snort/snort.conf") or die 'Unable to write snort config file.'; - - my @rules = `cd /etc/snort/rules/ && ls *.rules 2>/dev/null`; # With this loop the rule might be display with correct rulepath set - foreach (@rules) { - chomp $_; - my $temp = join(";",@snortconfig); - if ( $temp =~ /$_/ ){next;} - else { push(@snortconfig,"#include \$RULE_PATH/".$_);} - } - - # Loop over each line - foreach my $line (@snortconfig) { - # Trim the line - chomp $line; +#Get GUI values +&Header::getcgihash(\%cgiparams); - # Check for a line with .rules - if ($line =~ /\.rules$/) { - # Parse out rule file name - $rule = $line; - $rule =~ s/\$RULE_PATH\///i; - $rule =~ s/ ?include ?//i; - $rule =~ s/\#//i; - my $snortrulepathrule = "$snortrulepath/$rule"; - - # Open rule file and read in contents - open(RULEFILE, "$snortrulepath/$rule") or die "Unable to read snort rule file for reading => $snortrulepath/$rule."; - my @snortrulefile = ; - close(RULEFILE); - open(RULEFILE, ">$snortrulepath/$rule") or die "Unable to write snort rule file for writing $snortrulepath/$rule"; +## Add/edit an entry to the ignore file. +# +if (($cgiparams{'WHITELIST'} eq $Lang::tr{'add'}) || ($cgiparams{'WHITELIST'} eq $Lang::tr{'update'})) { - # Local vars - my $dashlinecnt = 0; - my $desclook = 1; - my $snortruledesc = ''; - my %snortruledef = (); - my $rulecnt = 1; - - # Loop over rule file contents - foreach my $ruleline (@snortrulefile) { - chomp $ruleline; - - # If still looking for a description - if ($desclook) { - # If line does not start with a # anymore, then done looking for a description - if ($ruleline !~ /^\#/) { - $desclook = 0; - } + # Check if any input has been performed. + if ($cgiparams{'IGNORE_ENTRY_ADDRESS'} ne '') { - # If see more than one dashed line, (start to) create rule file description - if ($dashlinecnt > 1) { - # Check for a line starting with a # - if ($ruleline =~ /^\#/ and $ruleline !~ /^\#alert/) { - # Create tempruleline - my $tempruleline = $ruleline; - - # Strip off # and clean up line - $tempruleline =~ s/\# ?//i; - - # Check for part of a description - if ($snortruledesc eq '') { - $snortruledesc = $tempruleline; - } else { - $snortruledesc .= " $tempruleline"; - } - } else { - # Must be done - $desclook = 0; - } - } + # Check if the given input is no valid IP-address or IP-address with subnet, display an error message. + if ((!&General::validip($cgiparams{'IGNORE_ENTRY_ADDRESS'})) && (!&General::validipandmask($cgiparams{'IGNORE_ENTRY_ADDRESS'}))) { + $errormessage = "$Lang::tr{'guardian invalid address or subnet'}"; + } + } else { + $errormessage = "$Lang::tr{'guardian empty input'}"; + } - # If have a dashed line, increment count - if ($ruleline =~ /\# ?\-+/) { - $dashlinecnt++; - } - } else { - # Parse out rule file rule's message for display - if ($ruleline =~ /(msg\:\"[^\"]+\";)/) { - my $msg = ''; - $msg = $1; - $msg =~ s/msg\:\"//i; - $msg =~ s/\";//i; - $snortruledef{$rulecnt}{'Description'} = $msg; - - # Check for 'Save' and rule file displayed in query string - if (($snortsettings{'ACTION'} eq $Lang::tr{'update'}) && ($ENV{'QUERY_STRING'} =~ /$rule/i)) { - # Check for a disable rule which is now enabled, or an enabled rule which is now disabled - if ((($ruleline =~ /^\#/) && (exists $snortsettings{"SNORT_RULE_$rule\_$rulecnt"})) || (($ruleline !~ /^\#/) && (!exists $snortsettings{"SNORT_RULE_$rule\_$rulecnt"}))) { - $restartsnortrequired = 1; - } - - # Strip out leading # from rule line - $ruleline =~ s/\# ?//i; - - # Check if it does not exists (which means it is disabled), append a # - if (!exists $snortsettings{"SNORT_RULE_$rule\_$rulecnt"}) { - $ruleline = "#"." $ruleline"; - } - } - - # Check if ruleline does not begin with a #, so it is enabled - if ($ruleline !~ /^\#/) { - $snortruledef{$rulecnt++}{'State'} = 'Enabled'; - } else { - # Otherwise it is disabled - $snortruledef{$rulecnt++}{'State'} = 'Disabled'; - } - } - } + # Go further if there was no error. + if ($errormessage eq '') { + my %ignored = (); + my $id; + my $status; - # Print ruleline to RULEFILE - print RULEFILE "$ruleline\n"; - } + # Assign hash values. + my $new_entry_address = $cgiparams{'IGNORE_ENTRY_ADDRESS'}; + my $new_entry_remark = $cgiparams{'IGNORE_ENTRY_REMARK'}; - # Close RULEFILE - close(RULEFILE); + # Read-in ignoredfile. + &General::readhasharray($IDS::ignored_file, \%ignored); - # Check for 'Save' - if ($snortsettings{'ACTION'} eq $Lang::tr{'update'}) { - # Check for a disable rule which is now enabled, or an enabled rule which is now disabled - if ((($line =~ /^\#/) && (exists $snortsettings{"SNORT_RULE_$rule"})) || (($line !~ /^\#/) && (!exists $snortsettings{"SNORT_RULE_$rule"}))) { - $restartsnortrequired = 1; - } + # Check if we should edit an existing entry and got an ID. + if (($cgiparams{'WHITELIST'} eq $Lang::tr{'update'}) && ($cgiparams{'ID'})) { + # Assin the provided id. + $id = $cgiparams{'ID'}; - # Strip out leading # from rule line - $line =~ s/\# ?//i; + # Undef the given ID. + undef($cgiparams{'ID'}); - # Check if it does not exists (which means it is disabled), append a # - if (!exists $snortsettings{"SNORT_RULE_$rule"}) { - $line = "# $line"; - } + # Grab the configured status of the corresponding entry. + $status = $ignored{$id}[2]; + } else { + # Each newly added entry automatically should be enabled. + $status = "enabled"; - } + # Generate the ID for the new entry. + # + # Sort the keys by their ID and store them in an array. + my @keys = sort { $a <=> $b } keys %ignored; - # Check for rule state - if ($line =~ /^\#/) { - $snortrules{$rule}{"State"} = "Disabled"; - } else { - $snortrules{$rule}{"State"} = "Enabled"; - } + # Reverse the key array. + my @reversed = reverse(@keys); - # Set rule description - $snortrules{$rule}{"Description"} = $snortruledesc; + # Obtain the last used id. + my $last_id = @reversed[0]; - # Loop over sorted rules - foreach my $ruledef (sort {$a <=> $b} keys(%snortruledef)) { - $snortrules{$rule}{"Definition"}{$ruledef}{'Description'} = $snortruledef{$ruledef}{'Description'}; - $snortrules{$rule}{"Definition"}{$ruledef}{'State'} = $snortruledef{$ruledef}{'State'}; - } + # Increase the last id by one and use it as id for the new entry. + $id = ++$last_id; + } + + # Add/Modify the entry to/in the ignored hash. + $ignored{$id} = ["$new_entry_address", "$new_entry_remark", "$status"]; + + # Write the changed ignored hash to the ignored file. + &General::writehasharray($IDS::ignored_file, \%ignored); - $snortruledesc = ''; - print FILE "$line\n"; - } elsif ($line =~ /var RULE_PATH/) { - ($tmp, $tmp, $snortrulepath) = split(' ', $line); - print FILE "$line\n"; + # Regenerate the ignore file. + &IDS::generate_ignore_file(); + } + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } + +## Toggle Enabled/Disabled for an existing entry on the ignore list. +# + +} elsif ($cgiparams{'WHITELIST'} eq $Lang::tr{'toggle enable disable'}) { + my %ignored = (); + + # Only go further, if an ID has been passed. + if ($cgiparams{'ID'}) { + # Assign the given ID. + my $id = $cgiparams{'ID'}; + + # Undef the given ID. + undef($cgiparams{'ID'}); + + # Read-in ignoredfile. + &General::readhasharray($IDS::ignored_file, \%ignored); + + # Grab the configured status of the corresponding entry. + my $status = $ignored{$id}[2]; + + # Switch the status. + if ($status eq "disabled") { + $status = "enabled"; } else { - print FILE "$line\n"; + $status = "disabled"; + } + + # Modify the status of the existing entry. + $ignored{$id} = ["$ignored{$id}[0]", "$ignored{$id}[1]", "$status"]; + + # Write the changed ignored hash to the ignored file. + &General::writehasharray($IDS::ignored_file, \%ignored); + + # Regenerate the ignore file. + &IDS::generate_ignore_file(); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); } } - close(FILE); - if ($restartsnortrequired) { - system('/usr/local/bin/snortctrl restart >/dev/null'); +## Remove entry from ignore list. +# +} elsif ($cgiparams{'WHITELIST'} eq $Lang::tr{'remove'}) { + my %ignored = (); + + # Read-in ignoredfile. + &General::readhasharray($IDS::ignored_file, \%ignored); + + # Drop entry from the hash. + delete($ignored{$cgiparams{'ID'}}); + + # Undef the given ID. + undef($cgiparams{'ID'}); + + # Write the changed ignored hash to the ignored file. + &General::writehasharray($IDS::ignored_file, \%ignored); + + # Regenerate the ignore file. + &IDS::generate_ignore_file(); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); } } -####################### End added for snort rules control ################################# +# Check if any error has been stored. +if (-e $IDS::storederrorfile) { + # Open file to read in the stored error message. + open(FILE, "<$IDS::storederrorfile") or die "Could not open $IDS::storederrorfile. $!\n"; -if ($snortsettings{'OINKCODE'} ne "") { - $errormessage = $Lang::tr{'invalid input for oink code'} unless ($snortsettings{'OINKCODE'} =~ /^[a-z0-9]+$/); + # Read the stored error message. + $errormessage = ; + + # Close file. + close (FILE); + + # Delete the file, which is now not longer required. + unlink($IDS::storederrorfile); } -if (!$errormessage) { - if ($snortsettings{'RULES'} eq 'subscripted') { - $url=" https://www.snort.org/rules/snortrules-snapshot-29111.tar.gz?oinkcode=$snortsettings{'OINKCODE'}"; - } elsif ($snortsettings{'RULES'} eq 'registered') { - $url=" https://www.snort.org/rules/snortrules-snapshot-29111.tar.gz?oinkcode=$snortsettings{'OINKCODE'}"; - } elsif ($snortsettings{'RULES'} eq 'community') { - $url=" https://www.snort.org/rules/community"; - } else { - $url="http://rules.emergingthreats.net/open/snort-2.9.0/emerging.rules.tar.gz"; +## Grab all available snort rules and store them in the idsrules hash. +# +# Open snort 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"); + + # Ignore empty files. + next if (-z "$IDS::rulespath/$file"); + + # Use a regular expression to find files ending in .rules + next unless ($file =~ m/\.rules$/); + + # Ignore files which are not read-able. + next unless (-R "$IDS::rulespath/$file"); + + # Skip whitelist rules file. + next if( $file eq "whitelist.rules"); + + # Call subfunction to read-in rulefile and add rules to + # the idsrules hash. + &readrulesfile("$file"); } - if ($snortsettings{'ACTION'} eq $Lang::tr{'save'} && $snortsettings{'ACTION2'} eq "snort" ) { - &General::writehash("${General::swroot}/snort/settings", \%snortsettings); - if ($snortsettings{'ENABLE_SNORT'} eq 'on') - { - system ('/usr/bin/touch', "${General::swroot}/snort/enable"); - } else { - unlink "${General::swroot}/snort/enable"; - } - if ($snortsettings{'ENABLE_SNORT_GREEN'} eq 'on') - { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_green"); - } else { - unlink "${General::swroot}/snort/enable_green"; +closedir(DIR); + +# 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"; + + # Read-in content. + my @lines = ; + + # Close file. + close(FILE); + + # Loop through the array. + foreach my $line (@lines) { + # Remove newlines. + chomp($line); + + # Skip comments. + next if ($line =~ /\#/); + + # Skip blank lines. + next if ($line =~ /^\s*$/); + + # Gather rule sid and message from the ruleline. + if ($line =~ /.*- (.*)/) { + my $rulefile = $1; + + # Check if the current rulefile exists in the %idsrules hash. + # If not, the file probably does not exist anymore or contains + # no rules. + if($idsrules{$rulefile}) { + # Add the rulefile state to the %idsrules hash. + $idsrules{$rulefile}{'Rulefile'}{'State'} = "on"; + } } - if ($snortsettings{'ENABLE_SNORT_BLUE'} eq 'on') - { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_blue"); - } else { - unlink "${General::swroot}/snort/enable_blue"; + } +} + +# Save ruleset configuration. +if ($cgiparams{'RULESET'} eq $Lang::tr{'save'}) { + my %oldsettings; + + # Read-in current (old) IDS settings. + &General::readhash("$IDS::rules_settings_file", \%oldsettings); + + # Prevent form name from been stored in conf file. + delete $cgiparams{'RULESET'}; + + # Check if an oinkcode has been provided. + if ($cgiparams{'OINKCODE'}) { + # Check if the oinkcode contains unallowed chars. + unless ($cgiparams{'OINKCODE'} =~ /^[a-z0-9]+$/) { + $errormessage = $Lang::tr{'invalid input for oink code'}; } - if ($snortsettings{'ENABLE_SNORT_ORANGE'} eq 'on') - { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_orange"); - } else { - unlink "${General::swroot}/snort/enable_orange"; + } + + # 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 download it. + unless (%idsrules) { + # 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'}"; } - if ($snortsettings{'ENABLE_PREPROCESSOR_HTTP_INSPECT'} eq 'on') - { - system ('/usr/bin/touch', "${General::swroot}/snort/enable_preprocessor_http_inspect"); - } else { - unlink "${General::swroot}/snort/enable_preprocessor_http_inspect"; + + # Check if enought free disk space is availabe. + if(&IDS::checkdiskspace()) { + $errormessage = "$Lang::tr{'not enough disk space'}"; } - system('/usr/local/bin/snortctrl restart >/dev/null'); + # Check if any errors happend. + unless ($errormessage) { + # Lock the webpage and print notice about downloading + # a new ruleset. + &working_notice("$Lang::tr{'snort working'}"); + + # 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(); + } + + # Perform a reload of the page. + &reload(); + } } - # INSTALLMD5 is not in the form, so not retrieved by getcgihash - &General::readhash("${General::swroot}/snort/settings", \%snortsettings); +# Save ruleset. +} elsif ($cgiparams{'RULESET'} eq $Lang::tr{'update'}) { + # Arrays to store which rulefiles have been enabled and will be used. + my @enabled_rulefiles; - if ($snortsettings{'ACTION'} eq $Lang::tr{'download new ruleset'} || $snortsettings{'ACTION'} eq $Lang::tr{'upload new ruleset'}) { - my @df = `/bin/df -B M /var`; - foreach my $line (@df) { - next if $line =~ m/^Filesystem/; - my $return; + # Hash to store the user-enabled and disabled sids. + my %enabled_disabled_sids; - if ($line =~ m/dev/ ) { - $line =~ m/^.* (\d+)M.*$/; - my @temp = split(/ +/,$line); - if ($1<300) { - $errormessage = "$Lang::tr{'not enough disk space'} < 300MB, /var $1MB"; - } else { - if ( $snortsettings{'ACTION'} eq $Lang::tr{'download new ruleset'}) { - &downloadrulesfile(); - sleep(3); - $return = `cat /var/tmp/log 2>/dev/null`; - - } elsif ( $snortsettings{'ACTION'} eq $Lang::tr{'upload new ruleset'}) { - my $upload = $a->param("UPLOAD"); - open UPLOADFILE, ">/var/tmp/snortrules.tar.gz"; - binmode $upload; - while ( <$upload> ) { - print UPLOADFILE; - } - close UPLOADFILE; - } + # Loop through the hash of idsrules. + foreach my $rulefile(keys %idsrules) { + # Check if the rulefile is enabled. + if ($cgiparams{$rulefile} eq "on") { + # Add rulefile to the array of enabled rulefiles. + push(@enabled_rulefiles, $rulefile); + + # Drop item from cgiparams hash. + delete $cgiparams{$rulefile}; + } + } - if ($return =~ "ERROR") { - $errormessage = "
".$return."
"; - } else { - system("/usr/local/bin/oinkmaster.pl -v -s -u file:///var/tmp/snortrules.tar.gz -C /var/ipfire/snort/oinkmaster.conf -o /etc/snort/rules >>/var/tmp/log 2>&1 &"); - sleep(2); + # Read-in the files for enabled/disabled sids. + # This will be done by calling the read_enabled_disabled_sids_file function two times + # and merge the returned hashes together into the enabled_disabled_sids hash. + %enabled_disabled_sids = ( + &read_enabled_disabled_sids_file($IDS::disabled_sids_file), + &read_enabled_disabled_sids_file($IDS::enabled_sids_file)); + + # Loop through the hash of idsrules. + foreach my $rulefile (keys %idsrules) { + # Loop through the single rules of the rulefile. + foreach my $sid (keys %{$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}; + } + } + } + } + + # 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; + } + } + } + + # Close file for enabled_sids after writing. + close(ENABLED_FILE); + + # Close file for disabled_sids after writing. + close(DISABLED_FILE); + + # Call function to generate and write the used rulefiles file. + &IDS::write_used_rulefiles_file(@enabled_rulefiles); + + # Lock the webpage and print message. + &working_notice("$Lang::tr{'snort working'}"); + + # Call oinkmaster to alter the ruleset. + &IDS::oinkmaster(); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } + + # Reload page. + &reload(); + +# Download new ruleset. +} elsif ($cgiparams{'RULESET'} eq $Lang::tr{'update ruleset'}) { + # Check if the red device is active. + unless (-e "${General::swroot}/red/active") { + $errormessage = "$Lang::tr{'could not download latest updates'} - $Lang::tr{'system is offline'}"; + } + + # Check if enought free disk space is availabe. + if(&IDS::checkdiskspace()) { + $errormessage = "$Lang::tr{'not enough disk space'}"; + } + + # Check if any errors happend. + unless ($errormessage) { + # Lock the webpage and print notice about downloading + # a new ruleset. + &working_notice("$Lang::tr{'snort working'}"); + + # 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); + + # Preform a reload of the page. + &reload(); + } else { + # Call subfunction to launch oinkmaster. + &IDS::oinkmaster(); + + # Check if the IDS is running. + if(&IDS::ids_is_running()) { + # Call suricatactrl to perform a reload. + &IDS::call_suricatactrl("reload"); + } + + # Perform a reload of the page. + &reload(); + } + } +# Save snort settings. +} elsif ($cgiparams{'IDS'} eq $Lang::tr{'save'}) { + my %oldidssettings; + my $reload_page; + my $monitored_zones = 0; + + # Read-in current (old) IDS settings. + &General::readhash("$IDS::ids_settings_file", \%oldidssettings); + + # 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'}; + } + + # Loop through the array of available interfaces. + foreach my $zone (@network_zones) { + # Convert interface name into upper case. + my $zone_upper = uc($zone); + + # Check if the IDS is enabled for this interaces. + if ($cgiparams{"ENABLE_IDS_$zone_upper"}) { + # Increase count. + $monitored_zones++; } } + + # Check if at least one zone should be monitored, or show an error. + unless ($monitored_zones >= 1) { + $errormessage = $Lang::tr{'ids no network zone'}; + } + } + + # Go on if there are no error messages. + if (!$errormessage) { + # Store settings into settings file. + &General::writehash("$IDS::ids_settings_file", \%cgiparams); + } + + # Generate file to store the home net. + &IDS::generate_home_net_file(); + + # Temporary variable to set the ruleaction. + # Default is "drop" to use suricata as IPS. + my $ruleaction="drop"; + + # Check if the traffic only should be monitored. + if($cgiparams{'MONITOR_TRAFFIC_ONLY'} eq 'on') { + # Switch the ruleaction to "alert". + # Suricata acts as an IDS only. + $ruleaction="alert"; + } + + # Write the modify sid's file and pass the taken ruleaction. + &IDS::write_modify_sids_file($ruleaction); + + # Check if "MONITOR_TRAFFIC_ONLY" has been changed. + if($cgiparams{'MONITOR_TRAFFIC_ONLY'} ne $oldidssettings{'MONITOR_TRAFFIC_ONLY'}) { + # Check if a ruleset exists. + if (%idsrules) { + # Lock the webpage and print message. + &working_notice("$Lang::tr{'snort working'}"); + + # Call oinkmaster to alter the ruleset. + &IDS::oinkmaster(); + + # Set reload_page to "True". + $reload_page="True"; + } + } + + # Check if the IDS currently is running. + if(&IDS::ids_is_running()) { + # Check if ENABLE_IDS is set to on. + if($cgiparams{'ENABLE_IDS'} eq "on") { + # Call suricatactrl to perform a reload of suricata. + &IDS::call_suricatactrl("reload"); + } else { + # Call suricatactrl to stop suricata. + &IDS::call_suricatactrl("stop"); + } + } else { + # Call suricatactrl to start suricata. + &IDS::call_suricatactrl("start"); + } + + # Check if the page should be reloaded. + if ($reload_page) { + # Perform a reload of the page. + &reload(); } } -$checked{'ENABLE_SNORT'}{'off'} = ''; -$checked{'ENABLE_SNORT'}{'on'} = ''; -$checked{'ENABLE_SNORT'}{$snortsettings{'ENABLE_SNORT'}} = "checked='checked'"; -$checked{'ENABLE_SNORT_GREEN'}{'off'} = ''; -$checked{'ENABLE_SNORT_GREEN'}{'on'} = ''; -$checked{'ENABLE_SNORT_GREEN'}{$snortsettings{'ENABLE_SNORT_GREEN'}} = "checked='checked'"; -$checked{'ENABLE_SNORT_BLUE'}{'off'} = ''; -$checked{'ENABLE_SNORT_BLUE'}{'on'} = ''; -$checked{'ENABLE_SNORT_BLUE'}{$snortsettings{'ENABLE_SNORT_BLUE'}} = "checked='checked'"; -$checked{'ENABLE_SNORT_ORANGE'}{'off'} = ''; -$checked{'ENABLE_SNORT_ORANGE'}{'on'} = ''; -$checked{'ENABLE_SNORT_ORANGE'}{$snortsettings{'ENABLE_SNORT_ORANGE'}} = "checked='checked'"; +# Read-in idssettings and rulesetsettings +&General::readhash("$IDS::ids_settings_file", \%idssettings); +&General::readhash("$IDS::rules_settings_file", \%rulessettings); + +# If no autoupdate intervall has been configured yet, set default value. +unless(exists($rulessettings{'AUTOUPDATE_INTERVAL'})) { + # Set default to "weekly". + $rulessettings{'AUTOUPDATE_INTERVAL'} = 'weekly'; +} + +# Read-in ignored hosts. +&General::readhasharray("$IDS::settingsdir/ignored", \%ignored); + +$checked{'ENABLE_IDS'}{'off'} = ''; +$checked{'ENABLE_IDS'}{'on'} = ''; +$checked{'ENABLE_IDS'}{$idssettings{'ENABLE_IDS'}} = "checked='checked'"; +$checked{'MONITOR_TRAFFIC_ONLY'}{'off'} = ''; +$checked{'MONITOR_TRAFFIC_ONLY'}{'on'} = ''; +$checked{'MONITOR_TRAFFIC_ONLY'}{$idssettings{'MONITOR_TRAFFIC_ONLY'}} = "checked='checked'"; $selected{'RULES'}{'nothing'} = ''; $selected{'RULES'}{'community'} = ''; $selected{'RULES'}{'emerging'} = ''; $selected{'RULES'}{'registered'} = ''; $selected{'RULES'}{'subscripted'} = ''; -$selected{'RULES'}{$snortsettings{'RULES'}} = "selected='selected'"; +$selected{'RULES'}{$rulessettings{'RULES'}} = "selected='selected'"; +$selected{'AUTOUPDATE_INTERVAL'}{'off'} = ''; +$selected{'AUTOUPDATE_INTERVAL'}{'daily'} = ''; +$selected{'AUTOUPDATE_INTERVAL'}{'weekly'} = ''; +$selected{'AUTOUPDATE_INTERVAL'}{$rulessettings{'AUTOUPDATE_INTERVAL'}} = "selected='selected'"; &Header::openpage($Lang::tr{'intrusion detection system'}, 1, ''); -####################### Added for snort rules control ################################# -print ""; +### Java Script ### print < - - + END ; -####################### End added for snort rules control ################################# &Header::openbigbox('100%', 'left', '', $errormessage); -############### -# DEBUG DEBUG -# &Header::openbox('100%', 'left', 'DEBUG'); -# my $debugCount = 0; -# foreach my $line (sort keys %snortsettings) { -# print "$line = $snortsettings{$line}
\n"; -# $debugCount++; -# } -# print " Count: $debugCount\n"; -# &Header::closebox(); -# DEBUG DEBUG -############### - if ($errormessage) { &Header::openbox('100%', 'left', $Lang::tr{'error messages'}); print "$errormessage\n"; @@ -415,250 +680,439 @@ if ($errormessage) { &Header::closebox(); } -my $return = `pidof oinkmaster.pl -x`; -chomp($return); -if ($return) { - &Header::openbox( 'Waiting', 1, "" ); +# Draw current state of the IDS +&Header::openbox('100%', 'left', $Lang::tr{'intrusion detection system'}); + +# Check if the IDS is running and obtain the process-id. +my $pid = &IDS::ids_is_running(); + +# Display some useful information, if suricata daemon is running. +if ($pid) { + # Gather used memory. + my $memory = &get_memory_usage($pid); + print < - - $Lang::tr{  - - $Lang::tr{'snort working'} - -
- -
-
+		
+			
+				
+			
+
+			
+				
+				
+			
+
+			
+				
+				
+				
+			
+
+			
+				
+				
+				
+			
+		
$Lang::tr{'intrusion detection'}
$Lang::tr{'guardian daemon'}$Lang::tr{'running'}
PID$Lang::tr{'memory'}
$pid$memory KB
END - my @output = `tail -20 /var/tmp/log`; - foreach (@output) { - print "$_"; - } +} else { + # Otherwise display a hint that the service is not launched. print < + + + + + + + + +
$Lang::tr{'intrusion detection'}
$Lang::tr{'guardian daemon'}$Lang::tr{'stopped'}
END - &Header::closebox(); - &Header::closebigbox(); - &Header::closepage(); - exit; - refreshpage(); } -&Header::openbox('100%', 'left', $Lang::tr{'intrusion detection system'}); +my $rulesdate; + +# Check if a ruleset allready has been downloaded. +if ( -f "$IDS::rulestarball"){ + # Call stat on the filename to obtain detailed information. + my @Info = stat("$IDS::rulestarball"); + + # Grab details about the creation time. + $rulesdate = localtime($Info[9]); +} + +# Only show this area, if a ruleset is present. +if (%idsrules) { + + print <

$Lang::tr{'settings'}

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








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

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

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

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

+ + + + Oinkcode:  + + + +   + + END ; -if ( -e "/var/tmp/snortrules.tar.gz"){ - my @Info = stat("/var/tmp/snortrules.tar.gz"); - $snortsettings{'INSTALLDATE'} = localtime($Info[9]); -} -print " $Lang::tr{'updates installed'}: $snortsettings{'INSTALLDATE'}"; + # Check if a ruleset has been downloaded yet. + if (%idsrules) { + # Display button to update the ruleset. + print"\n"; + } +print < + -print < - -

- - - - -
+ + END ; -if ($results ne '') { - print "$results"; -} +&Header::closebox(); + +# +# Whitelist / Ignorelist +# +&Header::openbox('100%', 'center', $Lang::tr{'guardian ignored hosts'}); + +print < + + $Lang::tr{'ip address'} + $Lang::tr{'remark'} + + +END + # Check if some hosts have been added to be ignored. + if (keys (%ignored)) { + my $col = ""; + + # Loop through all entries of the hash. + while( (my $key) = each %ignored) { + # Assign data array positions to some nice variable names. + my $address = $ignored{$key}[0]; + my $remark = $ignored{$key}[1]; + my $status = $ignored{$key}[2]; + + # Check if the key (id) number is even or not. + if ($cgiparams{'ID'} eq $key) { + $col="bgcolor='${Header::colouryellow}'"; + } elsif ($key % 2) { + $col="bgcolor='$color{'color22'}'"; + } else { + $col="bgcolor='$color{'color20'}'"; + } + + # Choose icon for the checkbox. + my $gif; + my $gdesc; + + # Check if the status is enabled and select the correct image and description. + if ($status eq 'enabled' ) { + $gif = 'on.gif'; + $gdesc = $Lang::tr{'click to disable'}; + } else { + $gif = 'off.gif'; + $gdesc = $Lang::tr{'click to enable'}; + } + +print < + $address + $remark + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + +END + } + } else { + # Print notice that currently no hosts are ignored. + print "\n"; + print "$Lang::tr{'guardian no entries'}\n"; + print "\n"; + } + + print "\n"; + + # Section to add new elements or edit existing ones. +print < +
+
+ +
+ +END + + # Assign correct headline and button text. + my $buttontext; + my $entry_address; + my $entry_remark; + + # Check if an ID (key) has been given, in this case an existing entry should be edited. + if ($cgiparams{'ID'} ne '') { + $buttontext = $Lang::tr{'update'}; + print "\n"; + + # Grab address and remark for the given key. + $entry_address = $ignored{$cgiparams{'ID'}}[0]; + $entry_remark = $ignored{$cgiparams{'ID'}}[1]; + } else { + $buttontext = $Lang::tr{'add'}; + print "\n"; + } + +print < + + + + + + + + + + +
$Lang::tr{'update'}
$Lang::tr{'dnsforward add a new entry'}
$Lang::tr{'ip address'}: $Lang::tr{'remark'}:
+
+END &Header::closebox(); -####################### Added for snort rules control ################################# -if ( -e "${General::swroot}/snort/enable" || -e "${General::swroot}/snort/enable_green" || -e "${General::swroot}/snort/enable_blue" || -e "${General::swroot}/snort/enable_orange" ) { +# Only show the section for configuring the ruleset if one is present. +if (%idsrules) { &Header::openbox('100%', 'LEFT', $Lang::tr{'intrusion detection system rules'}); - # Output display table for rule files - print ""; } - print "
"; - print ""; + print"\n"; - # Local vars - my $ruledisplaycnt = 1; - my $rulecnt = keys %snortrules; - $rulecnt++; - $rulecnt = $rulecnt / 2; + # Output display table for rule files + print "
\n"; # Loop over each rule file - foreach my $rulefile (sort keys(%snortrules)) { + foreach my $rulefile (sort keys(%idsrules)) { my $rulechecked = ''; - # Hide inkompatible Block rules - if ($rulefile =~'-BLOCK.rules') { - next; - } - - # Check if reached half-way through rule file rules to start new column - if ($ruledisplaycnt > $rulecnt) { - print "
"; - $ruledisplaycnt = 0; - } - # Check if rule file is enabled - if ($snortrules{$rulefile}{"State"} eq 'Enabled') { + if ($idsrules{$rulefile}{'Rulefile'}{'State'} eq 'on') { $rulechecked = 'CHECKED'; } - # Create rule file link, vars array, and display flag - my $rulefilelink = "?RULEFILE=$rulefile"; - my $rulefiletoclose = ''; - my @queryvars = (); - my $displayrulefilerules = 0; - - # Check for passed in query string - if ($ENV{'QUERY_STRING'}) { - # Split out vars - @queryvars = split(/\&/, $ENV{'QUERY_STRING'}); - - # Loop over values - foreach $value (@queryvars) { - # Split out var pairs - ($var, $linkedrulefile) = split(/=/, $value); - - # Check if var is 'RULEFILE' - if ($var eq 'RULEFILE') { - # Check if rulefile equals linkedrulefile - if ($rulefile eq $linkedrulefile) { - # Set display flag - $displayrulefilerules = 1; - - # Strip out rulefile from rulefilelink - $rulefilelink =~ s/RULEFILE=$linkedrulefile//g; - } else { - # Add linked rule file to rulefilelink - $rulefilelink .= "&RULEFILE=$linkedrulefile"; - } - } - } - } + # Convert rulefile name into category name. + my $categoryname = &_rulefile_to_category($rulefile); - # Strip out extra & & ? from rulefilelink - $rulefilelink =~ s/^\?\&/\?/i; + # Table and rows for the rule files. + print"\n"; + print"\n"; + print"\n"; + print"\n"; + print"\n"; - # Check for a single '?' and replace with page for proper link display - if ($rulefilelink eq '?') { - $rulefilelink = "ids.cgi"; - } - - # Output rule file name and checkbox - print ""; - print "\n"; + print"\n"; + print ""; + + # Increment rule count + $lines++; + } - # Increment ruledisplaycnt - $ruledisplaycnt++; + # If do not have a second rule for row, create empty cell + if (($lines % 2) != 0) { + print ""; + } + + # Close display table + print "
\n"; + print"\n"; + print"$rulefile\n"; + print"SHOW\n"; + print"
$rulefile
"; + # Rows which will be hidden per default and will contain the single rules. + print"
"; - print <"; + +print < - +   @@ -669,31 +1123,205 @@ END &Header::closebox(); } -####################### End added for snort rules control ################################# &Header::closebigbox(); &Header::closepage(); -sub downloadrulesfile { - my $peer; - my $peerport; +# +## A function to display a notice, to lock the webpage and +## tell the user which action currently will be performed. +# +sub working_notice ($) { + my ($message) = @_; + + &Header::openpage($Lang::tr{'intrusion detection system'}, 1, ''); + &Header::openbigbox('100%', 'left', '', $errormessage); + &Header::openbox( 'Waiting', 1,); + print < + + $Lang::tr{ + $message + + +END + &Header::closebox(); + &Header::closebigbox(); + &Header::closepage(); +} - unlink("/var/tmp/log"); +# +## A tiny function to perform a reload of the webpage after one second. +# +sub reload () { + print "\n"; - unless (-e "${General::swroot}/red/active") { - $errormessage = $Lang::tr{'could not download latest updates'}; - return undef; + # Stop the script. + exit; +} + +# +## Private function to read-in and parse rules of a given rulefile. +# +## The given file will be read, parsed and all valid rules will be stored by ID, +## message/description and it's state in the idsrules hash. +# +sub readrulesfile ($) { + my $rulefile = shift; + + # Open rule file and read in contents + open(RULEFILE, "$IDS::rulespath/$rulefile") or die "Unable to read $rulefile!"; + + # Store file content in an array. + my @lines = ; + + # Close file. + close(RULEFILE); + + # Loop over rule file contents + foreach my $line (@lines) { + # Remove whitespaces. + chomp $line; + + # Skip blank lines. + next if ($line =~ /^\s*$/); + + # Local vars. + my $sid; + my $msg; + + # Gather rule sid and message from the ruleline. + if ($line =~ m/.*msg:\"(.*?)\"\; .* sid:(.*?); /) { + $msg = $1; + $sid = $2; + + # Check if a rule has been found. + if ($sid && $msg) { + # Add rule to the idsrules hash. + $idsrules{$rulefile}{$sid}{'Description'} = $msg; + + # Grab status of the rule. Check if ruleline starts with a "dash". + if ($line =~ /^\#/) { + # If yes, the rule is disabled. + $idsrules{$rulefile}{$sid}{'State'} = "off"; + } else { + # Otherwise the rule is enabled. + $idsrules{$rulefile}{$sid}{'State'} = "on"; + } + } + } } +} - my %proxysettings=(); - &General::readhash("${General::swroot}/proxy/settings", \%proxysettings); +# +## Function to get the used memory of a given process-id. +# +sub get_memory_usage($) { + my ($pid) = @_; + + my $memory = 0; + + # Try to open the status file for the given process-id on the pseudo + # file system proc. + if (open(FILE, "/proc/$pid/status")) { + # Loop through the entire file. + while () { + # Splitt current line content and store them into variables. + my ($key, $value) = split(":", $_, 2); + + # Check if the current key is the one which contains the memory usage. + # The wanted one is VmRSS which contains the Real-memory (resident set) + # of the entire process. + if ($key eq "VmRSS") { + # Found the memory usage add it to the memory variable. + $memory += $value; + + # Break the loop. + last; + } + } - if ($_=$proxysettings{'UPSTREAM_PROXY'}) { - ($peer, $peerport) = (/^(?:[a-zA-Z ]+\:\/\/)?(?:[A-Za-z0-9\_\.\-]*?(?:\:[A-Za-z0-9\_\.\-]*?)?\@)?([a-zA-Z0-9\.\_\-]*?)(?:\:([0-9]{1,5}))?(?:\/.*?)?$/); + # Close file handle. + close(FILE); + + # Return memory usage. + return $memory; } - if ($peer) { - system("wget -r --proxy=on --proxy-user=$proxysettings{'UPSTREAM_USER'} --proxy-passwd=$proxysettings{'UPSTREAM_PASSWORD'} -e http_proxy=http://$peer:$peerport/ -o /var/tmp/log --output-document=/var/tmp/snortrules.tar.gz $url"); - } else { - system("wget -r -o /var/tmp/log --output-document=/var/tmp/snortrules.tar.gz $url"); + # If the file could not be open, return nothing. + return; +} + +# +## Function to read-in the given enabled or disables sids file. +# +sub read_enabled_disabled_sids_file($) { + my ($file) = @_; + + # Temporary hash to store the sids and their state. It will be + # returned at the end of this function. + my %temphash; + + # Open the given filename. + open(FILE, "$file") or die "Could not open $file. $!\n"; + + # Loop through the file. + while() { + # Remove newlines. + chomp $_; + + # Skip blank lines. + next if ($_ =~ /^\s*$/); + + # Skip coments. + next if ($_ =~ /^\#/); + + # Splitt line into sid and state part. + my ($state, $sid) = split(" ", $_); + + # Skip line if the sid is not numeric. + next unless ($sid =~ /\d+/ ); + + # Check if the sid was enabled. + if ($state eq "enablesid") { + # Add the sid and its state as enabled to the temporary hash. + $temphash{$sid} = "enabled"; + # Check if the sid was disabled. + } elsif ($state eq "disablesid") { + # Add the sid and its state as disabled to the temporary hash. + $temphash{$sid} = "disabled"; + # Invalid state - skip the current sid and state. + } else { + next; + } } + + # Close filehandle. + close(FILE); + + # Return the hash. + return %temphash; +} + +# +## Private function to convert a given rulefile to a category name. +## ( No file extension anymore and if the name contained a dot, it +## would be replaced by a underline sign.) +# +sub _rulefile_to_category($) { + my ($filename) = @_; + + # Splitt the filename into single chunks and store them in a + # temorary array. + my @parts = split(/\./, $filename); + + # Return / Remove last element of the temporary array. + # This removes the file extension. + pop @parts; + + # Join together the single elements of the temporary array. + # If these are more than one, use a "underline" for joining. + my $category = join '_', @parts; + + # Return the converted filename. + return $category; }