oinkmaster-2.0004074400017500001750000000000001037561221100136625ustar00andreasoandreasooinkmaster-2.0/README.templates010064400017500001750000000113331037561221100166160ustar00andreasoandreaso$Id: README.templates,v 1.11 2006/01/29 14:50:25 andreas_o Exp $ # General info ------------ Modifysid templates are nothing more than an easier way to use the standard modifysid expressions. With a template, you only need to define the (often complex) modifysid expression once, and then call the template by name every time you want to use it instead of repeating the modifysid expression. For more information about modifysid, see the default oinkmaster.conf file. Basically, a modifysid expression is simply a substitution expression that will be applied on specified rules or files after each update, so that you can make some tweaks to the rules. Lots of template examples can be found in the template-examples.conf file. Usage syntax ------------ First you use "define_template" to define a template, and then "use_template" to use it. You must make sure the template is defined before you attempt to use it. You can either define and/or use the templates directly in your regular oinkmaster.conf or put them in a separate file, e.g. templates.conf. When using a separate file, make sure both files are loaded, i.e. either start Oinkmaster with "oinkmaster.pl -C templates.conf -C oinkmaster.conf ..." or use the "include" directive inside oinkmaster.conf. The syntax to define a template is: define_template "modifythis" | "withthis" "modifythis" is a Perl regular expression that will match the pattern you want to replace with the "withthis" expression. It is basically passed to a s/modifythis/withthis/ statement in Perl. This means that you must escape special characters to match them as strings. The syntax to use a template is: use_template [ "arg1" "arg2" ... ] As , you can also specify the wildcard ("*"), a comma-separated list of SIDs or even one or more filenames, just as with a regular modifysid expression. Everything after is optional, unless you require arguments in your template definition (keep reading). The arguments must be quoted strings, separated with space. The "modifythis" | "withthis" stuff in define_template works just like in a regular modifysid, but it has one more useful feature. The modifysid expression (the substitution string and/or the replacement string) can contain the special string %ARGx%, where x is a number from 1 and up. Before the substitution on the signature occurs, all the %ARGx% will be replaced with the corresponding arguments to use_template. So %ARG1% will be replaced with the first argument, %ARG2% with the second one, and so on. This is useful when you for example want to add "tag" statements (or thresholding/limiting or whatever) to rules using a modifysid template, but you want the number of seconds to tag to be different for different rules. By giving this value as argument when using the template, there is no need to write a new template for each value for the number of seconds. This may sound confusing, but have a look at the examples in template-examples.conf and it will hopefully make sense. If you use variables in the substitution expression, it is strongly recommended to always specify them like ${varname} instead of $varname (${1} instead of $1 for example) to avoid parsing confusion in some situations. Usage hints ----------- When appending new stuff to rules using templates (or regular modifysid expressions) it usually doesn't matter where in the rule you put it unless it affects the way Snort parses and optimizes the rules. The important thing is to write the substitution expression so that it will continue to work even if the original rule becomes updated somehow. Imagine this rule: alert tcp any any -> any any (msg: "foo"; flow:established; sid: 123;) Now if we want to add something to it, we need to find some part of the rule to match, and then replace that part with itself + the stuff we want to add. A bad example to do this is to match "flow:established;" and replace it with "flow:established; newstuff;", because if the rule is updated and the flow statement is changed to "flow:established,to_server;", our substitution expression would no longer match. A better approach is to match against something you know isn't going to change, like the SID statement or the very end of the rule. Also try not to be strict about whitespaces. For example, remember that a SID statement can be written as "sid:123;" or "sid: 123;" or "sid : 123 ;" and so on. As use_template statements are simply translated into modifysid statements, you can use multiple use_template for the same SID. They will be processed in order of appearance in the config file. Also remember that they apply both on active and inactive (disabled) rules. Example template definitions ---------------------------- See the template-examples.conf file! oinkmaster-2.0/oinkmaster.pl010074400017500001750000002656741037561221100164760ustar00andreasoandreaso#!/usr/bin/perl -w # $Id: oinkmaster.pl,v 1.406 2006/02/10 13:02:44 andreas_o Exp $ # # Copyright (c) 2001-2006 Andreas Östling # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # 3. Neither the name of the author nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use 5.006001; use strict; use File::Basename; use File::Copy; use File::Path; use File::Spec; use Getopt::Long; use File::Temp qw(tempdir); sub show_usage(); sub parse_cmdline($); sub read_config($ $); sub sanity_check(); sub download_file($ $); sub unpack_rules_archive($ $ $); sub join_tmp_rules_dirs($ $ @); sub process_rules($ $ $ $ $ $); sub process_rule($ $ $ $ $ $ $ $); sub setup_rules_hash($ $); sub get_first_only($ $ $); sub print_changes($ $); sub print_changetype($ $ $ $); sub print_summary_change($ $); sub make_backup($ $); sub get_changes($ $ $); sub update_rules($ @); sub copy_rules($ $); sub is_in_path($); sub get_next_entry($ $ $ $ $ $); sub get_new_vars($ $ $ $); sub add_new_vars($ $); sub write_new_vars($ $); sub msdos_to_cygwin_path($); sub parse_mod_expr($ $ $ $); sub untaint_path($); sub approve_changes(); sub parse_singleline_rule($ $ $); sub join_multilines($); sub minimize_diff($ $); sub catch_sigint(); sub clean_exit($); my $VERSION = 'Oinkmaster v2.0, Copyright (C) 2001-2006 '. 'Andreas Östling '; my $OUTFILE = 'snortrules.tar.gz'; my $RULES_DIR = 'rules'; my $PRINT_NEW = 1; my $PRINT_OLD = 2; my $PRINT_BOTH = 3; my %config = ( careful => 0, check_removed => 0, config_test_mode => 0, enable_all => 0, interactive => 0, make_backup => 0, minimize_diff => 0, min_files => 1, min_rules => 1, quiet => 0, summary_output => 0, super_quiet => 0, update_vars => 0, use_external_bins => 1, verbose => 0, use_path_checks => 1, rule_actions => "alert|drop|log|pass|reject|sdrop|activate|dynamic", tmp_basedir => $ENV{TMP} || $ENV{TMPDIR} || $ENV{TEMPDIR} || '/tmp', ); # Regexp to match the start of a multi-line rule. # %ACTIONS% will be replaced with content of $config{actions} later. # sid and msg will then be looked for in parse_singleline_rule(). my $MULTILINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.*\\\\\s*\n$'; # '; # Regexp to match a single-line rule. # sid and msg will then be looked for in parse_singleline_rule(). my $SINGLELINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.+;\s*\)\s*$'; # '; # Match var line where var name goes into $1. my $VAR_REGEXP = '^\s*var\s+(\S+)\s+(\S+)'; # Allowed characters in misc paths/filenames, including the ones in the tarball. my $OK_PATH_CHARS = 'a-zA-Z\d\ _\(\)\[\]\.\-+:\\\/~@,='; # Default locations for configuration file. my @DEFAULT_CONFIG_FILES = qw( /etc/oinkmaster.conf /usr/local/etc/oinkmaster.conf ); my @DEFAULT_DIST_VAR_FILES = qw( snort.conf ); my (%loaded, $tmpdir); #### MAIN #### # No buffering. select(STDERR); $| = 1; select(STDOUT); $| = 1; my $start_date = scalar(localtime); # Assume the required Perl modules are available if we're on Windows. $config{use_external_bins} = 0 if ($^O eq "MSWin32"); # Parse command line arguments and add at least %config{output_dir}. parse_cmdline(\%config); # If no config was specified on command line, look for one in default locations. if ($#{$config{config_files}} == -1) { foreach my $config (@DEFAULT_CONFIG_FILES) { if (-e "$config") { push(@{${config{config_files}}}, $config); last; } } } # If no dist var file was specified on command line, set to default file(s). if ($#{$config{dist_var_files}} == -1) { foreach my $var_file (@DEFAULT_DIST_VAR_FILES) { push(@{${config{dist_var_files}}}, $var_file); } } # If config is still not defined, we can't continue. if ($#{$config{config_files}} == -1) { clean_exit("configuration file not found in default locations\n". "(@DEFAULT_CONFIG_FILES)\n". "Put it there or use the \"-C \" argument."); } read_config($_, \%config) for @{$config{config_files}}; # Now substitute "%ACTIONS%" with $config{rule_actions}, which may have # been modified after reading the config file. $SINGLELINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; $MULTILINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; # If we're told not to use external binaries, load the Perl modules now. unless ($config{use_external_bins}) { print STDERR "Loading Perl modules.\n" if ($config{verbose}); eval { require IO::Zlib; require Archive::Tar; require LWP::UserAgent; }; clean_exit("failed to load required Perl modules:\n\n$@\n". "Install them or set use_external_bins to 1 ". "if you want to use external binaries instead.") if ($@); } # Do some basic sanity checking and exit if something fails. # A new PATH will be set. sanity_check(); $SIG{INT} = \&catch_sigint; # Create temporary dir. $tmpdir = tempdir("oinkmaster.XXXXXXXXXX", DIR => File::Spec->rel2abs($config{tmp_basedir})) or clean_exit("could not create temporary directory in $config{tmp_basedir}: $!"); # If we're in config test mode and have come this far, we're done. if ($config{config_test_mode}) { print "No fatal errors in configuration.\n"; clean_exit(""); } umask($config{umask}) if exists($config{umask}); # Download and unpack all the rules archives into separate tmp dirs. my @url_tmpdirs; foreach my $url (@{$config{url}}) { my $url_tmpdir = tempdir("url.XXXXXXXXXX", DIR => $tmpdir) or clean_exit("could not create temporary directory in $tmpdir: $!"); push(@url_tmpdirs, "$url_tmpdir/$RULES_DIR"); if ($url =~ /^dir:\/\/(.+)/) { mkdir("$url_tmpdir/$RULES_DIR") or clean_exit("Could not create $url_tmpdir/$RULES_DIR"); copy_rules($1, "$url_tmpdir/$RULES_DIR"); } else { download_file($url, "$url_tmpdir/$OUTFILE"); unpack_rules_archive("$url", "$url_tmpdir/$OUTFILE", $RULES_DIR); } } # Copy all rules files from the tmp dirs into $RULES_DIR in the tmp directory. # File matching 'skipfile' a directive will not be copied. # Filenames (with full path) will be stored as %new_files{filename}. # Will exit in case of duplicate filenames. my $num_files = join_tmp_rules_dirs("$tmpdir/$RULES_DIR", \my %new_files, @url_tmpdirs); # Make sure we have at least the minimum number of files. clean_exit("not enough rules files in downloaded rules archive(s).\n". "Number of rules files is $num_files but minimum is set to $config{min_files}.") if ($num_files < $config{min_files}); # This is to read in possible 'localsid' rules. my %rh_tmp = setup_rules_hash(\%new_files, $config{output_dir}); # Disable/modify/clean downloaded rules. my $num_rules = process_rules(\@{$config{sid_modify_list}}, \%{$config{sid_disable_list}}, \%{$config{sid_enable_list}}, \%{$config{sid_local_list}}, \%rh_tmp, \%new_files); # Make sure we have at least the minimum number of rules. clean_exit("not enough rules in downloaded archive(s).\n". "Number of rules is $num_rules but minimum is set to $config{min_rules}.") if ($num_rules < $config{min_rules}); # Setup a hash containing the content of all processed rules files. my %rh = setup_rules_hash(\%new_files, $config{output_dir}); # Compare the new rules to the old ones. my %changes = get_changes(\%rh, \%new_files, $RULES_DIR); # Check for variables that exist in dist snort.conf(s) but not in local snort.conf. get_new_vars(\%changes, \@{$config{dist_var_files}}, $config{varfile}, \@url_tmpdirs) if ($config{update_vars}); # Find out if something had changed. my $something_changed = 0; $something_changed = 1 if (keys(%{$changes{modified_files}}) || keys(%{$changes{added_files}}) || keys(%{$changes{removed_files}}) || $#{$changes{new_vars}} > -1); # Update files listed in %changes{modified_files} (copy the new files # from the temporary directory into our output directory) and add new # variables to the local snort.conf if requested, unless we're running in # careful mode. Create backup first if running with -b. my $printed = 0; if ($something_changed) { if ($config{careful}) { print STDERR "Skipping backup since we are running in careful mode.\n" if ($config{make_backup} && (!$config{quiet})); } else { if ($config{interactive}) { print_changes(\%changes, \%rh); $printed = 1; } if (!$config{interactive} || ($config{interactive} && approve_changes)) { make_backup($config{output_dir}, $config{backup_dir}) if ($config{make_backup}); add_new_vars(\%changes, $config{varfile}) if ($config{update_vars}); update_rules($config{output_dir}, keys(%{$changes{modified_files}})); } } } else { print STDERR "No files modified - no need to backup old files, skipping.\n" if ($config{make_backup} && !$config{quiet}); } print "\nOinkmaster is running in careful mode - not updating anything.\n" if ($something_changed && $config{careful}); print_changes(\%changes, \%rh) if (!$printed && ($something_changed || !$config{quiet})); # Everything worked. Do a clean exit without any error message. clean_exit(""); # END OF MAIN # # Show usage information and exit. sub show_usage() { my $progname = basename($0); print STDERR << "RTFM"; $VERSION Usage: $progname -o [options] is where to put the new files. This should be the directory where you store your Snort rules. Options: -b Backup your old rules into before overwriting them -c Careful mode (dry run) - check for changes but do not update anything -C Use this configuration file instead of the default May be specified multiple times to load multiple files -e Enable all rules that are disabled by default -h Show this usage information -i Interactive mode - you will be asked to approve the changes (if any) -m Minimize diff when printing result by removing common parts in rules -q Quiet mode - no output unless changes were found -Q Super-quiet mode - like -q but even more quiet -r Check for rules files that exist in the output directory but not in the downloaded rules archive -s Leave out details in rules results, just print SID, msg and filename -S Look for new variables in this file in the downloaded archive instead of the default (@DEFAULT_DIST_VAR_FILES). Used in conjunction with -U. May be specified multiple times to search multiple files. -T Config test - just check configuration file(s) for errors/warnings -u Download from this URL instead of URL(s) in the configuration file (http|https|ftp|file|scp:// ... .tar.gz|.gz, or dir://) May be specified multiple times to grab multiple rules archives -U Merge new variables from downloaded snort.conf(s) into -v Verbose mode (debug) -V Show version and exit RTFM exit; } # Parse the command line arguments and exit if we don't like them. sub parse_cmdline($) { my $cfg_ref = shift; Getopt::Long::Configure("bundling"); my $cmdline_ok = GetOptions( "b=s" => \$$cfg_ref{backup_dir}, "c" => \$$cfg_ref{careful}, "C=s" => \@{$$cfg_ref{config_files}}, "e" => \$$cfg_ref{enable_all}, "h" => \&show_usage, "i" => \$$cfg_ref{interactive}, "m" => \$$cfg_ref{minimize_diff}, "o=s" => \$$cfg_ref{output_dir}, "q" => \$$cfg_ref{quiet}, "Q" => \$$cfg_ref{super_quiet}, "r" => \$$cfg_ref{check_removed}, "s" => \$$cfg_ref{summary_output}, "S=s" => \@{$$cfg_ref{dist_var_files}}, "T" => \$$cfg_ref{config_test_mode}, "u=s" => \@{$$cfg_ref{url}}, "U=s" => \$$cfg_ref{varfile}, "v" => \$$cfg_ref{verbose}, "V" => sub { print "$VERSION\n"; exit(0); } ); show_usage unless ($cmdline_ok && $#ARGV == -1); $$cfg_ref{quiet} = 1 if ($$cfg_ref{super_quiet}); $$cfg_ref{update_vars} = 1 if ($$cfg_ref{varfile}); if ($$cfg_ref{backup_dir}) { $$cfg_ref{backup_dir} = File::Spec->canonpath($$cfg_ref{backup_dir}); $$cfg_ref{make_backup} = 1; } # Cannot specify dist var files without specifying var target file. if (@{$$cfg_ref{dist_var_files}} && !$$cfg_ref{update_vars}) { clean_exit("You can not specify distribution variable file(s) without ". "also specifying local file to merge into"); } # -o is the only required option in normal usage. if ($$cfg_ref{output_dir}) { $$cfg_ref{output_dir} = File::Spec->canonpath($$cfg_ref{output_dir}); } else { warn("Error: no output directory specified.\n"); show_usage(); } # Mark that url was set on command line (so we don't override it later). $$cfg_ref{cmdline_url} = 1 if ($#{$config{url}} > -1); } # Read in stuff from the configuration file. sub read_config($ $) { my $config_file = shift; my $cfg_ref = shift; my $linenum = 0; my $multi; my %templates; $config_file = File::Spec->canonpath(File::Spec->rel2abs($config_file)); clean_exit("configuration file \"$config_file\" does not exist.\n") unless (-e "$config_file"); clean_exit("\"$config_file\" is not a file.\n") unless (-f "$config_file"); print STDERR "Loading $config_file\n" unless ($config{quiet}); # Avoid loading the same file multiple times to avoid infinite recursion etc. if ($^O eq "MSWin32") { clean_exit("attempt to load \"$config_file\" twice.") if ($loaded{$config_file}++); } else { my ($dev, $ino) = (stat($config_file))[0,1] or clean_exit("unable to stat $config_file: $!"); clean_exit("attempt to load \"$config_file\" twice.") if ($loaded{$dev, $ino}++); } open(CONF, "<", "$config_file") or clean_exit("could not open configuration file \"$config_file\": $!"); my @conf = ; close(CONF); LINE:while ($_ = shift(@conf)) { $linenum++; unless ($multi) { s/^\s*//; s/^#.*//; } # Multi-line start/continuation. if (/\\\s*\n$/) { s/\\\s*\n$//; s/^\s*#.*//; # Be strict about removing #comments in modifysid/define_template statements, as # they may contain other '#' chars. if (defined($multi) && ($multi =~ /^modifysid/i || $multi =~ /^define_template/i)) { s/#.*// if (/^\s*\d+[,\s\d]+#/); } else { s/\s*\#.*// unless (/^modifysid/i || /^define_template/i); } $multi .= $_; next LINE; } # Last line of multi-line directive. if (defined($multi)) { $multi .= $_; $_ = $multi; undef($multi); } # Remove traling whitespaces (*after* a possible multi-line is rebuilt). s/\s*$//; # Remove comments unless it's a modifysid/define_template line # (the "#" may be part of the modifysid expression). s/\s*\#.*// unless (/^modifysid/i || /^define_template/i); # Skip blank lines. next unless (/\S/); # Use a template and make $_ a "modifysid" line. if (/^use_template\s+(\S+)\s+(\S+[^"]*)\s*(".*")*(?:#.*)*/i) { my ($template_name, $sid, $args) = ($1, $2, $3); if (exists($templates{$template_name})) { my $template = $templates{$template_name}; # so we don't substitute %ARGx% globally # Evaluate each "%ARGx%" in the template to the corresponding value. if (defined($args)) { my @args = split(/"\s+"/, $args); foreach my $i (1 .. @args) { $args[$i - 1] =~ s/^"//; $args[$i - 1] =~ s/"$//; $template =~ s/%ARG$i%/$args[$i - 1]/g; } } # There should be no %ARGx% stuff left now. if ($template =~ /%ARG\d%/) { warn("WARNING: too few arguments for template \"$template_name\"\n"); $_ = "error"; # so it will be reported as an invalid line later } unless ($_ eq "error") { $_ = "modifysid $sid $template\n"; print STDERR "Template \"$template_name\" expanded to: $_" if ($config{verbose}); } } else { warn("WARNING: template \"$template_name\" has not been defined\n"); } } # new template definition. if (/^define_template\s+(\S+)\s+(".+"\s+\|\s+".*")\s*(?:#.*)*$/i) { my ($template_name, $template) = ($1, $2); if (exists($templates{$template_name})) { warn("WARNING: line $linenum in $config_file: ". "template \"$template_name\" already defined, keeping old\n"); } else { $templates{$template_name} = $template; } # modifysid "substthis" | "withthis" } elsif (/^modifysids*\s+(\S+.*)\s+"(.+)"\s+\|\s+"(.*)"\s*(?:#.*)*$/i) { my ($sid_list, $subst, $repl) = ($1, $2, $3); warn("WARNING: line $linenum in $config_file is invalid, ignoring\n") unless(parse_mod_expr(\@{$$cfg_ref{sid_modify_list}}, $sid_list, $subst, $repl)); # disablesid } elsif (/^disablesids*\s+(\d.*)/i) { my $sid_list = $1; foreach my $sid (split(/\s*,\s*/, $sid_list)) { if ($sid =~ /^\d+$/) { $$cfg_ref{sid_disable_list}{$sid}++; } else { warn("WARNING: line $linenum in $config_file: ". "\"$sid\" is not a valid SID, ignoring\n"); } } # localsid } elsif (/^localsids*\s+(\d.*)/i) { my $sid_list = $1; foreach my $sid (split(/\s*,\s*/, $sid_list)) { if ($sid =~ /^\d+$/) { $$cfg_ref{sid_local_list}{$sid}++; } else { warn("WARNING: line $linenum in $config_file: ". "\"$sid\" is not a valid SID, ignoring\n"); } } # enablesid } elsif (/^enablesids*\s+(\d.*)/i) { my $sid_list = $1; foreach my $sid (split(/\s*,\s*/, $sid_list)) { if ($sid =~ /^\d+$/) { $$cfg_ref{sid_enable_list}{$sid}++; } else { warn("WARNING: line $linenum in $config_file: ". "\"$sid\" is not a valid SID, ignoring\n"); } } # skipfile } elsif (/^skipfiles*\s+(.*)/i) { my $args = $1; foreach my $file (split(/\s*,\s*/, $args)) { if ($file =~ /^\S+$/) { $config{verbose} && print STDERR "Adding file to ignore list: $file.\n"; $$cfg_ref{file_ignore_list}{$file}++; } else { warn("WARNING: line $linenum in $config_file is invalid, ignoring\n"); } } } elsif (/^url\s*=\s*(.*)/i) { push(@{$$cfg_ref{url}}, $1) unless ($$cfg_ref{cmdline_url}); } elsif (/^path\s*=\s*(.+)/i) { $$cfg_ref{path} = $1; } elsif (/^update_files\s*=\s*(.+)/i) { $$cfg_ref{update_files} = $1; } elsif (/^rule_actions\s*=\s*(.+)/i) { $$cfg_ref{rule_actions} = $1; } elsif (/^umask\s*=\s*([0-7]{4})$/i) { $$cfg_ref{umask} = oct($1); } elsif (/^min_files\s*=\s*(\d+)/i) { $$cfg_ref{min_files} = $1; } elsif (/^min_rules\s*=\s*(\d+)/i) { $$cfg_ref{min_rules} = $1; } elsif (/^tmpdir\s*=\s*(.+)/i) { $$cfg_ref{tmp_basedir} = $1; } elsif (/^use_external_bins\s*=\s*([01])/i) { $$cfg_ref{use_external_bins} = $1; } elsif (/^scp_key\s*=\s*(.+)/i) { $$cfg_ref{scp_key} = $1; } elsif (/^use_path_checks\s*=\s*([01])/i) { $$cfg_ref{use_path_checks} = $1; } elsif (/^user_agent\s*=\s*(.+)/i) { $$cfg_ref{user_agent} = $1; } elsif (/^include\s+(\S+.*)/i) { my $include = $1; read_config($include, $cfg_ref); } else { warn("WARNING: line $linenum in $config_file is invalid, ignoring\n"); } } } # Make a few basic tests to make sure things look ok. # Will also set a new PATH as defined in the config file. sub sanity_check() { my @req_params = qw(path update_files); # required parameters in conf my @req_binaries = qw(gzip tar); # required binaries (unless we use modules) # Can't use both quiet mode and verbose mode. clean_exit("quiet mode and verbose mode at the same time doesn't make sense.") if ($config{quiet} && $config{verbose}); # Can't use multiple output modes. clean_exit("can't use multiple output modes at the same time.") if ($config{minimize_diff} && $config{summary_output}); # Make sure all required variables are defined in the config file. foreach my $param (@req_params) { clean_exit("the required parameter \"$param\" is not defined in the configuration file.") unless (exists($config{$param})); } # We now know a path was defined in the config, so set it. # If we're under cygwin and path was specified as msdos style, convert # it to cygwin style to avoid problems. if ($^O eq "cygwin" && $config{path} =~ /^[a-zA-Z]:[\/\\]/) { $ENV{PATH} = ""; foreach my $path (split(/;/, $config{path})) { $ENV{PATH} .= "$path:" if (msdos_to_cygwin_path(\$path)); } chop($ENV{PATH}); } else { $ENV{PATH} = $config{path}; } # Reset environment variables that may cause trouble. delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; # Make sure $config{update_files} is a valid regexp. eval { "foo" =~ /$config{update_files}/; }; clean_exit("update_files (\"$config{update_files}\") is not a valid regexp: $@") if ($@); # Make sure $config{rule_actions} is a valid regexp. eval { "foo" =~ /$config{rule_actions}/; }; clean_exit("rule_actions (\"$config{rule_actions}\") is not a valid regexp: $@") if ($@); # If a variable file (probably local snort.conf) has been specified, # it must exist. It must also be writable unless we're in careful mode. if ($config{update_vars}) { $config{varfile} = untaint_path($config{varfile}); clean_exit("variable file \"$config{varfile}\" does not exist.") unless (-e "$config{varfile}"); clean_exit("variable file \"$config{varfile}\" is not a file.") unless (-f "$config{varfile}"); clean_exit("variable file \"$config{varfile}\" is not writable by you.") if (!$config{careful} && !-w "$config{varfile}"); # Make sure dist var files don't contain [back]slashes # (probably means user confused it with local var file). my %dist_var_files; foreach my $dist_var_file (@{${config{dist_var_files}}}) { clean_exit("variable file \"$dist_var_file\" specified multiple times") if (exists($dist_var_files{$dist_var_file})); $dist_var_files{$dist_var_file} = 1; clean_exit("variable file \"$dist_var_file\" contains slashes or backslashes ". "but it must be specified as a filename (without path) ". "that exists in the downloaded rules, e.g. \"snort.conf\"") if ($dist_var_file =~ /\// || $dist_var_file =~ /\\/); } } # Make sure all required binaries can be found, unless # we're used to use Perl modules instead. # Wget is only required if url is http[s] or ftp. if ($config{use_external_bins}) { foreach my $binary (@req_binaries) { clean_exit("$binary not found in PATH ($ENV{PATH}).") unless (is_in_path($binary)); } } # Make sure $url is defined (either by -u or url=... in the conf). clean_exit("URL not specified. Specify at least one \"url=\" in the \n". "Oinkmaster configuration file or use the \"-u \" argument") if ($#{$config{url}} == -1); # Make sure all urls look ok, and untaint them. my @urls = @{$config{url}}; $#{$config{url}} = -1; foreach my $url (@urls) { clean_exit("incorrect URL: \"$url\"") unless ($url =~ /^((?:https*|ftp|file|scp):\/\/.+\.(?:tar\.gz|tgz))$/ || $url =~ /^(dir:\/\/.+)/); my $ok_url = $1; if ($ok_url =~ /^dir:\/\/(.+)/) { my $dir = untaint_path($1); clean_exit("\"$dir\" does not exist or is not a directory") unless (-d $dir); # Simple check if the output dir is specified as url (probably a mistake). if (File::Spec->canonpath(File::Spec->rel2abs($dir)) eq File::Spec->canonpath(File::Spec->rel2abs($config{output_dir}))) { clean_exit("Download directory can not be same as output directory"); } } push(@{$config{url}}, $ok_url); } # Wget must be found if url is http[s]:// or ftp://. if ($config{use_external_bins}) { clean_exit("wget not found in PATH ($ENV{PATH}).") if ($config{'url'} =~ /^(https*|ftp):/ && !is_in_path("wget")); } # scp must be found if scp://... clean_exit("scp not found in PATH ($ENV{PATH}).") if ($config{'url'} =~ /^scp:/ && !is_in_path("scp")); # ssh key must exist if specified and url is scp://... clean_exit("ssh key \"$config{scp_key}\" does not exist.") if ($config{'url'} =~ /^scp:/ && exists($config{scp_key}) && !-e $config{scp_key}); # Untaint output directory string. $config{output_dir} = untaint_path($config{output_dir}); # Make sure the output directory exists and is readable. clean_exit("the output directory \"$config{output_dir}\" doesn't exist ". "or isn't readable by you.") if (!-d "$config{output_dir}" || !-x "$config{output_dir}"); # Make sure the output directory is writable unless running in careful mode. clean_exit("the output directory \"$config{output_dir}\" isn't writable by you.") if (!$config{careful} && !-w "$config{output_dir}"); # Make sure we have read permission on all rules files in the output dir, # and also write permission unless we're in careful mode. # This is to avoid bailing out in the middle of an execution if a copy # fails because of permission problem. opendir(OUTDIR, "$config{output_dir}") or clean_exit("could not open directory $config{output_dir}: $!"); while ($_ = readdir(OUTDIR)) { next if (/^\.\.?$/ || exists($config{file_ignore_list}{$_})); if (/$config{update_files}/) { unless (-r "$config{output_dir}/$_") { closedir(OUTDIR); clean_exit("no read permission on \"$config{output_dir}/$_\"\n". "Read permission is required on all rules files ". "inside the output directory.\n") } if (!$config{careful} && !-w "$config{output_dir}/$_") { closedir(OUTDIR); clean_exit("no write permission on \"$config{output_dir}/$_\"\n". "Write permission is required on all rules files ". "inside the output directory.\n") } } } closedir(OUTDIR); # Make sure the backup directory exists and is writable if running with -b. if ($config{make_backup}) { $config{backup_dir} = untaint_path($config{backup_dir}); clean_exit("the backup directory \"$config{backup_dir}\" doesn't exist or ". "isn't writable by you.") if (!-d "$config{backup_dir}" || !-w "$config{backup_dir}"); } # Convert tmp_basedir to cygwin style if running cygwin and msdos style was specified. if ($^O eq "cygwin" && $config{tmp_basedir} =~ /^[a-zA-Z]:[\/\\]/) { msdos_to_cygwin_path(\$config{tmp_basedir}) or clean_exit("could not convert temporary dir to cygwin style"); } # Make sure temporary directory exists. clean_exit("the temporary directory \"$config{tmp_basedir}\" does not ". "exist or isn't writable by you.") if (!-d "$config{tmp_basedir}" || !-w "$config{tmp_basedir}"); # Also untaint it. $config{tmp_basedir} = untaint_path($config{tmp_basedir}); # Make sure stdin and stdout are ttys if we're running in interactive mode. clean_exit("you can not run in interactive mode when STDIN/STDOUT is not a TTY.") if ($config{interactive} && !(-t STDIN && -t STDOUT)); } # Download the rules archive. sub download_file($ $) { my $url = shift; my $localfile = shift; my $log = "$tmpdir/wget.log"; my $ret; # If there seems to be a password in the url, replace it with "*password*" # and use new string when printing the url to screen. my $obfuscated_url = $url; $obfuscated_url = "$1:*password*\@$2" if ($obfuscated_url =~ /^(\S+:\/\/.+?):.+?@(.+)/); # Ofbuscate oinkcode as well. $obfuscated_url = "$1*oinkcode*$2" if ($obfuscated_url =~ /^(\S+:\/\/.+\.cgi\/)[0-9a-z]{32,64}(\/.+)/i); my @user_agent_opt; @user_agent_opt = ("-U", $config{user_agent}) if (exists($config{user_agent})); # Use wget if URL starts with "http[s]" or "ftp" and we use external binaries. if ($config{use_external_bins} && $url =~ /^(?:https*|ftp)/) { print STDERR "Downloading file from $obfuscated_url... " unless ($config{quiet}); if ($config{verbose}) { print STDERR "\n"; my @wget_cmd = ("wget", "-v", "-O", $localfile, $url, @user_agent_opt); clean_exit("could not download from $obfuscated_url") if (system(@wget_cmd)); } else { my @wget_cmd = ("wget", "-v", "-o", $log, "-O", $localfile, $url, @user_agent_opt); if (system(@wget_cmd)) { my $log_output; open(LOG, "<", "$log") or clean_exit("could not open $log for reading: $!"); # Sanitize oinkcode in wget's log (password is automatically sanitized). while () { $_ = "$1*oinkcode*$2" if (/(\S+:\/\/.+\.cgi\/)[0-9a-z]{32,64}(\/.+)/i); $log_output .= $_; } close(LOG); clean_exit("could not download from $obfuscated_url. ". "Output from wget follows:\n\n $log_output"); } print STDERR "done.\n" unless ($config{quiet}); } # Use LWP if URL starts with "http[s]" or "ftp" and use_external_bins=0. } elsif (!$config{use_external_bins} && $url =~ /^(?:https*|ftp)/) { print STDERR "Downloading file from $obfuscated_url... " unless ($config{quiet}); my %lwp_opt; $lwp_opt{agent} = $config{user_agent} if (exists($config{user_agent})); my $ua = LWP::UserAgent->new(%lwp_opt); $ua->env_proxy; my $request = HTTP::Request->new(GET => $url); my $response = $ua->request($request, $localfile); clean_exit("could not download from $obfuscated_url: " . $response->status_line) unless $response->is_success; print "done.\n" unless ($config{quiet}); # Grab file from local filesystem if file://... } elsif ($url =~ /^file/) { $url =~ s/^file:\/\///; clean_exit("the file $url does not exist.") unless (-e "$url"); clean_exit("the file $url is empty.") unless (-s "$url"); print STDERR "Copying file from $url... " unless ($config{quiet}); copy("$url", "$localfile") or clean_exit("unable to copy $url to $localfile: $!"); print STDERR "done.\n" unless ($config{quiet}); # Grab file using scp if scp://... } elsif ($url =~ /^scp/) { $url =~ s/^scp:\/\///; my @cmd; push(@cmd, "scp"); push(@cmd, "-i", "$config{scp_key}") if (exists($config{scp_key})); push(@cmd, "-q") if ($config{quiet}); push(@cmd, "-v") if ($config{verbose}); push(@cmd, "$url", "$localfile"); print STDERR "Copying file from $url using scp:\n" unless ($config{quiet}); clean_exit("scp returned error when trying to copy $url") if (system(@cmd)); # Unknown download method. } else { clean_exit("unknown or unsupported download method\n"); } # Make sure the downloaded file actually exists. clean_exit("failed to download $url: ". "local target file $localfile doesn't exist after download.") unless (-e "$localfile"); # Also make sure it's at least non-empty. clean_exit("failed to download $url: local target file $localfile is empty ". "after download (perhaps you're out of diskspace or file in url is empty?)") unless (-s "$localfile"); } # Copy all rules files from the tmp dirs (one for each url) # into a single directory inside the tmp dir, except for files # matching a 'skipfile' directive'. # Will exit in case of colliding filenames. sub join_tmp_rules_dirs($ $ @) { my $rules_dir = shift; my $new_files_ref = shift; my @url_tmpdirs = @_; my %rules_files; clean_exit("failed to create directory \"$rules_dir\": $!") unless (mkdir($rules_dir)); foreach my $url_tmpdir (@url_tmpdirs) { opendir(URL_TMPDIR, "$url_tmpdir") or clean_exit("could not open directory \"$url_tmpdir\": $!"); while ($_ = readdir(URL_TMPDIR)) { next if (/^\.\.?$/ || exists($config{file_ignore_list}{$_}) || !/$config{update_files}/); if (exists($rules_files{$_})) { closedir(URL_TMPDIR); clean_exit("a file called \"$_\" exists in multiple rules archives") } # Make sure it's a regular file. unless (-f "$url_tmpdir/$_" && !-l "$url_tmpdir/$_") { closedir(URL_TMPDIR); clean_exit("downloaded \"$_\" is not a regular file.") } $rules_files{$_} = 1; $$new_files_ref{"$rules_dir/$_"} = 1; my $src_file = untaint_path("$url_tmpdir/$_"); unless (copy("$src_file", "$rules_dir")) { closedir(URL_TMPDIR); clean_exit("could not copy \"$src_file\" to \"$rules_dir\": $!"); } } closedir(URL_TMPDIR); } return (keys(%$new_files_ref)); } # Make a few basic sanity checks on the rules archive and then # uncompress/untar it if everything looked ok. sub unpack_rules_archive($ $ $) { my $url = shift; # only used when printing warnings/errors my $archive = shift; my $rules_dir = shift; my ($tar, @tar_content); my $old_dir = untaint_path(File::Spec->rel2abs(File::Spec->curdir())); my $dir = dirname($archive); chdir("$dir") or clean_exit("$url: could not change directory to \"$dir\": $!"); if ($config{use_external_bins}) { # Run integrity check on the gzip file. clean_exit("$url: integrity check on gzip file failed (file transfer failed or ". "file in URL not in gzip format?).") if (system("gzip", "-t", "$archive")); # Decompress it. system("gzip", "-d", "$archive") and clean_exit("$url: unable to uncompress $archive."); # Suffix has now changed from .tar.gz|.tgz to .tar. $archive =~ s/\.gz$//; # Make sure the .tar file now exists. # (Gzip may not return an error if it was not a gzipped file...) clean_exit("$url: failed to unpack gzip file (file transfer failed or ". "file in URL not in tar'ed gzip format?).") unless (-e "$archive"); my $stdout_file = "$tmpdir/tar_content.out"; open(OLDOUT, ">&STDOUT") or clean_exit("could not dup STDOUT: $!"); open(STDOUT, ">$stdout_file") or clean_exit("could not redirect STDOUT: $!"); my $ret = system("tar", "tf", "$archive"); close(STDOUT); open(STDOUT, ">&OLDOUT") or clean_exit("could not dup STDOUT: $!"); close(OLDOUT); clean_exit("$url: could not list files in tar archive (is it broken?)") if ($ret); open(TAR, "$stdout_file") or clean_exit("failed to open $stdout_file: $!"); @tar_content = ; close(TAR); # use_external_bins=0 } else { $tar = Archive::Tar->new($archive, 1); clean_exit("$url: failed to read $archive (file transfer failed or ". "file in URL not in tar'ed gzip format?).") unless (defined($tar)); @tar_content = $tar->list_files(); } # Make sure we could grab some content from the tarball. clean_exit("$url: could not list files in tar archive (is it broken?)") if ($#tar_content < 0); # For each filename in the archive, do some basic sanity checks. foreach my $filename (@tar_content) { chomp($filename); # We don't want absolute filename. clean_exit("$url: rules archive contains absolute filename. ". "Offending file/line:\n$filename") if ($filename =~ /^\//); # We don't want to have any weird characters anywhere in the filename. clean_exit("$url: illegal character in filename in tar archive. Allowed are ". "$OK_PATH_CHARS\nOffending file/line:\n$filename") if ($config{use_path_checks} && $filename =~ /[^$OK_PATH_CHARS]/); # We don't want to unpack any "../../" junk (check is useless now though). clean_exit("$url: filename in tar archive contains \"..\".\n". "Offending file/line:\n$filename") if ($filename =~ /\.\./); } # Looks good. Now we can untar it. print STDERR "Archive successfully downloaded, unpacking... " unless ($config{quiet}); if ($config{use_external_bins}) { clean_exit("failed to untar $archive.") if system("tar", "xf", "$archive"); } else { mkdir("$rules_dir") or clean_exit("could not create \"$rules_dir\" directory: $!\n"); foreach my $file ($tar->list_files) { next unless ($file =~ /^$rules_dir\/[^\/]+$/); # only ^rules/$ my $content = $tar->get_content($file); # Symlinks in the archive will make get_content return undef. clean_exit("could not get content from file \"$file\" in downloaded archive, ". "make sure it is a regular file\n") unless (defined($content)); open(RULEFILE, ">", "$file") or clean_exit("could not open \"$file\" for writing: $!\n"); print RULEFILE $content; close(RULEFILE); } } # Make sure that non-empty rules directory existed in archive. # We permit empty rules directory if min_files is set to 0 though. clean_exit("$url: no \"$rules_dir\" directory found in tar file.") unless (-d "$dir/$rules_dir"); my $num_files = 0; opendir(RULESDIR, "$dir/$rules_dir") or clean_exit("could not open directory \"$dir/$rules_dir\": $!"); while ($_ = readdir(RULESDIR)) { next if (/^\.\.?$/); $num_files++; } closedir(RULESDIR); clean_exit("$url: directory \"$rules_dir\" in unpacked archive is empty") if ($num_files == 0 && $config{min_files} != 0); chdir($old_dir) or clean_exit("could not change directory back to $old_dir: $!"); print STDERR "done.\n" unless ($config{quiet}); } # Open all rules files in the temporary directory and disable/modify all # rules/lines as requested in oinkmaster.conf, and then write back to the # same files. Also clean unwanted whitespaces and duplicate sids from them. sub process_rules($ $ $ $ $ $) { my $modify_sid_ref = shift; my $disable_sid_ref = shift; my $enable_sid_ref = shift; my $local_sid_ref = shift; my $rh_tmp_ref = shift; my $newfiles_ref = shift; my %sids; my %stats = ( disabled => 0, enabled => 0, modified => 0, total => 0, ); warn("WARNING: all rules that are disabled by default will be enabled\n") if ($config{enable_all} && !$config{quiet}); print STDERR "Processing downloaded rules... " unless ($config{quiet}); print STDERR "\n" if ($config{verbose}); # Phase #1 - process all active rules and store in temporary hash. # In case of dups, we use the one with the highest rev. foreach my $file (sort(keys(%$newfiles_ref))) { open(INFILE, "<", "$file") or clean_exit("could not open $file for reading: $!"); my @infile = ; close(INFILE); my ($single, $multi, $nonrule, $msg, $sid); RULELOOP:while (get_next_entry(\@infile, \$single, \$multi, \$nonrule, \$msg, \$sid)) { # We don't care about non-rules in this phase. next RULELOOP if (defined($nonrule)); # Even if it was a single-line rule, we want a copy in $multi. $multi = $single unless (defined($multi)); my %rule = ( single => $single, multi => $multi, ); # modify/disable/enable this rule as requested unless there is a matching # localsid statement. Possible verbose messages and warnings will be printed. unless (exists($$local_sid_ref{$sid})) { process_rule($modify_sid_ref, $disable_sid_ref, $enable_sid_ref, \%rule, $sid, \%stats, 1, basename($file)); } $stats{total}++; $single = $rule{single}; $multi = $rule{multi}; # Only care about active rules in this phase (the rule may have been # disabled by a disablesid or a modifysid statement above, so we can't # do this check earlier). next RULELOOP if ($multi =~ /^#/); # Is it a dup? If so, see if this seems to be more recent (higher rev). if (exists($sids{$sid})) { warn("\nWARNING: duplicate SID in downloaded archive, SID=$sid, ". "only keeping rule with highest 'rev'\n") unless($config{super_quiet}); my ($old_rev) = ($sids{$sid}{single} =~ /\brev\s*:\s*(\d+)\s*;/); my ($new_rev) = ($single =~ /\brev\s*:\s*(\d+)\s*;/); # This is so rules with a rev gets higher prio than # rules without any rev. $old_rev = -1 unless (defined($old_rev)); $new_rev = -1 unless (defined($new_rev)); # If this rev is higher than the one in the last stored rule with # this sid, replace rule with this one. This is also done if the # revs are equal because we assume the rule appearing last in the # rules file is the more recent rule. if ($new_rev >= $old_rev) { $sids{$sid}{single} = $single; $sids{$sid}{multi} = $multi; } # No dup. } else { $sids{$sid}{single} = $single; $sids{$sid}{multi} = $multi; } } } # Phase #2 - read all rules files again, but when writing active rules # back to the files, use the one stored in the sid hash (which is free of dups). foreach my $file (sort(keys(%$newfiles_ref))) { open(INFILE, "<", "$file") or clean_exit("could not open $file for reading: $!"); my @infile = ; close(INFILE); # Write back to the same file. open(OUTFILE, ">", "$file") or clean_exit("could not open $file for writing: $!"); my ($single, $multi, $nonrule, $msg, $sid); RULELOOP:while (get_next_entry(\@infile, \$single, \$multi, \$nonrule, \$msg, \$sid)) { if (defined($nonrule)) { print OUTFILE "$nonrule"; next RULELOOP; } # Even if it was a single-line rule, we want a copy in $multi. $multi = $single unless (defined($multi)); # If this rule is marked as localized and has not yet been written, # write the old version to the new rules file. if (exists($$local_sid_ref{$sid}) && !exists($sids{$sid}{printed})) { # Just ignore the rule in the downloaded file if it doesn't # exist in the same local file. unless(exists($$rh_tmp_ref{old}{rules}{basename($file)}{$sid})) { warn("WARNING: SID $sid is marked as local and exists in ". "downloaded " . basename($file) . " but the SID does not ". "exist in the local file, ignoring rule\n") if ($config{verbose}); next RULELOOP; } print OUTFILE $$rh_tmp_ref{old}{rules}{basename($file)}{$sid}; $sids{$sid}{printed} = 1; warn("SID $sid is marked as local, keeping your version from ". basename($file) . ".\n". "Your version: $$rh_tmp_ref{old}{rules}{basename($file)}{$sid}". "Downloaded version: $multi\n") if ($config{verbose}); next RULELOOP; } my %rule = ( single => $single, multi => $multi, ); # modify/disable/enable this rule. Possible verbose messages and warnings # will not be printed (again) as this was done in the first phase. # We send the stats to a dummy var as this was collected on the # first phase as well. process_rule($modify_sid_ref, $disable_sid_ref, $enable_sid_ref, \%rule, $sid, \my %unused_stats, 0, basename($file)); $single = $rule{single}; $multi = $rule{multi}; # Disabled rules are printed right back to the file, unless # there also is an active rule with the same sid. Als o make # sure we only print the sid once, even though it's disabled. if ($multi =~ /^#/ && !exists($sids{$sid}) && !exists($sids{$sid}{printed})) { print OUTFILE $multi; $sids{$sid}{printed} = 1; next RULELOOP; } # If this sid has not yet been printed and this is the place where # the sid with the highest rev was, print the rule to the file. # (There can be multiple totally different rules with the same sid # and we don't want to put the wrong rule in the wrong place. if (!exists($sids{$sid}{printed}) && $single eq $sids{$sid}{single}) { print OUTFILE $multi; $sids{$sid}{printed} = 1; } } close(OUTFILE); } print STDERR "disabled $stats{disabled}, enabled $stats{enabled}, ". "modified $stats{modified}, total=$stats{total}\n" unless ($config{quiet}); # Print warnings on attempt at enablesid/disablesid/localsid on non-existent # rule if we're in verbose mode. if ($config{verbose}) { foreach my $sid (keys(%$enable_sid_ref)) { warn("WARNING: attempt to use \"enablesid\" on non-existent SID $sid\n") unless (exists($sids{$sid})); } foreach my $sid (keys(%$disable_sid_ref)) { warn("WARNING: attempt to use \"disablesid\" on non-existent SID $sid\n") unless (exists($sids{$sid})); } foreach my $sid (keys(%$local_sid_ref)) { warn("WARNING: attempt to use \"localsid\" on non-existent SID $sid\n") unless (exists($sids{$sid})); } } # Print warnings on attempt at modifysid'ing non-existent stuff, unless quiet mode. unless ($config{quiet}) { my %new_files; foreach my $file (sort(keys(%$newfiles_ref))) { $new_files{basename($file)} = 1; } my %mod_tmp; foreach my $mod_expr (@$modify_sid_ref) { my ($type, $arg) = ($mod_expr->[2], $mod_expr->[3]); $mod_tmp{$type}{$arg} = 1; } foreach my $sid (keys(%{$mod_tmp{sid}})) { warn("WARNING: attempt to use \"modifysid\" on non-existent SID $sid\n") unless (exists($sids{$sid})); } foreach my $file (keys(%{$mod_tmp{file}})) { warn("WARNING: attempt to use \"modifysid\" on non-existent file $file\n") unless(exists($new_files{$file})); } } # Return total number of valid rules. return ($stats{total}); } # Process (modify/enable/disable) a rule as requested. sub process_rule($ $ $ $ $ $ $ $) { my $modify_sid_ref = shift; my $disable_sid_ref = shift; my $enable_sid_ref = shift; my $rule_ref = shift; my $sid = shift; my $stats_ref = shift; my $print_messages = shift; my $filename = shift; # Just for easier access. my $single = $$rule_ref{single}; my $multi = $$rule_ref{multi}; # Some rules may be commented out by default. # Enable them if -e is specified (both single-line and multi-line, # version, because we don't know which version one we're going to # use below. # Enable them if -e is specified. if ($multi =~ /^#/ && $config{enable_all}) { $multi =~ s/^#*//; $multi =~ s/\n#*/\n/g; $single =~ s/^#*//; $$stats_ref{enabled}++; } # Modify rule if requested. For disablesid/enablesid we work # on the multi-line version of the rule (if exists). For # modifysid that's no good since we don't know where in the # rule the trailing backslashes and newlines are going to be # and we don't want them to affect the regexp. MOD_EXP:foreach my $mod_expr (@$modify_sid_ref) { my ($subst, $repl, $type, $arg) = ($mod_expr->[0], $mod_expr->[1], $mod_expr->[2], $mod_expr->[3]); my $print_modify_warnings = 0; $print_modify_warnings = 1 if (!$config{super_quiet} && $print_messages && $type eq "sid"); if ($type eq "wildcard" || ($type eq "sid" && $sid eq $arg) || ($type eq "file" && $filename eq $arg)) { if ($single =~ /$subst/si) { print STDERR "Modifying rule, SID=$sid, filename=$filename, ". "match type=$type, subst=$subst, ". "repl=$repl\nBefore: $single" if ($print_messages && $config{verbose}); # If user specified a backreference but the regexp did not set $1 - don't modify rule. if (!defined($1) && ($repl =~ /[^\\]\$\d+/ || $repl =~ /[^\\]\$\{\d+\}/ || $repl =~ /^qq\/\$\d+/ || $repl =~ /^qq\/\$\{\d+\}/)) { warn("WARNING: SID $sid matches modifysid expression \"$subst\" but ". "backreference variable \$1 is undefined after match, ". "keeping original rule\n") if ($print_modify_warnings); next MOD_EXP; } # Do the substitution on the single-line version and put it # back in $multi. $single =~ s/$subst/$repl/eei; $multi = $single; print STDERR "After: $single\n" if ($print_messages && $config{verbose}); $$stats_ref{modified}++; } else { if ($print_modify_warnings) { warn("WARNING: SID $sid does not match modifysid ". "expression \"$subst\", keeping original rule\n"); } } } } # Disable rule if requested and it's not already disabled. if (exists($$disable_sid_ref{$sid}) && $multi !~ /^\s*#/) { $multi = "#$multi"; $multi =~ s/\n([^#].+)/\n#$1/g; $$stats_ref{disabled}++; } # Enable rule if requested and it's not already enabled. if (exists($$enable_sid_ref{$sid}) && $multi =~ /^\s*#/) { $multi =~ s/^#+//; $multi =~ s/\n#+(.+)/\n$1/g; $$stats_ref{enabled}++; } $$rule_ref{single} = $single; $$rule_ref{multi} = $multi; } # Setup rules hash. # Format for rules will be: rh{old|new}{rules{filename}{sid} = single-line rule # Format for non-rules will be: rh{old|new}{other}{filename} = array of lines # List of added files will be stored as rh{added_files}{filename} sub setup_rules_hash($ $) { my $new_files_ref = shift; my $output_dir = shift; my (%rh, %old_sids); print STDERR "Setting up rules structures... " unless ($config{quiet}); foreach my $file (sort(keys(%$new_files_ref))) { warn("\nWARNING: downloaded rules file $file is empty\n") if (!-s "$file" && $config{verbose}); open(NEWFILE, "<", "$file") or clean_exit("could not open $file for reading: $!"); my @newfile = ; close(NEWFILE); # From now on we don't care about the path, so remove it. $file = basename($file); my ($single, $multi, $nonrule, $msg, $sid); while (get_next_entry(\@newfile, \$single, \$multi, \$nonrule, \$msg, \$sid)) { if (defined($single)) { $rh{new}{rules}{"$file"}{"$sid"} = $single; } else { push(@{$rh{new}{other}{"$file"}}, $nonrule); } } # Also read in old (aka local) file if it exists. # We do a sid dup check in these files. if (-f "$output_dir/$file") { open(OLDFILE, "<", "$output_dir/$file") or clean_exit("could not open $output_dir/$file for reading: $!"); my @oldfile = ; close(OLDFILE); while (get_next_entry(\@oldfile, \$single, \$multi, \$nonrule, undef, \$sid)) { if (defined($single)) { warn("\nWARNING: duplicate SID in your local rules, SID ". "$sid exists multiple times, you may need to fix this manually!\n") if (exists($old_sids{$sid})); $rh{old}{rules}{"$file"}{"$sid"} = $single; $old_sids{$sid}++; } else { push(@{$rh{old}{other}{"$file"}}, $nonrule); } } } else { $rh{added_files}{"$file"}++; } } print STDERR "done.\n" unless ($config{quiet}); return (%rh); } # Return lines that exist only in first array but not in second one. sub get_first_only($ $ $) { my $first_only_ref = shift; my $first_arr_ref = shift; my $second_arr_ref = shift; my %arr_hash; @arr_hash{@$second_arr_ref} = (); foreach my $line (@$first_arr_ref) { # Skip blank lines and CVS Id tags. next unless ($line =~ /\S/); next if ($line =~ /^\s*#+\s*\$I\S:.+Exp\s*\$/); push(@$first_only_ref, $line) unless(exists($arr_hash{$line})); } } # Backup files in output dir matching $config{update_files} into the backup dir. sub make_backup($ $) { my $src_dir = shift; # dir with the rules to be backed up my $dest_dir = shift; # where to put the backup tarball my ($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0 .. 5]; my $date = sprintf("%4d%02d%02d-%02d%02d%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec); my $backup_tarball = "rules-backup-$date.tar"; my $backup_tmp_dir = File::Spec->catdir("$tmpdir", "rules-backup-$date"); my $dest_file = File::Spec->catfile("$dest_dir", "$backup_tarball.gz"); print STDERR "Creating backup of old rules..." unless ($config{quiet}); mkdir("$backup_tmp_dir", 0700) or clean_exit("could not create temporary backup directory $backup_tmp_dir: $!"); # Copy all rules files from the rules dir to the temporary backup dir. opendir(OLDRULES, "$src_dir") or clean_exit("could not open directory $src_dir: $!"); while ($_ = readdir(OLDRULES)) { next if (/^\.\.?$/); if (/$config{update_files}/) { my $src_file = untaint_path("$src_dir/$_"); copy("$src_file", "$backup_tmp_dir/") or warn("WARNING: could not copy $src_file to $backup_tmp_dir/: $!"); } } closedir(OLDRULES); # Also backup the -U (as "variable-file.conf") if specified. if ($config{update_vars}) { copy("$config{varfile}", "$backup_tmp_dir/variable-file.conf") or warn("WARNING: could not copy $config{varfile} to $backup_tmp_dir: $!") } my $old_dir = untaint_path(File::Spec->rel2abs(File::Spec->curdir())); # Change directory to $tmpdir (so we'll be right below the directory where # we have our rules to be backed up). chdir("$tmpdir") or clean_exit("could not change directory to $tmpdir: $!"); if ($config{use_external_bins}) { clean_exit("tar command returned error when archiving backup files.\n") if (system("tar","cf","$backup_tarball","rules-backup-$date")); clean_exit("gzip command returned error when compressing backup file.\n") if (system("gzip","$backup_tarball")); $backup_tarball .= ".gz"; } else { my $tar = Archive::Tar->new; opendir(RULES, "rules-backup-$date") or clean_exit("unable to open directory \"rules-backup-$date\": $!"); while ($_ = readdir(RULES)) { next if (/^\.\.?$/); $tar->add_files("rules-backup-$date/$_"); } closedir(RULES); $backup_tarball .= ".gz"; # Write tarball. Print stupid error message if it fails as # we can't use $tar->error or Tar::error on all platforms. $tar->write("$backup_tarball", 1); clean_exit("could not create backup archive: tarball empty after creation\n") unless (-s "$backup_tarball"); } # Change back to old directory (so it will work with -b as either # an absolute or a relative path. chdir("$old_dir") or clean_exit("could not change directory back to $old_dir: $!"); copy("$tmpdir/$backup_tarball", "$dest_file") or clean_exit("unable to copy $tmpdir/$backup_tarball to $dest_file/: $!\n"); print STDERR " saved as $dest_file.\n" unless ($config{quiet}); } # Print the results. sub print_changes($ $) { my $ch_ref = shift; my $rh_ref = shift; my ($sec, $min, $hour, $mday, $mon, $year) = (localtime)[0 .. 5]; my $date = sprintf("%4d%02d%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec); print "\n[***] Results from Oinkmaster started $date [***]\n"; # Print new variables. if ($config{update_vars}) { if ($#{$$ch_ref{new_vars}} > -1) { print "\n[*] New variables: [*]\n"; foreach my $var (@{$$ch_ref{new_vars}}) { print " $var"; } } else { print "\n[*] New variables: [*]\n None.\n" unless ($config{super_quiet}); } } # Print rules modifications. print "\n[*] Rules modifications: [*]\n None.\n" if (!keys(%{$$ch_ref{rules}}) && !$config{super_quiet}); # Print added rules. if (exists($$ch_ref{rules}{added})) { print "\n[+++] Added rules: [+++]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{added}}, $rh_ref); } else { print_changetype($PRINT_NEW, "Added to", \%{$$ch_ref{rules}{added}}, $rh_ref); } } # Print enabled rules. if (exists($$ch_ref{rules}{ena})) { print "\n[+++] Enabled rules: [+++]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{ena}}, $rh_ref); } else { print_changetype($PRINT_NEW, "Enabled in", \%{$$ch_ref{rules}{ena}}, $rh_ref); } } # Print enabled + modified rules. if (exists($$ch_ref{rules}{ena_mod})) { print "\n[+++] Enabled and modified rules: [+++]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{ena_mod}}, $rh_ref); } else { print_changetype($PRINT_BOTH, "Enabled and modified in", \%{$$ch_ref{rules}{ena_mod}}, $rh_ref); } } # Print modified active rules. if (exists($$ch_ref{rules}{mod_act})) { print "\n[///] Modified active rules: [///]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{mod_act}}, $rh_ref); } else { print_changetype($PRINT_BOTH, "Modified active in", \%{$$ch_ref{rules}{mod_act}}, $rh_ref); } } # Print modified inactive rules. if (exists($$ch_ref{rules}{mod_ina})) { print "\n[///] Modified inactive rules: [///]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{mod_ina}}, $rh_ref); } else { print_changetype($PRINT_BOTH, "Modified inactive in", \%{$$ch_ref{rules}{mod_ina}}, $rh_ref); } } # Print disabled + modified rules. if (exists($$ch_ref{rules}{dis_mod})) { print "\n[---] Disabled and modified rules: [---]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{dis_mod}}, $rh_ref); } else { print_changetype($PRINT_BOTH, "Disabled and modified in", \%{$$ch_ref{rules}{dis_mod}}, $rh_ref); } } # Print disabled rules. if (exists($$ch_ref{rules}{dis})) { print "\n[---] Disabled rules: [---]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{dis}}, $rh_ref); } else { print_changetype($PRINT_NEW, "Disabled in", \%{$$ch_ref{rules}{dis}}, $rh_ref); } } # Print removed rules. if (exists($$ch_ref{rules}{removed})) { print "\n[---] Removed rules: [---]\n"; if ($config{summary_output}) { print_summary_change(\%{$$ch_ref{rules}{removed}}, $rh_ref); } else { print_changetype($PRINT_OLD, "Removed from", \%{$$ch_ref{rules}{removed}}, $rh_ref); } } # Print non-rule modifications. print "\n[*] Non-rule line modifications: [*]\n None.\n" if (!keys(%{$$ch_ref{other}}) && !$config{super_quiet}); # Print added non-rule lines. if (exists($$ch_ref{other}{added})) { print "\n[+++] Added non-rule lines: [+++]\n"; foreach my $file (sort({uc($a) cmp uc($b)} keys(%{$$ch_ref{other}{added}}))) { my $num = $#{$$ch_ref{other}{added}{$file}} + 1; print "\n -> Added to $file ($num):\n"; foreach my $line (@{$$ch_ref{other}{added}{$file}}) { print " $line"; } } } # Print removed non-rule lines. if (keys(%{$$ch_ref{other}{removed}}) > 0) { print "\n[---] Removed non-rule lines: [---]\n"; foreach my $file (sort({uc($a) cmp uc($b)} keys(%{$$ch_ref{other}{removed}}))) { my $num = $#{$$ch_ref{other}{removed}{$file}} + 1; print "\n -> Removed from $file ($num):\n"; foreach my $other (@{$$ch_ref{other}{removed}{$file}}) { print " $other"; } } } # Print list of added files. if (keys(%{$$ch_ref{added_files}})) { print "\n[+] Added files (consider updating your snort.conf to include them if needed): [+]\n\n"; foreach my $added_file (sort({uc($a) cmp uc($b)} keys(%{$$ch_ref{added_files}}))) { print " -> $added_file\n"; } } else { print "\n[*] Added files: [*]\n None.\n" unless ($config{super_quiet} || $config{summary_output}); } # Print list of possibly removed files if requested. if ($config{check_removed}) { if (keys(%{$$ch_ref{removed_files}})) { print "\n[-] Files possibly removed from the archive ". "(consider removing them from your snort.conf if needed): [-]\n\n"; foreach my $removed_file (sort({uc($a) cmp uc($b)} keys(%{$$ch_ref{removed_files}}))) { print " -> $removed_file\n"; } } else { print "\n[*] Files possibly removed from the archive: [*]\n None.\n" unless ($config{super_quiet} || $config{summary_output}); } } print "\n"; } # Helper for print_changes(). sub print_changetype($ $ $ $) { my $type = shift; # $PRINT_OLD|$PRINT_NEW|$PRINT_BOTH my $string = shift; # string to print before filename my $ch_ref = shift; # reference to an entry in the rules changes hash my $rh_ref = shift; # reference to rules hash foreach my $file (sort({uc($a) cmp uc($b)} keys(%$ch_ref))) { my $num = keys(%{$$ch_ref{$file}}); print "\n -> $string $file ($num):\n"; foreach my $sid (keys(%{$$ch_ref{$file}})) { if ($type == $PRINT_OLD) { print " $$rh_ref{old}{rules}{$file}{$sid}" } elsif ($type == $PRINT_NEW) { print " $$rh_ref{new}{rules}{$file}{$sid}" } elsif ($type == $PRINT_BOTH) { my $old = $$rh_ref{old}{rules}{$file}{$sid}; my $new = $$rh_ref{new}{rules}{$file}{$sid}; if ($config{minimize_diff}) { my ($old, $new) = minimize_diff($old, $new); print "\n old SID $sid: $old"; print " new SID $sid: $new"; } else { print "\n old: $old"; print " new: $new"; } } } } } # Print changes in bmc style, i.e. only sid and msg, no full details. sub print_summary_change($ $) { my $ch_ref = shift; # reference to an entry in the rules changes hash my $rh_ref = shift; # reference to rules hash my (@sids, %sidmap); print "\n"; # First get all the sids (may be spread across multiple files. foreach my $file (keys(%$ch_ref)) { foreach my $sid (keys(%{$$ch_ref{$file}})) { push(@sids, $sid); if (exists($$rh_ref{new}{rules}{$file}{$sid})) { $sidmap{$sid}{rule} = $$rh_ref{new}{rules}{$file}{$sid}; } else { $sidmap{$sid}{rule} = $$rh_ref{old}{rules}{$file}{$sid}; } $sidmap{$sid}{file} = $file; } } # Print rules, sorted by sid. foreach my $sid (sort {$a <=> $b} (@sids)) { my @rule = $sidmap{$sid}{rule}; my $file = $sidmap{$sid}{file}; get_next_entry(\@rule, undef, undef, undef, \(my $msg), undef); printf("%8d - %s (%s)\n", $sid, $msg, $file); } print "\n"; } # Compare the new rules to the old ones. sub get_changes($ $ $) { my $rh_ref = shift; my $new_files_ref = shift; my $rules_dir = shift; my %changes; print STDERR "Comparing new files to the old ones... " unless ($config{quiet}); # We have the list of added files (without full path) in $rh_ref{added_files} # but we'd rather want to have it in $changes{added_files} now. $changes{added_files} = $$rh_ref{added_files}; # New files are also regarded as modified since we want to update # (i.e. add) those as well. Here we want them with full path. foreach my $file (keys(%{$changes{added_files}})) { $changes{modified_files}{"$tmpdir/$rules_dir/$file"}++; } # Add list of possibly removed files if requested. if ($config{check_removed}) { opendir(OLDRULES, "$config{output_dir}") or clean_exit("could not open directory $config{output_dir}: $!"); while ($_ = readdir(OLDRULES)) { next if (/^\.\.?$/); $changes{removed_files}{"$_"} = 1 if (/$config{update_files}/ && !exists($config{file_ignore_list}{$_}) && !-e "$tmpdir/$rules_dir/$_"); } closedir(OLDRULES); } # For each new rules file... FILELOOP:foreach my $file_w_path (sort(keys(%$new_files_ref))) { my $file = basename($file_w_path); # Skip comparison if it's an added file. next FILELOOP if (exists($$rh_ref{added_files}{$file})); # For each sid in the new file... foreach my $sid (keys(%{$$rh_ref{new}{rules}{$file}})) { my $new_rule = $$rh_ref{new}{rules}{$file}{$sid}; # Sid also exists in the old file? if (exists($$rh_ref{old}{rules}{$file}{$sid})) { my $old_rule = $$rh_ref{old}{rules}{$file}{$sid}; # Are they identical? unless ($new_rule eq $old_rule) { $changes{modified_files}{$file_w_path}++; # Find out in which way the rules are different. if ("#$old_rule" eq $new_rule) { $changes{rules}{dis}{$file}{$sid}++; } elsif ($old_rule eq "#$new_rule") { $changes{rules}{ena}{$file}{$sid}++; } elsif ($old_rule =~ /^\s*#/ && $new_rule !~ /^\s*#/) { $changes{rules}{ena_mod}{$file}{$sid}++; } elsif ($old_rule !~ /^\s*#/ && $new_rule =~ /^\s*#/) { $changes{rules}{dis_mod}{$file}{$sid}++; } elsif ($old_rule =~ /^\s*#/ && $new_rule =~ /^\s*#/) { $changes{rules}{mod_ina}{$file}{$sid}++; } else { $changes{rules}{mod_act}{$file}{$sid}++; } } } else { # sid not found in old file, i.e. it's added $changes{modified_files}{$file_w_path}++; $changes{rules}{added}{$file}{$sid}++; } } # foreach sid # Check for removed rules, i.e. sids that exist in the old file but # not in the new one. foreach my $sid (keys(%{$$rh_ref{old}{rules}{$file}})) { unless (exists($$rh_ref{new}{rules}{$file}{$sid})) { $changes{modified_files}{$file_w_path}++; $changes{rules}{removed}{$file}{$sid}++; } } # Check for added non-rule lines. get_first_only(\my @added, \@{$$rh_ref{new}{other}{$file}}, \@{$$rh_ref{old}{other}{$file}}); if (scalar(@added)) { @{$changes{other}{added}{$file}} = @added; $changes{modified_files}{$file_w_path}++; } # Check for removed non-rule lines. get_first_only(\my @removed, \@{$$rh_ref{old}{other}{$file}}, \@{$$rh_ref{new}{other}{$file}}); if (scalar(@removed)) { @{$changes{other}{removed}{$file}} = @removed; $changes{modified_files}{$file_w_path}++; } } # foreach new file print STDERR "done.\n" unless ($config{quiet}); return (%changes); } # Simply copy the modified rules files to the output directory. sub update_rules($ @) { my $dst_dir = shift; my @modified_files = @_; print STDERR "Updating local rules files... " if (!$config{quiet} || $config{interactive}); foreach my $file_w_path (@modified_files) { copy("$file_w_path", "$dst_dir") or clean_exit("could not copy $file_w_path to $dst_dir: $!"); } print STDERR "done.\n" if (!$config{quiet} || $config{interactive}); } # Simply copy rules files from one dir to another. # Links are not allowed. sub copy_rules($ $) { my $src_dir = shift; my $dst_dir = shift; print STDERR "Copying rules from $src_dir... " if (!$config{quiet} || $config{interactive}); opendir(SRC_DIR, $src_dir) or clean_exit("could not open directory $src_dir: $!"); my $num_files = 0; while ($_ = readdir(SRC_DIR)) { next if (/^\.\.?$/ || exists($config{file_ignore_list}{$_}) || !/$config{update_files}/); my $src_file = untaint_path("$src_dir/$_"); # Make sure it's a regular file. unless (-f "$src_file" && !-l "$src_file") { closedir(SRC_DIR); clean_exit("\"$src_file\" is not a regular file.") } unless (copy($src_file, $dst_dir)) { closedir(SRC_DIR); clean_exit("could not copy \"$src_file\" to \"$dst_dir\"/: $!"); } $num_files++; } closedir(SRC_DIR); print STDERR "$num_files files copied.\n" if (!$config{quiet} || $config{interactive}); } # Return true if file is in PATH and is executable. sub is_in_path($) { my $file = shift; foreach my $dir (File::Spec->path()) { if ((-f "$dir/$file" && -x "$dir/$file") || (-f "$dir/$file.exe" && -x "$dir/$file.exe")) { print STDERR "Found $file binary in $dir\n" if ($config{verbose}); return (1); } } return (0); } # get_next_entry() will parse the array referenced in the first arg # and return the next entry. The array should contain a rules file, # and the returned entry will be removed from the array. # An entry is one of: # - single-line rule (put in 2nd ref) # - multi-line rule (put in 3rd ref) # - non-rule line (put in 4th ref) # If the entry is a multi-line rule, its single-line version is also # returned (put in the 2nd ref). # If it's a rule, the msg string will be put in 4th ref and sid in 5th. sub get_next_entry($ $ $ $ $ $) { my $arr_ref = shift; my $single_ref = shift; my $multi_ref = shift; my $nonrule_ref = shift; my $msg_ref = shift; my $sid_ref = shift; undef($$single_ref); undef($$multi_ref); undef($$nonrule_ref); undef($$msg_ref); undef($$sid_ref); my $line = shift(@$arr_ref) || return(0); my $disabled = 0; my $broken = 0; chomp($line); $line .= "\n"; # Possible beginning of multi-line rule? if ($line =~ /$MULTILINE_RULE_REGEXP/oi) { $$single_ref = $line; $$multi_ref = $line; $disabled = 1 if ($line =~ /^\s*#/); # Keep on reading as long as line ends with "\". while (!$broken && $line =~ /\\\s*\n$/) { # Remove trailing "\" and newline for single-line version. $$single_ref =~ s/\\\s*\n//; # If there are no more lines, this can not be a valid multi-line rule. if (!($line = shift(@$arr_ref))) { warn("\nWARNING: got EOF while parsing multi-line rule: $$multi_ref\n") if ($config{verbose}); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } # Multi-line continuation. $$multi_ref .= $line; # If there are non-comment lines in the middle of a disabled rule, # mark the rule as broken to return as non-rule lines. if ($line !~ /^\s*#/ && $disabled) { $broken = 1; } elsif ($line =~ /^\s*#/ && !$disabled) { # comment line (with trailing slash) in the middle of an active rule - ignore it } else { $line =~ s/^\s*#*\s*//; # remove leading # in single-line version $$single_ref .= $line; } } # while line ends with "\" # Single-line version should now be a valid rule. # If not, it wasn't a valid multi-line rule after all. if (!$broken && parse_singleline_rule($$single_ref, $msg_ref, $sid_ref)) { $$single_ref =~ s/^\s*//; # remove leading whitespaces $$single_ref =~ s/^#+\s*/#/; # remove whitespaces next to leading # $$single_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces $$multi_ref =~ s/^\s*//; $$multi_ref =~ s/\s*\n$/\n/; $$multi_ref =~ s/^#+\s*/#/; return (1); # return multi # Invalid multi-line rule. } else { warn("\nWARNING: invalid multi-line rule: $$single_ref\n") if ($config{verbose} && $$multi_ref !~ /^\s*#/); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } # Check if it's a regular single-line rule. } elsif (parse_singleline_rule($line, $msg_ref, $sid_ref)) { $$single_ref = $line; $$single_ref =~ s/^\s*//; $$single_ref =~ s/^#+\s*/#/; $$single_ref =~ s/\s*\n$/\n/; return (1); # return single # Non-rule line. } else { # Do extra check and warn if it *might* be a rule anyway, # but that we just couldn't parse for some reason. warn("\nWARNING: line may be a rule but it could not be parsed ". "(missing sid?): $line\n") if ($config{verbose} && $line =~ /^\s*alert .+msg\s*:\s*".+"\s*;/); $$nonrule_ref = $line; $$nonrule_ref =~ s/\s*\n$/\n/; return (1); # return non-rule } } # Look for variables that exist in dist var files but not in local var file. sub get_new_vars($ $ $ $) { my $ch_ref = shift; my $dist_var_files_ref = shift; my $local_var_file = shift; my $url_tmpdirs_ref = shift; my %new_vars; my (%old_vars, %dist_var_files, %found_dist_var_files); my $confs_found = 0; # Warn in case we can't find a specified dist file. foreach my $dir (@$url_tmpdirs_ref) { foreach my $dist_var_file (@$dist_var_files_ref) { if (-e "$dir/$dist_var_file") { $found_dist_var_files{$dist_var_file} = 1; $confs_found++; } } } foreach my $dist_var_file (@$dist_var_files_ref) { unless (exists($found_dist_var_files{$dist_var_file})) { warn("WARNING: did not find variable file \"$dist_var_file\" in ". "downloaded archive(s)\n") unless($config{quiet}); } } unless ($confs_found) { unless ($config{quiet}) { warn("WARNING: no variable files found in downloaded archive(s), ". "aborting check for new variables\n"); return; } } # Read in variable names from old (target) var file. open(LOCAL_VAR_FILE, "<", "$local_var_file") or clean_exit("could not open $local_var_file for reading: $!"); my @local_var_conf = ; foreach $_ (join_multilines(\@local_var_conf)) { $old_vars{lc($1)}++ if (/$VAR_REGEXP/i); } close(LOCAL_VAR_FILE); # Read in variables from new file(s). foreach my $dir (@$url_tmpdirs_ref) { foreach my $dist_var_file (@$dist_var_files_ref) { my $conf = "$dir/$dist_var_file"; if (-e "$conf") { my $num_new = 0; print STDERR "Checking downloaded $dist_var_file for new variables... " unless ($config{quiet}); open(DIST_CONF, "<", "$conf") or clean_exit("could not open $conf for reading: $!"); my @dist_var_conf = ; close(DIST_CONF); foreach $_ (join_multilines(\@dist_var_conf)) { if (/$VAR_REGEXP/i && !exists($old_vars{lc($1)})) { my ($varname, $varval) = (lc($1), $2); if (exists($new_vars{$varname})) { warn("\nWARNING: new variable \"$varname\" is defined multiple ". "times in downloaded files\n"); } s/^\s*//; push(@{$$ch_ref{new_vars}}, "$_\n"); $new_vars{$varname} = $varval; $num_new++; } } close(DIST_CONF); print STDERR "$num_new new found.\n" unless ($config{quiet}); } } } } # Add new variables to local snort.conf. sub add_new_vars($ $) { my $ch_ref = shift; my $varfile = shift; my $tmp_varfile = "$tmpdir/tmp_varfile.conf"; my $new_content; return unless ($#{$changes{new_vars}} > -1); print STDERR "Adding new variables to $varfile... " unless ($config{quiet}); open(OLD_LOCAL_CONF, "<", "$varfile") or clean_exit("could not open $varfile for reading: $!"); my @old_content = ; close(OLD_LOCAL_CONF); open(NEW_LOCAL_CONF, ">", "$tmp_varfile") or clean_exit("could not open $tmp_varfile for writing: $!"); my @old_vars = grep(/$VAR_REGEXP/i, @old_content); # If any vars exist in old file, put new vars right after them. if ($#old_vars > -1) { while ($_ = shift(@old_content)) { print NEW_LOCAL_CONF $_; last if ($_ eq $old_vars[$#old_vars]); } } print NEW_LOCAL_CONF @{$changes{new_vars}}; print NEW_LOCAL_CONF @old_content; close(NEW_LOCAL_CONF); clean_exit("could not copy $tmp_varfile to $varfile: $!") unless (copy("$tmp_varfile", "$varfile")); print STDERR "done.\n" unless ($config{quiet}); } # Convert msdos style path to cygwin style, e.g. # c:\foo => /cygdrive/c/foo sub msdos_to_cygwin_path($) { my $path_ref = shift; if ($$path_ref =~ /^([a-zA-Z]):[\/\\](.*)/) { my ($drive, $dir) = ($1, $2); $dir =~ s/\\/\//g; $$path_ref = "/cygdrive/$drive/$dir"; return (1); } return (0); } # Parse and process a modifysid expression. # Return 1 if valid, or otherwise 0. sub parse_mod_expr($ $ $ $) { my $mod_list_ref = shift; # where to store valid entries my $sid_arg_list = shift; # comma-separated list of SIDs/files or wildcard my $subst = shift; # regexp to look for my $repl = shift; # regexp to replace it with my @tmp_mod_list; $sid_arg_list =~ s/\s+$//; foreach my $sid_arg (split(/\s*,\s*/, $sid_arg_list)) { my $type = ""; $type = "sid" if ($sid_arg =~ /^\d+$/); $type = "file" if ($sid_arg =~ /^\S+.*\.\S+$/); $type = "wildcard" if ($sid_arg eq "*"); return (0) unless ($type); # Sanity check to make sure user escaped at least all the "$" in $subst. if ($subst =~ /[^\\]\$./ || $subst =~ /^\$/) { warn("WARNING: unescaped \$ in expression \"$subst\", all special ". "characters must be escaped\n"); return (0); } # Only allow backreference variables. The check should at least catch some user typos. if (($repl =~ /[^\\]\$(\D.)/ && $1 !~ /{\d/) || $repl =~ /[^\\]\$$/ || ($repl =~ /^\$(\D.)/ && $1 !~ /{\d/)) { warn("WARNING: illegal replacement expression \"$repl\": unescaped \$ ". "that isn't a backreference\n"); return (0); } # Don't permit unescaped @. if ($repl =~ /[^\\]\@/ || $repl =~ /^\@/) { warn("WARNING: illegal replacement expression \"$repl\": unescaped \@\n"); return (0); } # Make sure the regexp is valid. my $repl_qq = "qq/$repl/"; my $dummy = "foo"; eval { $dummy =~ s/$subst/$repl_qq/ee; }; # We should probably check for warnings as well as errors... if ($@) { warn("Invalid regexp: $@"); return (0); } push(@tmp_mod_list, [$subst, $repl_qq, $type, $sid_arg]); } # If we come this far, all sids and the regexp were parsed successfully, so # append them to real mod list array. foreach my $mod_entry (@tmp_mod_list) { push(@$mod_list_ref, $mod_entry); } return (1); } # Untaint a path. Die if it contains illegal chars. sub untaint_path($) { my $path = shift; my $orig_path = $path; return $path unless ($config{use_path_checks}); (($path) = $path =~ /^([$OK_PATH_CHARS]+)$/) or clean_exit("illegal character in path/filename ". "\"$orig_path\", allowed are $OK_PATH_CHARS\n". "Fix this or set use_path_checks=0 in oinkmaster.conf ". "to disable this check completely if it is too strict.\n"); return ($path); } # Ask user to approve changes. Return 1 for yes, 0 for no. sub approve_changes() { my $answer = ""; while ($answer !~ /^[yn]/i) { print "Do you approve these changes? [Yn] "; $answer = ; $answer = "y" unless ($answer =~ /\S/); } return ($answer =~ /^y/i); } # Remove common leading and trailing stuff from two rules. sub minimize_diff($ $) { my $old_rule = shift; my $new_rule = shift; my $original_old = $old_rule; my $original_new = $new_rule; # Additional chars to print next to the diffing part. my $additional_chars = 20; # Remove the rev keyword from the rules, as it often # makes the whole diff minimizing useless. $old_rule =~ s/\s*\b(rev\s*:\s*\d+\s*;)\s*//; my $old_rev = $1; $new_rule =~ s/\s*\b(rev\s*:\s*\d+\s*;)\s*//; my $new_rev = $1; # If rev was the only thing that changed, we want to restore the rev # before continuing so we don't remove common stuff from rules that # are identical. if ($old_rule eq $new_rule) { $old_rule = $original_old; $new_rule = $original_new; } # Temporarily remove possible leading # so it works nicely # with modified rules that are also being either enabled or disabled. my $old_is_disabled = 0; my $new_is_disabled = 0; $old_is_disabled = 1 if ($old_rule =~ s/^#//); $new_is_disabled = 1 if ($new_rule =~ s/^#//); # Go forward char by char until they aren't equeal. # $i will bet set to the index where they diff. my @old = split(//, $old_rule); my @new = split(//, $new_rule); my $i = 0; while ($i <= $#old && $i <= $#new && $old[$i] eq $new[$i]) { $i++; } # Now same thing but backwards. # $j will bet set to the index where they diff. @old = reverse(split(//, $old_rule)); @new = reverse(split(//, $new_rule)); my $j = 0; while ($j <= $#old && $j <= $#new && $old[$j] eq $new[$j]) { $j++; } # Print some additional chars on either side, if there is room for it. $i -= $additional_chars; $i = 0 if ($i < 0); $j = -$j + $additional_chars; $j = 0 if ($j > -1); my ($old, $new); # Print entire rules (i.e. they can not be shortened). if (!$i && !$j) { $old = $old_rule; $new = $new_rule; # Leading and trailing stuff can be removed. } elsif ($i && $j) { $old = "..." . substr($old_rule, $i, $j) . "..."; $new = "..." . substr($new_rule, $i, $j) . "..."; # Trailing stuff can be removed. } elsif (!$i && $j) { $old = substr($old_rule, $i, $j) . "..."; $new = substr($new_rule, $i, $j) . "..."; # Leading stuff can be removed. } elsif ($i && !$j) { $old = "..." . substr($old_rule, $i); $new = "..." . substr($new_rule, $i); } chomp($old, $new); $old .= "\n"; $new .= "\n"; # Restore possible leading # now. $old = "#$old" if ($old_is_disabled); $new = "#$new" if ($new_is_disabled); return ($old, $new); } # Check a string and return 1 if it's a valid single-line snort rule. # Msg string is put in second arg, sid in third (those are the only # required keywords, besides the leading rule actions). sub parse_singleline_rule($ $ $) { my $line = shift; my $msg_ref = shift; my $sid_ref = shift; undef($$msg_ref); undef($$sid_ref); if ($line =~ /$SINGLELINE_RULE_REGEXP/oi) { if ($line =~ /\bmsg\s*:\s*"(.+?)"\s*;/i) { $$msg_ref = $1; } else { return (0); } if ($line =~ /\bsid\s*:\s*(\d+)\s*;/i) { $$sid_ref = $1; } else { return (0); } return (1); } return (0); } # Merge multiline directives in an array by simply removing traling backslashes. sub join_multilines($) { my $multiline_conf_ref = shift; my $joined_conf = ""; foreach $_ (@$multiline_conf_ref) { s/\\\s*\n$//; $joined_conf .= $_; } return (split/\n/, $joined_conf); } # Catch SIGINT. sub catch_sigint() { $SIG{INT} = 'IGNORE'; print STDERR "\nInterrupted, cleaning up.\n"; sleep(1); clean_exit("interrupted by signal"); } # Remove temporary directory and exit. # If a non-empty string is given as argument, it will be regarded # as an error message and we will use die() with the message instead # of just exit(0). sub clean_exit($) { my $err_msg = shift; $SIG{INT} = 'DEFAULT'; if (defined($tmpdir) && -d "$tmpdir") { chdir(File::Spec->rootdir()); rmtree("$tmpdir", 0, 1); undef($tmpdir); } if (!defined($err_msg) || $err_msg eq "") { exit(0); } else { chomp($err_msg); die("\n$0: Error: $err_msg\n\nOink, oink. Exiting...\n"); } } #### EOF #### oinkmaster-2.0/FAQ010064400017500001750000001245131037561221100143000ustar00andreasoandreaso# $Id: FAQ,v 1.48 2006/01/21 08:25:05 andreas_o Exp $ # The must current version if this FAQ can be found at the Oinkmaster web site, http://oinkmaster.sourceforge.net/ Oinkmaster FAQ -------------- Q1: What URL should I download the Snort signatures (rules) from? Q2: Oinkmaster says the file in URL does not exist (or it exists but doesn't work with my Snort). Q3: Can Oinkmaster be useful for distributing rules and configurations locally to my sensors? Q4: Can Oinkmaster be useful for distributing my homemade rules to my sensors? Q5: In which order will the localsid/modifysid/disablesid/enablesid configuration directives be processed? Q6: How can the 'include' statement in oinkmaster.conf be useful? Q7: Can Oinkmaster restart Snort after an update? Q8: Can I tell Oinkmaster to not touch certain SIDs in sid-msg.map so the entries I add for my local rules don't get deleted? Q9: I want to copy the rules archive from a remote host to the sensor via scp without having to supply a password or passphrase, but I also don't want to give the sensors full access to that host. Can this be done? Q10: How do I setup proxy configuration? Q11: Can Oinkmaster be used to update rules from www.whitehats.com? Q12: Will Oinkmaster fail when new keywords are introduced in the rules? Q13: Under what license are the official Snort rules released, and can I modify them without violating it? Q14: Is there a GUI for Oinkmaster? Q15: When starting Oinkmaster it says my Perl version is too old, or gives me errors about "too many arguments for open". Q16: If I make a modification directly in a rules file that is updated by Oinkmaster, will that change be permanent? Q17: Should I run Oinkmaster as root? Q18: I get "/usr/bin/perl: bad interpreter" when starting. Q19: How often should I run Oinkmaster? Q20: Can Oinkmaster send email or execute a command when the rules have been modified? Q21: What do I do if I need to locally customize (not disable) some rules (and so that those modifications become permanent)? Q22: I want to disable rules by editing the files directly and putting a "#" in front of the rules. How do these changes become permanent? Q23: How do I update the rules from multiple sources, like the official ones at www.snort.org and the ones at www.bleedingsnort.com? Q24: How do I know which rules to disable? Q25: Why do I get alerts like "Snort Alert [1:123456:0]", i.e. "Snort Alert" instead of the rule's msg string? Q26: How do I keep my sid-msg.map up-to-date? Q27: How to I make Snort reload the new rules without stopping the current Snort process? Q28: Can I use an external utility to download the rules archive rather than one of the built-in methods? Q29: I update from multiple URLs at the same time and get the error "a file called 'sid-msg.map' exists in multiple rules archives" (or the same message for some other file). Q30: I get this error when running Oinkmaster: "URL not specified. Specify at least one 'url=' in the Oinkmaster configuration file or use the '-u ' argument" Q31: How do I add a new classification when using Oinkmaster? Q32: What Perl modules do I need to be able to set use_external_bins = 0? Q33: Can I tell Oinkmaster to disable all rules by default, and only enable and update specific ones? -------------- Q1: What URL should I download the Snort signatures (rules) from? A1: Quick instructions if you want to update using the official free Snort signatures: 1) Go to http://www.snort.org/ and register. You will receive a password in your mail shortly after. 2) Go to the Snort site and login, and choose to edit your user profile. 3) You will find the most current instructions under "Oinkmaster Download Codes" on that page. Basically press the "Get Code" button to generate a code. This will generate a code looking something like "5a081649c06a277e1022e1284bdc8fabda70e2a4". Then construct the URL like this: http://www.snort.org/pub-bin/oinkmaster.cgi// The filename depends on which version of Snort you use, so you must remember to always use the right one. If you for example use Snort 2.4, the url would look like this (without wrap): http://www.snort.org/pub-bin/oinkmaster.cgi/5a081649c06a277e1022e1284b dc8fabda70e2a4/snortrules-snapshot-2.4.tar.gz You can now specify this URL as "url = " in oinkmaster.conf or use the "-u " command line argument. Here is some more information about downloading Snort signatures. Oinkmaster can be used to update Snort signatures from many different places, like the official ones at www.snort.org or the "Bleeding Snort" ones from www.bleedingsnort.com. Here follows information about the official Snort signatures at www.snort.org Third party rules sources should have their own instructions. Remember that you should never ever update the Snort signatures from an untrusted source! In March 2005, Sourcefire changed the license of the Snort signatures. There are currently two different types of free rules tarballs on the official Snort site. The first one is the 5-day delayed Sourcefire VRT Certified Rules which you must register to get access to It's free to register, but to get them without the 5-day delay you must pay Sourcefire. The second type is the Community Rules released under GPL. See http://www.snort.org/rules/ for details about the different types of rules. The good news is that you can update all of them using Oinkmaster. To download the Sourcefire VRT licensed rules, see the instructions above. For the Community rules, no registration is required, so just point to the right URL (currently http://www.snort.org/pub-bin/downloads.cgi/ Download/comm_rules/Community-Rules.tar.gz). As of Oinkmaster v1.2, you can specify multiple URLs to grab multiple rules archives at the same time. Note that when updating from multiple sources, e.g. both the Sourcefire VRT Certified Rules and the Community Rules, and you use a program such as Barnyard that requires a sid-msg.map file, you must handle this somehow. More information about this can be found elsewhere in this FAQ. It's generally recommended to use the Sourcefire VRT licensed rules as a minimum and other rules sources as a complement if requested. Q2: Oinkmaster says the file in URL does not exist (or it exists but doesn't work with my Snort). A2: Check the "url = ..." line in oinkmaster.conf (or argument to "-u" if you specify it on command line). The URL you should use depends on the version of Snort you run (check with snort -V). If Snort complains about unknown keywords, you are probably trying to use rules that aren't compatible with your Snort version. See Q1 for more information. If you are absolutely sure you are using the right rules for your version of Snort but still have problems, you may need to read the Snort manual because it is likely that your local configuration is broken. For example, the new rules maybe contains new variables that you forgot to add. But of course there can be times when the rules archive actually is broken somehow (remember that auto-update of the rules with automatic Snort restart is bad unless you are careful). These issues are often mentioned on the Snort mailing lists. Q3: Can Oinkmaster be useful for distributing rules and configurations locally to my sensors? A3: Yes, there are several situations where Oinkmaster can be handy when dealing with local rules management. If you have only a few sensors, you can simply run Oinkmaster in a traditional way to update the rules from the Internet on all of them. However, if the sensors have no way to talk to the outside, and/or you have many sensors you want to control in a convenient way from a single place, you'll want to consider another solution. A common way to solve this is to do some very simple scripting and have a "master" host which downloads the rules using Oinkmaster and does some global modifications to them. With some simple scripting, it can then push the rules archive to each sensor which runs Oinkmaster on it. You can also have each sensor run Oinkmaster and pull the rules archive from the master, for example using -u scp://... and point to the master. Each sensor can have its own oinkmaster.conf to fine-tune the rules (even override global settings set by the master). The rules archive can of course contain more than just the rules, such as Snort/Barnyard/Oinkmaster configuration files and the sid-msg.map and so on. An example step-by-step list how this could be done: 1) foohost, the master, uses Oinkmaster in a traditional way with its own oinkmaster.conf to download the rules into some local directory. Rules that you modify here will become modified on ALL sensors by default. This is a convenient way to disable rules that you don't want to use on any sensors instead of having to disable them on each sensor, for example. If you don't want any global preprocessing of the rules, you could just use a simple wget of the archive on this host (or run Oinkmaster with a NOOP config). 2) Create a script that re-creates the rules tarball from the rules just downloaded. You could then either push this new archive to all the sensors or have each sensor pull the tarball from foohost using '-u scp://user@foohost:/...'. If your sensor does not have a way to talk to other hosts at all (like when only using receive-only cables), the rules archive will obviously have to be distributed manually using a removable media like a USB memory stick or something. If you pushed the archive to the sensors, they should run Oinkmaster with '-u file:///...' and point to this file. In each local oinkmaster.conf, you can fine-tune the rules to fit this particular sensor. Q4: Can Oinkmaster be useful for distributing my homemade rules to my sensors? A4: Yes! This can be done almost exactly like in the example above. The main difference is that in step 1, you don't download from the Internet but rather point to a rules tarball created from a local directory (perhaps checked out from a cvs repository) containing your homemade rules. There are a few scripts in Oinkmaster's contrib/ directory (that all handle multi-line rules) that may be useful in this case. For example, addsid.pl will parse all your rules and add the next available SID to rules that don't have any. This may be useful to run before creating the rules tarball. So when adding new homemade rules on the master (and you only have to add them there since they will be distributed to all your sensors automatically), just leave out the SID and it will be filled in for you automatically. You can also use create-sidmap.pl to create a new sid-msg.map from your rules. Q5: In which order will the localsid/modifysid/disablesid/enablesid configuration directives be processed? A5: For each rule, the following is performed: 1) If there is a matching localsid statement (no matter where in the configuration file it is), skip further processing 2) If -e was specified on the command line, enable the rule 3) Process all matching modifysid and use_template statements in the same order as they appeared in the configuration file 4) Disable the rule if there is a matching disablesid statement (no matter where in the configuration file it is) 5) Enable the rule if there is a matching enablesid statement (no matter where in the configuration file it is) Usually you don't have to worry about this as it only matters if you have multiple types of processing directives for the same rule and that affects each other. Q6: How can the 'include' statement in oinkmaster.conf be useful? A6: There are many situations where this is useful, but perhaps the most common usage is to be able to use one global and one sensor-specific configuration file. You would usually want to make sure to include the sensor-specific file last since possible values that are redefined overrides the previous ones (except for the "url" option, as you are allowed to specify multiple URLs). Remember that you can also use multiple "-C" arguments when starting Oinkmaster to make it load multiple configuration files. They will be loaded in the order of appearance on the command line. Q7: Can Oinkmaster restart Snort after an update? A7: No. This functionality does not belong in Oinkmaster since almost everyone has their own way of restarting Snort. Automatic restart of Snort after an update is usually not a good idea either. See the README for more information. If you really want to do an automatic restart, just write a little wrapper that does what you want, and remember to run "snort -T" first and refuse to restart if the test is not successful. Of course, you should only restart Snort if there actually were any changes in the rules. Q8: Can I tell Oinkmaster to not touch certain SIDs in sid-msg.map so the entries I add for my local rules don't get deleted? A8: No. What you may want to consider instead is to use a separate sid-msg.map for your local rules (and not touch the map for the official rules), and then join the two before feeding it to Barnyard/whatever. Or, what is probably even better, is to generate the sid-msg.map automatically yourself after each update. There is one script in Snort's contrib directory that does this, and there is also one that comes with Oinkmaster (contrib/create-sidmap.pl). The latter handles multi-line rules and can also take an arbitrary number of rules directories as argument. It is described in detail elsewhere in this FAQ. Q9: I want to copy the rules archive from a remote host to the sensor via scp without having to supply a password or passphrase, but I also don't want to give the sensors full access to that host. Can this be done? A9: Yes. You should really rely on the OpenSSH documentation on how to do this as it is well documented there, but an attempt at explaining how it could be done follows. The instructions assumes OpenSSH on both client and server. If you want to automate the process of just copying a file with scp, you should never have to give one host full access to the other. And in this particular case, there is absolutely no need to run as root on either side. One solution is to generate a key pair where the private key has no passphrase and the public key has restrictions to only allow the copy. (If you don't plan on running Oinkmaster unattended, it is of course recommended to use a passphrase anyway, even though the public key should still contain as many restrictions as possible.) First, generate the key pair on the sensor (i.e. the host that is going to run Oinkmaster, not the master host that keeps the archive): ssh-keygen -t rsa -C 'oinkmaster copy' -N '' -f oinkmaster_scp This will give you two files in the current directory, oinkmaster_scp (private key) and oinkmaster_scp.pub (public key). The public key must now be edited to add some restrictions to it. Details about how to add restrictions to a host is described in the OpenSSH manual. Perhaps the most important restriction in this case is the command="command". You should also use as many other restrictions as possible, such as from="pattern-list" to only allow the sensor(s), no-pty to prevent pty allocation, and also turn off all types of forwarding. You will see that the public key begins with something like "ssh-rsa AAAA...". To only add the command restriction, modify the key to say something like: command="scp -f /etc/snort/snortrules.tar.gz" ssh-rsa AAAA... This assumes that the tarball will be found as /etc/snort/snortrules.tar.gz on the master host, but change it to the location where you store the tarball there. When you are done editing oinkmaster_scp.pub, append the contents of it to the master host's ~/.ssh/authorized_keys for the user that will be used to perform the copy. This should not be a privileged user, the only requirement is that this user has read access to the rules archive. Now make sure that you put the private key (oinkmaster_scp) in a suitable directory on the sensor. Then tell Oinkmaster to use this key by specifying "scp_key = ..." in oinkmaster.conf, You could use something like "scp_key = /home/oinkmaster/.ssh/oinkmaster_scp". Or you can use ~/.ssh/config to specify the key as described in the OpenSSH manual. Then you should be able to run Oinkmaster: oinkmaster.pl -u scp://user@foohost:/etc/snort/snortrules.tar.gz -o rules It will copy the archive without asking for a password/passphrase. If you start Oinkmaster in verbose mode (-v), scp will be run in verbose mode as well. Q10: How do I setup proxy configuration? A10: If you download the snort rules archive from a web site (e.g. www.snort.org) and you are a behind an http proxy, you need to tell Oinkmaster to use that proxy or the download will fail. How you do that depends if you use wget (i.e. use use_external_bins=1, the default, in oinkmaster.conf) or libwww-perl (use_external_bins=0). You can read their man pages for complete configuration instructions, "man wget" or "man LWP::UserAgent". Quick instructions for the impatient: For wget, you can either edit the wget configuration file (usually /etc/wgetrc for global settings and ~/wgetrc for user settings) and set the http_proxy option. For example: http_proxy = http://your_proxy_host:3128:/ (or http_proxy = http://user:password@your_proxy_host:3128/ if you are required to authenticate to the proxy). You can also set the http_proxy environment variable. If you want your proxy server not to return the file in its cache but rather get it from the web server again, add "header = Pragma: no-cache" to your wget configuration file. For libwww-perl, set the http_proxy environment variable as described above (i.e. to "http://your_proxy_host:3128:/" or "http://user:password@your_proxy_host:3128/"). Q11: Can Oinkmaster be used to update rules from www.whitehats.com? A11: No, the Snort rules on www.whitehats.com have not been maintained for a long long time and should not be used, although the arachNIDS database is still a good source of information for older rules. (And for Oinkmaster to work in a technical sense, the rules package must be a gzipped tar file that contains a directory called "rules", which holds all the rules files.) Q12: Will Oinkmaster fail when new keywords are introduced in the rules? A12: No. Not unless there is a bug in Oinkmaster anyway. Oinkmaster was designed to be stupid and not try to understand every single part of the rules. Basically, for a rule to be successfully identified by Oinkmaster, it must start with one of the keywords in 'rule_actions' specified in oinkmaster.conf. It must also contain 'msg: "some message";' and 'sid: ;'. Some basic syntax checks are also performed, but the rest is ignored. If a rule cannot be successfully parsed for some reason, it will simply be regarded as a non-rule line (or multiple non-rule lines if it is a multi-line rule). Since non-rule lines are also compared and updated by Oinkmaster, things will work as usual anyway except that actions such as disablesid on that rule will have no effect. Remember that even if Oinkmaster may not have problems with the new rules, they can still be invalid when being read by Snort. This has happened a couple of times in the past when the official rules tarballs contained broken rules (containing keywords that weren't valid for that version of Snort) put there by accident. Until those broken rules are fixed, the easiest thing to do is usually to simply add temporary "disablesid" statements for the them. Q13: Under what license are the official Snort rules released, and can I modify them without violating it? A13: In March 2005 Sourcefire changed the license, but it should be no problem as long as you don't redistribute the rules outside your organization. See http://www.snort.org/rules/ for the details. Q14: Is there a GUI for Oinkmaster? A14: Yes, check out README.gui and contrib/oinkgui.pl. This is just a primitive front-end for Oinkmaster though. Oinkmaster was designed to be simple and also easy to use in scripts, so if you want a more advanced graphical interface for maintaining Snort rules, you should probably be looking at another tool instead. Q15: When starting Oinkmaster it says my Perl version is too old, or gives me errors about "too many arguments for open ...". A15: Your Perl version needs to be upgraded to at least 5.6.1. The issue is common on FreeBSD boxes, which usually use Perl 5.005. There is a more recent Perl version in the ports tree though (lang/perl5.8 at the time of writing), so just install that one and execute "/usr/local/bin/use.perl port" to globally enable it. Q16: If I make a modification directly in a rules file that is updated by Oinkmaster, will that change be permanent? A16: No! Permanent modifications in files that are updated by Oinkmaster must be done by editing oinkmaster.conf. If you edit a rules file directly, the next time you run Oinkmaster it will see that the file is different than the downloaded one, and so your file becomes overwritten. There is no such thing as "if you edit a rule, it will automatically be marked as locally customized and therefore not automatically updated". This may be different than some other rules updating tools, but it is the expected behavior as Oinkmaster was designed to work this way. You can however use the "localsid" keyword to achieve this behavior. See Q21 for recommendations how to handle local customization of the rules. Q17: Should I run Oinkmaster as root? A17: No! You must run Oinkmaster as a user that has read/write access to your rules directory and all rules files in it, but it should *NOT* be a privileged user such as root! Never run Oinkmaster as root. Adding a new user to be used for running Oinkmaster is a good idea, just make sure it has read/write access to your rules. (The user you run Snort as, if different than the Oinkmaster user, usually only needs read access to the rules.) Q18: I get "/usr/bin/perl: bad interpreter" when starting. A18: By default, Oinkmaster assumes that Perl is found as /usr/bin/perl. If you get this message, you need to edit oinkmaster.pl and modify the first line so it points to the right location for your system (try "which perl"). Q19: How often should I run Oinkmaster? A19: The short answer would be that once a day is probably reasonable in most cases. The official rules tarballs on www.snort.org are currently generated from the CVS repository many times a day, but that does not mean there are always any changes in it. My experience is that the rules are sometimes updated daily, and sometimes in bursts with several days/weeks apart (also depending on which rules tarball you use). If you've run Oinkmaster several times for a few days and it still says that nothing has changed, this does not automatically mean anything is wrong. If you want to verify that Oinkmaster is working you can do some arbitrary modifications in some rules file in your rules directory and then run Oinkmaster. If those changes are noticed by Oinkmaster, everything probably works as expected. If you are behind a web proxy, make sure it is not trying to be nice by caching the rules archive and always give you the old version instead of actually polling the real site for an updated version (see Q10). Check out http://www.snort.org/ for the latest information about the rules and the rules release process (Oinkmaster and its author has nothing to do with this). Q20: Can Oinkmaster send email or execute a command when the rules have been modified? A20: There is no built-in function in Oinkmaster to do that, because that functionality does not belong there. However, it is very easy to do it with a small wrapper. You can for example run Oinkmaster with the -q flag so it doesn't give any output unless the rules had changed, and redirect the output to a file. Then check the size of this file. If it is empty, nothing had changed. If it has some content, something had changed. So then you can either send the content of the file in an email and/or execute some command. Here is a basic wrapper script to show how you could do this. There is also an example in the README file. #!/bin/sh tmpfile=`mktemp /tmp/oinkmaster.XXXXXXX` || exit oinkmaster.pl -o /etc/snort/rules -q > $tmpfile if [ -s "$tmpfile" ]; then echo "The rules have changed." # Do stuff here, like send content of $tmpfile in # an email or execute some command. else echo "No changes in the rules." fi rm $tmpfile Q21: What do I do if I need to locally customize (not disable) some rules (and so that those modifications become permanent)? A21: As seen in Q16 you cannot just edit a local rules file and customize the rules, because they would be overwritten during the next update. There is however often a need of being able to do such modifications to the rules, and there are a few different ways you could do this with Oinkmaster. One way is often the best one and the other two are often less good, depending on the situation of course. The often best way - modifysid (and templates) ---------------------------------------------- If you need to modify rules slightly, this is definitely the way to go. For documentation about modifysid, see oinkmaster.conf (don't forget README.templates and template-examples.conf if you use modifysid a lot and want to simplify the process). A modifysid statement is a way to apply a substitution expression on specified rules after each update. Here comes the whole point of why using this approach is often the best one: If the default version of this rule is later improved somehow in the new rules archive, you would automatically get the updated version while your customization would still apply (assuming your substitution expression still matches the new version of the rule, of course). This means that even rules that you need to customize can be continued to be automatically maintained! Some typical examples where using modifysid is the best way to go is where you pretty much like the default rule and always want to use the latest version of it, but you want to change some detail (such as switching $HOME_NET/$EXTERNAL_NET) or add something to it (such as a tag or flexresp statement). Another point is that all these local customizations by using modifysid become kind of self-documenting, which you will appreciate when looking at your rules and wonder what those local modifications you've done over the years actually are. The sometimes less good way #1 ------------------------------ If you need to modify a rule heavily in such way that it becomes too complex and too meaningless to try to use and maintain a modifysid statement for it, you need to solve the problem in another way. I would recommend using a "disablesid" statement for that rule and then rewrite and copy it to some local rules file (one that is not updated by Oinkmaster) and change its SID to a local one. Note however that by doing this, the rule will no longer be automatically maintained! Be very careful so you don't end up with lots of rules that become forgotten and not maintained, as this is the exact opposite of what Oinkmaster is created for. A positive side-effect of using "disablesid" for these rules is that Oinkmaster keeps track of changes even in disabled rules in the official rules files. So you may want to pay attention to these - you might find an interesting change some day that you want to apply to your version of the rule as well. The sometimes less good way #2 ------------------------------ As of Oinkmaster 1.1, you can use variant of the above as it introduced the "localsid" keyword. By using "localsid " in oinkmaster.conf, you mark this rule as being "locally modified" which means it will never get overwritten. Instead, your local version of the rule is kept. Any updates in the official version of the rule is silently discarded, but if you run in verbose mode (-v) Oinkmaster will print the current difference between your version and the official version. Again, be warned that all "localsid" rules will no longer be automatically maintained! If you are using this feature more than in a very few cases, chances are that you are doing something wrong. Q22: I want to disable rules by editing the files directly and putting a "#" in front of the rules. How to these changes become permanent? A22: As can be read elsewhere in this FAQ, you shouldn't ever modify rules in any way by editing the rules files directly when using Oinkmaster. Normally, the right way to disable rules is to use the "disablesid" keyword. However, if you for some reason really want to disable rules by editing the rules files directly and putting a "#" in front of unwanted rules, like if you're about to use Oinkmaster for the first time and you already have a bunch of disabled rules, you can use a little trick. Use contrib/makesidex.pl to generate "disablesid" lines for those disabled rules and feed them to Oinkmaster. You can for example use something like this to update this information automatically and redirect to a file included by Oinkmaster (we don't touch the main config file): makesidex.pl /etc/snort/rules >autodisable.conf 2>/dev/null && \ oinkmaster.pl -C /etc/oinkmaster.conf -C autodisable.conf \ -o /etc/snort/rules Q23: How do I update the rules from multiple sources, like the official ones at www.snort.org and the ones at www.bleedingsnort.com? A23: One way is to simply run Oinkmaster once for each URL and with different output directories. This way you don't have to worry about filename collisions etc. As of Oinkmaster 1.2 though, you can specify multiple URLs at the same time, either by using several url= in the configuration file or by specifying several -u on the command line. Beware that even when using multiple URLs, all the downloaded rules files will be put in the same output directory. So if a filename exists in multiple rules archives, there is going to be trouble (Oinkmaster will detect this and abort before touching anything). Any 'skipfile' or other directive you may have in your Oinkmaster configuration that takes a filename as argument will apply on any matching file regardless of which rules archive it was in. If you specify multiple URLs and one of them happen to be broken somehow, Oinkmaster will exit and not process the other ones. This may be good or bad, depending on the situation. Note that if you run Barnyard (or any other program that use unified logs) when using rules from multiple sources (and/or local rules), you must make sure that your SID message map file (sid-msg.map) is up-to-date and contains entries from all those sources, including your local rules if any. This procedure is described elsewhere in this FAQ. Q24: How do I know which rules to disable? A24: This is a very hard question that really has very little to do with Oinkmaster. But here are a few tips: - Remember that there are many ways to stop alerts from firing, and disabling the rule is not always the best way. For example, if all you want is to stop the alert from/to a particular host, you should not disable the rule completely. Instead, check the Snort manual on how to use BPF filters, suppressing and pass rules and choose the best method for the situation. - Review your Oinkmaster configuration files every now and then, especially rules you have disabled or modified. Rules you disabled or modified a long time ago may have improved, or your environment may have changed since then. - Subscribe to relevant mailing lists, like snort-sigs. Q25: Why do I get alerts like "Snort Alert [1:123456:0]", i.e. "Snort Alert" instead of the rule's msg string? A25: It happens when you run Barnyard with a sid-msg.map file that is not up-to-date. When Snort generates an alert in unified format, it does not include the rule's msg string, just the rule's SID (a number to identify a rule). When Barnyard then converts the unified alerts to something human readable or something to insert in the database, it must somehow be able to lookup the msg string for each rule by using their SIDs. This is done via the file sid-msg.map. This file must contains an entry for every rule. This means that when adding new rules, this file must be updated as well (how to do this can be found elsewhere in this FAQ). Also, make sure to restart both Barnyard and Snort so they stay in sync. You must also make sure that the gen-msg.map file is current, but it probably only requires updating when switching Snort versions. If you wonder what the numbers in [1:123456:0] means, the first is the generator ID (1 means it's a rule), the second number is the SID (123456 in this case) and the last one is the revision (0 in this case, which probably mean the rule has no revision keyword in it). See the Snort and Barnyard manuals for more information. Q26: How do I keep my sid-msg.map up-to-date? A26: When using Barnyard and the like, it's important that the sid-msg.map file is always up-to-date, or you will end up with alerts like "Snort Alert [1:123456:0]" instead of getting the rule's real msg string. There are several ways to do this. One way is to assume that an updated SID map file is included in each rules archive and to update those along with the rules, and then merge them into a new file which you then feed to Barnyard/whatever. Another way (which I think is much better) is to ignore the SID map files in all the rules archives and instead generate it yourself after each rules update. There is a script in Oinkmaster's contrib directory, create-sidmap.pl, which can generate a sid-msg.map file from multiple rules directories. It can handle multi-line rules and will also warn you in case of duplicate SIDs, regardless of the file or directory. This includes any local rules you may have written, which is very important as the SIDs in those may collide with those in a 3rd party rules archive. Just remember to run create-sidmap.pl after each rules update, and also to to restart Barnyard/whatever to use the new SID map if there were any changes. A tiny example just to show the idea how an update wrapper could look like when updating from multiple sources and then generating the sid-msg.map file: #!/bin/sh oinkmaster.pl -u http://www.bleedingsnort.com/... -o /etc/snort/rules/bleeding/ oinkmaster.pl -u http://www.snort.org/... -o /etc/snort/rules/official/ create-sidmap.pl /etc/snort/rules/official/ \ /etc/snort/rules/bleeding/ \ /etc/snort/rules/local/ \ > /etc/snort/sid-msg.map You may also want to put in some sanity checks like aborting in case of duplicate SIDs and restarting Barnyard/Snort if everything went ok and there were changes and so on, you get the point. Q27: How to I make Snort reload the new rules without stopping the current Snort process? A27: This is not really an Oinkmaster question so you may want to check the Snort documentation for the most current information. Basically, the answer to the question is that you can't do it. You'll have to restart the Snort process to reload the rules. If you don't want to miss any traffic while doing that, one trick is to start a new Snort process before stopping the old one (possible existing state information will be lost though). Some might argue that sending a SIGHUP signal will reload the rules without stopping Snort, but that's not true as that will simply make Snort stop and restart itself. Also, this currently requires that you run Snort as root and non-chrooted. Don't ever do that. Q2: Can I use an external utility to download the rules archive rather than one of the built-in methods? A2: Yes, use whatever program you want and download the rules archive to the local filesystem first, and then run Oinkmaster and point to that file using file:// as URL specification (url= in oinkmaster.conf or -u on the command line). Q29: I update from multiple URLs at the same time and get the error "a file called 'sid-msg.map' exists in multiple rules archives" (or the same message for some other file). A29: When updating from multiple URLs at the same time, the archives can not contain files with identical filenames as the result will go into a single output directory. Most rules archives include a sid-msg.map file that is used by Barnyard and similar programs to map a SID number into a rule msg string. When updating from multiple URLs at the same time and you get the error message, you must add "skipfile sid-msg.map" to your oinkmaster.conf to ignore that file in all archives. If you need a sid-msg.map file, you must generate it yourself. How this is done is described elsewhere in this FAQ. Q30: I get this error when running Oinkmaster: "URL not specified. Specify at least one 'url=' in the Oinkmaster configuration file or use the '-u ' argument" A30: The error message explains it all - you did not tell Oinkmaster where to get the rules archive. Look in the default oinkmaster.conf for more information. As of v1.2, no URL is specified by default in the distribution oinkmaster.conf, so you must add at least one. Q31: How do I add a new classification when using Oinkmaster? Q31: It's often convenient to add new classifications for local signatures that you create. You can not however put those classification definitions in the regular classifications.conf file that is updated by Oinkmaster, as those modifications would be lost after an update. Instead, you can for example put your local classification definitions in a file called local-classification.conf and include that file with "include /path/to/local-classifications.conf" from your snort.conf, just like you also include the regular classification.conf file. Q32: What Perl modules do I need to be able to set use_external_bins = 0? A32: To use pure Perl instead and not use any external binaries (except when using scp do download the rules), you need the Perl modules Archive::Tar, IO::Zlib and LWP::UserAgent. You can get them from http://search.cpan.org. If you're on *BSD, they're most likely also available in the ports tree. On OpenBSD they're called p5-Archive-Tar, p5-IO-Zlib and p5-libwww. On Windows with ActivePerl, all those modules are included and used by default. Q33: Can I tell Oinkmaster to disable all rules by default, and only enable and update specific ones? A33: Yes. This may be convenient if you for example want to update rules from a third party, but you do not want to use their new rules by default - you only want to use some specific SIDs and keep those updated automatically. This can be achieved with a 'modifysid' expression to disable all (or some) rules first, and then specify 'enablesid ' to activate the rules you know you want to use. Let's say there are a few specific rules you like from the Bleeding Snort project, but you don't care about their other rules. You could copy those rules into some local rules file but that way they would not get updated if the Bleeding Snort project makes updates to their versions of the rules. The Oinkmaster way is better. First, use a 'modifysid' statement to disable all rules containing the "BLEEDING-EDGE" tag: modifysid * "^(.*BLEEDING\-EDGE.*)$" | "#${1}" Now, as 'enablesid' statements are processed after the 'modifysid' statements, we can override them and reenable the rules we like, for example: enablesid 2000566, 2000567, 2000568 Now only the Bleeding Snort rules with SID 2000566, 2000567 and 2000568 will be enabled and you can keep them automatically updated. Oinkmaster will still update the other rules but they will stay disabled (commented out), and changes in those will show up as "Modified inactive rules". Remember that if you find a new rule you want to use, you must add an 'enablesid' statement for it as all added rules will be disabled by default. oinkmaster-2.0/UPGRADING010064400017500001750000000040711037561221100152050ustar00andreasoandreaso# $Id: UPGRADING,v 1.27 2006/01/28 21:41:31 andreas_o Exp $ # Usually you just overwrite the old oinkmaster.pl file with the new one and then run as usual. A good tip though is to always have a quick look at the new documentation and default oinkmaster.conf for new options and features. Also, to be on the safe side it's recommended to do an extra backup of the rules first and to do a test run with -c (careful mode) to make sure you don't get unexpected results. Here are a few notes about issues between specific versions that might require some attention. 0.9 -> 1.0 ---------- You can now run without external binaries if you set use_external_bins to 0 in oinkmaster.conf and have the required Perl modules. It's set to 0 by default on Win32 (since ActivePerl 5.8.1+ comes with the required Perl modules) and 1 by default on other system (i.e. keep old behavior). Add "use_external_bins = 0" or "use_external_bins = 1" to override the default. See the new default oinkmaster.conf for more information. Because the layout of the Snort rules archives has changed on www.snort.org, you must make sure that "url" is correctly set to point to the right version for your version of Snort. See oinkmaster.conf or the Oinkmaster FAQ for more information. 0.7 -> 0.8 ---------- The arguments to 'modifysid' are now regarded as regular expressions, so if you're using that, it may (or may not) mean that you have to adjust your arguments to reflect this. 0.6 -> 0.7 ---------- When upgrading to 0.7, you should know that oinkmaster.conf will now be searched for in /usr/local/etc/ by default (instead of in the current directory). You will have to put it there or use the -C argument. 0.3 -> 0.4 ---------- The file oinkmaster.conf is now used for configuration. It's probably easiest to start with the new default config and apply your old customization to it. The "file" and "sid" keywords have been renamed to "skipfile" and "disablesid" respectively. Also, a few command line switches were removed since they're not needed anymore. Please read the new documentation. oinkmaster-2.0/README010064400017500001750000000407051037561221100146260ustar00andreasoandreaso# $Id: README,v 1.84 2006/01/28 22:11:12 andreas_o Exp $ # Introduction ~~~~~~~~~~~~ Oinkmaster is written by Andreas Östling . The homepage is at http://oinkmaster.sourceforge.net/ Oinkmaster is simple Perl script released under the BSD license that helps you keep your Snort rules current with little or no user interaction. It has quite a few useful features regarding rules management, such as ability to enable, disable and modify specified rules after each update. It will tell you the exact changes from your previous rules, so you have total control of what's going on. It may be useful in conjunction with any program that can use Snort rules, like Snort (doh!) or Prelude-NIDS. Oinkmaster is most often used to grab the latest official rules tarball from www.snort.org and apply a set of modifications to them (such as disabling unwanted ones), but it can just as well be used to manage your local rules and also third party rules and distribute them to multiple sensors with ability to fine-tune the rules on each sensor or group of sensors. Oinkmaster is designed to integrate well with other scripts and you can easily setup a very powerful rules management system. See the FAQ for hints and suggestions. Use Oinkmaster with care and at your own risk. Check the INSTALL file for quick installation instructions. You may also find the FAQ useful, and README.gui if you want to use the graphical front-end. If you're on Windows, have a look at README.win32. Information about the available command line options can be found in the manual page (oinkmaster.1). If it's not installed on your system you don't want to install it, you can read it directly with "groff -man -Tascii oinkmaster.1 | less". Check out http://www.snort.org/ for more information about Snort and its rules. Requirements ~~~~~~~~~~~~ It should work on most Unix-like systems with Perl 5.6.1 or later. You also need either the binaries tar, gzip and wget, or the Perl modules Archive::Tar, IO::Zlib and LWP::UserAgent. See oinkmaster.conf for more information about this. So far it has been successfully tested on OpenBSD, FreeBSD, NetBSD, Linux, AIX, Solaris, Mac OS X, QNX and Microsoft Windows. What problem does Oinkmaster solve? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To have any use of your NIDS, you must keep your signatures (or "rules") current. On www.snort.org for example, there is a rules tarball that is updated with new and improved rules on a regular basis. Very soon you will realize that this tarball contains rules that are not suitable for your environment, so you delete (or comment out) them from your local rules files. There may also be rules that you have to tweak in different ways to suit you better. Everything is fine until it's time to update the rules since you will have to apply all these modifications again (and you must also keep track of these modifications so you can repeat them after each update). This clearly becomes a boring and difficult task if you do it manually, especially if you have many sensors and make lots of local fine-tuning of the rules. This is where Oinkmaster comes in - it will automatically do those boring modifications to the rules that you would otherwise have to do manually after each update. How it works ~~~~~~~~~~~~ It's actually really simple. Here follows a step by step list of how a typical rules update with Oinkmaster works. Optional features may of course activate additional procedures, but this is what the basics look like. 1) It fetches the Snort rules archive from the specified location and puts it in a temporary directory. 2) It unpacks this archive and looks for a directory called "rules" in it. All rules files in here (i.e. files matching the "rules_update" regexp specified in oinkmaster.conf) are processed as specified in oinkmaster.conf. It basically opens each rules file, cleans some leading/trailing whitespaces from it, enables/disables/modifies rules as requested, and writes the result back to the same file again. 3) These new files are now compared to the ones in your output directory (the one specified with "-o"), file by file. All lines are compared, not just the actual rules, although a more complicated comparison is performed on the rules so that the we can later present a result that is easier to parse for a human. Files that are not identical are copied from the temporary directory to the output directory. Detailed result of the comparison is then printed. Usage information ~~~~~~~~~~~~~~~~~ Try ./oinkmaster.pl -h for more usage information and available options. Full descriptions of the options can be found in the Oinkmaster manual page. There are also many useful notes and examples in the default oinkmaster.conf. As a first test, you can create an empty rules directory, for example /tmp/rules/. Then try executing "oinkmaster.pl -o /tmp/rules". Since your /tmp/rules/ directory is empty, all rules files in the downloaded archive will be regarded as added, and copied to the output directory. Then try "oinkmaster.pl -o /tmp/rules" one more time. This time the files in the downloaded archive will be compared to the ones in /tmp/rules/ and Oinkmaster will tell you if something has been updated. If you already have several rules commented out (or removed) in your current local rules, you need to add the SIDs (Snort rule ID) of those to oinkmaster.conf manually (or check out the contrib directory for a help script) before running Oinkmaster, or they will be re-enabled after each update. You disable rules by adding "disablesid " to oinkmaster.conf, where is the SID of the rule in question. So if you want the rule with SID 12345 to be commented out after each update, you add the line "disablesid 12345" to oinkmaster.conf. When you update the rules the next time, this rule will become commented out and Oinkmaster will notify you of that change. You can also add entire files to be totally ignored by adding "skipfile filename" options to oinkmaster.conf where "filename" is a file in the distribution archive you don't care about at all. These files will not be checked for changes and they will not be added or updated. For example if you don't include the file icmp-info.rules from your snort.conf and don't care about keeping it up to date and don't want to be notified of changes in it, you can to add the line "skipfile icmp-info.rules" to oinkmaster.conf. Although it may be a good idea to track changes even for rules files you don't use (i.e. that you don't include from your snort.conf). Who knows, you might find something interesting some day. The skilled/brave/stupid people can also use the "modifysid" keyword in oinkmaster.conf to make arbitrary changes to certain rules after each download. This is an excellent way to screw up your rules, so don't use it unless you really have to. Oinkmaster can be run manually or automatically as a cron job. But of course, be very careful when doing the latter. Things could easily get messed up from one update to another because of different layout in the rules archive, typo in a rule, bugs in the script, and so on. At least run snort -T on the new rules before restarting your Snort process. And as always when updating the Snort rules there may be some changes that you really don't like (or at least want to know about before using). Remember that after switching to Oinkmaster for performing the rules updating, permanent modifications to the rules files must be done by editing oinkmaster.conf, not by editing your rules files directly as such modifications will be lost after an update. Usage examples ~~~~~~~~~~~~~~ To automatically update the rules every night at 02:30 and make a backup of the old ones if there were any changes and send difference notification to syslog, you could use something like this in your crontab: 30 2 * * * oinkmaster.pl -o /etc/snort/rules/ -b /etc/snort/backup 2>&1 |logger -t oinkmaster If you want the output to be sent to you in a mail instead, you could use something like: oinkmaster.pl ... 2>&1 | mail -s "subject" you@example.com If you just want to check for changes in the rules but not actually update your existing rules, you can use the -c flag for "careful" mode. To silently check for updates as a cron job and not sending any e-mail unless there were updates available, you could use something like this (wrapped for readability): 30 2 * * * TMP=`mktemp /tmp/oinkmaster.XXXXXXXX` && (oinkmaster.pl -o /etc/snort/rules/ -q -c > $TMP 2>&1; if [ -s $TMP ]; then mail -s "subject" you@example.com < $TMP; fi; rm $TMP) This example only works on systems with the mktemp command but could easily be rewritten for others. You get the point. The important thing here is to use the -q argument (quiet mode) so Oinkmaster doesn't give any output unless there were updates available or if there were warning/error messages. You should of course change directories and subject/e-mail addresses in the examples above as appropriate. More usage examples can be found in the Oinkmaster manual page. Oinkmaster output ~~~~~~~~~~~~~~~~~ If there were any changes, Oinkmaster will tell you about them. Here is what the different changes in the rules mean: o Added: - New rule (the SID did not exist in the old rules file). o Enabled: - The rule was commented out in the old rules file, but is now activated (perhaps because the SID was removed from oinkmaster.conf). o Enabled and modified: - The rule was commented out in the old rules file, but is now activated. The actual rule had also been modified. o Removed: - The rule no longer exists in the new rules file. o Disabled: - The rule still exists but has now been commented out (perhaps because a disablesid statement with this SID was added to oinkmaster.conf). o Disabled and modified: - The rule still exists but has now been commented out. The actual rule had also been modified. o Modified active: - The rule has been modified and is an active rule. o Modified inactive: - The rule has been modified but is currently an inactive (commented out) rule. Non-rule changes will also be printed. Non-rule lines are simply lines that are not actual rules (or rules that for some reason could not be successfully parsed), and Oinkmaster will keep track of those as well. Here comes some typical example output. We see that an active rule in exploit.rules has been modified and that a rule in web-frontpage.rules has been disabled. [***] Results from Oinkmaster started Thu Dec 11 09:39:06 2003 [***] [///] Modified active rules: [///] -> Modified active in exploit.rules (1): old: alert tcp $EXTERNAL_NET any -> $HOME_NET 515 (msg:"EXPLOIT LPRng overflow"; flow:to_server; content: "|43 07 89 5B 08 8D 4B 08 89 43 0C B0 0B CD 80 31 C0 FE C0 CD 80 E8 94 FF FF FF 2F 62 69 6E 2F 73 68 0A|"; reference:cve,CVE-2000-0917; reference:bugtraq,1712; classtype:attempted-admin; sid:301; rev:4;) new: alert tcp $EXTERNAL_NET any -> $HOME_NET 515 (msg:"EXPLOIT LPRng overflow"; flow:to_server,established; content: "|43 07 89 5B 08 8D 4B 08 89 43 0C B0 0B CD 80 31 C0 FE C0 CD 80 E8 94 FF FF FF 2F 62 69 6E 2F 73 68 0A|"; reference:cve,CVE-2000-0917; reference:bugtraq,1712; classtype:attempted-admin; sid:301; rev:4;) [---] Disabled rules: [---] -> Disabled in web-frontpage.rules (1): #alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"WEB-FRONTPAGE /_vti_bin/ access";flow:to_server,established; uricontent:"/_vti_bin/"; nocase; classtype:web-application-activity; sid:1288; rev:5;) [*] Non-rule line modifications: [*] None. [*] Added files: [*] None. If you think it's too hard to see the actual difference between the old and new rule, you could use the "-m" argument to remove common leading and trailing parts from the rules (see the manual page for details). The above modified rule would then instead look like this: [///] Modified active rules: [///] -> Modified active in exploit.rules (1): old SID 301: ...low"; flow:to_server; content: "|43 07 8... new SID 301: ...low"; flow:to_server,established; content: "|43 07 8... If there were any added rules files (i.e. files that have not been included in the rules archive before and that have now been added to your output directory), you should consider including them from your Snort configuration file by adding an "include " statement for it. If there were any files removed from the archive, you should probably consider excluding them from your Snort configuration file (you must use -r to check for removed files). Note that if a SID is moved from one file to another, it will be regarded as removed from the first one and added to the new one. This is good, because if a rule we like is moved from file A to file B and we only include file A from our snort.conf, we sure would like to be informed of that. Misc important notes ~~~~~~~~~~~~~~~~~~~~ o It's a really good idea to separate your local rules files (if you have any) from the official rules files by keeping them in separate directories. This way you don't have to worry about filename collisions and that some of your local file can become overwritten by files from the downloaded rules archive. It's also a good idea to keep an extra (unused) copy of snort.conf in the rules directory but specify another snort.conf (one that's not in your output directory specified by -o to Oinkmaster) when starting Snort. This way you can keep track of changes in the snort.conf file from the rules archive while keeping your customized copy intact. You do not want to update your production snort.conf automatically!. If there are added/removed rules files in the archive or added/modified variables in snort.conf, you will have to edit your production copy of snort.conf manually and update as needed. Oinkmaster does not do that kind of stuff. Yes, this means that if there is a new variable added the distribution snort.conf and that is used in the new rules, your new rules will not even load until you define that variable in your snort.conf manually. See, Auto-updating with auto-restart is bad unless you're careful. You can run with -U though, which means that Oinkmaster will search snort.conf in the downloaded rules archive(s) for all variables (the "var varname something" lines) and then verify against , which probably is your local production copy of snort.conf. Variables that exist in the distribution snort.conf but not in will be added to directly after any other variables it may contain. Variables that have just been modified are NOT updated, the -U argument only cares about new ones. o Oinkmaster never deletes any rules files from your system. So if a file called foo.rules is usually included in the rules distribution archive but is one day removed from it, the old foo.rules will still be left in your rules directory. Oinkmaster will notify you of this but only if you use the "-r" argument, which means that each rules file (*.rules, *.conf ...) in your output directory will be checked to see if it also exists in the downloaded rules archive. If it doesn't, it will be regarded as removed from the archive and a message will be printed because you may want to edit your Snort configuration file to exclude this file and possibly remove it from your system as well. If you use a separate directory for your Snort rules from snort.org (i.e. you have possible local rules files in another directory) you probably want to use the "-r" argument. Otherwise it will be very misleading since your local rules files obviously don't exist in the rules distribution, and will therefore be regarded as removed every time. o Remember that there are many ways to stop alerts from firing, and disabling the rule is not always the best way. For example, if all you want is to stop the alert from/to a particular host, you should not disable the rule completely. Instead, check the Snort manual on how to use BPF filters, suppressing and pass rules, and choose the best method for the situation. Also check out the document entitled "How to stop Snort alerts from being generated / how to (not) ignore traffic" on the Oinkmaster web site. o Don't depend too much on this script for the rules updating process. It's just intended to be a small help and you should always keep an eye on it. Manual reviewing of all your rules and configuration should always be done. oinkmaster-2.0/README.win32010064400017500001750000000113271037561221100155650ustar00andreasoandreaso# $Id: README.win32,v 1.18 2006/01/28 21:40:23 andreas_o Exp $ # Introduction ------------ Oinkmaster should work just as well on Windows as on Unix/Linux. There are three known ways to run Oinkmaster on Windows: - ActivePerl - Cygwin from within a Cygwin shell - Cygwin but not from within a Cygwin shell Which method you choose is mostly a matter of taste, although I'd say that ActivePerl is the best way to go for most people. You can find installation/usage instructions for each one of these below, but first a few general useful notes about Oinkmaster and Windows. A few useful notes about Oinkmaster and Windows ----------------------------------------------- o You must always specify the -C argument and point to your oinkmaster.conf since it's looked for in a Unix directory by default o Use of the Oinkmaster GUI is optional o When using Cygwin (standalone or from within a Cygwin shell) you should be able to use both msdos style paths (e.g. c:\foo) or Cygwin style paths (e.g. /cygdrive/c/foo) everywhere, but remember that backslashes needs to be quoted ("c:\foo") or escaped (c:\\foo) when starting from a Cygwin shell. o Remember that filenames/directories with spaces specified on the command line need to be quoted, e.g. -C "c:\some directory\oinkmaster.conf" o Depending on your Perl installation, you may or may not be able to call oinkmaster.pl directly. If .pl files are not associated with Perl on your system, you must run "perl oinkmaster.pl ..." instead of just "oinkmaster.pl ...". o Oinkmaster usually requires a few external binaries (tar, gzip and wget). As of Oinkmaster v1.0 though, you don't need them if you have the Perl modules Archive::Tar, IO::Zlib and LWP::UserAgent. The good news is that ActivePerl 5.8.1+ comes with these modules and that Oinkmaster uses them by default on Windows. See the default oinkmaster.conf for more information about this. Running with ActivePerl ----------------------- - If you don't have ActivePerl installed already, get the most recent version from http://www.activestate.com/Products/ActivePerl/. ActivePerl 5.8.1 and later contains all required Perl modules (even for the Oinkmaster GUI), no external binaries are required. If your ActivePerl is older than 5.8.1 and you for some reason don't want to upgrade to a more recent version, you need to execute the command "ppm install IO::Zlib" from a command shell. This is not required on 5.8.1 and later since that module is already included there. - Install Oinkmaster by following the INSTALL file. - Done! Now you should be able to run Oinkmaster. Usage example: c:\perl\bin\perl c:\oink\oinkmaster.pl -C c:\oink\oinkmaster.conf -o c:\oink\rules Your paths may of course be different. Running with Cygwin from within a Cygwin shell ---------------------------------------------- - If you don't have a recent version of Cygwin installed already, get it from http://www.cygwin.com/. Make sure to include at least the packages gzip, Perl, tar and wget. - Install Oinkmaster by following the INSTALL file. - Done! Now you should be able to run Oinkmaster. You run it by first starting the Cygwin environment and then simply call oinkmaster.pl with the requested arguments. For example: /usr/local/bin/oinkmaster.pl -C /etc/snort/oinkmaster.conf -o /etc/snort/rules If you need to specify a path that is not inside the Cygwin directory structure, just use something like: /usr/local/bin/oinkmaster.pl -C /etc/snort/oinkmaster.conf -o "c:\snort\rules" Or the same but using Cygwin style: /usr/local/bin/oinkmaster.pl -C /etc/snort/oinkmaster.conf -o /cygdrive/c/snort/rules Running with Cygwin but not from within a Cygwin shell ------------------------------------------------------ This assumes that you want to run Oinkmaster using Cygwin, but without first starting a Cygwin shell. - If you don't have a recent version of Cygwin installed already, get it from http://www.cygwin.com/. Make sure to include at least the packages gzip, Perl, tar and wget. - Install Oinkmaster by following the INSTALL file. - Make sure the 'path' option in oinkmaster.conf includes the directory/directories containing the required Cygwin libraries and binaries. You can specify the path as either msdos style or Cygwin style, e.g.: path = c:\cygwin;c:\cygwin\bin or the same but as Cygwin style: path = /cygdrive/c/cygwin:/cygdrive/c/cygwin/bin - Done! Now you should be able to run Oinkmaster. (You may want to start a cmd.exe shell first so you see what's going on.) For example, if Cygwin is installed in c:\cygwin and Oinkmaster and the rules directory is in c:\oink\, you can run: c:\cygwin\bin\perl c:\oink\oinkmaster.pl -C c:\oink\oinkmaster.conf -o c:\oink\rules oinkmaster-2.0/README.gui010064400017500001750000000066411037561221100154120ustar00andreasoandreaso# $Id: README.gui,v 1.19 2006/01/28 21:46:12 andreas_o Exp $ # Intro ~~~~~ The Oinkmaster GUI was my very first attempt at creating a graphical user interface in Perl/Tk. It was written just for fun and the result is not the prettiest interface you've seen, but it kind of works. It runs on most Unix-like systems and also on Windows with ActivePerl. It's not well-written and may not be a high priority for me in the future, so use at your own risk. Send feedback to Andreas Östling . How to install ~~~~~~~~~~~~~~ The Oinkmaster GUI can be found as oinkgui.pl in Oinkmaster's contrib directory. If you're lucky, you can just execute it right away. Minimum screen resolution required is 1024x800. The GUI is simply a front-end used to create the right command line and then execute Oinkmaster with it. In other words, oinkmaster.pl can be run without the GUI but the GUI can not be run without oinkmaster.pl. It may be easiest if you make sure you have Oinkmaster (without the GUI) up and running first, although it's not required. See Oinkmaster's own INSTALL and README (and README.win32 of you're on Windows) for more information. The GUI is written in Perl and requires the Perl Tk module. If you're on UNIX and don't have the Tk module already, install the latest version from http://www.cpan.org/authors/id/NI-S/. Unpack it and run "perl Makefile.pl && make && sudo make install". If you're on *BSD, it's probably better to install the /usr/ports/*/p5-Tk/ port instead. There are probably packages out there for most Linux distributions as well. If you're on Windows with ActivePerl, you do not need any extra modules since everything is probably installed already. Should the Tk module be missing, you can install it with the command "ppm install Tk" in a DOS shell. If you want a non-ugly interface when browsing for directories, you need to install the Win32::FileOp module. This is strongly recommended and is as simple as typing "ppm install Win32::FileOp" in a DOS shell. After you've made sure you have the Tk module installed, simply execute oinkgui.pl (it's found in Oinkmaster's contrib directory, and you may want to copy it to some better location first). You don't have to be in a DOS/Unix shell to start it - you can create a shortcut for it on your desktop if you like. Usage ~~~~~~ When you start the GUI for the first time, all required files/directories will not be automatically set (although some of them may be). All entries under the "Required files and directories" tab must be set correctly or you will not be able to do anything. When they are set correctly (i.e. when all the labels have turned green), you are ready to go. The files/directories under the "Optional files and directories" tab are not required, and if you leave a field empty, that option will simply not be used. When all required fields are set, you should be ready to update the rules by pushing the "Update rules!" button. When you're happy with all the filename entries and options, you probably want to push the "Save config" button. All GUI settings will then be saved to a configuration file (~/.oinkguirc) which you can load later with the "Load config" button. It's also loaded automatically on startup. Hint: Place the mouse pointer over a button or filename entry and a short help message will be displayed under the big black output window. To find out more about Oinkmaster, consult its own documentation. oinkmaster-2.0/LICENSE010064400017500001750000000030601037561221100147440ustar00andreasoandreaso Copyright (c) 2001-2005 Andreas Östling All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. oinkmaster-2.0/INSTALL010064400017500001750000000047071037561221100150010ustar00andreasoandreaso# $Id: INSTALL,v 1.53 2006/01/28 21:27:03 andreas_o Exp $ # Quick installation instructions for Oinkmaster ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1) Put oinkmaster.pl in some suitable directory, for example /usr/local/bin/. Put oinkmaster.conf in /etc/ or /usr/local/etc/ (this is where Oinkmaster will search for it by default). You may also want to copy the man page (oinkmaster.1) to something like /usr/local/man/man1/. 2) Edit oinkmaster.conf that you copied in step 1). The defaults should be fine for most users, although one thing you must change is "url = ", which specifies the location of the rules archive. The URL to use depends on which version of Snort you run and also what type of rules you want to use. Some may require registration. See Q1 in the FAQ for more information. 3) Decide in which directory you want to put the new rules. If you have Snort up and running already, you should use the directory where you keep the rules files. It's a very good idea to create a backup of it first. You must run Oinkmaster as a user that has read/write access to your rules directory and all rules files in it. It should however *NOT* be a privileged user such as root! Never run Oinkmaster as root. Done! Assuming your rules directory is /etc/snort/rules/, you can now update those rules by running: oinkmaster.pl -o /etc/snort/rules Extra installation notes ~~~~~~~~~~~~~~~~~~~~~~~~ If you're new to Oinkmaster, it's recommended that you read the entire README and FAQ. You may also run oinkmaster.pl -h to list all available command line options. They are described in more detail in the Oinkmaster manual page. See the FAQ if you need to setup proxy configuration. In oinkmaster.conf you will tell Oinkmaster things like which rules or files you want to disable/enable/modify/ignore. If you already have several rules commented out (or removed) in your current rules files, you must add "disablesid" statements for those SIDs to oinkmaster.conf so they don't get re-enabled after updating the rules (there is a help script for that, see makesidex.pl in the contrib directory). Remember that after switching to Oinkmaster for updating the rules, all permanent modifications to the rules must be done by editing oinkmaster.conf, not by editing the rules files directly. If you need more help, see the documentation at http://oinkmaster.sourceforge.net/ or ask on the Oinkmaster mailing list. oinkmaster-2.0/ChangeLog010064400017500001750000000525011037561221100155150ustar00andreasoandreaso# $Id: ChangeLog,v 1.186 2006/02/18 12:16:47 andreas_o Exp $ # Oinkmaster v2.0 - 20060218 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Major version bump to celebrate that Oinkmaster has now turned 5 years old! o When using the -U argument to merge new variables, you can now also specify -S to specify which files in the downloaded rules archive(s) to search for new variables. If -S is not specified, only snort.conf is searched, just like before. As usual, no variable merge is done unless -U is specified. You may specify multiple -S arguments to search for variables in multiple files, e.g. "... -S snort.conf -S foo.conf ...". o Handle multi-line variable definitions when merging variables. o Obfuscate the oinkcode when printing URLs, unless running in verbose mode. o Added ability to grab rules files from a local directory by specifying dir:// as URL. o Suppress warnings about using modifysid on non-existent SIDs when running in quiet mode. o Added some modifysid sanity checks o Added Q33 to the FAQ on how to disable all rules by default and only activate specific ones, with example how to automatically update only a few selected rules from Bleeding Snort. o Other documentation updates. o Misc improvements to the config file parser. o You can now set the HTTP User-Agent header string for Oinkmaster by specifying "user_agent = ..." in oinkmaster.conf. The default User-Agent string is unchanged, i.e. the string that LWP or wget set it to by default. Based on patch from Derek Edwards. o Make contrib/addmsg.pl handle some multi-line statements. o contrib/create-sidmap.pl: - don't ignore rules that are commented out - ignore the file deleted.rules by default (you don't use it, do you?!) Oinkmaster v1.2 - 20050417 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Slightly improved modifysid/template documentation and examples. For example, added a new template to disable a SID but only if it has a specific revision number so you can disable a rule temporarily and automatically start using it again if it becomes updated. Thanks to Russell Fulton for this suggestion. o Suppress warnings about non-matching modifysid expressions when running in super quiet mode (-Q). o Permit .tgz suffix for rules archive. o Permit filename as argument to modifysid (and use_template) to apply a substitution expression on all rules in the specified files(s). o You can now download multiple rules archives from different URLs at the same time, either by specifying several -u on the command line or by using several url= directives in the Oinkmaster configuration file(s). See the default oinkmaster.conf and the FAQ for more information. o Many updates to the FAQ, especially regarding how to update rules from multiple sources. Also added info about how to use Oinkmaster after Sourcefire changed the license of the rules. o Because of the license change, no URL is specified in the default oinkmaster.conf anymore. You will have to follow the instructions in there and activate the requested URL(s). o modifysid/use_template statements of all types will now be processed in the exact order as specified in the config file. Previously, all the wildcards were processed last. o Slightly improved error handling when running under Windows. o Removed some useless buttons and changed default URL list in the GUI. o Misc other minor fixes. Oinkmaster v1.1 - 20041009 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Support template-based modifysid expressions so you can define a template once and then use that one instead of repeating complex modifysid expressions. Documentation for this is found in README.templates and usage examples is found in template-examples.conf. o New option -s for summarized output (aka bmc mode) to leave out the details when printing results for added/removed/modified rules. Only the sid and msg string of the rules are printed, plus the filename. Non-rule changes are printed as usual. o New option -m to minimize/simplify the resulting output for modified rules. This means that identical leading and trailing parts of the new and old rule are removed so the actual change is much easier to see. Some characters to the left and right of the diffing parts are kept to get some context. More information and example output can be found in the updated manual page (oinkmaster.1). o Support -s and -m in the GUI as well (the "diff mode" buttons) o Better handling of duplicate rules (i.e. rules with the same SID) for files in the downloaded archive: - If all the duplicates are disabled, only one of them is passed on to the local rules file - If one of the rules is enabled and the other one disabled, the disabled one is discarded - If both rules are active, the one with the highest 'rev' is used - If one of the rules has a rev and the other does not, the one with the rev is used - If the duplicate rules have the same rev, the one appearing last in the file is used o You can now split long configuration directives in oinkmaster.conf to multiple lines using the regular trailing \ syntax. o All modifysid substitutions on multi-line rules (including when using templates) now work on the single-line version of the rule so that you don't have to care about where the trailing backslashes and newlines are. o When running in super quiet mode (-Q), possible warnings about duplicate SIDs in the downloaded rules are suppressed. o Allow location of editor to be set in the GUI and do not search for a default one in a predefined list anymore. o Removed 'P' flag from tar as it is incompatible together with 't' in gtar, which is now used by default on FreeBSD 5.2-CURRENT and later (PR ports/70806). Thanks to Saneto Takanori for reporting. o The GUI will now always use the same Perl binary when executing oinkmaster.pl as the one running the GUI itself. o By popular demand: support marking rules as locally modified to prevent them from being overwritten. See oinkmaster.conf and the FAQ for documentation about "localsid". Do not use this unless you really have to as it's very easy to end up with lots of sigs that aren't maintained anymore. o The default URL in oinkmaster.conf is now http://www.snort.org/dl/rules/snortrules-snapshot-2_2.tar.gz as 2.2 is the latest stable version of Snort at the time of this release. o The FAQ has been updated, especially the sections about local customization of rules. o Fixed bug so -e works correctly in conjunction with modifysid. Thanks to Alex Butcher. o Fixed bug that prevented ability to load multiple configs under Win32. o Fixed bug so that modifysid expressions are case-insensitive again (as documented). o Fixed a bunch of documentation typos (thanks to JP Vossen!). Oinkmaster v1.0 - 20040515 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Default URL in distribution oinkmaster.conf is now http://www.snort.org/dl/rules/snortrules-snapshot-2_1.tar.gz. Don't forget to change it if it's not the right one for your version of Snort! o You can now set "rule_actions = ..." in oinkmaster.conf to tell Oinkmaster what keywords are valid as the start of a Snort rule. Useful if you create your own ruletypes and want those lines to be regarded as rules instead of non-rule lines. If unset, "alert|drop|log|pass|reject|sdrop|activate|dynamic" will be used (same as before). o You can now run without external binaries if you have the required Perl modules installed (Archive::Tar, IO::Zlib and LWP::UserAgent). You can set use_external_bins to 0 or 1 in oinkmaster.conf to override the default. 0 means to use the Perl modules, 1 means to use external binaries. It's set to 0 by default on Win32 (since the required Perl modules are already included in ActivePerl 5.8.1+), and 1 on other systems (i.e. same behavior as before). This makes it much easier to install Oinkmaster on Windows/ActivePerl. See the new default oinkmaster.conf for more information. o A simple graphical multi-platform front-end to Oinkmaster written in Perl/Tk is included in the contrib directory (oinkgui.pl). See README.gui for more information. Screenshots are available on Oinkmaster's homepage. o contrib/makesidex.pl has been rewritten to handle multi-line rules and multiple rules directories. It will now also include the rule's "msg" string as a comment on each disablesid line it prints. Usage syntax is unchanged. o The other contrib scripts have been improved with misc feature updates and small bug fixes as well. For example, addmsg.pl now handles multiple rules directories just like the others. All scripts now give a short description when run without arguments. Full descriptions can still be found in contrib/README.contrib. o The new default oinkmaster.conf has been updated with more and better examples (mostly "modifysid" stuff). o Slightly improved multi-line rule parsing. o Perl version is checked on startup and must be >= 5.6.1. o Permission on all rules files in the output directory that are subject to become updated by Oinkmaster (i.e. files matching the "update_rules" regexp and that are not ignore by a "skipfile") are now checked before starting, so that we don't bail out in a middle of execution if a copy of an updated file should fail because of permission problem. o A manual page is now included which describes all the command line options in detail. o Major documentation updates (INSTALL, README, README.win32, FAQ). o Many other improvements. Oinkmaster v0.9 - 20031201 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o It's much faster now. o Added ability to use "include " in oinkmaster configuration files. will be parsed (just like a regular oinkmaster.conf) as soon as the include statement is seen, and then return and continue parsing the rest of the original file. If an option is re-defined, it will override the previous value. You can use as many 'include' statements as you wish, and also include even more files from included files. o Also permit an arbitrary number of "-C" arguments to be specified on command line to load multiple config files. They will be loaded in the order of appearance. Thanks to Rickard Cedergren for inspiration with his 'policy-based sensor setup' doc and patch. o Permit https://... in url specification. Only useful if your wget is SSL-enabled and you download from an SSL-enabled site. o Permit scp://@: in url specification. The rules archive will be copied from remotehost using scp (only tested with OpenSSH). You can specify a private key with scp_key = ... in oinkmaster.conf (or set it in ~/.ssh/config). o You can now specify "-i" for interactive mode. You will be asked to approve the changes before Oinkmaster modifies anything. o Added 'enablesid' option to oinkmaster.conf. o Slightly improved rules parsing (order of sid and msg does not matter). o oinkmaster.conf will be searched for in /etc/ and /usr/local/etc/ by default. o Make contrib/create-sidmap.pl and contrib/addsid.pl take an arbitrary number of directories as argument. o Added a FAQ. o wget is now always run in verbose mode, although the output is not displayed unless you run Oinkmaster in verbose mode as well, or if an error occurs (i.e. no more need to re-run in verbose mode just to get decent error messages). o deleted.rules is now ignored (with a "skipfile delete.rules") in the default oinkmaster.conf. o You can now specify a wildcard ('*') to the modifysid keyword, like modifysid * "foo" | "bar", to apply the substitution expression to all matching rules. This enables you to do stuff like convert all rules of a certain classtype to 'drop' rules, or replace all 'flow' keywords with "flags: A+;", and so on. See oinkmaster.conf for examples. o Include seconds in filename of backup tarball. o File::Temp is used for creation of temporary directory. You probably already have that module unless you run an ancient version of Perl. o Many other minor improvements. Oinkmaster v0.8 - 20030902 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Experimental multi-line rule support. o Make contrib/addsid.pl and contrib/addmsg.pl handle multi-line rules as well. o Added create-sidmap.pl to contrib section. It's a script that generates a SID map from a directory with rules files. (Like Snort's Regen-sidmap, but this one handles multi-line rules). o The 'modifysid' keyword now regards the arguments as regular expressions. So if you already use this function, you may have to adjust the arguments. Based on a patch from operator@email.it. Also added a bunch of examples. o Understand Snort_inline's "drop", "sdrop" and "reject" rules. o You can now start Perl with tainting checks enabled if you like, if you have a reasonably recent version of Perl. o The default temporary directory will be checked for in the environment variables TMP, TMPDIR and TEMPDIR. "/tmp" will be used if none was set. You can override this by setting tmpdir in oinkmaster.conf. o A bunch of Win32/Cygwin fixes, mostly from Ueli Kistler . o A bunch of fixes that makes it work on native Win32 with ActivePerl. o Added a README.win32 with information about Oinkmaster on Windows. o Added command line argument "-T" to test the configuration and then exit. o Better check for duplicate SIDs. Check is done across all files now, and duplicate SIDs in downloaded archive are discarded (only first one is kept) and not copied to the local files. This will avoid some annoying warning messages about duplicates in the local files that won't go away until the files gets updated again. Pointed out by elof@sentor.se. o Slightly modified format when printing results (hopefully easier to read when there are a large number of changes). o New options in oinkmaster.conf: min_files and min_rules. If number of rules files or number of rules are not at least min_files and min_rules respectively, the rules tarball is regarded as broken and the update is aborted. Both are set to 1 by default. o New command line option, -U . If specified, variable definitions that exist in the distribution snort.conf but not in will be inserted at the beginning of it. See README for more info. o Avoid using move() from File::Copy since some versions of it lies about the error message (e.g. "Cross-device link" instead of "Permission denied"). Thanks to Del Armstrong for investigating this issue! Oinkmaster v0.7 - 20030217 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o oinkmaster.conf will now be searched for in /usr/local/etc/ by default (instead of in the current directory). You will have to put it there or use the -C argument. o Don't print warning when using "-e" in quiet mode. o "-p" ("preserve comments") is now not just obsolete, but invalid. o New argument, "-Q" to run in über-quiet mode. Same as quiet mode except that it doesn't print the "None." stuff when printing results. o Support file:// syntax in URL specification. Rules archive will be copied directly from the local filesystem (which must have been put there ahead of time). Suggested by aidan.carty@entropy.ie. o sid-msg.map will now be updated by default. o Warn instead of exit in case of invalid lines in the config file. o Added possibility to modify SIDs, using the "modifysid" keyword in the config file (use with care). o Added makesidex.pl by Jerry Applebaum to contrib section. Useful to new Oinkmaster users who already have many rules commented out and don't want to create oinkmaster.conf manually from scratch. It strips out the SIDs from everything you've commented out and writes them out in a format that can be appended to oinkmaster.conf. o Added addmsg.pl (by myself) to contrib section. It's a script that will parse your oinkmaster.conf for "disablesid" lines and add to them their SID message as a #comment. o Fixed bug related to deleted rules printout. o Default URL is now http://www.snort.org/dl/rules/snortrules-stable.tar.gz. o In verbose mode, print warning if a file in the downloaded archive is empty. o Don't require write permission in the output directory when running with -c. Pointed out by arjones@simultan.dyndns.org. o Don't care about trailing whitespaces for non-rule lines (to avoid getting a diff where the same line appears to have been both added and removed when only a trailing whitespace has been removed etc). o Allow umask to be specified in oinkmaster.conf. o Many internal cleanups and minor bug fixes. Oinkmaster v0.6 - 20020706 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Rules that are disabled by default in the rules distribution will now NOT be re-enabled by Oinkmaster by default, finally. I.e. "-p" is obsolete. If you want to enable those rules you now have to use "-e". o A couple of minor documentation updates. Oinkmaster v0.5 - 20020506 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Fixed bug where all filenames would not always be printed. o Fixed minor whitespace idiocy. o Print which rules that are being preserved or enabled (for rules that are commented out by default) when running in verbose mode. o SID dup warnings will no longer be printed when running in quiet mode. o Document example where running silently as a cron job, only sending e-mail when there were updates available. o Document that using -p is usually a good idea. Oinkmaster v0.4 - 20020422 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o More or less rewritten from scratch. o The file rules.ignore has been renamed to oinkmaster.conf, which contains more than just the SIDs to disable (have a look at it). o Slightly improved error checking. o Better output format (or at least it has changed). o Oinkmaster will now also update other files (and print changes in them) than *.rules and classification.conf. The default is now *.rules, *.conf, *.config, *.txt and *.map so we also update the MIB and sid-msg.map files for example. Files to be updated is defined by a regexp in oinkmaster.conf so this can easily be changed. o Removed several stupid command line options. o Print warning message if two (or more) rules share the same SID. o Added option -p to preserve disabled rules in downloaded archive. o "file" and "sid" keywords in oinkmaster.conf are now invalid. They are instead called "skipfile" and "disablesid" (to avoid confusion when possibly adding new keywords in the future). o Instead of specifying one "disablesid X" per line, you can now also use comma-separated lists, e.g. "disablesid 1,2,3,4,42". Same goes for the skipfile keyword. o Various minor bug fixes and tweaks. o Some documentation updates. Oinkmaster v0.3 - never (was rewritten and became v0.4) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Oinkmaster v0.2 - 20010906 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Added optional "-a " argument (which is passed to wget's "-t" argument) to configure maximum number of download retries before giving up. o Warning message will be printed if there is any invalid line in the ignore file. o Fixed a few stupid bugs. o Default URL is now http://www.snort.org/downloads/snortrules.tar.gz PLEASE be aware of the following description of the rules in the above archive: "This is a CVS snapshot of the snort signatures every 30 minutes. NOTE: These signatures may only work with snort-current" Oinkmaster v0.1 - 20010726 ~~~~~~~~~~~~~~~~~~~~~~~~~~ o Initial release (based on arachnids_upd v0.4). arachnids_upd v0.4 - 20010526 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o Minor optimization fix, which a few people pointed out. arachnids_upd v0.3 - 20010508 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o Much more easy-to-read output of the rule changes (the diff command is no longer used). The changes are split into eight categories: o Added (new): - New rule (the IDS number did not exist in the old ruleset). o Added (enabled): - The rule (with this IDS number) was commented in the old ruleset, but is now activated. o Added (enabled) and modified: - The rule (with this IDS number) was commented in the old ruleset, but is now activated. The actual rule had also been modified. o Removed (deleted): - The rule (with this IDS number) does no longer exist in any way. o Removed (disabled): - The rule (with this IDS number) still exists but have now been commented. o Removed (disabled) and modified: - The rule (with this IDS number) still exists but have now been commented. The actual rule had also been modified. o Modified active: - The rule (with this IDS number) have been modified and is an active rule. o Modified inactive: - The rule (with this IDS number) have been modified but is currently an inactive (commented) rule. o Added INSTALL file with basic installation instructions. o Minor enhancements. o Minor documentation updates. arachnids_upd v0.2 - 20010422 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o The ruleset file in the URL must now be gzipped. Default URL is now http://www.whitehats.com/ids/vision.rules.gz. An integrity check (gzip -t) will be run on this file after download. o You must now use -b instead of just -b. Backups will be stored in this directory. o If there were no changes in the new ruleset, no backup will be done and the current ruleset will remain untouched. o Added contribution script from Dejan Muhamedagic (see contrib/). I'm not sure it's quite as useful now in v0.2 though. o Added -q option to run in quiet mode. No output unless the rules had changed. o Minor documentation updates. o Minor bug fixes and other minor enhancements (see arachnids_upd.pl -h for new options). arachnids_upd v0.1 - 20010210 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o Initial release. oinkmaster-2.0/oinkmaster.1010064400017500001750000000246371037561221100162120ustar00andreasoandreaso.\" $Id: oinkmaster.1,v 1.26 2005/06/16 18:26:47 andreas_o Exp $ .\" Copyright (c) 2004-2005 Andreas Ostling .\" .\" Command to generate the man page: groff -man -Tascii oinkmaster.1 .\" .TH OINKMASTER 1 "January 14, 2004" .SH NAME .B oinkmaster \- update Snort signatures .SH SYNOPSIS .B oinkmaster.pl -o .I outdir [options] .SH DESCRIPTION Oinkmaster is simple tool that helps you keep your Snort rules current with little or no user interaction. It downloads a tarball containing the new rules and can then enable, disable or even make arbitrary modifications to specified rules before updating your local rules files. It will also tell you the exact changes from your previous rules. .SH OPTIONS The only required argument to Oinkmaster is .B -o .I outdir where .I outdir is the directory to put the new rules files in. This should be where you keep your rules locally. The downloaded files will be compared to the ones in here before possibly overwriting them. .PP Optional arguments: .IP "\fB-b \fIdir\fP If the rules have been modified, a tarball of your old rules will be put in .I dir before overwriting them with the new files. No backup is done if no file has changed or if Oinkmaster is running in careful mode. .IP "\fB-c Run in careful mode. This means that Oinkmaster will only check for updates and print them, but not update anything. .IP "\fB-C \fIcfg\fP Use this configuration file instead of the default. If not specified, oinkmaster.conf will be looked for in /etc/ and then /usr/local/etc/. You can specify multiple .B -C .I cfg to load multiple configuration files. They will be loaded in order of appearance on the command line. If an option is redefined, it overrides the previous value (except for the "url" option, as you are allowed to specify multiple URLs). .IP "\fB-e Enable rules that are disabled by default in the downloaded rules archive by removing all the leading "#" from them. If there are any disabled rules in the archive, they will stay that way unless you use this option. Remember that they are disabled for a reason (they may not even work), so use this option with care. .IP "\fB-h Show valid command line arguments with short descriptions .IP "\fB-i Enable interactive mode. You will be asked to approve the changes (if any) before updating anything. .IP "\fB-m Minimize/simplify the diff when printing result for modified rules by removing common leading and trailing parts of the old and new rule so it's easier to see the actual change. A few characters to the left and to the right of the change are also printed so you get some context. The rev keyword is ignored when the comparison and removal of common parts is performed because it would often make the whole idea fail. (If you feel it's important to be able to verify that the rev number has increased when a rule has been updated, do not use the minimized diff mode.) Normally when a rule has changed the entire old and new versions are printed, but the actual change between them can be hard to see if the rules are long, complex and many. .PP The normal output could look like this: .PP .nf Old: alert tcp any any -> any 22 (msg: "foo"; flags: A+; rev:1;) New: alert tcp any any -> any 123 (msg: "foo"; flags: A+; rev:2;) .fi .PP When using .B -m it would instead look something like: .PP .nf Old: ...any any -> any 22 (msg: "foo";... New: ...any any -> any 123 (msg: "foo";... .fi .IP "\fB-q Run in quiet mode. Nothing is printed unless there are changes in the rules or if there are errors or warnings. .IP "\fB-Q Run in super-quiet mode. This is the same as .B -q but even more quiet when printing the results (the "None." stuff is not printed). It will also suppress some other warning messages such as those for duplicate SIDs and non-matching modifysid expressions. .IP "\fB-r Check for rules files that exist in the output directory but not in the downloaded rules archive, i.e. files that may have been removed from the distribution archive. .IP "\fB-s Leave out details when printing results (aka bmc mode). This means that the entire added / removed / modified rules will not be printed, just their SID and msg string, plus the filename. Non-rule changes are printed as usual. This output mode could be useful for example if you send the output by email to people who don't really care about the details of the rules, just the fact that they have been updated. Example output when running with .B -s .PP .nf [+++] Added rules: [+++] 1607 - WEB-CGI HyperSeek hsx.cgi access (web-cgi.rules) 1775 - MYSQL root login attempt (mysql.rules) [///] Modified active rules: [///] 302 - EXPLOIT Redhat 7.0 lprd overflow (exploit.rules) 304 - EXPLOIT SCO calserver overflow (exploit.rules) 305 - EXPLOIT delegate proxy overflow (exploit.rules) 306 - EXPLOIT VQServer admin (exploit.rules) .fi .PP .IP "\fB-S \fIfile\fP Used in conjuction with with .B -U to specify which file(s) in the downloaded archive(s) to search for new variables. When not specified, snort.conf is checked. You may specify multiple .B -S .I file to search for new variables in multiple files. .IP "\fB-T Check the configuration file(s) for fatal errors and then exit. Possible warning messages are printed as well. .IP "\fB-u \fIurl\fP Download the rules archive from .I url instead of the location specified in the configuration file. It must start with file://, ftp://, http://, https:// or scp:// and end with ".tar.gz" or ".tgz". The file must be a gzipped tarball containing a directory named "rules", holding all the rules files. It must not contain any symlinks. You can also point to a local directory with dir://. For the official Snort rules, the URL to use depends on the version of Snort you run and it might also require registration. Visit the rules download section at the Snort web site to find the right URL and more information. Remember to update the URL when upgrading to a new major version of Snort. You may specify multiple .B -u .I url to grab multiple rules archives from different locations. All rules files in the archives will be put in the same output directory so if the same filename exists in multiple archives, Oinkmaster will print an error message and exit. That's why it's usually recommended to instead run Oinkmaster once for each URL and use separate output directories. If .B -u .I url is specified, it overrides any URLs specified in the configuration file(s). Note that if multiple URLs are specified and one of them is broken, Oinkmaster will exit immediately without further processing. This can be good or bad, depending on the situation. .IP "\fB-U \fIfile\fP Variables (i.e. "var foo bar" lines) that exist in downloaded snort.conf but not in .I file will be added to .I file right after any other variables it may contain. Modified existing variables are not merged, only new ones. .I file is normally your production copy of snort.conf (which should not be a file that is updated by Oinkmaster the normal way). This feature is to prevent Snort from breaking in case there are new variables added in the downloaded rules, as Snort can not start if the rules use variables that aren't defined anywhere. By default when using .B -U , the file snort.conf in the downloaded archive is search for new variables but you can override this with the .B -S .I file argument. If you download from multiple URLs, Oinkmaster will look for a snort.conf in each downloaded rules archive. .IP "\fB-v Run in verbose/debug mode. Should probably only be used in case you need to debug your settings, like verifying complex modifysid statements. It will also tell you if you try to use "disablesid" on non-existent SIDs. Warnings about using enablesid/localsid/modifysid on non-existent SIDs are always printed unless running in quiet mode, as those are usually more important (using "disablesid" on a non-existent rule is a NOOP anyway). .IP "\fB-V Show version and exit. .SH EXAMPLES Download rules archive from default location specified in oinkmaster.conf and put the new rules in /etc/rules/: .PP .nf \fB oinkmaster.pl -o /etc/rules \fP .fi .PP Grab rules archive from local filesystem and do not print anything unless it contains updated rules: .PP .nf \fB oinkmaster.pl -u file:///tmp/rules.tar.gz -o /etc/rules -q \fP .fi .PP Download rules archive from default location, make backup of old rules if there were updates, and send output by e-mail. (Note however that if you plan on distributing files with Oinkmaster that could be considered sensitive, such as Snort configuration files containing database passwords, you should of course not send the output by e-mail without first encrypting the content.): .PP .nf \fB oinkmaster.pl -o /etc/snort/rules -b /etc/snort/backup 2>&1 | \fP\\ \fB mail -s "subject" user@example.com .fi .PP Grab three different rules archives and merge variables that exist in downloaded snort.conf and foo.conf but not in local /etc/snort/snort.conf: .PP .nf \fB oinkmaster.pl -u file:///tmp/foo.rules.tar.gz \fP\\ \fB -u http://somewhere/rules.tar.gz -u https://blah/rules.tar.gz \fP\\ \fB -o /etc/rules -S snort.conf -S foo.conf -U /etc/snort/snort.conf .fi .PP Load settings from two different files, use scp to download rules archive from a remote host where you have put the rules archive, merge variables from downloaded snort.conf, and send results by e-mail only if anything changed or if there were any error messages. It assumes that the "mktemp" command is available on the system: .PP .nf \fB TMP=`mktemp /tmp/oinkmaster.XXXXXX` && \fP\\ \fB (oinkmaster.pl -C /etc/oinkmaster-global.conf \fP\\ \fB -C /etc/oinkmaster-sensor.conf -o /etc/rules \fP\\ \fB -U /etc/snort.conf \fP\\ \fB -u scp://user@example.com:/home/user/rules.tar.gz \fP\\ \fB > $TMP 2>&1; if [ -s $TMP ]; then mail -s "subject" \fP\\ \fB you@example.com < $TMP; fi; rm $TMP) \fP .fi .PP .SH FILES .B /etc/oinkmaster.conf .br .B /usr/local/etc/oinkmaster.conf .SH BUGS If you find a bug, report it by e-mail to the author. Always include as much information as possible. .SH HISTORY The initial version was released in early 2001 under the name arachnids_upd. It worked only with the ArachNIDS Snort rules, but as times changed, it was rewritten to work with the official Snort rules and the new name became Oinkmaster. .SH AUTHOR Andreas Ostling .SH SEE ALSO The online documentation at http://oinkmaster.sf.net/ contains more information. oinkmaster-2.0/template-examples.conf010064400017500001750000000131631037561221100202420ustar00andreasoandreaso# $Id: template-examples.conf,v 1.9 2006/01/29 14:54:58 andreas_o Exp $ # # Disclaimer: # DO NOT USE THIS FILE DIRECTLY. The templates in here are only # quick examples just to give you some ideas and are subject to be # changed/deleted in future releases. If there is a template you want # to use, review if carefully and put it in your own oinkmaster.conf (or # your own templates.conf or whatever) instead of using this file # directly. If you create your own templates and want them added to # this file, please send them to me. # For more information about templates, see README.templates. # Here are a bunch of sample template definitions, later followed by # examples how to use them. # Tag by src for 10 seconds by adding the string # "tag: host,src,10,seconds;" right after the SID statement. define_template add_src_tagging \ "\b(sid\s*:\s*\d+\s*;)" | \ "${1} tag: host,src,10,seconds;" # If you want to append stuff at the very end of a rule, you could do # something like this: # define_template add_src_tagging \ # "\)\n$" | \ # "tag: host,src,10,seconds;)\n" # Take text given as argument and append it to the rule's "msg" string. define_template append_msg \ "\b(msg\s*:\s*".+?)"\s*;+s*" | \ "${1}%ARG1%";" # Add thresholding of type "both", count 30, seconds 60. define_template add_threshold \ "\b(sid\s*:\s*\d+\s*;)" | \ "${1} threshold:type both,track by_dst,count 30,seconds 60;" # Add by_dst thresholding with values for count and seconds given # as arguments when using the template. define_template add_threshold_with_values \ "\b(sid\s*:\s*\d+\s*;)" | \ "${1} threshold:type both,track by_dst,count %ARG1%,seconds %ARG2%;" # Change classtype to the one specified as argument. define_template change_classtype \ "\bclasstype\s*:\s*\S+\s*;" | \ "classtype:%ARG1%;" # Convert an active alert rule to a drop rule define_template make_drop "^alert\s" | "drop " # Remove "flow" statement. define_template remove_flow "flow\s*:\s*[a-z,_ ]+;" | "" # Comment out rules containing a certain option. define_template disable_by_keyword "(.*\b%ARG1%\s*:.+;.*)" | "#${1}" # Switch $EXTERNAL_NET/$HOME_NET in a rule watching for stuff from # $EXTERNAL_NET to $HOME_NET, so it becomes $HOME_NET to $EXTERNAL_NET. define_template check_outgoing \ "(.+) \$EXTERNAL_NET (.+) \$HOME_NET (.+)" | \ "${1} \$HOME_NET ${2} \$EXTERNAL_NET ${3}" # Switch $EXTERNAL_NET/$HOME_NET in a rule watching for stuff from # $HOME_NET to $EXTERNAL_NET, so it becomes $EXTERNAL_NET to $HOME_NET. define_template check_incoming \ "(.+) \$HOME_NET (.+) \$EXTERNAL_NET (.+)" | \ "${1} \$EXTERNAL_NET ${2} \$HOME_NET ${3}" # Make source and destination address specifications in an alert rule # become "any", regardless of their current values. define_template src_dst_any_any \ "^(alert\s+\S+)\s+\S+\s+(.*?>)\s+\S+" | "${1} any ${2} any" # Just like disablesid but also add a comment line before the disabled # rule. define_template disable_with_comment \ "^alert\s" | "# Rule disabled by Oinkmaster, reason=%ARG1%:\n#alert " # Delete an active rule by removing it from the file completely. define_template delete_rule \ "^alert\s.+$" | "" # Tag by src for the number of seconds given as argument. Also include # this number in a string appended to the rule's msg. define_template tag_src_and_append_msg \ "^(\s*alert\s+.+\bmsg\s*:\s*".+?)"\s*;+s*(.*)\b(sid\s*:\s*\d+\s*;)" | \ "${1}, tagging for %ARG1% seconds";${2}${3} tag: host,src,%ARG1%,seconds;" # This is a template to disable a rule only if it has a specific # revision. Very useful if you want to temporarily disable a rule # because of false positives and you want to start using the rule again # as soon as it is updated (i.e. when the "rev" keyword changes). # The revision is specified as argument when using the template. define_template disablesid_rev "(.+\brev\s*:\s*%ARG1%\s*;.*)" | "#${1}" # Now some examples how to use the above templates. # Add tagging by src to SID 1324. # use_template add_src_tagging 1324 # Append the string " - added text!" to the msg of SID 1324. # use_template append_msg 1324 " - added text!" # Add thresholding with values hardcoded into the add_threshold template. # use_template add_threshold 1326 # Add thresholding with count value given as first argument # and seconds value given as second argument. # use_template add_threshold_with_values 1326 "10" "30" # Change the classtype to "some-other-classtype" in SID 1324 and 1325. # use_template change_classtype 1324,1325 "some-other-classtype" # Make SID 1324 a drop rule. # use_template make_drop 1324 # Make all rules in exploit.rules to be drop rules. # use_template make_drop exploit.rules # Remove the "flow" statement from SID 1324. # use_template remove_flow 1324 # Disable all rules that are using the "uricontent" keyword. # use_template disable_by_keyword * "uricontent" # Reverse $EXTERNAL_NET and $HOME_NET in SID 1324 to watch only # for outgoing attacks of this kind. # use_template check_outgoing 1324 # Make both src and dst "any" in SID 1326. # use_template src_dst_any_any 1326 # Disable rule 1323, and also add a comment line above it # in the rules file. # use_template disable_with_comment 1323 "I don't like this rule" # Add 60 seconds tagging to SID 528, and also add this # information to its msg string. # use_template tag_src_and_append_msg 528 "60" # This will delete (not disable) the SIDs 1323, 1324 and 1326. # use_template delete_rule 1323, 1324, 1326 # This will delete ALL active rules. Not a very useful example :) # use_template delete_rule * # Disable SID 1324, but only if the revision (the "rev" keyword) is 3. # use_template disablesid_rev 1324 "3" oinkmaster-2.0/oinkmaster.conf010064400017500001750000000500771037561221100167740ustar00andreasoandreaso# $Id: oinkmaster.conf,v 1.132 2006/02/02 12:05:08 andreas_o Exp $ # # This file is pretty big by default, but don't worry. # The only things required are "path" and "update_files". You must also # set "url" to point to the correct rules archive for your version of # Snort, unless you prefer to specify this on the command line. # The rest in here is just a few recommended defaults, and examples # how to use all the other optional features and give some ideas how they # could be used. # Remember not to let untrusted users edit Oinkmaster configuration # files, as things like the PATH to use during execution is defined # in here. # Use "url = " to specify the location of the rules archive to # download. The url must begin with http://, https://, ftp://, file:// # or scp:// and end with .tar.gz or .tgz, and the file must be a # gzipped tarball what contains a directory named "rules". # You can also point to a local directory with dir://. # Multiple "url = " lines can be specified to grab multiple rules # archives from different locations. # # Note: if URL is specified on the command line, it overrides all # possible URLs specified in the configuration file(s). # # The location of the official Snort rules you should use depends # on which Snort version you run. Basically, you should go to # http://www.snort.org/rules/ and follow the instructions # there to pick the right URL for your version of Snort # (and remember to update the URL when upgrading Snort in the # future). You can of course also specify locations to third party # rules. # # As of March 2005, you must register on the Snort site to get access # to the official Snort rules. This will get you an "oinkcode". # You then specify the URL as # http://www.snort.org/pub-bin/oinkmaster.cgi// # For example, if your code is 5a081649c06a277e1022e1284b and # you use Snort 2.4, the url to use would be (without the wrap): # http://www.snort.org/pub-bin/oinkmaster.cgi/ # 5a081649c06a277e1022e1284bdc8fabda70e2a4/snortrules-snapshot-2.4.tar.gz # See the Oinkmaster FAQ Q1 and http://www.snort.org/rules/ for # more information. # URL examples follows. Replace with the code you get on the # Snort site in your registered user profile. # Example for Snort 2.4 # url = http://www.snort.org/pub-bin/oinkmaster.cgi//snortrules-snapshot-2.4.tar.gz # Example for Snort-current ("current" means cvs snapshots). # url = http://www.snort.org/pub-bin/oinkmaster.cgi//snortrules-snapshot-CURRENT.tar.gz # Example for Community rules # url = http://www.snort.org/pub-bin/downloads.cgi/Download/comm_rules/Community-Rules.tar.gz # Example for rules from the Bleeding Snort project # url = http://www.bleedingsnort.com/bleeding.rules.tar.gz # If you prefer to download the rules archive from outside Oinkmaster, # you can then point to the file on your local filesystem by using # file://, for example: # url = file:///tmp/snortrules.tar.gz # In rare cases you may want to grab the rules directly from a # local directory (don't confuse this with the output directory). # url = dir:///etc/snort/src/rules # Example to use scp to copy the rules archive from another host. # Only OpenSSH is tested. See the FAQ for more information. # url = scp://user@somehost.example.com:/somedir/snortrules.tar.gz # If you use -u scp://... and need to specify a private ssh key (passed # as -i to the scp command) you can specify it here or add an # entry in ~/.ssh/config for the Oinkmaster user as described in the # OpenSSH manual. # scp_key = /home/oinkmaster/oinkmaster_privkey # The PATH to use during execution. If you prefer to use external # binaries (i.e. use_external_bins=1, see below), tar and gzip must be # found, and also wget if downloading via ftp, http or https. All with # optional .exe suffix. If you're on Cygwin, make sure that the path # contains the Cygwin binaries and not the native Win32 binaries or # you will get problems. # Assume UNIX style by default: path = /bin:/usr/bin:/usr/local/bin # Example if running native Win32 or standalone Cygwin: # path = c:\oinkmaster;c:\oinkmaster\bin # Example if running standalone Cygwin and you prefer Cygwin style path: # path = /cygdrive/c/oinkmaster:/cygdrive/c/oinkmaster/bin # We normally use external binaries (wget, tar and gzip) since they're # already available on most systems and do a good job. If you have the # Perl modules Archive::Tar, IO::Zlib and LWP::UserAgent, you can use # those instead if you like. You can set use_external_bins below to # choose which method you prefer. It's set to 0 by default on Win32 # (i.e. use Perl modules), and 1 on other systems (i.e. use external # binaries). The reason for that is that the required Perl modules # are included on Windows/ActivePerl 5.8.1+, so it's easier to use # those than to install the ported Unix tools. (Note that if you're # using scp to download the archive, external scp binary is still # used.) # use_external_bins = 0 # Temporary directory to use. This directory must exist when starting and # Oinkmaster will then create a temporary sub directory in here. # Keep it as a #comment if you want to use the default. # The default will be checked for in the environment variables TMP, # TMPDIR or TEMPDIR, or otherwise use "/tmp" if none of them was set. # Example for UNIX. # tmpdir = /home/oinkmaster/tmp/ # Example if running native Win32 or Cygwin. # tmpdir = c:\tmp # Example if running Cygwin and you prefer Cygwin style path. # tmpdir = /cygdrive/c/tmp # The umask to use during execution if you want it to be something # else than the current value when starting Oinkmaster. # This will affect the mode bits when writing new files. # Keep it commented out to keep your system's current umask. # umask = 0027 # Files in the archive(s) matching this regular expression will be # checked for changes, and then updated or added if needed. # All other files will be ignored. You can then choose to skip # individual files by specifying the "skipfile" keyword below. # Normally you shouldn't need to change this one. update_files = \.rules$|\.config$|\.conf$|\.txt$|\.map$ # Regexp of keywords that starts a Snort rule. # May be useful if you create your own ruletypes and want those # lines to be regarded as rules as well. # rule_actions = alert|drop|log|pass|reject|sdrop|activate|dynamic # If the number of rules files in the downloaded archive matching the # 'update_files' regexp is below min_files, or if the number # of rules is below min_rules, the rules are regarded as broken # and the update is aborted with an error message. # Both are set to 1 by default (i.e. the archive is only regarded as # broken if it's totally empty). # If you download from multiple URLs, the count is the total number # of files/rules across all archives. # min_files = 1 # min_rules = 1 # By default, a basic sanity check is performed on most paths/filenames # to see if they contain illegal characters that may screw things up. # If this check is too strict for your system (e.g. you get bogus # "illegal characters in filename" errors because of your local language # etc) and you're sure you want to disable the checks completely, # set use_path_checks to 0. # use_path_checks = 1 # If you want Oinkmaster to send a User-Agent HTTP header string # other than the default one for wget/LWP, set this variable. # user_agent = Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) # You can include other files anywhere in here by using # "include ". will be parsed just like a regular # oinkmaster.conf as soon as the include statement is seen, and then # return and continue parsing the rest of the original file. If an # option is redefined, it will override the previous value. You can use # as many "include" statements as you wish, and also include even more # files from included files. Example to load stuff from "/etc/foo.conf". # include /etc/foo.conf ####################################################################### # Files to totally skip (i.e. never update or check for changes) # # # # Syntax: skipfile filename # # or: skipfile filename1, filename2, filename3, ... # ####################################################################### # Ignore local.rules from the rules archive by default since we might # have put some local rules in our own local.rules and we don't want it # to get overwritten by the empty one from the archive after each # update. skipfile local.rules # The file deleted.rules contains rules that have been deleted from # other files, so there is usually no point in updating it. skipfile deleted.rules # Also skip snort.conf by default since we don't want to overwrite our # own snort.conf if we have it in the same directory as the rules. If # you have your own production copy of snort.conf in another directory, # it may be really nice to check for changes in this file though, # especially since variables are sometimes added or modified and # new/old files are included/excluded. skipfile snort.conf # You may want to consider ignoring threshold.conf for the same reasons # as for snort.conf, i.e. if you customize it locally and don't want it # to become overwritten by the default one. It may be better to put # local thresholding/suppressing in some local file and still update # and use the official one though, in case important stuff is added to # it some day. We do update it by default, but it's your call. # skipfile threshold.conf # If you update from multiple URLs at the same time you may need to # ignore the sid-msg.map (and generate it yourself if you need one) as # it's usually included in each rules tarball. See the FAQ for more info. # skipfile sid-msg.map ########################################################################## # SIDs to modify after each update (only for the skilled/stupid/brave). # # Don't use it unless you have to. There is nothing that stops you from # # modifying rules in such ways that they become invalid or generally # # break things. You have been warned. # # If you just want to disable SIDs, please skip this section and have a # # look at the "disablesid" keyword below. # # # # You may specify multiple modifysid directives for the same SID (they # # will be processed in order of appearance), and you may also specify a # # list of SIDs on which the substitution should be applied. # # If the argument is in the form something.something it's regarded # # as a filename and the substitution will apply on all rules in that # # file. The wildcard ("*") can be used to apply the substitution on all # # rules regardless of the SID or file. Please avoid using #comments # # at the end of modifysid lines, they may confuse the parser in some # # situations. # # # # Syntax: # # modifysid SID "replacethis" | "withthis" # # or: # # modifysid SID1, SID2, SID3, ... "replacethis" | "withthis" # # or: # # modifysid file "replacethis" | "withthis" # # or: # # modifysid * "replacethis" | "withthis" # # # # The strings within the quotes will basically be passed to a # # s/replacethis/withthis/ statement in Perl, so they must be valid # # regular expressions. The strings are case-insensitive and only the # # first occurrence will be replaced. If there are multiple occurrences # # you want to replace, simply repeat the same modifysid line. # # As the strings are regular expressions, you MUST escape special # # characters like $ \ / ( ) | by prepending a "\" to them. # # # # If you specify a modifysid statement for a multi-line rule, Oinkmaster # # will first translate the rule into a single-line version and then # # perform the substitution, so you don't have to care about the trailing # # backslashes and newlines. # # # # If you use backreference variables in the substitution expression, # # it's strongly recommended to specify them as ${1} instead of $1 and so # # on, to avoid parsing confusion with unexpected results in some # # situations. Note that modifysid statements will process both active # # and inactive (disabled) rules. # # # # You may want to check out README.templates and template-examples.conf # # to find how you can simplify the modifysid usage by using templates. # ########################################################################## # Example to enable a rule (in this case SID 1325) that is disabled by # default, by simply replacing leading "#alert" with "alert". # (You should really use 'enablesid' for this though.) # Oinkmaster removes whitespaces next to the leading "#" so you don't # have to worry about that, but be careful about possible whitespace in # other places when writing the regexps. # modifysid 1325 "^#alert" | "alert" # You could also do this to enable it no matter what type of rule it is # (alert, log, pass, etc). # modifysid 1325 "^#" | "" # Example to add "tag" stuff to SID 1325. # modifysid 1325 "sid:1325;" | "sid:1325; tag: host, src, 300, seconds;" # Example to make SID 1378 a 'drop' rule (valid if you're running # Snort_inline). # modifysid 1378 "^alert" | "drop" # Example to replace first occurrence of $EXTERNAL_NET with $HOME_NET # in SID 302. # modifysid 302 "\$EXTERNAL_NET" | "\$HOME_NET" # You can also specify that a substitution should apply on multiple SIDs. # modifysid 302,429,1821 "\$EXTERNAL_NET" | "\$HOME_NET" # You can take advantage of the fact that it's regular expressions and # do more complex stuff. This example (for Snort_inline) adds a 'replace' # statement to SID 1324 that replaces "/bin/sh" with "/foo/sh". # modifysid 1324 "(content\s*:\s*"\/bin\/sh"\s*;)" | \ # "${1} replace:"\/foo\/sh";" # If you for some reason would like to add a comment inside the actual # rules file, like the reason why you disabled this rule, you can do # like this (you would normally add such comments in oinkmaster.conf # though). # modifysid 1324 "(.+)" | "# 20020101: disabled this rule just for fun:\n#${1}" # Here is an example that is actually useful. Let's say you don't care # about incoming welchia pings (detected by SID 483 at the time of # writing) but you want to know when infected hosts on your network # scans hosts on the outside. (Remember that watching for outgoing # malicious packets is often just as important as watching for incoming # ones, especially in this case.) The rule currently looks like # "alert icmp $EXTERNAL_NET any -> $HOME_NET any ..." # but we want to switch that so it becomes # "alert icmp $HOME_NET any -> $EXTERNAL_NET any ...". # Here is how it could be done. # modifysid 483 \ # "(.+) \$EXTERNAL_NET (.+) \$HOME_NET (.+)" | \ # "${1} \$HOME_NET ${2} \$EXTERNAL_NET ${3}" # The wildcard (modifysid * ...) can be used to do all kinds of # interesting things. The substitution expression will be applied on all # matching rules. First, a silly example to replace "foo" with "bar" in # all rules (that have the string "foo" in them, that is.) # modifysid * "foo" | "bar" # If you for some reason don't want to use the stream preprocessor to # match established streams, you may want to replace the 'flow' # statement with 'flags:A+;' in all those rules. # modifysid * "flow:[a-z,_ ]+;" | "flags:A+;" # Example to convert all rules of classtype attempted-admin to 'drop' # rules (for Snort_inline only, obviously). # modifysid * "^alert (.*classtype\s*:\s*attempted-admin)" | "drop ${1}" # This one will append some text to the 'msg' string for all rules that # have the 'tag' keyword in them. # modifysid * "(.*msg:\s*".+?)"(\s*;.+;\s*tag:.*)" | \ # "${1}, going to tag this baby"${2}" # There may be times when you want to replace multiple occurrences of a # certain keyword/string in a rule and not just the first one. To # replace the first two occurrences of "foo" with "bar" in SID 100, # simply repeat the modifysid statement: # modifysid 100 "foo" | "bar" # modifysid 100 "foo" | "bar" # Or you can even specify a SID list but repeat the same SID as many # times as required, like: # modifysid 100,100,100 "foo" | "bar" # Enable all rules in the file exploit.rules. # modifysid exploit.rules "^#" | "" # Enable all rules in exploit.rules, icmp-info.rules and also SID 1171. # modifysid exploit.rules, snmp.rules, 1171 "^#" | "" ######################################################################## # SIDs that we don't want to update. # # If you for some reason don't want a specific rule to be updated # # (e.g. you made local modifications to it and you never want to # # update it and don't care about changes in the official version), you # # can specify a "localsid" statement for it. This means that the old # # version of the rule (i.e. the one in the rules file on your # # harddrive) is always kept, regardless if the official version has # # been updated. Please do not use this feature unless in special # # cases as it's easy to end up with many signatures that aren't # # maintained anymore. See the FAQ for details about this and hints # # about better solutions regarding customization of rules. # # # # Syntax: localsid SID # # or: localsid SID1, SID2, SID3, ... # ######################################################################## # Example to never update SID 1325. # localsid 1325 ######################################################################## # SIDs to enable after each update. # # Will simply remove all the leading '#' for a specified SID (if it's # # a multi-line rule, the leading '#' for all lines are removed.) # # These will be processed after all the modifysid and disablesid # # statements. Using 'enablesid' on a rule that is not disabled is a # # NOOP. # # # # Syntax: enablesid SID # # or: enablesid SID1, SID2, SID3, ... # ######################################################################## # Example to enable SID 1325. # enablesid 1325 ######################################################################## # SIDs to comment out, i.e. disable, after each update by placing a # # '#' in front of the rule (if it's a multi-line rule, it will be put # # in front of all lines). # # # # Syntax: disablesid SID # # or: disablesid SID1, SID2, SID3, ... # ######################################################################## # You can specify one SID per line. # disablesid 1 # disablesid 2 # disablesid 3 # And also as comma-separated lists. # disablesid 4,5,6 # It's a good idea to also add comment about why you disable the sid: # disablesid 1324 # 20020101: disabled this SID just because I can oinkmaster-2.0/contrib004074400017500001750000000000001037561221100153225ustar00andreasoandreasooinkmaster-2.0/contrib/create-sidmap.pl010074400017500001750000000212131037561221100204530ustar00andreasoandreaso#!/usr/bin/perl -w # $Id: create-sidmap.pl,v 1.21 2005/12/31 13:42:46 andreas_o Exp $ # # Copyright (c) 2004-2006 Andreas Östling # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # 3. Neither the name of the author nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use strict; sub get_next_entry($ $ $ $ $ $); sub parse_singleline_rule($ $ $); # Files to ignore. my %skipfiles = ( 'deleted.rules' => 1, ); # Regexp to match the start of a multi-line rule. # %ACTIONS% will be replaced with content of $config{actions} later. my $MULTILINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.*\\\\\s*\n$'; # '; # Regexp to match a single-line rule. my $SINGLELINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.+;\s*\)\s*$'; # '; my $USAGE = << "RTFM"; Parse active rules in *.rules in one or more directories and create a SID map. Result is sent to standard output, which can be redirected to a sid-msg.map file. Usage: $0 [rulesdir2, ...] RTFM my $verbose = 1; my (%sidmap, %config); my @rulesdirs = @ARGV; die($USAGE) unless ($#rulesdirs > -1); $config{rule_actions} = "alert|drop|log|pass|reject|sdrop|activate|dynamic"; $SINGLELINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; $MULTILINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; # Read in all rules from each rules file (*.rules) in each rules dir. # into %sidmap. foreach my $rulesdir (@rulesdirs) { opendir(RULESDIR, "$rulesdir") or die("could not open \"$rulesdir\": $!\n"); while (my $file = readdir(RULESDIR)) { next unless ($file =~ /\.rules$/); next if ($skipfiles{$file}); open(FILE, "$rulesdir/$file") or die("could not open \"$rulesdir/$file\": $!\n"); my @file = ; close(FILE); my ($single, $multi, $nonrule, $msg, $sid); while (get_next_entry(\@file, \$single, \$multi, \$nonrule, \$msg, \$sid)) { if (defined($single)) { warn("WARNING: duplicate SID: $sid (discarding old)\n") if (exists($sidmap{$sid})); $sidmap{$sid} = "$sid || $msg"; # Print all references. Borrowed from Brian Caswell's regen-sidmap script. my $ref = $single; while ($ref =~ s/(.*)reference\s*:\s*([^\;]+)(.*)$/$1 $3/) { $sidmap{$sid} .= " || $2" } $sidmap{$sid} .= "\n"; } } } } # Print results. foreach my $sid (sort { $a <=> $b } keys(%sidmap)) { print "$sidmap{$sid}"; } # Same as in oinkmaster.pl. sub get_next_entry($ $ $ $ $ $) { my $arr_ref = shift; my $single_ref = shift; my $multi_ref = shift; my $nonrule_ref = shift; my $msg_ref = shift; my $sid_ref = shift; undef($$single_ref); undef($$multi_ref); undef($$nonrule_ref); undef($$msg_ref); undef($$sid_ref); my $line = shift(@$arr_ref) || return(0); my $disabled = 0; my $broken = 0; # Possible beginning of multi-line rule? if ($line =~ /$MULTILINE_RULE_REGEXP/oi) { $$single_ref = $line; $$multi_ref = $line; $disabled = 1 if ($line =~ /^\s*#/); # Keep on reading as long as line ends with "\". while (!$broken && $line =~ /\\\s*\n$/) { # Remove trailing "\" and newline for single-line version. $$single_ref =~ s/\\\s*\n//; # If there are no more lines, this can not be a valid multi-line rule. if (!($line = shift(@$arr_ref))) { warn("\nWARNING: got EOF while parsing multi-line rule: $$multi_ref\n") if ($config{verbose}); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } # Multi-line continuation. $$multi_ref .= $line; # If there are non-comment lines in the middle of a disabled rule, # mark the rule as broken to return as non-rule lines. if ($line !~ /^\s*#/ && $disabled) { $broken = 1; } elsif ($line =~ /^\s*#/ && !$disabled) { # comment line (with trailing slash) in the middle of an active rule - ignore it } else { $line =~ s/^\s*#*\s*//; # remove leading # in single-line version $$single_ref .= $line; } } # while line ends with "\" # Single-line version should now be a valid rule. # If not, it wasn't a valid multi-line rule after all. if (!$broken && parse_singleline_rule($$single_ref, $msg_ref, $sid_ref)) { $$single_ref =~ s/^\s*//; # remove leading whitespaces $$single_ref =~ s/^#+\s*/#/; # remove whitespaces next to leading # $$single_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces $$multi_ref =~ s/^\s*//; $$multi_ref =~ s/\s*\n$/\n/; $$multi_ref =~ s/^#+\s*/#/; return (1); # return multi } else { warn("\nWARNING: invalid multi-line rule: $$single_ref\n") if ($config{verbose} && $$multi_ref !~ /^\s*#/); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } } elsif (parse_singleline_rule($line, $msg_ref, $sid_ref)) { $$single_ref = $line; $$single_ref =~ s/^\s*//; $$single_ref =~ s/^#+\s*/#/; $$single_ref =~ s/\s*\n$/\n/; return (1); # return single } else { # non-rule line # Do extra check and warn if it *might* be a rule anyway, # but that we just couldn't parse for some reason. warn("\nWARNING: line may be a rule but it could not be parsed ". "(missing sid or msg?): $line\n") if ($config{verbose} && $line =~ /^\s*alert .+msg\s*:\s*".+"\s*;/); $$nonrule_ref = $line; $$nonrule_ref =~ s/\s*\n$/\n/; return (1); # return non-rule } } # Same as in oinkmaster.pl. sub parse_singleline_rule($ $ $) { my $line = shift; my $msg_ref = shift; my $sid_ref = shift; if ($line =~ /$SINGLELINE_RULE_REGEXP/oi) { if ($line =~ /\bmsg\s*:\s*"(.+?)"\s*;/i) { $$msg_ref = $1; } else { return (0); } if ($line =~ /\bsid\s*:\s*(\d+)\s*;/i) { $$sid_ref = $1; } else { return (0); } return (1); } return (0); } oinkmaster-2.0/contrib/addsid.pl010074400017500001750000000271411037561221100171730ustar00andreasoandreaso#!/usr/bin/perl -w # $Id: addsid.pl,v 1.30 2005/12/31 13:42:46 andreas_o Exp $ # # Copyright (c) 2004-2006 Andreas Östling # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # 3. Neither the name of the author nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use strict; sub get_next_entry($ $ $ $ $ $); sub parse_singleline_rule($ $ $); sub get_next_available_sid(@); # Set this to the default classtype you want to add, if missing. # Set to 0 or "" if you don't want to add a classtype. my $CLASSTYPE = "misc-attack"; # If ADD_REV is set to 1, "rev: 1;" will be added to rule if it has no rev. # Set to 0 if you don't want to add it. my $ADD_REV = 1; # Minimum SID to add. Normally, the next available SID will be used, # unless it's below this value. Only SIDs >= 1000000 are reserved for # personal use. my $MIN_SID = 1000001; # Regexp to match the start of a multi-line rule. # %ACTIONS% will be replaced with content of $config{actions} later. my $MULTILINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.*\\\\\s*\n$'; # '; # Regexp to match a single-line rule. my $SINGLELINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.+;\s*\)\s*$'; # '; my $USAGE = << "RTFM"; Parse *.rules in one or more directories and add "sid:;" to active rules that don't have any "sid" entry, starting with the next available SID after parsing all rules files (but $MIN_SID at minumum). Also, "rev:1;" is added to rules without a "rev" entry, and "classtype:misc-attack;" is added to rules without a "classtype" entry (edit options at the top of $0 if you want to change this). Usage: $0 [rulesdir2, ...] RTFM # Start in verbose mode. my $verbose = 1; my (%all_sids, %active_sids, %config); my @rulesdirs = @ARGV; die($USAGE) unless ($#rulesdirs > -1); $config{rule_actions} = "alert|drop|log|pass|reject|sdrop|activate|dynamic"; $SINGLELINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; $MULTILINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; # Find out the next available SID. my $next_sid = get_next_available_sid(@rulesdirs); # Avoid seeing possible warnings about broken rules twice. $verbose = 0; # Add sid/rev/classtype to active rules that don't have any. foreach my $dir (@rulesdirs) { opendir(RULESDIR, "$dir") or die("could not open \"$dir\": $!\n"); while (my $file = readdir(RULESDIR)) { next unless ($file =~ /\.rules$/); open(OLDFILE, "$dir/$file") or die("could not open \"$dir/$file\": $!\n"); my @file = ; close(OLDFILE); open(NEWFILE, ">", "$dir/$file") or die("could not open \"$dir/$file\" for writing: $!\n"); my ($single, $multi, $nonrule, $msg, $sid); while (get_next_entry(\@file, \$single, \$multi, \$nonrule, \$msg, \$sid)) { if (defined($nonrule)) { print NEWFILE "$nonrule"; next; } $multi = $single unless (defined($multi)); # Don't care about inactive rules. if ($single =~ /^\s*#/) { print NEWFILE "$multi"; next; } my $added; # Add SID. if ($single !~ /sid\s*:\s*\d+\s*;/) { $added .= "SID $next_sid,"; $multi =~ s/\)\s*\n/sid:$next_sid;)\n/; $next_sid++; } # Add revision. if ($ADD_REV && $single !~ /rev\s*:\s*\d+\s*;/) { $added .= "rev,"; $multi =~ s/\)\s*\n/rev:1;)\n/; } # Add classtype. if ($CLASSTYPE && $single !~ /classtype\s*:\s*.+\s*;/) { $added .= "classtype $CLASSTYPE,"; $multi =~ s/\)\s*\n/classtype:$CLASSTYPE;)\n/; } if (defined($added)) { $added =~ s/,$//; print "Adding $added to rule \"$msg\"\n" if (defined($added)); } print NEWFILE "$multi"; } close(NEWFILE); } closedir(RULESDIR); } # Read in *.rules in given directory and return highest SID. sub get_next_available_sid(@) { my @dirs = @_; foreach my $dir (@dirs) { opendir(RULESDIR, "$dir") or die("could not open \"$dir\": $!\n"); # Only care about *.rules. while (my $file = readdir(RULESDIR)) { next unless ($file =~ /\.rules$/); open(OLDFILE, "<$dir/$file") or die("could not open \"$dir/$file\": $!\n"); my @file = ; close(OLDFILE); my ($single, $multi, $nonrule, $msg, $sid); while (get_next_entry(\@file, \$single, \$multi, \$nonrule, \$msg, \$sid)) { if (defined($single) && defined($sid)) { $all_sids{$sid}++; # If this is an active rule add to %active_sids and # warn if it already exists. if ($single =~ /^\s*alert/) { print STDERR "WARNING: duplicate SID: $sid\n" if (exists($active_sids{$sid})); $active_sids{$sid}++ } } } } } # Sort sids and use highest one + 1, unless it's below MIN_SID. @_ = sort {$a <=> $b} keys(%all_sids); my $sid = pop(@_); if (!defined($sid)) { $sid = $MIN_SID } else { $sid++; } # If it's below MIN_SID, use MIN_SID instead. $sid = $MIN_SID if ($sid < $MIN_SID); return ($sid) } sub get_next_entry($ $ $ $ $ $) { my $arr_ref = shift; my $single_ref = shift; my $multi_ref = shift; my $nonrule_ref = shift; my $msg_ref = shift; my $sid_ref = shift; undef($$single_ref); undef($$multi_ref); undef($$nonrule_ref); undef($$msg_ref); undef($$sid_ref); my $line = shift(@$arr_ref) || return(0); my $disabled = 0; my $broken = 0; # Possible beginning of multi-line rule? if ($line =~ /$MULTILINE_RULE_REGEXP/oi) { $$single_ref = $line; $$multi_ref = $line; $disabled = 1 if ($line =~ /^\s*#/); # Keep on reading as long as line ends with "\". while (!$broken && $line =~ /\\\s*\n$/) { # Remove trailing "\" and newline for single-line version. $$single_ref =~ s/\\\s*\n//; # If there are no more lines, this can not be a valid multi-line rule. if (!($line = shift(@$arr_ref))) { warn("\nWARNING: got EOF while parsing multi-line rule: $$multi_ref\n") if ($config{verbose}); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } # Multi-line continuation. $$multi_ref .= $line; # If there are non-comment lines in the middle of a disabled rule, # mark the rule as broken to return as non-rule lines. if ($line !~ /^\s*#/ && $disabled) { $broken = 1; } elsif ($line =~ /^\s*#/ && !$disabled) { # comment line (with trailing slash) in the middle of an active rule - ignore it } else { $line =~ s/^\s*#*\s*//; # remove leading # in single-line version $$single_ref .= $line; } } # while line ends with "\" # Single-line version should now be a valid rule. # If not, it wasn't a valid multi-line rule after all. if (!$broken && parse_singleline_rule($$single_ref, $msg_ref, $sid_ref)) { $$single_ref =~ s/^\s*//; # remove leading whitespaces $$single_ref =~ s/^#+\s*/#/; # remove whitespaces next to leading # $$single_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces $$multi_ref =~ s/^\s*//; $$multi_ref =~ s/\s*\n$/\n/; $$multi_ref =~ s/^#+\s*/#/; return (1); # return multi } else { warn("\nWARNING: invalid multi-line rule: $$single_ref\n") if ($config{verbose} && $$multi_ref !~ /^\s*#/); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } } elsif (parse_singleline_rule($line, $msg_ref, $sid_ref)) { $$single_ref = $line; $$single_ref =~ s/^\s*//; $$single_ref =~ s/^#+\s*/#/; $$single_ref =~ s/\s*\n$/\n/; return (1); # return single } else { # non-rule line # Do extra check and warn if it *might* be a rule anyway, # but that we just couldn't parse for some reason. warn("\nWARNING: line may be a rule but it could not be parsed ". "(missing sid or msg?): $line\n") if ($config{verbose} && $line =~ /^\s*alert .+msg\s*:\s*".+"\s*;/); $$nonrule_ref = $line; $$nonrule_ref =~ s/\s*\n$/\n/; return (1); # return non-rule } } # From oinkmaster.pl except that this version # has been modified so that the sid is *optional*. sub parse_singleline_rule($ $ $) { my $line = shift; my $msg_ref = shift; my $sid_ref = shift; if ($line =~ /$SINGLELINE_RULE_REGEXP/oi) { if ($line =~ /\bmsg\s*:\s*"(.+?)"\s*;/i) { $$msg_ref = $1; } else { return (0); } if ($line =~ /\bsid\s*:\s*(\d+)\s*;/i) { $$sid_ref = $1; # } else { # return (0); } return (1); } return (0); } oinkmaster-2.0/contrib/makesidex.pl010074400017500001750000000203401037561221100177070ustar00andreasoandreaso#!/usr/bin/perl -w # $Id: makesidex.pl,v 1.11 2005/12/31 13:42:46 andreas_o Exp $ # # Copyright (c) 2004-2006 Andreas Östling # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # 3. Neither the name of the author nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use strict; sub get_next_entry($ $ $ $ $ $); sub parse_singleline_rule($ $ $); # Regexp to match the start of a multi-line rule. # %ACTIONS% will be replaced with content of $config{actions} later. my $MULTILINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.*\\\\\s*\n$'; # '; # Regexp to match a single-line rule. my $SINGLELINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.+;\s*\)\s*$'; # '; my $USAGE = << "RTFM"; Parse *.rules in one or more directories and look for all rules that are disabled (i.e. begin with "#") and print "disablesid # " to standard output for all those rules. This output can be redirected to a file, which will be understood by Oinkmaster. Usage: $0 [rulesdir2, ...] RTFM my $verbose = 1; my (%disabled, %config); my @rulesdirs = @ARGV; die($USAGE) unless ($#rulesdirs > -1); $config{rule_actions} = "alert|drop|log|pass|reject|sdrop|activate|dynamic"; $SINGLELINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; $MULTILINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; foreach my $rulesdir (@rulesdirs) { opendir(RULESDIR, "$rulesdir") or die("could not open \"$rulesdir\": $!\n"); while (my $file = readdir(RULESDIR)) { next unless ($file =~ /\.rules$/); open(FILE, "$rulesdir/$file") or die("could not open \"$rulesdir/$file\": $!\n"); my @file = ; close(FILE); my ($single, $multi, $nonrule, $msg, $sid); while (get_next_entry(\@file, \$single, \$multi, \$nonrule, \$msg, \$sid)) { $single = $multi if (defined($multi)); $disabled{$sid} = $msg if (defined($single) && $single =~ /^\s*#/); } } } # Print results. foreach my $sid (sort { $a <=> $b } keys(%disabled)) { printf("%-25s # %s\n", "disablesid $sid", $disabled{$sid}); } # Same as in oinkmaster.pl. sub get_next_entry($ $ $ $ $ $) { my $arr_ref = shift; my $single_ref = shift; my $multi_ref = shift; my $nonrule_ref = shift; my $msg_ref = shift; my $sid_ref = shift; undef($$single_ref); undef($$multi_ref); undef($$nonrule_ref); undef($$msg_ref); undef($$sid_ref); my $line = shift(@$arr_ref) || return(0); my $disabled = 0; my $broken = 0; # Possible beginning of multi-line rule? if ($line =~ /$MULTILINE_RULE_REGEXP/oi) { $$single_ref = $line; $$multi_ref = $line; $disabled = 1 if ($line =~ /^\s*#/); # Keep on reading as long as line ends with "\". while (!$broken && $line =~ /\\\s*\n$/) { # Remove trailing "\" and newline for single-line version. $$single_ref =~ s/\\\s*\n//; # If there are no more lines, this can not be a valid multi-line rule. if (!($line = shift(@$arr_ref))) { warn("\nWARNING: got EOF while parsing multi-line rule: $$multi_ref\n") if ($config{verbose}); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } # Multi-line continuation. $$multi_ref .= $line; # If there are non-comment lines in the middle of a disabled rule, # mark the rule as broken to return as non-rule lines. if ($line !~ /^\s*#/ && $disabled) { $broken = 1; } elsif ($line =~ /^\s*#/ && !$disabled) { # comment line (with trailing slash) in the middle of an active rule - ignore it } else { $line =~ s/^\s*#*\s*//; # remove leading # in single-line version $$single_ref .= $line; } } # while line ends with "\" # Single-line version should now be a valid rule. # If not, it wasn't a valid multi-line rule after all. if (!$broken && parse_singleline_rule($$single_ref, $msg_ref, $sid_ref)) { $$single_ref =~ s/^\s*//; # remove leading whitespaces $$single_ref =~ s/^#+\s*/#/; # remove whitespaces next to leading # $$single_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces $$multi_ref =~ s/^\s*//; $$multi_ref =~ s/\s*\n$/\n/; $$multi_ref =~ s/^#+\s*/#/; return (1); # return multi } else { warn("\nWARNING: invalid multi-line rule: $$single_ref\n") if ($config{verbose} && $$multi_ref !~ /^\s*#/); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } } elsif (parse_singleline_rule($line, $msg_ref, $sid_ref)) { $$single_ref = $line; $$single_ref =~ s/^\s*//; $$single_ref =~ s/^#+\s*/#/; $$single_ref =~ s/\s*\n$/\n/; return (1); # return single } else { # non-rule line # Do extra check and warn if it *might* be a rule anyway, # but that we just couldn't parse for some reason. warn("\nWARNING: line may be a rule but it could not be parsed ". "(missing sid or msg?): $line\n") if ($config{verbose} && $line =~ /^\s*alert .+msg\s*:\s*".+"\s*;/); $$nonrule_ref = $line; $$nonrule_ref =~ s/\s*\n$/\n/; return (1); # return non-rule } } # Same as in oinkmaster.pl. sub parse_singleline_rule($ $ $) { my $line = shift; my $msg_ref = shift; my $sid_ref = shift; if ($line =~ /$SINGLELINE_RULE_REGEXP/oi) { if ($line =~ /\bmsg\s*:\s*"(.+?)"\s*;/i) { $$msg_ref = $1; } else { return (0); } if ($line =~ /\bsid\s*:\s*(\d+)\s*;/i) { $$sid_ref = $1; } else { return (0); } return (1); } return (0); } oinkmaster-2.0/contrib/addmsg.pl010074400017500001750000000220031037561221100171720ustar00andreasoandreaso#!/usr/bin/perl -w # $Id: addmsg.pl,v 1.19 2005/12/31 13:42:46 andreas_o Exp $ # # Copyright (c) 2004-2006 Andreas Östling # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # 3. Neither the name of the author nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use strict; sub get_next_entry($ $ $ $ $ $); sub parse_singleline_rule($ $ $); my $USAGE = << "RTFM"; Parse Oinkmaster configuration file and add the rule's "msg" string as a #comment for each disablesid/enablesid line. Usage: $0 [rulesdir2, ...] The new config file will be printed to standard output, so you probably want to redirect the output to a new file (*NOT* the same file you used as input, because that will destroy the file!). For example: $0 /etc/oinkmaster.conf /etc/rules/ > oinkmaster.conf.new If oinkmaster.conf.new looks ok, simply rename it to /etc/oinkmaster.conf. RTFM # Regexp to match the start of a multi-line rule. # %ACTIONS% will be replaced with content of $config{actions} later. my $MULTILINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.*\\\\\s*\n$'; # '; # Regexp to match a single-line rule. my $SINGLELINE_RULE_REGEXP = '^\s*#*\s*(?:%ACTIONS%)'. '\s.+;\s*\)\s*$'; # '; my $config = shift || die($USAGE); my @rulesdirs = @ARGV; die($USAGE) unless ($#rulesdirs > -1); my $verbose = 1; my (%sidmsgmap, %config); $config{rule_actions} = "alert|drop|log|pass|reject|sdrop|activate|dynamic"; $SINGLELINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; $MULTILINE_RULE_REGEXP =~ s/%ACTIONS%/$config{rule_actions}/; # Read in oinkmaster.conf. open(CONFIG, "<" , "$config") or die("could not open \"$config\" for reading: $!\n"); my @config = ; close(CONFIG); # Read in *.rules in all rulesdirs and create %sidmsgmap ($sidmsgmap{sid} = msg). foreach my $rulesdir (@rulesdirs) { opendir(RULESDIR, "$rulesdir") or die("could not open \"$rulesdir\": $!\n"); while (my $file = readdir(RULESDIR)) { next unless ($file =~ /\.rules$/); open(FILE, "<", "$rulesdir/$file") or die("could not open \"$rulesdir/$file\": $!\n"); my @file = ; close(FILE); my ($single, $multi, $nonrule, $msg, $sid); while (get_next_entry(\@file, \$single, \$multi, \$nonrule, \$msg, \$sid)) { $sidmsgmap{$sid} = $msg if (defined($single)); } } } # Print new oinkmaster.conf. while ($_ = shift(@config)) { if (/^\s*(?:disable|enable|local)sid\s+(\d+)\s*$/ || /^\s*(\d+)\s*,\s*\\$/ || /^\s*(\d+)\s*$/) { my $sid = $1; my $is_multiline = 0; chomp; if (/\\$/) { $is_multiline = 1; s/\\$//; } $_ = sprintf("%-25s", $_); if (exists($sidmsgmap{$sid})) { print "$_ # $sidmsgmap{$sid}"; } else { print "$_"; } print " \\" if ($is_multiline); print "\n"; } else { print; } } # From oinkmaster.pl. sub get_next_entry($ $ $ $ $ $) { my $arr_ref = shift; my $single_ref = shift; my $multi_ref = shift; my $nonrule_ref = shift; my $msg_ref = shift; my $sid_ref = shift; undef($$single_ref); undef($$multi_ref); undef($$nonrule_ref); undef($$msg_ref); undef($$sid_ref); my $line = shift(@$arr_ref) || return(0); my $disabled = 0; my $broken = 0; # Possible beginning of multi-line rule? if ($line =~ /$MULTILINE_RULE_REGEXP/oi) { $$single_ref = $line; $$multi_ref = $line; $disabled = 1 if ($line =~ /^\s*#/); # Keep on reading as long as line ends with "\". while (!$broken && $line =~ /\\\s*\n$/) { # Remove trailing "\" and newline for single-line version. $$single_ref =~ s/\\\s*\n//; # If there are no more lines, this can not be a valid multi-line rule. if (!($line = shift(@$arr_ref))) { warn("\nWARNING: got EOF while parsing multi-line rule: $$multi_ref\n") if ($config{verbose}); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } # Multi-line continuation. $$multi_ref .= $line; # If there are non-comment lines in the middle of a disabled rule, # mark the rule as broken to return as non-rule lines. if ($line !~ /^\s*#/ && $disabled) { $broken = 1; } elsif ($line =~ /^\s*#/ && !$disabled) { # comment line (with trailing slash) in the middle of an active rule - ignore it } else { $line =~ s/^\s*#*\s*//; # remove leading # in single-line version $$single_ref .= $line; } } # while line ends with "\" # Single-line version should now be a valid rule. # If not, it wasn't a valid multi-line rule after all. if (!$broken && parse_singleline_rule($$single_ref, $msg_ref, $sid_ref)) { $$single_ref =~ s/^\s*//; # remove leading whitespaces $$single_ref =~ s/^#+\s*/#/; # remove whitespaces next to leading # $$single_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces $$multi_ref =~ s/^\s*//; $$multi_ref =~ s/\s*\n$/\n/; $$multi_ref =~ s/^#+\s*/#/; return (1); # return multi } else { warn("\nWARNING: invalid multi-line rule: $$single_ref\n") if ($config{verbose} && $$multi_ref !~ /^\s*#/); @_ = split(/\n/, $$multi_ref); undef($$multi_ref); undef($$single_ref); # First line of broken multi-line rule will be returned as a non-rule line. $$nonrule_ref = shift(@_) . "\n"; $$nonrule_ref =~ s/\s*\n$/\n/; # remove trailing whitespaces # The rest is put back to the array again. foreach $_ (reverse((@_))) { unshift(@$arr_ref, "$_\n"); } return (1); # return non-rule } } elsif (parse_singleline_rule($line, $msg_ref, $sid_ref)) { $$single_ref = $line; $$single_ref =~ s/^\s*//; $$single_ref =~ s/^#+\s*/#/; $$single_ref =~ s/\s*\n$/\n/; return (1); # return single } else { # non-rule line # Do extra check and warn if it *might* be a rule anyway, # but that we just couldn't parse for some reason. warn("\nWARNING: line may be a rule but it could not be parsed ". "(missing sid or msg?): $line\n") if ($config{verbose} && $line =~ /^\s*alert .+msg\s*:\s*".+"\s*;/); $$nonrule_ref = $line; $$nonrule_ref =~ s/\s*\n$/\n/; return (1); # return non-rule } } # From oinkmaster.pl. sub parse_singleline_rule($ $ $) { my $line = shift; my $msg_ref = shift; my $sid_ref = shift; if ($line =~ /$SINGLELINE_RULE_REGEXP/oi) { if ($line =~ /\bmsg\s*:\s*"(.+?)"\s*;/i) { $$msg_ref = $1; } else { return (0); } if ($line =~ /\bsid\s*:\s*(\d+)\s*;/i) { $$sid_ref = $1; } else { return (0); } return (1); } return (0); } oinkmaster-2.0/contrib/README.contrib010064400017500001750000000066311037561221100177250ustar00andreasoandreaso# $Id: README.contrib,v 1.21 2005/10/18 10:41:20 andreas_o Exp $ # ------------------------------------------------------------------------------- * oinkgui.pl by Andreas Östling A graphical front-end to Oinkmaster written in Perl/Tk. See README.gui for complete documentation. ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- * addsid.pl by Andreas Östling A script that parses *.rules in all specified directories and adds a SID to (active) rules that don't have any. (Actually, rev and classtype are also added if missing, unless you edit addsid.pl and tune this.) The script first looks for the current highest SID (even in inactive rules) and starts at the next one, unless this value is below MIN_SID (defined inside addsid.pl). By default, this value is set to 1000001 since this is the lowest SID assigned for local usage. Handles multi-line rules. ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- * create-sidmap.pl by Andreas Östling A script that parses all active rules in *.rules in all specified directories and creates a SID map. (Like Snort's regen-sidmap, but this one handles multi-line rules.) Result goes to standard output which can be redirected to a sid-msg.map file. ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- * makesidex.pl, originally by Jerry Applebaum but later rewritten by Andreas Östling to handle multi-line rules and multiple rules directories. It reads *.rules in all specified directories, looks for all disabled rules and prints a "disablesid # " line for each disabled rule. The output can be appended to oinkmaster.conf. Useful to new Oinkmaster users. ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- * addmsg.pl by Andreas Östling : A script that will parse your oinkmaster.conf for localsid/enablesid/disablesid lines and add their rule message as a #comment. If your oinkmaster.conf looks like this before addmsg.pl has been run: disablesid 286 disablesid 287 disablesid 288 It will look something like this afterward: disablesid 286 # POP3 EXPLOIT x86 bsd overflow disablesid 287 # POP3 EXPLOIT x86 bsd overflow disablesid 288 # POP3 EXPLOIT x86 linux overflow addmsg.pl will not touch lines that already has a comment in them. It's not able to handle SID lists when written like this: disablesid 1,2,3, ... But it should handle them if written like this: disablesid \ 1, \ 2, \ 3 The new config file will be printed to standard output, so you probably want to redirect the output to a file, for example: ./addmsg.pl oinkmaster.conf rules/ > oinkmaster.conf.new If oinkmaster.conf.new looks ok, simply rename it to oinkmaster.conf. Do NOT redirect to the same file you read from, as this will destroy that file. ------------------------------------------------------------------------------- oinkmaster-2.0/contrib/oinkgui.pl010074400017500001750000000701711037561221100174110ustar00andreasoandreaso#!/usr/bin/perl -w # $Id: oinkgui.pl,v 1.52 2005/12/31 13:42:46 andreas_o Exp $ # # Copyright (c) 2004-2006 Andreas Östling # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above # copyright notice, this list of conditions and the following # disclaimer. # # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials # provided with the distribution. # # 3. Neither the name of the author nor the names of its # contributors may be used to endorse or promote products # derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use 5.006001; use strict; use File::Spec; use Tk; use Tk::Balloon; use Tk::BrowseEntry; use Tk::FileSelect; use Tk::NoteBook; use Tk::ROText; use constant CSIDL_DRIVES => 17; sub update_rules(); sub clear_messages(); sub create_cmdline($); sub fileDialog($ $ $ $); sub load_config(); sub save_config(); sub save_messages(); sub update_file_label_color($ $ $); sub create_fileSelectFrame($ $ $ $ $ $); sub create_checkbutton($ $ $); sub create_radiobutton($ $ $); sub create_actionbutton($ $ $); sub execute_oinkmaster(@); sub logmsg($ $); my $version = 'Oinkmaster GUI v1.1'; my @oinkmaster_conf = qw( /etc/oinkmaster.conf /usr/local/etc/oinkmaster.conf ); # List of URLs that will show up in the URL BrowseEntry. my @urls = qw( http://www.bleedingsnort.com/bleeding.rules.tar.gz http://www.snort.org/pub-bin/downloads.cgi/Download/comm_rules/Community-Rules.tar.gz http://www.snort.org/pub-bin/oinkmaster.cgi//snortrules-snapshot-CURRENT.tar.gz http://www.snort.org/pub-bin/oinkmaster.cgi//snortrules-snapshot-2.3.tar.gz ); my %color = ( background => 'Bisque3', button => 'Bisque2', label => 'Bisque1', notebook_bg => 'Bisque2', notebook_inact => 'Bisque3', file_label_ok => '#00e000', file_label_not_ok => 'red', out_frame_fg => 'white', out_frame_bg => 'black', entry_bg => 'white', button_active => 'white', button_bg => 'Bisque4', ); my %config = ( animate => 1, careful => 0, enable_all => 0, check_removed => 0, output_mode => 'normal', diff_mode => 'detailed', perl => $^X, oinkmaster => "", oinkmaster_conf => "", outdir => "", url => "", varfile => "", backupdir => "", editor => "", ); my %help = ( # File locations. oinkscript => 'Location of the executable Oinkmaster script (oinkmaster.pl).', oinkconf => 'The Oinkmaster configuration file to use.', outdir => 'Where to put the new rules. This should be the directory where you '. 'store your current rules.', url => 'Alternate location of rules archive to download/copy. '. 'Leave empty to use the location set in oinkmaster.conf.', varfile => 'Variables that exist in downloaded snort.conf but not in '. 'this file will be added to it. Leave empty to skip.', backupdir => 'Directory to put tarball of old rules before overwriting them. '. 'Leave empty to skip backup.', editor => 'Full path to editor to execute when pressing the "edit" button '. '(wordpad is recommended on Windows). ', # Checkbuttons. careful => 'In careful mode, Oinkmaster will just check for changes, '. 'not update anything.', enable => 'Some rules may be commented out by default (for a reason!). '. 'This option will make Oinkmaster enable those.', removed => 'Check for rules files that exist in the output directory but not '. 'in the downloaded rules archive.', # Action buttons. clear => 'Clear current output messages.', save => 'Save current output messages to file.', exit => 'Exit the GUI.', update => 'Execute Oinkmaster to update the rules.', test => 'Test current Oinkmaster configuration. ' . 'If there are no fatal errors, you are ready to update the rules.', version => 'Request version information from Oinkmaster.', ); my $gui_config_file = ""; my $use_fileop = 0; #### MAIN #### select STDERR; $| = 1; select STDOUT; $| = 1; # Find out if can use Win32::FileOp. if ($^O eq 'MSWin32') { BEGIN { $^W = 0 } $use_fileop = 1 if (eval "require Win32::FileOp"); } # Find out which oinkmaster.pl file to default to. foreach my $dir (File::Spec->path()) { my $file = "$dir/oinkmaster"; if (-f "$file" && (-x "$file" || $^O eq 'MSWin32')) { $config{oinkmaster} = $file; last; } elsif (-f "$file.pl" && (-x "$file" || $^O eq 'MSWin32')) { $config{oinkmaster} = "$file.pl"; last; } } # Find out which oinkmaster config file to default to. foreach my $file (@oinkmaster_conf) { if (-e "$file") { $config{oinkmaster_conf} = $file; last; } } # Find out where the GUI config file is (it's not required). if ($ENV{HOME}) { $gui_config_file = "$ENV{HOME}/.oinkguirc" } elsif ($ENV{HOMEDRIVE} && $ENV{HOMEPATH}) { $gui_config_file = "$ENV{HOMEDRIVE}$ENV{HOMEPATH}\\.oinkguirc"; } # Create main window. my $main = MainWindow->new( -background => "$color{background}", -title => "$version", ); # Create scrolled frame with output messages. my $out_frame = $main->Scrolled('ROText', -setgrid => 'true', -scrollbars => 'e', -background => $color{out_frame_bg}, -foreground => $color{out_frame_fg}, ); my $help_label = $main->Label( -relief => 'groove', -background => "$color{label}", ); my $balloon = $main->Balloon( -statusbar => $help_label, ); # Create notebook. my $notebook = $main->NoteBook( -ipadx => 6, -ipady => 6, -background => $color{notebook_bg}, -inactivebackground => $color{notebook_inact}, -backpagecolor => $color{background}, ); # Create tab with required files/dirs. my $req_tab = $notebook->add("required", -label => "Required files and directories", -underline => 0, ); $req_tab->configure(-bg => "$color{notebook_inact}"); # Create frame with oinkmaster.pl location. my $filetypes = [ ['Oinkmaster script', 'oinkmaster.pl'], ['All files', '*' ] ]; my $oinkscript_frame = create_fileSelectFrame($req_tab, "oinkmaster.pl", 'EXECFILE', \$config{oinkmaster}, 'NOEDIT', $filetypes); $balloon->attach($oinkscript_frame, -statusmsg => $help{oinkscript}); # Create frame with oinkmaster.conf location. $filetypes = [ ['configuration files', '.conf'], ['All files', '*' ] ]; my $oinkconf_frame = create_fileSelectFrame($req_tab, "oinkmaster.conf", 'ROFILE', \$config{oinkmaster_conf}, 'EDIT', $filetypes); $balloon->attach($oinkconf_frame, -statusmsg => $help{oinkconf}); # Create frame with output directory. my $outdir_frame = create_fileSelectFrame($req_tab, "output directory", 'WRDIR', \$config{outdir}, 'NOEDIT', undef); $balloon->attach($outdir_frame, -statusmsg => $help{outdir}); # Create tab with optional files/dirs. my $opt_tab = $notebook->add("optional", -label => "Optional files and directories", -underline => 0, ); $opt_tab->configure(-bg => "$color{notebook_inact}"); # Create frame with alternate URL location. $filetypes = [ ['compressed tar files', '.tar.gz'] ]; my $url_frame = create_fileSelectFrame($opt_tab, "Alternate URL", 'URL', \$config{url}, 'NOEDIT', $filetypes); $balloon->attach($url_frame, -statusmsg => $help{url}); # Create frame with variable file. $filetypes = [ ['Snort configuration files', ['.conf', '.config']], ['All files', '*' ] ]; my $varfile_frame = create_fileSelectFrame($opt_tab, "Variable file", 'WRFILE', \$config{varfile}, 'EDIT', $filetypes); $balloon->attach($varfile_frame, -statusmsg => $help{varfile}); # Create frame with backup dir location. my $backupdir_frame = create_fileSelectFrame($opt_tab, "Backup directory", 'WRDIR', \$config{backupdir}, 'NOEDIT', undef); $balloon->attach($backupdir_frame, -statusmsg => $help{backupdir}); # Create frame with editor location. $filetypes = [ ['executable files', ['.exe']], ['All files', '*' ] ]; my $editor_frame = create_fileSelectFrame($opt_tab, "Editor", 'EXECFILE', \$config{editor}, 'NOEDIT', $filetypes); $balloon->attach($editor_frame, -statusmsg => $help{editor}); $notebook->pack( -expand => 'no', -fill => 'x', -padx => '5', -pady => '5', -side => 'top' ); # Create the frame to the left. my $left_frame = $main->Frame( -background => "$color{label}", -border => '2', )->pack( -side => 'left', -fill => 'y', ); # Create "GUI settings" label. $left_frame->Label( -text => "GUI settings:", -background => "$color{label}", )->pack( -side => 'top', -fill => 'x', ); create_actionbutton($left_frame, "Load saved settings", \&load_config); create_actionbutton($left_frame, "Save current settings", \&save_config); # Create "options" label at the top of the left frame. $left_frame->Label( -text => "Options:", -background => "$color{label}", )->pack(-side => 'top', -fill => 'x', ); # Create checkbuttons in the left frame. $balloon->attach( create_checkbutton($left_frame, "Careful mode", \$config{careful}), -statusmsg => $help{careful} ); $balloon->attach( create_checkbutton($left_frame, "Enable all", \$config{enable_all}), -statusmsg => $help{enable} ); $balloon->attach( create_checkbutton($left_frame, "Check for removed files", \$config{check_removed}), -statusmsg => $help{removed} ); # Create "mode" label. $left_frame->Label( -text => "Output mode:", -background => "$color{label}", )->pack( -side => 'top', -fill => 'x', ); # Create mode radiobuttons in the left frame. create_radiobutton($left_frame, "super-quiet", \$config{output_mode}); create_radiobutton($left_frame, "quiet", \$config{output_mode}); create_radiobutton($left_frame, "normal", \$config{output_mode}); create_radiobutton($left_frame, "verbose", \$config{output_mode}); # Create "Diff mode" label. $left_frame->Label( -text => "Diff mode:", -background => "$color{label}", )->pack( -side => 'top', -fill => 'x', ); create_radiobutton($left_frame, "detailed", \$config{diff_mode}); create_radiobutton($left_frame, "summarized", \$config{diff_mode}); create_radiobutton($left_frame, "remove common", \$config{diff_mode}); # Create "activity messages" label. $main->Label( -text => "Output messages:", -width => '130', -background => "$color{label}", )->pack( -side => 'top', -fill => 'x', ); # Pack output frame. $out_frame->pack( -expand => 'yes', -fill => 'both', ); # Pack help label below output window. $help_label->pack( -fill => 'x', ); # Create "actions" label. $left_frame->Label( -text => "Actions:", -background => "$color{label}", )->pack( -side => 'top', -fill => 'x', ); # Create action buttons. $balloon->attach( create_actionbutton($left_frame, "Update rules!", \&update_rules), -statusmsg => $help{update} ); $balloon->attach( create_actionbutton($left_frame, "Clear output messages", \&clear_messages), -statusmsg => $help{clear} ); $balloon->attach( create_actionbutton($left_frame, "Save output messages", \&save_messages), -statusmsg => $help{save} ); $balloon->attach( create_actionbutton($left_frame, "Exit", \&exit), -statusmsg => $help{exit} ); # Make the mousewheel scroll the output window. Taken from Mastering Perl/Tk. if ($^O eq 'MSWin32') { $out_frame->bind('' => [ sub { $_[0]->yview('scroll', -($_[1] / 120) * 3, 'units')}, Ev('D') ] ); } else { $out_frame->bind('<4>' => sub { $_[0]->yview('scroll', -3, 'units') unless $Tk::strictMotif; }); $out_frame->bind('<5>' => sub { $_[0]->yview('scroll', +3, 'units') unless $Tk::strictMotif; }); } # Now the fun begins. if ($config{animate}) { foreach (split(//, "Welcome to $version")) { logmsg("$_", 'MISC'); $out_frame->after(5); } } else { logmsg("Welcome to $version", 'MISC'); } logmsg("\n\n", 'MISC'); # Load gui settings into %config. load_config(); # Warn if any required file/directory is not set. logmsg("No oinkmaster.pl set, please select one above!\n\n", 'ERROR') if ($config{oinkmaster} !~ /\S/); logmsg("No oinkmaster configuration file set, please select one above!\n\n", 'ERROR') if ($config{oinkmaster_conf} !~ /\S/); logmsg("Output directory is not set, please select one above!\n\n", 'ERROR') if ($config{outdir} !~ /\S/); MainLoop; #### END #### sub fileDialog($ $ $ $) { my $var_ref = shift; my $title = shift; my $type = shift; my $filetypes = shift; my $dirname; if ($type eq 'WRDIR') { if ($use_fileop) { $dirname = Win32::FileOp::BrowseForFolder("title", CSIDL_DRIVES); } else { my $fs = $main->FileSelect(); $fs->configure(-verify => ['-d', '-w'], -title => $title); $dirname = $fs->Show; } $$var_ref = $dirname if ($dirname); } elsif ($type eq 'EXECFILE' || $type eq 'ROFILE' || $type eq 'WRFILE' || $type eq 'URL') { my $filename = $main->getOpenFile(-title => $title, -filetypes => $filetypes); $$var_ref = $filename if ($filename); } elsif ($type eq 'SAVEFILE') { my $filename = $main->getSaveFile(-title => $title, -filetypes => $filetypes); $$var_ref = $filename if ($filename); } else { logmsg("Unknown type ($type)\n", 'ERROR'); } } sub update_file_label_color($ $ $) { my $label = shift; my $filename = shift; my $type = shift; $filename =~ s/^\s+//; $filename =~ s/\s+$//; unless ($filename) { $label->configure(-background => $color{file_label_not_ok}); return (1); } if ($type eq "URL") { if ($filename =~ /^(?:http|ftp|scp):\/\/.+\.tar\.gz$/) { $label->configure(-background => $color{file_label_ok}); } elsif ($filename =~ /^(?:file:\/\/)*(.+\.tar\.gz)$/) { my $file = $1; if (-f "$file" && -r "$file") { $label->configure(-background => $color{file_label_ok}); } else { $label->configure(-background => $color{file_label_not_ok}); } } else { $label->configure(-background => $color{file_label_not_ok}); } } elsif ($type eq "ROFILE") { if (-f "$filename" && -r "$filename") { $label->configure(-background => $color{file_label_ok}); } else { $label->configure(-background => $color{file_label_not_ok}); } } elsif ($type eq "EXECFILE") { if (-f "$filename" && (-x "$filename" || $^O eq 'MSWin32')) { $label->configure(-background => $color{file_label_ok}); } else { $label->configure(-background => $color{file_label_not_ok}); } } elsif ($type eq "WRFILE") { if (-f "$filename" && -w "$filename") { $label->configure(-background => $color{file_label_ok}); } else { $label->configure(-background => $color{file_label_not_ok}); } } elsif ($type eq "WRDIR") { if (-d "$filename" && -w "$filename") { $label->configure(-background => $color{file_label_ok}); } else { $label->configure(-background => $color{file_label_not_ok}); } } else { print STDERR "incorrect type ($type)\n"; exit; } return (1); } sub create_checkbutton($ $ $) { my $frame = shift; my $name = shift; my $var_ref = shift; my $button = $frame->Checkbutton( -text => $name, -background => $color{button}, -activebackground => $color{button_active}, -highlightbackground => $color{button_bg}, -variable => $var_ref, -relief => 'raise', -anchor => 'w', )->pack( -fill => 'x', -side => 'top', -pady => '1', ); return ($button); } sub create_actionbutton($ $ $) { my $frame = shift; my $name = shift; my $func_ref = shift; my $button = $frame->Button( -text => $name, -command => sub { &$func_ref; $out_frame->focus; }, -background => $color{button}, -activebackground => $color{button_active}, -highlightbackground => $color{button_bg}, )->pack( -fill => 'x', ); return ($button); } sub create_radiobutton($ $ $) { my $frame = shift; my $name = shift; my $mode_ref = shift; my $button = $frame->Radiobutton( -text => $name, -highlightbackground => $color{button_bg}, -background => $color{button}, -activebackground => $color{button_active}, -variable => $mode_ref, -relief => 'raised', -anchor => 'w', -value => $name, )->pack( -side => 'top', -pady => '1', -fill => 'x', ); return ($button); } # Create