#!/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