| # Copyright (C) 1999-2007 Jay Beale |
| # Copyright (C) 2001-2008 Hewlett-Packard Development Company, L.P. |
| # Licensed under the GNU General Public License, version 2 |
| |
| package Bastille::API; |
| |
| ## TO DO: |
| # |
| # |
| # 1) Look for more places to insert error handling... |
| # |
| # 2) Document this module more |
| # |
| # |
| |
| |
| ########################################################################## |
| # |
| # This module forms the basis for the v1.1 API. |
| # |
| ########################################################################## |
| |
| # |
| # This module forms the initial basis for the Bastille Engine, implemented |
| # presently via a Perl API for Perl modules. |
| # |
| # This is still under construction -- it is very usable, but not very well |
| # documented, yet. |
| # |
| |
| ########################################################################## |
| # |
| # API Function Listing |
| # |
| ########################################################################## |
| # The routines which should be called by Bastille modules are listed here, |
| # though they are better documented throughout this file. |
| # |
| # Distro Specific Stuff: |
| # |
| # &GetDistro - figures out what distro we're running, if it knows it... |
| # &ConfigureForDistro - sets global variables based on the distro |
| # &GetGlobal - returns hash values defined in ConfigureForDistro |
| # |
| # &getGlobalConfig - returns value of hash set up by ReadConfig |
| # |
| # Logging Specific Stuff has moved to LogAPI.pm: |
| # |
| # &B_log(type,msg) -- takes care of all logging |
| # |
| # |
| # Input functions for the old input method... |
| # |
| # File open/close/backup functions |
| # |
| # &B_open * -- opens a file handle and logs the action/error (OLD WAY!) |
| # &B_open_plus -- opens a pair of file handles for the old and new version |
| # of a file; respects logonly flag. (NEW WAY) |
| # &B_close * -- closes a file handle and logs the action/error (OLD WAY!) |
| # &B_close_plus -- closes a pair of file handles opened by B_open_plus, |
| # backing up one file and renaming the new file to the |
| # old one's name, logging actions/errors. Respects the |
| # logonly flag -- needs B_backup file. Finally, sets |
| # new file's mode,uid,gid to old file's... (NEW WAY) |
| # &B_backup_file - backs up a file that is being changed/deleted into the |
| # $GLOBAL_BDIR{"backup"} directory. |
| # |
| # Non-content file modification functions |
| # |
| # &B_delete_file - deletes the named file, backing up a copy |
| # &B_create_file - creates the named file, if it doesn't exist |
| # |
| # &B_symlink - create a symlink to a file, recording the revert rm |
| # |
| # More stuff |
| # |
| # &B_createdir - make a directory, if it doesn't exist, record revert rmdir |
| # &B_cp - copy a file, respecting LOGONLY and revert func. |
| # &B_mknod - wrap mknod with revert and logonly and prefix functionality |
| # |
| # &B_read_sums - reads sum.csv file and parses input into the GLOBAL_SUM hash |
| # &B_write_sums - writes sum.csv file from GLOBAL_SUM hash |
| # &B_check_sum($) - take a file name and compares the stored cksum with the current |
| # cksum of said file |
| # &B_set_sum($) - takes a file name and gets that files current cksum then sets |
| # that sum in the GLOBAL_SUM hash |
| # &B_revert_log - create entry in shell script, executed later by bastille -r |
| # &showDisclaimer - Print the disclaimer and wait for 5 minutes for acceptance |
| ########################################################################### |
| # Note: GLOBAL_VERBOSE |
| # |
| # All logging functions now check GLOBAL_VERBOSE and, if set, will print |
| # all the information sent to log files to STDOUT/STDERR as well. |
| # |
| |
| # |
| # Note: GLOBAL_LOGONLY |
| # |
| # All Bastille API functions now check for the existence of a GLOBAL_LOGONLY |
| # variable. When said variable is set, no function actually modifies the |
| # system. |
| # |
| # Note: GLOBAL_DEBUG |
| # |
| # The B_log("DEBUG",...) function now checks GLOBAL_DEBUG and, if set, it will |
| # print all the information to a new debug-log file. If GLOBAL_VERBOSE is |
| # set it might log to STDOUT/STDERR as well (not yet implemented, pending |
| # discussion). Developers should populate appropriate places with &B_log(DEBUG) |
| # in order to be able to tell users to use this options and send the logs |
| # for inspection and debugging. |
| # |
| # |
| |
| |
| # Libraries for the Backup_file routine: Cwd and File::Path |
| use Cwd; |
| use Bastille::OSX_API; |
| use Bastille::LogAPI; |
| use File::Path; |
| use File::Basename; |
| |
| # Export the API functions listed below for use by the modules. |
| |
| use Exporter; |
| @ISA = qw ( Exporter ); |
| @EXPORT = qw( |
| setOptions GetDistro ConfigureForDistro B_log B_revert_log |
| SanitizeEnv |
| B_open B_close B_symlink StopLogging |
| B_open_plus B_close_plus |
| B_isFileinSumDB |
| B_create_file B_read_sums B_check_sum B_set_sum isSumDifferent listModifiedFiles |
| B_create_dir B_create_log_file |
| B_delete_file |
| B_cp B_place B_mknod |
| showDisclaimer |
| getSupportedOSHash |
| B_Backtick |
| B_System |
| isProcessRunning |
| checkProcsForService |
| |
| |
| $GLOBAL_OS $GLOBAL_ACTUAL_OS $CLI |
| $GLOBAL_LOGONLY $GLOBAL_VERBOSE $GLOBAL_DEBUG $GLOBAL_AUDITONLY $GLOBAL_AUDIT_NO_BROWSER $errorFlag |
| %GLOBAL_BIN %GLOBAL_DIR %GLOBAL_FILE |
| %GLOBAL_BDIR %GLOBAL_BFILE |
| %GLOBAL_CONFIG %GLOBAL_SUM |
| |
| %GLOBAL_SERVICE %GLOBAL_SERVTYPE %GLOBAL_PROCESS %GLOBAL_RC_CONFIG |
| %GLOBAL_TEST |
| |
| getGlobal setGlobal getGlobalConfig |
| |
| |
| B_parse_fstab |
| B_parse_mtab B_is_rpm_up_to_date |
| |
| NOTSECURE_CAN_CHANGE SECURE_CANT_CHANGE |
| NOT_INSTALLED INCONSISTENT MANUAL NOTEST SECURE_CAN_CHANGE |
| STRING_NOT_DEFINED NOT_INSTALLED_NOTSECURE DONT_KNOW |
| RELEVANT_HEADERQ NOTRELEVANT_HEADERQ |
| ); |
| |
| |
| |
| ###################################################### |
| ###Testing Functions |
| ################################################################## |
| |
| #Define "Constants" for test functions. Note these constants sometimes get |
| #interpreted as literal strings when used as hash references, so you may |
| # have to use CONSTANT() to disambiguate, like below. Sorry, it was either |
| # that or create even *more* global variables. |
| # See TestDriver.pm for definitions, and test design doc for full explaination |
| use constant { |
| NOTSECURE_CAN_CHANGE => 0, |
| SECURE_CANT_CHANGE => 1, |
| NOT_INSTALLED => 2, # (where the lack makes the system secure, eg telnet) |
| INCONSISTENT => 3, |
| MANUAL => 4, |
| NOTEST => 5, |
| SECURE_CAN_CHANGE => 6, |
| STRING_NOT_DEFINED => 7, |
| NOT_INSTALLED_NOTSECURE => 8, #(Where the missing s/w makes the system less secure eg IPFilter) |
| #Intentional duplicates follow |
| DONT_KNOW => 5, |
| RELEVANT_HEADERQ => 6, |
| NOTRELEVANT_HEADERQ => 0 |
| }; |
| |
| &SanitizeEnv; |
| |
| # Set up some common error messages. These are independent of |
| # operating system |
| |
| # These will allow us to line up the warnings and error messages |
| my $err ="ERROR: "; |
| my $spc =" "; |
| my $GLOBAL_OS="None"; |
| my $GLOBAL_ACTUAL_OS="None"; |
| my %GLOBAL_SUMS=(); |
| my $CLI=''; |
| |
| #OS independent Error Messages Follow, normally "bastille" script filters |
| #options before interactive or Bastille runs, so this check is often redundant |
| $GLOBAL_ERROR{"usage"}="\n". |
| "$spc Usage: bastille [ -b | -c | -x ] [ --os <version> ] [ -f <alternate config> ]\n". |
| "$spc bastille [ -r | --assess | --assessnobowser ]\n\n". |
| "$spc --assess : check status of system and report in browser\n". |
| "$spc --assessnobrowser : check status of system and list report locations\n". |
| "$spc -b : use a saved config file to apply changes\n". |
| "$spc directly to system\n". |
| "$spc -c : use the Curses (non-X11) TUI\n". |
| "$spc -f <alternate config>: populate answers with a different config file\n". |
| "$spc -r : revert all Bastille changes to-date\n". |
| "$spc -x : use the Perl/Tk (X11) GUI\n" . |
| "$spc --os <version> : ask all questions for the given operating system\n" . |
| "$spc version. e.g. --os RH6.0\n"; |
| |
| # These options don't work universally, so it's best not to |
| # document them here (yet). Hopefully, we'll get them |
| # straightened out soon. |
| #"$spc --log : log-only option\n". |
| #"$spc -v : verbose mode\n". |
| #"$spc --debug : debug mode\n"; |
| |
| |
| ############################################################################## |
| # |
| # Directory structure for Bastille Linux v1.2 and up |
| # |
| ############################################################################## |
| # |
| # /usr/sbin/ -- location of Bastille binaries |
| # /usr/lib/Bastille -- location of Bastille modules |
| # /usr/share/Bastille -- location of Bastille data files |
| # /etc/Bastille -- location of Bastille config files |
| # |
| # /var/log/Bastille -- location of Bastille log files |
| # /var/log/Bastille/revert -- directory holding all Bastille-created revert scripts |
| # /var/log/Bastille/revert/backup -- directory holding the original files that |
| # Bastille modifies, with permissions intact |
| # |
| ############################################################################## |
| |
| ############################################################################## |
| # |
| # Directory structure for HP-UX Bastille v2.0 and up |
| # |
| ############################################################################## |
| # |
| # /opt/sec_mgmt/bastille/bin/ -- location of Bastille binaries |
| # /opt/sec_mgmt/bastille/lib/ -- location of Bastille modules |
| # /etc/opt/sec_mgmt/bastille/ -- location of Bastille data and config files |
| # |
| # /var/opt/sec_mgmt/bastille/log/ -- location of Bastille log files |
| # /var/opt/sec_mgmt/bastille/revert -- directory holding all Bastille-created |
| # revert scripts and save files |
| # |
| ############################################################################## |
| |
| |
| ############################################################################## |
| ############################################################################## |
| ################## Actual functions start here... ########################### |
| ############################################################################## |
| ############################################################################## |
| |
| ########################################################################### |
| # setOptions takes six arguments, $GLOBAL_DEBUG, $GLOBAL_LOGONLY, |
| # $GLOBAL_VERBOSE, $GLOBAL_AUDITONLY, $GLOBAL_AUDIT_NO_BROWSER, and GLOBAL_OS; |
| ########################################################################### |
| sub setOptions($$$$$$) { |
| ($GLOBAL_DEBUG,$GLOBAL_LOGONLY,$GLOBAL_VERBOSE,$GLOBAL_AUDITONLY, |
| $GLOBAL_AUDIT_NO_BROWSER,$GLOBAL_OS) = @_; |
| if ($GLOBAL_AUDIT_NO_BROWSER) { |
| $GLOBAL_AUDITONLY = 1; |
| } |
| if (not(defined($GLOBAL_OS))){ |
| $GLOBAL_OS="None"; |
| } |
| } |
| ########################################################################### |
| # |
| # SanitizeEnv load a proper environment so Bastille cannot be tricked |
| # and Perl modules work correctly. |
| # |
| ########################################################################### |
| sub SanitizeEnv { |
| delete @ENV{'IFS','CDPATH','ENV','BASH_ENV'}; |
| $ENV{CDPATH}="."; |
| $ENV{BASH_ENV}= ""; |
| # Bin is needed here or else /usr/lib/perl5/5.005/Cwd.pm |
| # will not find `pwd` |
| # Detected while testing with -w, jfs |
| $ENV{PATH} = "/bin:/usr/bin"; |
| # Giorgi, is /usr/local/bin needed? (jfs) |
| } |
| |
| ########################################################################### |
| # |
| # GetDistro checks to see if the target is a known distribution and reports |
| # said distribution. |
| # |
| # This is used throughout the script, but also by ConfigureForDistro. |
| # |
| # |
| ########################################################################### |
| |
| sub GetDistro() { |
| |
| my ($release,$distro); |
| |
| # Only read files for the distro once. |
| # if the --os option was used then |
| if ($GLOBAL_OS eq "None") { |
| if ( -e "/etc/mandrake-release" ) { |
| open(MANDRAKE_RELEASE,"/etc/mandrake-release"); |
| $release=<MANDRAKE_RELEASE>; |
| |
| if ( ($release =~ /^Mandrake Linux release (\d+\.\d+\w*)/) or ($release =~ /^Linux Mandrake release (\d+\.\d+\w*)/) ) { |
| $distro="MN$1"; |
| } |
| elsif ( $release =~ /^Mandrakelinux release (\d+\.\d+)\b/ ) { |
| $distro="MN$1"; |
| } |
| else { |
| print STDERR "$err Couldn't determine Mandrake/Mandriva version! Setting to 10.1!\n"; |
| $distro="MN10.1"; |
| } |
| |
| close(MANDRAKE_RELEASE); |
| } |
| elsif ( -e "/etc/immunix-release" ) { |
| open(IMMUNIX_RELEASE,"/etc/immunix-release"); |
| $release=<IMMUNIX_RELEASE>; |
| unless ($release =~ /^Immunix Linux release (\d+\.\d+\w*)/) { |
| print STDERR "$err Couldn't determine Immunix version! Setting to 6.2!\n"; |
| $distro="RH6.2"; |
| } |
| else { |
| $distro="RH$1"; |
| } |
| close(*IMMUNIX_RELEASE); |
| } |
| elsif ( -e '/etc/fedora-release' ) { |
| open(FEDORA_RELEASE,'/etc/fedora-release'); |
| $release=<FEDORA_RELEASE>; |
| close FEDORA_RELEASE; |
| if ($release =~ /^Fedora Core release (\d+\.?\d*)/) { |
| $distro = "RHFC$1"; |
| } |
| elsif ($release =~ /^Fedora release (\d+\.?\d*)/) { |
| $distro = "RHFC$1"; |
| } |
| else { |
| print STDERR "$err Could not determine Fedora version! Setting to Fedora Core 8\n"; |
| $distro='RHFC8'; |
| } |
| } |
| elsif ( -e "/etc/redhat-release" ) { |
| open(*REDHAT_RELEASE,"/etc/redhat-release"); |
| $release=<REDHAT_RELEASE>; |
| if ($release =~ /^Red Hat Linux release (\d+\.?\d*\w*)/) { |
| $distro="RH$1"; |
| } |
| elsif ($release =~ /^Red Hat Linux .+ release (\d+)\.?\d*([AEW]S)/) { |
| $distro="RHEL$1$2"; |
| } |
| elsif ($release =~ /^Red Hat Enterprise Linux ([AEW]S) release (\d+)/) { |
| $distro="RHEL$2$1"; |
| } |
| elsif ($release =~ /^CentOS release (\d+\.\d+)/) { |
| my $version = $1; |
| if ($version =~ /^4\./) { |
| $distro='RHEL4AS'; |
| } |
| elsif ($version =~ /^3\./) { |
| $distro='RHEL3AS'; |
| } |
| else { |
| print STDERR "$err Could not determine CentOS version! Setting to Red Hat Enterprise 4 AS.\n"; |
| $distro='RHEL4AS'; |
| } |
| } |
| else { |
| # JJB/HP - Should this be B_log? |
| print STDERR "$err Couldn't determine Red Hat version! Setting to 9!\n"; |
| $distro="RH9"; |
| } |
| close(REDHAT_RELEASE); |
| |
| } |
| elsif ( -e "/etc/debian_version" ) { |
| $stable="3.1"; #Change this when Debian stable changes |
| open(*DEBIAN_RELEASE,"/etc/debian_version"); |
| $release=<DEBIAN_RELEASE>; |
| unless ($release =~ /^(\d+\.\d+\w*)/) { |
| print STDERR "$err System is not running a stable Debian GNU/Linux version. Setting to $stable.\n"; |
| $distro="DB$stable"; |
| } |
| else { |
| $distro="DB$1"; |
| } |
| close(DEBIAN_RELEASE); |
| } |
| elsif ( -e "/etc/SuSE-release" ) { |
| open(*SUSE_RELEASE,"/etc/SuSE-release"); |
| $release=<SUSE_RELEASE>; |
| if ($release =~ /^SuSE Linux (\d+\.\d+\w*)/i) { |
| $distro="SE$1"; |
| } |
| elsif ($release =~ /^SUSE LINUX Enterprise Server (\d+\.?\d?\w*)/i) { |
| $distro="SESLES$1"; |
| } |
| elsif ($release =~ /^SUSE Linux Enterprise Server (\d+\.?\d?\w*)/i) { |
| $distro="SESLES$1"; |
| } |
| elsif ($release =~ /^openSuSE (\d+\.\d+\w*)/i) { |
| $distro="SE$1"; |
| } |
| else { |
| print STDERR "$err Couldn't determine SuSE version! Setting to 10.3!\n"; |
| $distro="SE10.3"; |
| } |
| close(SUSE_RELEASE); |
| } |
| elsif ( -e "/etc/turbolinux-release") { |
| open(*TURBOLINUX_RELEASE,"/etc/turbolinux-release"); |
| $release=<TURBOLINUX_RELEASE>; |
| unless ($release =~ /^Turbolinux Workstation (\d+\.\d+\w*)/) { |
| print STDERR "$err Couldn't determine TurboLinux version! Setting to 7.0!\n"; |
| $distro="TB7.0"; |
| } |
| else { |
| $distro="TB$1"; |
| } |
| close(TURBOLINUX_RELEASE); |
| } |
| else { |
| # We're either on Mac OS X, HP-UX or an unsupported O/S. |
| if ( -x '/usr/bin/uname') { |
| # uname is in /usr/bin on Mac OS X and HP-UX |
| $release=`/usr/bin/uname -sr`; |
| } |
| else { |
| print STDERR "$err Could not determine operating system version!\n"; |
| $distro="unknown"; |
| } |
| |
| # Figure out what kind of system we're on. |
| if ($release ne "") { |
| if ($release =~ /^Darwin\s+(\d+)\.(\d+)/) { |
| if ($1 == 6 ) { |
| $distro = "OSX10.2"; |
| } |
| elsif ($1 == 7) { |
| $distro = "OSX10.3"; |
| } |
| elsif ($1 == 8) { |
| $distro = "OSX10.3"; |
| } |
| else { |
| $distro = "unknown"; |
| } |
| } |
| elsif ( $release =~ /(^HP-UX)\s*B\.(\d+\.\d+)/ ) { |
| $distro="$1$2"; |
| } |
| else { |
| print STDERR "$err Could not determine operating system version!\n"; |
| $distro="unknown"; |
| } |
| } |
| } |
| |
| $GLOBAL_OS=$distro; |
| } elsif (not (defined $GLOBAL_OS)) { |
| print "ERROR: GLOBAL OS Scoping Issue\n"; |
| } else { |
| $distro = $GLOBAL_OS; |
| } |
| |
| return $distro; |
| } |
| |
| ################################################################################### |
| # &getActualDistro; # |
| # # |
| # This subroutine returns the actual os version in which is running on. This # |
| # os version is independent of the --os switch feed to bastille. # |
| # # |
| ################################################################################### |
| sub getActualDistro { |
| # set local variable to $GLOBAL_OS |
| |
| if ($GLOBAL_ACTUAL_OS eq "None") { |
| my $os = $GLOBAL_OS; |
| # undef GLOBAL_OS so that the GetDistro routine will return |
| # the actualDistro, it might otherwise return the distro set |
| # by the --os switch. |
| $GLOBAL_OS = "None"; |
| $GLOBAL_ACTUAL_OS = &GetDistro; |
| # reset the GLOBAL_OS variable |
| $GLOBAL_OS = $os; |
| } |
| return $GLOBAL_ACTUAL_OS; |
| } |
| # These are helper routines which used to be included inside GetDistro |
| sub is_OS_supported($) { |
| my $os=$_[0]; |
| my $supported=0; |
| my %supportedOSHash = &getSupportedOSHash; |
| |
| foreach my $oSType (keys %supportedOSHash) { |
| foreach my $supported_os ( @{$supportedOSHash{$oSType}} ) { |
| if ( $supported_os eq $os ) { |
| $supported=1; |
| } |
| } |
| } |
| |
| return $supported; |
| } |
| |
| ############################################################################### |
| # getSupportedOSHash |
| # |
| # This subrountine returns a hash of supported OSTypes, which point to a |
| # a list of supported distros. When porting to a new distro, add the |
| # distro id to the hash in its appropriate list. |
| ############################################################################### |
| sub getSupportedOSHash () { |
| |
| my %osHash = ("LINUX" => [ |
| "DB2.2", "DB3.0", |
| "RH6.0","RH6.1","RH6.2","RH7.0", |
| "RH7.1","RH7.2","RH7.3","RH8.0", |
| "RH9", |
| "RHEL5", |
| "RHEL4AS","RHEL4ES","RHEL4WS", |
| "RHEL3AS","RHEL3ES","RHEL3WS", |
| "RHEL2AS","RHEL2ES","RHEL2WS", |
| "RHFC1","RHFC2","RHFC3","RHFC4", |
| "RHFC5","RHFC6","RHFC7","RHFC8", |
| "MN6.0","MN6.1 ","MN7.0","MN7.1", |
| "MN7.2","MN8.0","MN8.1","MN8.2", |
| "MN10.1", |
| "SE7.2","SE7.3", "SE8.0","SE8.1","SE9.0","SE9.1", |
| "SE9.2","SE9.3","SE10.0","SE10.1","SE10.2","SE10.3", |
| "SESLES8","SESLES9","SESLES10", |
| "TB7.0" |
| ], |
| |
| "HP-UX" => [ |
| "HP-UX11.00","HP-UX11.11", |
| "HP-UX11.22", "HP-UX11.23", |
| "HP-UX11.31" |
| ], |
| |
| "OSX" => [ |
| 'OSX10.2','OSX10.3','OSX10.4' |
| ] |
| ); |
| |
| return %osHash; |
| |
| } |
| |
| |
| ############################################################################### |
| # setFileLocations(OSMapFile, currentDistro); |
| # |
| # Given a file map location this subroutine will create the GLOBAL_* |
| # hash entries specified within this file. |
| ############################################################################### |
| sub setFileLocations($$) { |
| |
| my ($fileInfoFile,$currentDistro) = @_; |
| |
| # define a mapping from the first argument to the proper hash |
| my %map = ("BIN" => \%GLOBAL_BIN, |
| "FILE" => \%GLOBAL_FILE, |
| "BFILE" => \%GLOBAL_BFILE, |
| "DIR" => \%GLOBAL_DIR, |
| "BDIR" => \%GLOBAL_BDIR |
| ); |
| my @fileInfo = (); |
| |
| # File containing file location information |
| if(open(FILEINFO, "<$fileInfoFile" )) { |
| |
| @fileInfo = <FILEINFO>; |
| |
| close(FILEINFO); |
| |
| } |
| else { |
| print STDERR "$err Unable to find file location information for '$distro'.\n" . |
| "$spc Contact the Bastille support list for details.\n"; |
| exit(1); |
| } |
| |
| # Each line of the file map follows the pattern below: |
| # bdir,init.d,'/etc/rc.d/init.d',RH7.2,RH7.3 |
| # if the distro information is not available, e.g. |
| # bdir,init.d,'/etc/rc.d/init.d' |
| # then the line applies to all distros under the OSType |
| foreach my $file (@fileInfo) { |
| # Perl comments are allowed within the file but only entire line comments |
| if($file !~ /^\s+\#|^\s+$/) { |
| chomp $file; |
| # type relates to the map above, type bin will map to GLOBAL_BIN |
| # id is the identifier used as the hash key by the GLOBAL hash |
| # fileLocation is the full path to the file |
| # distroList is an optional list of distros that this particular |
| # file location, if no distro list is presented the file location |
| # is considered to apply to all distros |
| my ($type,$id,$fileLocation,@distroList) = split /\s*,\s*/, $file; |
| $fileLocation =~ s/^\'(.*)\'$/$1/; |
| if($#distroList == -1) { |
| $map{uc($type)}->{$id}=$fileLocation; |
| } |
| else { |
| foreach my $distro (@distroList) { |
| # if the current distro matches the distro listed then |
| # this file location applies |
| if($currentDistro =~ /$distro/) { |
| $map{uc($type)}->{$id}=$fileLocation; |
| } |
| } |
| } |
| } |
| } |
| unless(defined($map{uc("BFILE")}->{"current_config"})) { |
| &setGlobal("BFILE","current_config",&getGlobal("BFILE","config")); |
| } |
| } |
| |
| ############################################################################### |
| # setServiceInfo($OSServiceMapFile, $currentDistro |
| # |
| # Given the location of an OS Service map file, which describes |
| # a service in terms of configurables, processes and a service type. |
| # The subroutine fills out the GLOBAL_SERVICE, $GLOBAL_RC_CONFIG, GLOBAL_SERVTYPE, and |
| # GLOBAL_PROCESS hashes for a given service ID. |
| ############################################################################### |
| sub setServiceInfo($$) { |
| my ($serviceInfoFile,$currentDistro) = @_; |
| my @serviceInfo = (); |
| |
| if(open(SERVICEINFO, "<$serviceInfoFile" )) { |
| |
| @serviceInfo = <SERVICEINFO>; |
| |
| close(SERVICEINFO); |
| |
| } |
| else { |
| print STDERR "$err Unable to find service, service type, and process information\n" . |
| "$spc for '$distro'.\n" . |
| "$spc Contact the Bastille support list for details.\n"; |
| exit(1); |
| } |
| |
| |
| # The following loop, parses the entire (YOUR OS).service file |
| # to provide service information for YOUR OS. |
| # The files format is as follows: |
| # serviceID,servType,('service' 'configuration' 'list'),('process' 'list')[,DISTROS]* |
| # if distros are not present then the service is assumed to be |
| # relevant the the current distro |
| |
| |
| # |
| # More specifically, this file's format for rc-based daemons is: |
| # |
| # script_name,rc,(rc-config-file rc-config-file ...),(rc-variable1 rc-variable2 ...),('program_name1 program_name2 ...') |
| # |
| # ...where script_name is a file in /etc/init.d/ and |
| # ...program_nameN is a program launced by the script. |
| # |
| # This file's format for inet-based daemons is: |
| # |
| # identifier, inet, line name/file name, program name |
| # |
| # label,inet,(port1 port2 ...),(daemon1 daemon2 ...) |
| # |
| # ...where label is arbitrary, portN is one of the ports |
| # ...this one listens on, and daemonN is a program launched |
| # ...in response to a connection on a port. |
| |
| foreach my $service (@serviceInfo) { |
| # This file accepts simple whole line comments perl style |
| if($service !~ /^\s+\#|^\s+$/) { |
| chomp $service; |
| my ($serviceID,$servType,$strConfigList,$strServiceList, |
| $strProcessList,@distroList) = split /\s*,\s*/, $service; |
| |
| sub MakeArrayFromString($){ |
| my $entryString = $_[0]; |
| my @destArray = (); |
| if ($entryString =~ /\'\S+\'/) { #Make sure we have something to extract before we try |
| @destArray = split /\'\s+\'/, $entryString; |
| $destArray[0] =~ s/^\(\'(.+)$/$1/; # Remove leading quotation mark |
| $destArray[$#destArray] =~ s/^(.*)\'\)$/$1/; #Remove trailing quotation mark |
| } |
| return @destArray; |
| } |
| |
| # produce a list of configuration files from the files |
| # format ('configuration' 'files') |
| my @configList = MakeArrayFromString($strConfigList); |
| |
| # produce a list of service configurables from the files |
| # format ('service' 'configurable') |
| my @serviceList = MakeArrayFromString($strServiceList); |
| |
| # produce a list of process names from the files format |
| # ('my' 'process' 'list') |
| my @processList = MakeArrayFromString($strProcessList); |
| |
| # if distros were not specified then accept the service information |
| if($#distroList == -1) { |
| @{$GLOBAL_SERVICE{$serviceID}} = @serviceList; |
| $GLOBAL_SERVTYPE{$serviceID} = $servType; |
| @{$GLOBAL_PROCESS{$serviceID}} = @processList; |
| @{$GLOBAL_RC_CONFIG{$serviceID}} = @configList; |
| } |
| else { |
| # only if the current distro matches one of the listed distros |
| # include the service information. |
| foreach my $distro (@distroList) { |
| if($currentDistro =~ /$distro/) { |
| @{$GLOBAL_SERVICE{$serviceID}} = @serviceList; |
| $GLOBAL_SERVTYPE{$serviceID} = $servType; |
| @{$GLOBAL_PROCESS{$serviceID}} = @processList; |
| @{$GLOBAL_RC_CONFIG{$serviceID}} = @configList; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| |
| ############################################################################### |
| # getFileAndServiceInfo($distro,$actualDistro) |
| # |
| # This subrountine, given distribution information, will import system file |
| # and service information into the GLOBA_* hashes. |
| # |
| # NOTE: $distro and $actualDistro will only differ when the --os switch is |
| # used to generate a configuration file for an arbitrary operating |
| # system. |
| # |
| ############################################################################### |
| sub getFileAndServiceInfo($$) { |
| |
| my ($distro,$actualDistro) = @_; |
| |
| # defines the path to the OS map information for any supported OS type. |
| # OS map information is used to determine file locations for a given |
| # distribution. |
| my %oSInfoPath = ( |
| "LINUX" => "/usr/share/Bastille/OSMap/", |
| "HP-UX" => "/etc/opt/sec_mgmt/bastille/OSMap/", |
| "OSX" => "/usr/share/Bastille/OSMap/" |
| ); |
| |
| # returns the OS, LINUX, HP-UX, or OSX, associated with this |
| # distribution |
| my $actualOS = &getOSType($actualDistro); |
| my $oS = &getOSType($distro); |
| |
| if(defined $actualOS && defined $oS) { |
| my $bastilleInfoFile = $oSInfoPath{$actualOS} . "${actualOS}.bastille"; |
| my $systemInfoFile = $oSInfoPath{$actualOS} . "${oS}.system"; |
| my $serviceInfoFile = $oSInfoPath{$actualOS} . "${oS}.service"; |
| |
| if(-f $bastilleInfoFile) { |
| &setFileLocations($bastilleInfoFile,$actualDistro); |
| } |
| else { |
| print STDERR "$err Unable to find bastille file information.\n" . |
| "$spc $bastilleInfoFile does not exist on the system"; |
| exit(1); |
| } |
| |
| if(-f $systemInfoFile) { |
| &setFileLocations($systemInfoFile,$distro); |
| } |
| else { |
| print STDERR "$err Unable to find system file information.\n" . |
| "$spc $systemInfoFile does not exist on the system"; |
| exit(1); |
| } |
| # Service info File is optional |
| if(-f $serviceInfoFile) { |
| &setServiceInfo($serviceInfoFile,$distro); |
| } |
| } |
| else { |
| print STDERR "$err Unable to determine operating system type\n" . |
| "$spc for $actualDistro or $distro\n"; |
| exit(1); |
| } |
| |
| } |
| |
| |
| # returns the Operating System type associated with the specified |
| # distribution. |
| sub getOSType($) { |
| |
| my $distro = $_[0]; |
| |
| my %supportedOSHash = &getSupportedOSHash; |
| foreach my $oSType (keys %supportedOSHash) { |
| foreach my $oSDistro (@{$supportedOSHash{$oSType}}) { |
| if($distro eq $oSDistro) { |
| return $oSType; |
| } |
| } |
| } |
| |
| return undef; |
| |
| } |
| |
| |
| # Test subroutine used to debug file location info for new Distributions as |
| # they are ported. |
| sub dumpFileInfo { |
| print "Dumping File Information\n"; |
| foreach my $hashref (\%GLOBAL_BIN,\%GLOBAL_DIR,\%GLOBAL_FILE,\%GLOBAL_BFILE,\%GLOBAL_BDIR) { |
| foreach my $id (keys %{$hashref}) { |
| print "$id: ${$hashref}{$id}\n"; |
| } |
| print "-----------------------\n\n"; |
| } |
| } |
| |
| # Test subroutine used to debug service info for new Distributions as |
| # they are ported. |
| sub dumpServiceInfo { |
| print "Dumping Service Information\n"; |
| foreach my $serviceId (keys %GLOBAL_SERVICE) { |
| print "$serviceId:\n"; |
| print "Type - $GLOBAL_SERVTYPE{$serviceId}\n"; |
| print "Service List:\n"; |
| foreach my $service (@{$GLOBAL_SERVICE{$serviceId}}) { |
| print "$service "; |
| } |
| print "\nProcess List:\n"; |
| foreach my $process (@{$GLOBAL_PROCESS{$serviceId}}) { |
| print "$process "; |
| } |
| print "\n----------------------\n"; |
| } |
| } |
| |
| |
| ########################################################################### |
| # |
| # &ConfigureForDistro configures the API for a given distribution. This |
| # includes setting global variables that tell the Bastille API about |
| # given binaries and directories. |
| # |
| # WARNING: If a distro is not covered here, Bastille may not be 100% |
| # compatible with it, though 1.1 is written to be much smarter |
| # about unknown distros... |
| # |
| ########################################################################### |
| sub ConfigureForDistro { |
| |
| my $retval=1; |
| |
| # checking to see if the os version given is in fact supported |
| my $distro = &GetDistro; |
| |
| # checking to see if the actual os version is in fact supported |
| my $actualDistro = &getActualDistro; |
| $ENV{'LOCALE'}=''; # So that test cases checking for english results work ok. |
| if ((! &is_OS_supported($distro)) or (! &is_OS_supported($actualDistro)) ) { |
| # if either is not supported then print out a list of supported versions |
| if (! &is_OS_supported($distro)) { |
| print STDERR "$err '$distro' is not a supported operating system.\n"; |
| } |
| else { |
| print STDERR "$err Bastille is unable to operate correctly on this\n"; |
| print STDERR "$spc $distro operating system.\n"; |
| } |
| my %supportedOSHash = &getSupportedOSHash; |
| print STDERR "$spc Valid operating system versions are as follows:\n"; |
| |
| foreach my $oSType (keys %supportedOSHash) { |
| |
| print STDERR "$spc $oSType:\n$spc "; |
| |
| my $os_number = 1; |
| foreach my $os (@{$supportedOSHash{$oSType}}) { |
| print STDERR "'$os' "; |
| if ($os_number == 5){ |
| print STDERR "\n$spc "; |
| $os_number = 1; |
| } |
| else { |
| $os_number++; |
| } |
| |
| } |
| print STDERR "\n"; |
| } |
| |
| print "\n" . $GLOBAL_ERROR{"usage"}; |
| exit(1); |
| } |
| |
| # First, let's make sure that we do not create any files or |
| # directories with more permissive permissions than we |
| # intend via setting the Perl umask |
| umask(077); |
| |
| &getFileAndServiceInfo($distro,$actualDistro); |
| |
| # &dumpFileInfo; # great for debuging file location issues |
| # &dumpServiceInfo; # great for debuging service information issues |
| |
| # OS dependent error messages (after configuring file locations) |
| my $nodisclaim_file = &getGlobal('BFILE', "nodisclaimer"); |
| |
| $GLOBAL_ERROR{"disclaimer"}="$err Unable to touch $nodisclaim_file:" . |
| "$spc You must use Bastille\'s -n flag (for example:\n" . |
| "$spc bastille -f -n) or \'touch $nodisclaim_file \'\n"; |
| |
| return $retval; |
| } |
| |
| |
| ########################################################################### |
| ########################################################################### |
| # # |
| # The B_<perl_function> file utilities are replacements for their Perl # |
| # counterparts. These replacements log their actions and their errors, # |
| # but are very similar to said counterparts. # |
| # # |
| ########################################################################### |
| ########################################################################### |
| |
| |
| ########################################################################### |
| # B_open is used for opening a file for reading. B_open_plus is the preferred |
| # function for writing, since it saves a backup copy of the file for |
| # later restoration. |
| # |
| # B_open opens the given file handle, associated with the given filename |
| # and logs appropriately. |
| # |
| ########################################################################### |
| |
| sub B_open { |
| my $retval=1; |
| my ($handle,$filename)=@_; |
| |
| unless ($GLOBAL_LOGONLY) { |
| $retval = open $handle,$filename; |
| } |
| |
| ($handle) = "$_[0]" =~ /[^:]+::[^:]+::([^:]+)/; |
| &B_log("ACTION","open $handle,\"$filename\";\n"); |
| unless ($retval) { |
| &B_log("ERROR","open $handle, $filename failed...\n"); |
| } |
| |
| return $retval; |
| } |
| |
| ########################################################################### |
| # B_open_plus is the v1.1 open command. |
| # |
| # &B_open_plus($handle_file,$handle_original,$file) opens the file $file |
| # for reading and opens the file ${file}.bastille for writing. It is the |
| # counterpart to B_close_plus, which will move the original file to |
| # $GLOBAL_BDIR{"backup"} and will place the new file ${file}.bastille in its |
| # place. |
| # |
| # &B_open_plus makes the appropriate log entries in the action and error |
| # logs. |
| ########################################################################### |
| |
| sub B_open_plus { |
| |
| my ($handle_file,$handle_original,$file)=@_; |
| my $retval=1; |
| my $return_file=1; |
| my $return_old=1; |
| |
| my $original_file = $file; |
| |
| # Open the original file and open a copy for writing. |
| unless ($GLOBAL_LOGONLY) { |
| # if the temporary filename already exists then the open operation will fail. |
| if ( $file eq "" ){ |
| &B_log("ERROR","Internal Error - Attempt Made to Open Blank Filename"); |
| $return_old=0; |
| $return_file=0; |
| return 0; #False |
| } elsif (-e "${file}.bastille") { |
| &B_log("ERROR","Unable to open $file as the swap file ". |
| "${file}.bastille\" already exists. Rename the swap ". |
| "file to allow Bastille to make desired file modifications."); |
| $return_old=0; |
| $return_file=0; |
| } |
| else { |
| $return_old = open $handle_original,"$file"; |
| $return_file = open $handle_file,("> $file.bastille"); |
| } |
| } |
| |
| # Error handling/logging here... |
| #&B_log("ACTION","# Modifying file $original_file via temporary file $original_file.bastille\n"); |
| unless ($return_file) { |
| $retval=0; |
| &B_log("ERROR","open file: \"$original_file.bastille\" failed...\n"); |
| } |
| unless ($return_old) { |
| $retval=0; |
| &B_log("ERROR","open file: \"$original_file\" failed.\n"); |
| } |
| |
| return $retval; |
| |
| } |
| |
| ########################################################################### |
| # B_close was the v1.0 close command. It is still used in places in the |
| # code. |
| # However the use of B _close_plus, which implements a new, smarter, |
| # backup scheme is preferred. |
| # |
| # B_close closes the given file handle, associated with the given filename |
| # and logs appropriately. |
| ########################################################################### |
| |
| |
| sub B_close { |
| my $retval=1; |
| |
| unless ($GLOBAL_LOGONLY) { |
| $retval = close $_[0]; |
| } |
| |
| &B_log("ACTION", "close $_[0];\n"); |
| unless ($retval) { |
| &B_log("ERROR", "close $_[0] failed...\n"); |
| } |
| |
| return $retval; |
| } |
| |
| |
| ########################################################################### |
| # B_close_plus is the v1.1 close command. |
| # |
| # &B_close_plus($handle_file,$handle_original,$file) closes the files |
| # $file and ${file}.bastille, backs up $file to $GLOBAL_BDIR{"backup"} and |
| # renames ${file}.bastille to $file. This backup is made using the |
| # internal API function &B_backup_file. Further, it sets the new file's |
| # permissions and uid/gid to the same as the old file. |
| # |
| # B_close_plus is the counterpart to B_open_plus, which opened $file and |
| # $file.bastille with the file handles $handle_original and $handle_file, |
| # respectively. |
| # |
| # &B_close_plus makes the appropriate log entries in the action and error |
| # logs. |
| ########################################################################### |
| |
| sub B_close_plus { |
| my ($handle_file,$handle_original,$file)=@_; |
| my ($mode,$uid,$gid); |
| my @junk; |
| |
| my $original_file; |
| |
| my $retval=1; |
| my $return_file=1; |
| my $return_old=1; |
| |
| # Append the global prefix, but save the original for B_backup_file b/c |
| # it appends the prefix on its own... |
| |
| $original_file=$file; |
| |
| # |
| # Close the files and prepare for the rename |
| # |
| |
| if (($file eq "") or (not(-e $file ))) { |
| &B_log("ERROR","Internal Error, attempted to close a blank filename ". |
| "or nonexistent file."); |
| return 0; #False |
| } |
| |
| unless ($GLOBAL_LOGONLY) { |
| $return_file = close $handle_file; |
| $return_old = close $handle_original; |
| } |
| |
| # Error handling/logging here... |
| #&B_log("ACTION","#Closing $original_file and backing up to " . &getGlobal('BDIR', "backup")); |
| #&B_log("ACTION","/$original_file\n"); |
| |
| unless ($return_file) { |
| $retval=0; |
| &B_log("ERROR","close $original_file failed...\n"); |
| } |
| unless ($return_old) { |
| $retval=0; |
| &B_log("ERROR","close $original_file.bastille failed.\n"); |
| } |
| |
| # |
| # If we've had no errors, backup the old file and put the new one |
| # in its place, with the Right permissions. |
| # |
| |
| unless ( ($retval == 0) or $GLOBAL_LOGONLY) { |
| |
| # Read the permissions/owners on the old file |
| |
| @junk=stat ($file); |
| $mode=$junk[2]; |
| $uid=$junk[4]; |
| $gid=$junk[5]; |
| |
| # Set the permissions/owners on the new file |
| |
| chmod $mode, "$file.bastille" or &B_log("ERROR","Not able to retain permissions on $original_file!!!\n"); |
| chown $uid, $gid, "$file.bastille" or &B_log("ERROR","Not able to retain owners on $original_file!!!\n"); |
| |
| # Backup the old file and put a new one in place. |
| |
| &B_backup_file($original_file); |
| rename "$file.bastille", $file or |
| &B_log("ERROR","B_close_plus: not able to move $original_file.bastille to $original_file\n"); |
| |
| # We add the file to the GLOBAL_SUMS hash if it is not already present |
| &B_set_sum($file); |
| |
| } |
| |
| return $retval; |
| } |
| |
| ########################################################################### |
| # &B_backup_file ($file) makes a backup copy of the file $file in |
| # &getGlobal('BDIR', "backup"). Note that this routine is intended for internal |
| # use only -- only Bastille API functions should call B_backup_file. |
| # |
| ########################################################################### |
| |
| sub B_backup_file { |
| |
| my $file=$_[0]; |
| my $complain = 1; |
| my $original_file = $file; |
| |
| my $backup_dir = &getGlobal('BDIR', "backup"); |
| my $backup_file = $backup_dir . $original_file; |
| |
| my $retval=1; |
| |
| # First, separate the file into the directory and the relative filename |
| |
| my $directory =""; |
| if ($file =~ /^(.*)\/([^\/]+)$/) { |
| #$relative_file=$2; |
| $directory = $1; |
| } else { |
| $directory=cwd; |
| } |
| |
| # Now, if the directory does not exist, create it. |
| # Later: |
| # Try to set the same permissions on the patch directory that the |
| # original had...? |
| |
| unless ( -d ($backup_dir . $directory) ) { |
| mkpath(( $backup_dir . $directory),0,0700); |
| |
| } |
| |
| # Now we backup the file. If there is already a backup file there, |
| # we will leave it alone, since it exists from a previous run and |
| # should be the _original_ (possibly user-modified) distro's version |
| # of the file. |
| |
| if ( -e $file ) { |
| |
| unless ( -e $backup_file ) { |
| my $command=&getGlobal("BIN","cp"); |
| &B_Backtick("$command -p $file $backup_file"); |
| &B_revert_log (&getGlobal("BIN","mv"). " $backup_file $file"); |
| } |
| |
| } else { |
| # The file we were trying to backup doesn't exist. |
| |
| $retval=0; |
| # This is a non-fatal error, not worth complaining about |
| $complain = 0; |
| #&ErrorLog ("# Failed trying to backup file $file -- it doesn't exist!\n"); |
| } |
| |
| # Check to make sure that the file does exist in the backup location. |
| |
| unless ( -e $backup_file ) { |
| $retval=0; |
| if ( $complain == 1 ) { |
| &B_log("ERROR","Failed trying to backup $file -- the copy was not created.\n"); |
| } |
| } |
| |
| return $retval; |
| } |
| |
| |
| ########################################################################### |
| # &B_read_sums reads in the sum.csv file which contains information |
| # about Bastille modified files. The file structure is as follows: |
| # |
| # filename,filesize,cksum |
| # |
| # It reads the information into the GLOBAL_SUM hash i.e. |
| # $GLOBAL_SUM{$file}{sum} = $cksum |
| # $GLOBAL_SUM{$file}{filesize} = $size |
| # For the first run of Bastille on a given system this subroutine |
| # is a no-op, and returns "undefined." |
| ########################################################################### |
| |
| sub B_read_sums { |
| |
| my $sumFile = &getGlobal('BFILE',"sum.csv"); |
| |
| if ( -e $sumFile ) { |
| |
| open( SUM, "< $sumFile") or &B_log("ERROR","Unable to open $sumFile for read.\n$!\n"); |
| |
| while( my $line = <SUM> ) { |
| chomp $line; |
| my ($file,$filesize,$sum,$flag) = split /,/, $line; |
| if(-e $file) { |
| $GLOBAL_SUM{"$file"}{filesize} = $filesize; |
| $GLOBAL_SUM{"$file"}{sum} = $sum; |
| } |
| } |
| |
| close(SUM); |
| } else { |
| return undef; |
| } |
| } |
| |
| |
| ########################################################################### |
| # &B_write_sums writes out the sum.csv file which contains information |
| # about Bastille modified files. The file structure is as follows: |
| # |
| # filename,filesize,cksum |
| # |
| # It writes the information from the GLOBAL_SUM hash i.e. |
| # |
| # $file,$GLOBAL_SUM{$file}{sum},$GLOBAL_SUM{$file}{filesize} |
| # |
| # This subroutine requires access to the GLOBAL_SUM hash. |
| ########################################################################### |
| |
| sub B_write_sums { |
| |
| my $sumFile = &getGlobal('BFILE',"sum.csv"); |
| |
| if ( %GLOBAL_SUM ) { |
| |
| open( SUM, "> $sumFile") or &B_log("ERROR","Unable to open $sumFile for write.\n$!\n"); |
| |
| for my $file (sort keys %GLOBAL_SUM) { |
| if( -e $file) { |
| print SUM "$file,$GLOBAL_SUM{\"$file\"}{filesize},$GLOBAL_SUM{\"$file\"}{sum}\n"; |
| } |
| } |
| |
| close(SUM); |
| } |
| |
| } |
| |
| |
| ########################################################################### |
| # &B_check_sum($file) compares the stored cksum and filesize of the given |
| # file compared to the current cksum and filesize respectively. |
| # This subroutine also keeps the state of the sum check by setting the |
| # checked flag which tells the subroutine that on this run this file |
| # has already been checked. |
| # |
| # $GLOBAL_SUM{$file}{checked} = 1; |
| # |
| # This subroutine requires access to the GLOBAL_SUM hash. |
| # |
| # Returns 1 if sum checks out and 0 if not |
| ########################################################################### |
| |
| sub B_check_sum($) { |
| my $file = $_[0]; |
| my $cksum = &getGlobal('BIN',"cksum"); |
| |
| if (not(%GLOBAL_SUM)) { |
| &B_read_sums; |
| } |
| |
| if(-e $file) { |
| my ($sum,$size,$ckfile) = split(/\s+/, `$cksum $file`); |
| my $commandRetVal = ($? >> 8); # find the command's return value |
| |
| if($commandRetVal != 0) { |
| &B_log("ERROR","$cksum reported the following error:\n$!\n"); |
| return 0; |
| } else { |
| if ( exists $GLOBAL_SUM{$file} ) { |
| # if the file size or file sum differ from those recorded. |
| if (( $GLOBAL_SUM{$file}{filesize} == $size) and |
| ($GLOBAL_SUM{$file}{sum} == $sum )) { |
| return 1; #True, since saved state matches up, all is well. |
| } else { |
| return 0; #False, since saved state doesn't match |
| } |
| } else { |
| &B_log("ERROR","File: $file does not exist in sums database."); |
| return 0; |
| } |
| } |
| } else { |
| &B_log("ERROR","The file: $file does not exist for comparison in B_check_sum."); |
| return 0; |
| } |
| } |
| |
| # Don't think we need this anymore as function now check_sums returns |
| # results directly |
| #sub isSumDifferent($) { |
| # my $file = $_[0]; |
| # if(exists $GLOBAL_SUM{$file}) { |
| # return $GLOBAL_SUM{$file}{flag} |
| # } |
| #} |
| |
| sub listModifiedFiles { |
| my @listModifiedFiles=sort keys %GLOBAL_SUM; |
| return @listModifiedFiles; |
| } |
| |
| ########################################################################### |
| # &B_isFileinSumDB($file) checks to see if a given file's sum was saved. |
| # |
| # $GLOBAL_SUM{$file}{filesize} = $size; |
| # $GLOBAL_SUM{$file}{sum} = $cksum; |
| # |
| # This subroutine requires access to the GLOBAL_SUM hash. |
| ########################################################################### |
| |
| sub B_isFileinSumDB($) { |
| my $file = $_[0]; |
| |
| if (not(%GLOBAL_SUM)) { |
| &B_log("DEBUG","Reading in DB from B_isFileinSumDB"); |
| &B_read_sums; |
| } |
| if (exists($GLOBAL_SUM{"$file"})){ |
| &B_log("DEBUG","$file is in sum database"); |
| return 1; #true |
| } else { |
| &B_log("DEBUG","$file is not in sum database"); |
| return 0; #false |
| } |
| } |
| |
| ########################################################################### |
| # &B_set_sum($file) sets the current cksum and filesize of the given |
| # file into the GLOBAL_SUM hash. |
| # |
| # $GLOBAL_SUM{$file}{filesize} = $size; |
| # $GLOBAL_SUM{$file}{sum} = $cksum; |
| # |
| # This subroutine requires access to the GLOBAL_SUM hash. |
| ########################################################################### |
| |
| sub B_set_sum($) { |
| |
| my $file = $_[0]; |
| my $cksum = &getGlobal('BIN',"cksum"); |
| if( -e $file) { |
| |
| my ($sum,$size,$ckfile) = split(/\s+/, `$cksum $file`); |
| my $commandRetVal = ($? >> 8); # find the command's return value |
| |
| if($commandRetVal != 0) { |
| |
| &B_log("ERROR","$cksum reported the following error:\n$!\n"); |
| |
| } |
| else { |
| |
| # new file size and sum are added to the hash |
| $GLOBAL_SUM{$file}{filesize} = $size; |
| $GLOBAL_SUM{$file}{sum} = $sum; |
| &B_write_sums; |
| |
| } |
| } else { |
| &B_log("ERROR","Can not save chksum for file: $file since it does not exist"); |
| } |
| } |
| |
| |
| ########################################################################### |
| # |
| # &B_delete_file ($file) deletes the file $file and makes a backup to |
| # the backup directory. |
| # |
| ########################################################################## |
| |
| |
| sub B_delete_file($) { #Currently Linux only (TMPDIR) |
| #consideration: should create clear_sum routine if this is ever used to remove |
| # A Bastille-generated file. |
| |
| # |
| # This API routine deletes the named file, backing it up first to the |
| # backup directory. |
| # |
| |
| my $filename=shift @_; |
| my $retval=1; |
| |
| # We have to append the prefix ourselves since we don't use B_open_plus |
| |
| my $original_filename=$filename; |
| |
| &B_log("ACTION","Deleting (and backing-up) file $original_filename\n"); |
| &B_log("ACTION","rm $original_filename\n"); |
| |
| unless ($filename) { |
| &B_log("ERROR","B_delete_file called with no arguments!\n"); |
| } |
| |
| unless ($GLOBAL_LOGONLY) { |
| if ( B_backup_file($original_filename) ) { |
| unless ( unlink $filename ) { |
| &B_log("ERROR","Couldn't unlink file $original_filename"); |
| $retval=0; |
| } |
| } |
| else { |
| $retval=0; |
| &B_log("ERROR","B_delete_file did not delete $original_filename since it could not back it up\n"); |
| } |
| } |
| |
| $retval; |
| |
| } |
| |
| |
| ########################################################################### |
| # &B_create_file ($file) creates the file $file, if it doesn't already |
| # exist. |
| # It will set a default mode of 0700 and a default uid/gid or 0/0. |
| # |
| # &B_create_file, to support Bastille's revert functionality, writes an |
| # rm $file command to the end of the file &getGlobal('BFILE', "created-files"). |
| # |
| ########################################################################## |
| |
| |
| sub B_create_file($) { |
| |
| my $file = $_[0]; |
| my $retval=1; |
| |
| # We have to create the file ourselves since we don't use B_open_plus |
| |
| my $original_file = $file; |
| |
| if ($file eq ""){ |
| &B_log("ERROR","Internal Error, attempt made to create blank filename"); |
| return 0; #False |
| } |
| |
| unless ( -e $file ) { |
| |
| unless ($GLOBAL_LOGONLY) { |
| |
| # find the directory in which the file is to reside. |
| my $dirName = dirname($file); |
| # if the directory does not exist then |
| if(! -d $dirName) { |
| # create it. |
| mkpath ($dirName,0,0700); |
| } |
| |
| $retval=open CREATE_FILE,">$file"; |
| |
| if ($retval) { |
| close CREATE_FILE; |
| chmod 0700,$file; |
| # Make the revert functionality |
| &B_revert_log( &getGlobal('BIN','rm') . " $original_file \n"); |
| } else { |
| &B_log("ERROR","Couldn't create file $original_file even though " . |
| "it didn't already exist!\n"); |
| } |
| } |
| &B_log("ACTION","Created file $original_file\n"); |
| } else { |
| &B_log("DEBUG","Didn't create file $original_file since it already existed.\n"); |
| $retval=0; |
| } |
| |
| $retval; |
| } |
| |
| |
| ########################################################################### |
| # &B_create_dir ($dir) creates the directory $dir, if it doesn't already |
| # exist. |
| # It will set a default mode of 0700 and a default uid/gid or 0/0. |
| # |
| ########################################################################## |
| |
| |
| sub B_create_dir($) { |
| |
| my $dir = $_[0]; |
| my $retval=1; |
| |
| # We have to append the prefix ourselves since we don't use B_open_plus |
| |
| my $original_dir=$dir; |
| |
| unless ( -d $dir ) { |
| unless ($GLOBAL_LOGONLY) { |
| $retval=mkdir $dir,0700; |
| |
| if ($retval) { |
| # Make the revert functionality |
| &B_revert_log (&getGlobal('BIN','rmdir') . " $original_dir\n"); |
| } |
| else { |
| &B_log("ERROR","Couldn't create dir $original_dir even though it didn't already exist!"); |
| } |
| |
| } |
| &B_log("ACTION","Created directory $original_dir\n"); |
| } |
| else { |
| &B_log("ACTION","Didn't create directory $original_dir since it already existed.\n"); |
| $retval=0; |
| } |
| |
| $retval; |
| } |
| |
| |
| |
| ########################################################################### |
| # &B_symlink ($original_file,$new_symlink) creates a symbolic link from |
| # $original_file to $new_symlink. |
| # |
| # &B_symlink respects $GLOBAL_LOGONLY. It supports |
| # the revert functionality that you've come to know and love by adding every |
| # symbolic link it creates to &getGlobal('BFILE', "created-symlinks"), currently set to: |
| # |
| # /root/Bastille/revert/revert-created-symlinks |
| # |
| # The revert script, if it works like I think it should, will run this file, |
| # which should be a script or rm's... |
| # |
| ########################################################################## |
| |
| sub B_symlink($$) { |
| my ($source_file,$new_symlink)=@_; |
| my $retval=1; |
| my $original_source = $source_file; |
| my $original_symlink = $new_symlink; |
| |
| unless ($GLOBAL_LOGONLY) { |
| $retval=symlink $source_file,$new_symlink; |
| if ($retval) { |
| &B_revert_log (&getGlobal('BIN',"rm") . " $original_symlink\n"); |
| } |
| } |
| |
| &B_log("ACTION", "Created a symbolic link called $original_symlink from $original_source\n"); |
| &B_log("ACTION", "symlink \"$original_source\",\"$original_symlink\";\n"); |
| unless ($retval) { |
| &B_log("ERROR","Couldn't symlink $original_symlink -> $original_source\n"); |
| } |
| |
| $retval; |
| |
| } |
| |
| |
| sub B_cp($$) { |
| |
| my ($source,$target)=@_; |
| my $retval=0; |
| |
| my $had_to_backup_target=0; |
| |
| use File::Copy; |
| |
| my $original_source=$source; |
| my $original_target=$target; |
| |
| if( -e $target and -f $target ) { |
| &B_backup_file($original_target); |
| &B_log("ACTION","About to copy $original_source to $original_target -- had to backup target\n"); |
| $had_to_backup_target=1; |
| } |
| |
| $retval=copy($source,$target); |
| if ($retval) { |
| &B_log("ACTION","cp $original_source $original_target\n"); |
| |
| # |
| # We want to add a line to the &getGlobal('BFILE', "created-files") so that the |
| # file we just put at $original_target gets deleted. |
| # |
| &B_revert_log(&getGlobal('BIN',"rm") . " $original_target\n"); |
| } else { |
| &B_log("ERROR","Failed to copy $original_source to $original_target\n"); |
| } |
| # We add the file to the GLOBAL_SUMS hash if it is not already present |
| &B_set_sum($target); |
| $retval; |
| } |
| |
| |
| |
| ############################################################################ |
| # &B_place puts a file in place, using Perl's File::cp. This file is taken |
| # from &getGlobal('BDIR', "share") and is used to place a file that came with |
| # Bastille. |
| # |
| # This should be DEPRECATED in favor of &B_cp, since the only reason it exists |
| # is because of GLOBAL_PREFIX, which has been broken for quite some time. |
| # Otherwise, the two routines are identical. |
| # |
| # It respects $GLOBAL_LOGONLY. |
| # If $target is an already-existing file, it is backed up. |
| # |
| # revert either appends another "rm $target" to &getGlobal('BFILE', "revert-actions") or |
| # backs up the file that _was_ there into the &getGlobal('BDIR', "backup"), |
| # appending a "mv" to revert-actions to put it back. |
| # |
| ############################################################################ |
| |
| sub B_place { # Only Linux references left (Firewall / TMPDIR) |
| |
| my ($source,$target)=@_; |
| my $retval=0; |
| |
| my $had_to_backup_target=0; |
| |
| use File::Copy; |
| |
| my $original_source=$source; |
| $source = &getGlobal('BDIR', "share") . $source; |
| my $original_target=$target; |
| |
| if ( -e $target and -f $target ) { |
| &B_backup_file($original_target); |
| &B_log("ACTION","About to copy $original_source to $original_target -- had to backup target\n"); |
| $had_to_backup_target=1; |
| } |
| $retval=copy($source,$target); |
| if ($retval) { |
| &B_log("ACTION","placed file $original_source as $original_target\n"); |
| # |
| # We want to add a line to the &getGlobal('BFILE', "created-files") so that the |
| # file we just put at $original_target gets deleted. |
| &B_revert_log(&getGlobal('BIN',"rm") . " $original_target\n"); |
| } else { |
| &B_log("ERROR","Failed to place $original_source as $original_target\n"); |
| } |
| |
| # We add the file to the GLOBAL_SUMS hash if it is not already present |
| &B_set_sum($target); |
| |
| $retval; |
| } |
| |
| |
| |
| |
| |
| ############################################################################# |
| ############################################################################# |
| ############################################################################# |
| |
| ########################################################################### |
| # &B_mknod ($file) creates the node $file, if it doesn't already |
| # exist. It uses the prefix and suffix, like this: |
| # |
| # mknod $prefix $file $suffix |
| # |
| # This is just a wrapper to the mknod program, which tries to introduce |
| # revert functionality, by writing rm $file to the end of the |
| # file &getGlobal('BFILE', "created-files"). |
| # |
| ########################################################################## |
| |
| |
| sub B_mknod($$$) { |
| |
| my ($prefix,$file,$suffix) = @_; |
| my $retval=1; |
| |
| # We have to create the filename ourselves since we don't use B_open_plus |
| |
| my $original_file = $file; |
| |
| unless ( -e $file ) { |
| my $command = &getGlobal("BIN","mknod") . " $prefix $file $suffix"; |
| |
| if ( system($command) == 0) { |
| # Since system will return 0 on success, invert the error code |
| $retval=1; |
| } |
| else { |
| $retval=0; |
| } |
| |
| if ($retval) { |
| |
| # Make the revert functionality |
| &B_revert_log(&getGlobal('BIN',"rm") . " $original_file\n"); |
| } else { |
| &B_log("ERROR","Couldn't mknod $prefix $original_file $suffix even though it didn't already exist!\n"); |
| } |
| |
| |
| &B_log("ACTION","mknod $prefix $original_file $suffix\n"); |
| } |
| else { |
| &B_log("ACTION","Didn't mknod $prefix $original_file $suffix since $original_file already existed.\n"); |
| $retval=0; |
| } |
| |
| $retval; |
| } |
| |
| ########################################################################### |
| # &B_revert_log("reverse_command") prepends a command to a shell script. This |
| # shell script is intended to be run by bastille -r to reverse the changes that |
| # Bastille made, returning the files which Bastille changed to their original |
| # state. |
| ########################################################################### |
| |
| sub B_revert_log($) { |
| |
| my $revert_command = $_[0]; |
| my $revert_actions = &getGlobal('BFILE', "revert-actions"); |
| my $revertdir= &getGlobal('BDIR', "revert"); |
| my @lines; |
| |
| |
| if (! (-e $revert_actions)) { |
| mkpath($revertdir); #if this doesn't work next line catches |
| if (open REVERT_ACTIONS,">" . $revert_actions){ # create revert file |
| close REVERT_ACTIONS; # chown to root, rwx------ |
| chmod 0700,$revert_actions; |
| chown 0,0,$revert_actions; |
| } |
| else { |
| &B_log("FATAL","Can not create revert-actions file: $revert_actions.\n" . |
| " Unable to add the following command to the revert\n" . |
| " actions script: $revert_command\n"); |
| } |
| |
| } |
| |
| &B_open_plus (*REVERT_NEW, *REVERT_OLD, $revert_actions); |
| |
| while (my $line=<REVERT_OLD>) { #copy file into @lines |
| push (@lines,$line); |
| } |
| print REVERT_NEW $revert_command . "\n"; #make the revert command first in the new file |
| while (my $line = shift @lines) { #write the rest of the lines of the file |
| print REVERT_NEW $line; |
| } |
| close REVERT_OLD; |
| close REVERT_NEW; |
| if (rename "${revert_actions}.bastille", $revert_actions) { #replace the old file with the new file we |
| chmod 0700,$revert_actions; # just made / mirrors B_close_plus logic |
| chown 0,0,$revert_actions; |
| } else { |
| &B_log("ERROR","B_revert_log: not able to move ${revert_actions}.bastille to ${revert_actions}!!! $!) !!!\n"); |
| } |
| } |
| |
| |
| ########################################################################### |
| # &getGlobalConfig($$) |
| # |
| # returns the requested GLOBAL_CONFIG hash value, ignoring the error |
| # if the value does not exist (because every module uses this to find |
| # out if the question was answered "Y") |
| ########################################################################### |
| sub getGlobalConfig ($$) { |
| my $module = $_[0]; |
| my $key = $_[1]; |
| if (exists $GLOBAL_CONFIG{$module}{$key}) { |
| my $answer=$GLOBAL_CONFIG{$module}{$key}; |
| &B_log("ACTION","Answer to question $module.$key is \"$answer\".\n"); |
| return $answer; |
| } else { |
| &B_log("ACTION","Answer to question $module.$key is undefined."); |
| return undef; |
| } |
| } |
| |
| ########################################################################### |
| # &getGlobal($$) |
| # |
| # returns the requested GLOBAL_* hash value, and logs an error |
| # if the variable does not exist. |
| ########################################################################### |
| sub getGlobal ($$) { |
| my $type = uc($_[0]); |
| my $key = $_[1]; |
| |
| # define a mapping from the first argument to the proper hash |
| my %map = ("BIN" => \%GLOBAL_BIN, |
| "FILE" => \%GLOBAL_FILE, |
| "BFILE" => \%GLOBAL_BFILE, |
| "DIR" => \%GLOBAL_DIR, |
| "BDIR" => \%GLOBAL_BDIR, |
| "ERROR" => \%GLOBAL_ERROR, |
| "SERVICE" => \%GLOBAL_SERVICE, |
| "SERVTYPE" => \%GLOBAL_SERVTYPE, |
| "PROCESS" => \%GLOBAL_PROCESS, |
| "RCCONFIG" => \%GLOBAL_RC_CONFIG |
| ); |
| |
| # check to see if the desired key is in the desired hash |
| if (exists $map{$type}->{$key}) { |
| # get the value from the right hash with the key |
| return $map{$type}->{$key}; |
| } else { |
| # i.e. Bastille tried to use $GLOBAL_BIN{'cp'} but it does not exist. |
| # Note that we can't use B_log, since it uses getGlobal ... recursive before |
| # configureForDistro is run. |
| print STDERR "ERROR: Bastille tried to use \$GLOBAL_${type}\{\'$key\'} but it does not exist.\n"; |
| return undef; |
| } |
| } |
| |
| ########################################################################### |
| # &getGlobal($$) |
| # |
| # sets the requested GLOBAL_* hash value |
| ########################################################################### |
| sub setGlobal ($$$) { |
| my $type = uc($_[0]); |
| my $key = $_[1]; |
| my $input_value = $_[2]; |
| |
| # define a mapping from the first argument to the proper hash |
| my %map = ("BIN" => \%GLOBAL_BIN, |
| "FILE" => \%GLOBAL_FILE, |
| "BFILE" => \%GLOBAL_BFILE, |
| "DIR" => \%GLOBAL_DIR, |
| "BDIR" => \%GLOBAL_BDIR, |
| "ERROR" => \%GLOBAL_ERROR, |
| "SERVICE" => \%GLOBAL_SERVICE, |
| "SERVTYPE" => \%GLOBAL_SERVTYPE, |
| "PROCESS" => \%GLOBAL_PROCESS, |
| ); |
| |
| if ($map{$type}->{$key} = $input_value) { |
| return 1; |
| } else { |
| &B_log('ERROR','Internal Error, Unable to set global config value:' . $type . ", " .$key); |
| return 0; |
| } |
| } |
| |
| |
| ########################################################################### |
| # &showDisclaimer: |
| # Print the disclaimer and wait for 2 minutes for acceptance |
| # Do NOT do so if any of the following conditions hold |
| # 1. the -n option was used |
| # 2. the file ~/.bastille_disclaimer exists |
| ########################################################################### |
| |
| sub showDisclaimer($) { |
| |
| my $nodisclaim = $_[0]; |
| my $nodisclaim_file = &getGlobal('BFILE', "nodisclaimer"); |
| my $response; |
| my $WAIT_TIME = 300; # we'll wait for 5 minutes |
| my $developersAnd; |
| my $developersOr; |
| if ($GLOBAL_OS =~ "^HP-UX") { |
| $developersAnd ="HP AND ITS"; |
| $developersOr ="HP OR ITS"; |
| }else{ |
| $developersAnd ="JAY BEALE, THE BASTILLE DEVELOPERS, AND THEIR"; |
| $developersOr ="JAY BEALE, THE BASTILLE DEVELOPERS, OR THEIR"; |
| } |
| my $DISCLAIMER = |
| "\n" . |
| "Copyright (C) 1999-2006 Jay Beale\n" . |
| "Copyright (C) 1999-2001 Peter Watkins\n" . |
| "Copyright (C) 2000 Paul L. Allen\n" . |
| "Copyright (C) 2001-2007 Hewlett-Packard Development Company, L.P.\n" . |
| "Bastille is free software; you are welcome to redistribute it under\n" . |
| "certain conditions. See the \'COPYING\' file in your distribution for terms.\n\n" . |
| "DISCLAIMER. Use of Bastille can help optimize system security, but does not\n" . |
| "guarantee system security. Information about security obtained through use of\n" . |
| "Bastille is provided on an AS-IS basis only and is subject to change without\n" . |
| "notice. Customer acknowledges they are responsible for their system\'s security.\n" . |
| "TO THE EXTENT ALLOWED BY LOCAL LAW, Bastille (\"SOFTWARE\") IS PROVIDED TO YOU \n" . |
| "\"AS IS\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN,\n" . |
| "EXPRESS OR IMPLIED. $developersAnd SUPPLIERS\n" . |
| "DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE \n" . |
| "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.\n" . |
| "Some countries, states and provinces do not allow exclusions of implied\n" . |
| "warranties or conditions, so the above exclusion may not apply to you. You may\n" . |
| "have other rights that vary from country to country, state to state, or province\n" . |
| "to province. EXCEPT TO THE EXTENT PROHIBITED BY LOCAL LAW, IN NO EVENT WILL\n" . |
| "$developersOr SUBSIDIARIES, AFFILIATES OR\n" . |
| "SUPPLIERS BE LIABLE FOR DIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR OTHER\n" . |
| "DAMAGES (INCLUDING LOST PROFIT, LOST DATA, OR DOWNTIME COSTS), ARISING OUT OF\n" . |
| "THE USE, INABILITY TO USE, OR THE RESULTS OF USE OF THE SOFTWARE, WHETHER BASED\n" . |
| "IN WARRANTY, CONTRACT, TORT OR OTHER LEGAL THEORY, AND WHETHER OR NOT ADVISED\n" . |
| "OF THE POSSIBILITY OF SUCH DAMAGES. Your use of the Software is entirely at your\n" . |
| "own risk. Should the Software prove defective, you assume the entire cost of all\n" . |
| "service, repair or correction. Some countries, states and provinces do not allow\n" . |
| "the exclusion or limitation of liability for incidental or consequential \n" . |
| "damages, so the above limitation may not apply to you. This notice will only \n". |
| "display on the first run on a given system.\n". |
| "To suppress the disclaimer on other machines, use Bastille\'s -n flag (example: bastille -n).\n"; |
| |
| |
| # If the user has specified not to show the disclaimer, or |
| # the .bastille_disclaimer file already exists, then return |
| if( ( $nodisclaim ) || -e $nodisclaim_file ) { return 1; } |
| |
| # otherwise, show the disclaimer |
| print ($DISCLAIMER); |
| |
| # there is a response |
| my $touch = &getGlobal('BIN', "touch"); |
| my $retVal = system("$touch $nodisclaim_file"); |
| if( $retVal != 0 ) { |
| &ErrorLog ( &getGlobal('ERROR','disclaimer')); |
| } |
| } # showDisclaimer |
| |
| |
| |
| |
| ################################################################ |
| # &systemCall |
| #Function used by exported methods B_Backtick and B_system |
| #to handle the mechanics of system calls. |
| # This function also manages error handling. |
| # Input: a system call |
| # Output: a list containing the status, sstdout and stderr |
| # of the the system call |
| # |
| ################################################################ |
| sub systemCall ($){ |
| no strict; |
| local $command=$_[0]; # changed scoping so eval below can read it |
| |
| local $SIG{'ALRM'} = sub { die "timeout" }; # This subroutine exits the "eval" below. The program |
| # can then move on to the next operation. Used "local" |
| # to avoid name space collision with disclaim alarm. |
| local $WAIT_TIME=120; # Wait X seconds for system commands |
| local $commandOutput = ''; |
| my $errOutput = ''; |
| eval{ |
| $errorFile = &getGlobal('BFILE','stderrfile'); |
| unlink($errorFile); #To make sure we don't mix output |
| alarm($WAIT_TIME); # start a time-out for command to complete. Some commands hang, and we want to |
| # fail gracefully. When we call "die" it exits this eval statement |
| # with a value we use below |
| $commandOutput = `$command 2> $errorFile`; # run the command and gather its output |
| my $commandRetVal = ($? >> 8); # find the commands return value |
| if ($commandRetVal == 0) { |
| &B_log("ACTION","Executed Command: " . $command . "\n"); |
| &B_log("ACTION","Command Output: " . $commandOutput . "\n"); |
| die "success"; |
| } else { |
| die "failure"; |
| }; |
| }; |
| |
| my $exitcode=$@; |
| alarm(0); # End of the timed operation |
| |
| my $cat = &getGlobal("BIN","cat"); |
| if ( -e $errorFile ) { |
| $errOutput = `$cat $errorFile`; |
| } |
| |
| if ($exitcode) { # The eval command above will exit with one of the 3 values below |
| if ($exitcode =~ /timeout/) { |
| &B_log("WARNING","No response received from $command after $WAIT_TIME seconds.\n" . |
| "Command Output: " . $commandOutput . "\n"); |
| return (0,'',''); |
| } elsif ($exitcode =~ /success/) { |
| return (1,$commandOutput,$errOutput); |
| } elsif ($exitcode =~ /failure/) { |
| return (0,$commandOutput,$errOutput); |
| } else { |
| &B_log("FATAL","Unexpected return state from command execution: $command\n" . |
| "Command Output: " . $commandOutput . "\n"); |
| } |
| } |
| } |
| |
| ############################################# |
| # Use this **only** for commands used that are |
| # intended to test system state and |
| # not make any system change. Use this in place of the |
| # prior use of "backticks throughout Bastille |
| # Handles basic output redirection, but not for stdin |
| # Input: Command |
| # Output: Results |
| ############################################# |
| |
| sub B_Backtick($) { |
| my $command=$_[0]; |
| my $combineOutput=0; |
| my $stdoutRedir = ""; |
| my $stderrRedir = ""; |
| my $echo = &getGlobal('BIN','echo'); |
| |
| if (($command =~ s/2>&1//) or |
| (s/>&2//)){ |
| $combineOutput=1; |
| } |
| if ($command =~ s/>\s*([^>\s])+// ) { |
| $stdoutRedir = $1; |
| } |
| if ($command =~ s/2>\s*([^>\s])+// ) { |
| $stderrRedir = $1; |
| } |
| |
| my ($ranFine, $stdout, $stderr) = &systemCall($command); |
| if ($ranFine) { |
| &B_log("DEBUG","Command: $command succeeded for test with output: $stdout , ". |
| "and stderr: $stderr"); |
| } else { |
| &B_log("DEBUG","Command: $command failed for test with output: $stdout , ". |
| "and stderr: $stderr"); |
| } |
| if ($combineOutput) { |
| $stdout .= $stderr; |
| $stderr = $stdout; #these should be the same |
| } |
| if ($stdoutRedir ne "") { |
| system("$echo \'$stdout\' > $stdoutRedir"); |
| } |
| if ($stderrRedir ne "") { |
| system("$echo \'$stderr\' > $stderrRedir"); |
| } |
| return $stdout; |
| } |
| |
| #################################################################### |
| # &B_System($command,$revertcommand); |
| # This function executes a command, then places the associated |
| # revert command in revert file. It takes two parameters, the |
| # command and the command that reverts that command. |
| # |
| # uses ActionLog and ErrorLog for logging purposes. |
| ################################################################### |
| sub B_System ($$) { |
| my ($command,$revertcmd)=@_; |
| |
| my ($ranFine, $stdout, $stderr) = &systemCall($command); |
| if ($ranFine) { |
| &B_revert_log ("$revertcmd \n"); |
| if ($stderr ne '' ) { |
| &B_log("ACTION",$command . "suceeded with STDERR: " . |
| $stderr . "\n"); |
| } |
| return 1; |
| } else { |
| my $warningString = "Command Failed: " . $command . "\n" . |
| "Command Output: " . $stdout . "\n"; |
| if ($stderr ne '') { |
| $warningString .= "Error message: " . $stderr; |
| } |
| &B_log("WARNING", $warningString); |
| return 0; |
| } |
| } |
| |
| |
| ########################################################################### |
| # &isProcessRunning($procPattern); |
| # |
| # If called in scalar context this subroutine will return a 1 if the |
| # pattern specified can be matched against the process table. It will |
| # return a 0 otherwise. |
| # If called in the list context this subroutine will return the list |
| # of processes which matched the pattern supplied |
| # |
| # scalar return values: |
| # 0: pattern not in process table |
| # 1: pattern is in process table |
| # |
| # list return values: |
| # proc lines from the process table if they are found |
| ########################################################################### |
| sub isProcessRunning($) { |
| |
| my $procPattern= $_[0]; |
| my $ps = &getGlobal('BIN',"ps"); |
| |
| my $isRunning=0; |
| # process table. |
| my @psTable = `$ps -elf`; |
| # list of processes that match the $procPattern |
| my @procList; |
| foreach my $process (@psTable) { |
| if($process =~ $procPattern) { |
| $isRunning = 1; |
| push @procList, $process . "\n"; |
| } |
| } |
| |
| &B_log("DEBUG","$procPattern search yielded $isRunning\n\n"); |
| # if this subroutine was called in scalar context |
| if( ! wantarray ) { |
| return $isRunning; |
| } |
| |
| return @procList; |
| } |
| |
| |
| ########################################################################### |
| # &checkProcsForService($service); |
| # |
| # Checks if the given service is running by analyzing the process table. |
| # This is a helper function to checkServiceOnLinux and checkServiceOnHP |
| # |
| # Return values: |
| # SECURE_CANT_CHANGE() if the service is off |
| # INCONSISTENT() if the state of the service cannot be determined |
| # |
| # Mostly used in "check service" direct-return context, but added option use. |
| # to ignore warning if a check for a service ... where a found service doesn't |
| # have direct security problems. |
| # |
| ########################################################################### |
| sub checkProcsForService ($;$) { |
| my $service=$_[0]; |
| my $ignore_warning=$_[1]; |
| |
| my @psnames=@{ &getGlobal('PROCESS',$service)}; |
| |
| my @processes; |
| # inetd services don't have a separate process |
| foreach my $psname (@psnames) { |
| my @procList = &isProcessRunning($psname); |
| if(@procList >= 0){ |
| splice @processes,$#processes+1,0,@procList; |
| } |
| } |
| |
| if($#processes >= 0){ |
| if ((defined($ignore_warning)) and ($ignore_warning eq "ignore_warning")) { |
| &B_log("WARNING","The following processes were still running even though " . |
| "the corresponding service appears to be turned off. Bastille " . |
| "question and action will be skipped.\n\n" . |
| "@processes\n\n"); |
| # processes were still running, service is not off, but we don't know how |
| # to configure it so we skip the question |
| return INCONSISTENT(); |
| } else { |
| return NOTSECURE_CAN_CHANGE(); # In the case we're ignoring the warning, |
| # ie: checking to make *sure* a process |
| # is running, the answer isn't inconsistent |
| } |
| } else { |
| &B_log("DEBUG","$service is off. Found no processes running on the system."); |
| # no processes, so service is off |
| return SECURE_CANT_CHANGE(); |
| } |
| # Can't determine the state of the service by looking at the processes, |
| # so return INCONSISTENT(). |
| return INCONSISTENT(); |
| } |
| |
| ########################################################################### |
| # B_parse_fstab() |
| # |
| # Search the filesystem table for a specific mount point. |
| # |
| # scalar return value: |
| # The line form the table that matched the mount point, or the null string |
| # if no match was found. |
| # |
| # list return value: |
| # A list of parsed values from the line of the table that matched, with |
| # element [3] containing a reference to a hash of the mount options. The |
| # keys are: acl, dev, exec, rw, suid, sync, or user. The value of each key |
| # can be either 0 or 1. To access the hash, use code similar to this: |
| # %HashResult = %{(&B_parse_fstab($MountPoint))[3]}; |
| # |
| ########################################################################### |
| |
| sub B_parse_fstab($) |
| { |
| my $name = shift; |
| my $file = &getGlobal('FILE','fstab'); |
| my ($enable, $disable, $infile); |
| my @lineopt; |
| my $retline = ""; |
| my @retlist = (); |
| |
| unless (open FH, $file) { |
| &B_log('ERROR',"B_parse_fstab couldn't open fstab file at path $file.\n"); |
| return 0; |
| } |
| while (<FH>) { |
| s/\#.*//; |
| next unless /\S/; |
| @retlist = split; |
| next unless $retlist[1] eq $name; |
| $retline .= $_; |
| if (wantarray) { |
| my $option = { # initialize to defaults |
| acl => 0, # for ext2, etx3, reiserfs |
| dev => 1, |
| exec => 1, |
| rw => 1, |
| suid => 1, |
| sync => 0, |
| user => 0, |
| }; |
| |
| my @lineopt = split(',',$retlist[3]); |
| foreach my $entry (@lineopt) { |
| if ($entry eq 'acl') { |
| $option->{'acl'} = 1; |
| } |
| elsif ($entry eq 'nodev') { |
| $option->{'dev'} = 0; |
| } |
| elsif ($entry eq 'noexec') { |
| $option->{'exec'} = 0; |
| } |
| elsif ($entry eq 'ro') { |
| $option->{'rw'} = 0; |
| } |
| elsif ($entry eq 'nosuid') { |
| $option->{'suid'} = 0; |
| } |
| elsif ($entry eq 'sync') { |
| $option->{'sync'} = 1; |
| } |
| elsif ($entry eq 'user') { |
| $option->{'user'} = 1; |
| } |
| } |
| $retlist[3]= $option; |
| } |
| last; |
| } |
| |
| if (wantarray) |
| { |
| return @retlist; |
| } |
| else |
| { |
| return $retline; |
| } |
| |
| } |
| |
| |
| ########################################################################### |
| # B_parse_mtab() |
| # |
| # This routine returns a hash of devices and their mount points from mtab, |
| # simply so you can get a list of mounted filesystems. |
| # |
| ########################################################################### |
| |
| sub B_parse_mtab |
| { |
| my $mountpoints; |
| open(MTAB,&getGlobal('FILE','mtab')); |
| while(my $mtab_line = <MTAB>) { |
| #test if it's a device |
| if ($mtab_line =~ /^\//) |
| { |
| #parse out device and mount point |
| $mtab_line =~ /^(\S+)\s+(\S+)/; |
| $mountpoints->{$1} = $2; |
| } |
| } |
| return $mountpoints; |
| } |
| |
| |
| ########################################################################### |
| # B_is_rpm_up_to_date() |
| # |
| # |
| ########################################################################### |
| |
| sub B_is_rpm_up_to_date(@) |
| { |
| my($nameB,$verB,$relB,$epochB) = @_; |
| my $installedpkg = $nameB; |
| |
| if ($epochB =~ /(none)/) { |
| $epochB = 0; |
| } |
| |
| my $rpmA = `rpm -q --qf '%{VERSION}-%{RELEASE}-%{EPOCH}\n' $installedpkg`; |
| my $nameA = $nameB; |
| my ($verA,$relA,$epochA); |
| |
| my $retval; |
| |
| # First, if the RPM isn't installed, let's handle that. |
| if ($rpmA =~ /is not installed/) { |
| $retval = -1; |
| return $retval; |
| } |
| else { |
| # Next, let's try to parse the EVR information without as few |
| # calls as possible to rpm. |
| if ($rpmA =~ /([^-]+)-([^-]+)-([^-]+)$/) { |
| $verA = $1; |
| $relA = $2; |
| $epochA = $3; |
| } |
| else { |
| $nameA = `rpm -q --qf '%{NAME}' $installedpkg`; |
| $verA = `rpm -q --qf '%{VERSION}' $installedpkg`; |
| $relA = `rpm -q --qf '%{RELEASE}' $installedpkg`; |
| $epochA = `rpm -q --qf '%{EPOCH}' $installedpkg`; |
| } |
| } |
| |
| # Parse "none" as 0. |
| if ($epochA =~ /(none)/) { |
| $epochA = 0; |
| } |
| |
| # Handle the case where only one of them is zero. |
| if ($epochA == 0 xor $epochB == 0) |
| { |
| if ($epochA != 0) |
| { |
| $retval = 1; |
| } |
| else |
| { |
| $retval = 0; |
| } |
| } |
| else |
| { |
| # ...otherwise they are either both 0 or both non-zero and |
| # so the situation isn't trivial. |
| |
| # Check epoch first - highest epoch wins. |
| my $rpmcmp = &cmp_vers_part($epochA, $epochB); |
| #print "epoch rpmcmp is $rpmcmp\n"; |
| if ($rpmcmp > 0) |
| { |
| $retval = 1; |
| } |
| elsif ($rpmcmp < 0) |
| { |
| $retval = 0; |
| } |
| else |
| { |
| # Epochs were the same. Check Version now. |
| $rpmcmp = &cmp_vers_part($verA, $verB); |
| #print "epoch rpmcmp is $rpmcmp\n"; |
| if ($rpmcmp > 0) |
| { |
| $retval = 1; |
| } |
| elsif ($rpmcmp < 0) |
| { |
| $retval = 0; |
| } |
| else |
| { |
| # Versions were the same. Check Release now. |
| my $rpmcmp = &cmp_vers_part($relA, $relB); |
| #print "epoch rpmcmp is $rpmcmp\n"; |
| if ($rpmcmp >= 0) |
| { |
| $retval = 1; |
| } |
| elsif ($rpmcmp < 0) |
| { |
| $retval = 0; |
| } |
| } |
| } |
| } |
| return $retval; |
| } |
| |
| ################################################# |
| # Helper function for B_is_rpm_up_to_date() |
| ################################################# |
| |
| #This cmp_vers_part function taken from Kirk Bauer's Autorpm. |
| # This version comparison code was sent in by Robert Mitchell and, although |
| # not yet perfect, is better than the original one I had. He took the code |
| # from freshrpms and did some mods to it. Further mods by Simon Liddington |
| # <sjl96v@ecs.soton.ac.uk>. |
| # |
| # Splits string into minors on . and change from numeric to non-numeric |
| # characters. Minors are compared from the beginning of the string. If the |
| # minors are both numeric then they are numerically compared. If both minors |
| # are non-numeric and a single character they are alphabetically compared, if |
| # they are not a single character they are checked to be the same if the are not |
| # the result is unknown (currently we say the first is newer so that we have |
| # a choice to upgrade). If one minor is numeric and one non-numeric then the |
| # numeric one is newer as it has a longer version string. |
| # We also assume that (for example) .15 is equivalent to 0.15 |
| |
| sub cmp_vers_part($$) { |
| my($va, $vb) = @_; |
| my(@va_dots, @vb_dots); |
| my($a, $b); |
| my($i); |
| |
| if ($vb !~ /^pre/ and $va =~ s/^pre(\d+.*)$/$1/) { |
| if ($va eq $vb) { return -1; } |
| } elsif ($va !~ /^pre/ and $vb =~ s/^pre(\d+.*)$/$1/) { |
| if ($va eq $vb) { return 1; } |
| } |
| |
| @va_dots = split(/\./, $va); |
| @vb_dots = split(/\./, $vb); |
| |
| $a = shift(@va_dots); |
| $b = shift(@vb_dots); |
| # We also assume that (for example) .15 is equivalent to 0.15 |
| if ($a eq '' && $va ne '') { $a = "0"; } |
| if ($b eq '' && $vb ne '') { $b = "0"; } |
| while ((defined($a) && $a ne '') || (defined($b) && $b ne '')) { |
| # compare each minor from left to right |
| if ((not defined($a)) || ($a eq '')) { return -1; } # the longer version is newer |
| if ((not defined($b)) || ($b eq '')) { return 1; } |
| if ($a =~ /^\d+$/ && $b =~ /^\d+$/) { |
| # I have changed this so that when the two strings are numeric, but one or both |
| # of them start with a 0, then do a string compare - Kirk Bauer - 5/28/99 |
| if ($a =~ /^0/ or $b =~ /^0/) { |
| # We better string-compare so that netscape-4.6 is newer than netscape-4.08 |
| if ($a ne $b) {return ($a cmp $b);} |
| } |
| # numeric compare |
| if ($a != $b) { return $a <=> $b; } |
| } elsif ($a =~ /^\D+$/ && $b =~ /^\D+$/) { |
| # string compare |
| if (length($a) == 1 && length($b) == 1) { |
| # only minors with one letter seem to be useful for versioning |
| if ($a ne $b) { return $a cmp $b; } |
| } elsif (($a cmp $b) != 0) { |
| # otherwise we should at least check they are the same and if not say unknown |
| # say newer for now so at least we get choice whether to upgrade or not |
| return -1; |
| } |
| } elsif ( ($a =~ /^\D+$/ && $b =~ /^\d+$/) || ($a =~ /^\d+$/ && $b =~ /^\D+$/) ) { |
| # if we get a number in one and a word in another the one with a number |
| # has a longer version string |
| if ($a =~ /^\d+$/) { return 1; } |
| if ($b =~ /^\d+$/) { return -1; } |
| } else { |
| # minor needs splitting |
| $a =~ /\d+/ || $a =~ /\D+/; |
| # split the $a minor into numbers and non-numbers |
| my @va_bits = ($`, $&, $'); |
| $b =~ /\d+/ || $b =~ /\D+/; |
| # split the $b minor into numbers and non-numbers |
| my @vb_bits = ($`, $&, $'); |
| for ( my $j=2; $j >= 0; $j--) { |
| if ($va_bits[$j] ne '') { unshift(@va_dots,$va_bits[$j]); } |
| if ($vb_bits[$j] ne '') { unshift(@vb_dots,$vb_bits[$j]); } |
| } |
| } |
| $a = shift(@va_dots); |
| $b = shift(@vb_dots); |
| } |
| return 0; |
| } |
| |
| 1; |
| |