diff options
Diffstat (limited to 'meta-security/recipes-security/bastille')
23 files changed, 9053 insertions, 0 deletions
diff --git a/meta-security/recipes-security/bastille/bastille_3.2.1.bb b/meta-security/recipes-security/bastille/bastille_3.2.1.bb new file mode 100644 index 000000000..eee1a38e1 --- /dev/null +++ b/meta-security/recipes-security/bastille/bastille_3.2.1.bb @@ -0,0 +1,157 @@ +#The functionality of Bastille that is actually available is restricted. Please +#consult the README file for the meta-security layer for additional information. +SUMMARY = "Linux hardening tool" +DESCRIPTION = "Bastille Linux is a Hardening and Reporting/Auditing Program which enhances the security of a Linux box, by configuring daemons, system settings and firewalling." +LICENSE = "GPLv2" +LIC_FILES_CHKSUM = "file://${S}/COPYING;md5=c93c0550bd3173f4504b2cbd8991e50b" +# Bash is needed for set +o privileged (check busybox), might also need ncurses +DEPENDS = "virtual/kernel" +RDEPENDS_${PN} = "perl bash tcl perl-module-getopt-long perl-module-text-wrap lib-perl perl-module-file-path perl-module-mime-base64 perl-module-file-find perl-module-errno perl-module-file-glob perl-module-tie-hash-namedcapture perl-module-file-copy perl-module-english perl-module-exporter perl-module-cwd libcurses-perl coreutils" +FILES_${PN} += "/run/lock/subsys/bastille" + +inherit allarch module-base + +SRC_URI = "http://sourceforge.net/projects/bastille-linux/files/bastille-linux/3.2.1/Bastille-3.2.1.tar.bz2 \ + file://AccountPermission.pm \ + file://FileContent.pm \ + file://HPSpecific.pm \ + file://Miscellaneous.pm \ + file://ServiceAdmin.pm \ + file://config \ + file://fix_version_parse.patch \ + file://fixed_defined_warnings.patch \ + file://call_output_config.patch \ + file://fix_missing_use_directives.patch \ + file://fix_number_of_modules.patch \ + file://remove_questions_text_file_references.patch \ + file://simplify_B_place.patch \ + file://find_existing_config.patch \ + file://upgrade_options_processing.patch \ + file://accept_os_flag_in_backend.patch \ + file://allow_os_with_assess.patch \ + file://edit_usage_message.patch \ + file://organize_distro_discovery.patch \ + file://do_not_apply_config.patch \ + " + +SRC_URI[md5sum] = "df803f7e38085aa5da79f85d0539f91b" +SRC_URI[sha256sum] = "0ea25191b1dc1c8f91e1b6f8cb5436a3aa1e57418809ef902293448efed5021a" + +S = "${WORKDIR}/Bastille" + +do_install () { + install -d ${D}${sbindir} + install -d ${D}${libdir}/perl/site_perl/Curses + ln -sf perl ${D}/${libdir}/perl5 + + install -d ${D}${libdir}/Bastille + install -d ${D}${libdir}/Bastille/API + install -d ${D}${datadir}/Bastille + install -d ${D}${datadir}/Bastille/OSMap + install -d ${D}${datadir}/Bastille/OSMap/Modules + install -d ${D}${datadir}/Bastille/Questions + install -d ${D}${datadir}/Bastille/FKL/configs/ + install -d ${D}${localstatedir}/lock/subsys/bastille + install -d ${D}${localstatedir}/log/Bastille + install -d ${D}${sysconfdir}/Bastille + install -m 0755 AutomatedBastille ${D}${sbindir} + install -m 0755 BastilleBackEnd ${D}${sbindir} + install -m 0755 InteractiveBastille ${D}${sbindir} + install -m 0644 Modules.txt ${D}${datadir}/Bastille + # New Weights file(s). + install -m 0644 Weights.txt ${D}${datadir}/Bastille + # Castle graphic + install -m 0644 bastille.jpg ${D}${datadir}/Bastille/ + # Javascript file + install -m 0644 wz_tooltip.js ${D}${datadir}/Bastille/ + install -m 0644 Credits ${D}${datadir}/Bastille + install -m 0644 FKL/configs/fkl_config_redhat.cfg ${D}${datadir}/Bastille/FKL/configs/ + install -m 0755 RevertBastille ${D}${sbindir} + install -m 0755 bin/bastille ${D}${sbindir} + install -m 0644 bastille-firewall ${D}${datadir}/Bastille + install -m 0644 bastille-firewall-reset ${D}${datadir}/Bastille + install -m 0644 bastille-firewall-schedule ${D}${datadir}/Bastille + install -m 0644 bastille-tmpdir-defense.sh ${D}${datadir}/Bastille + install -m 0644 bastille-tmpdir.csh ${D}${datadir}/Bastille + install -m 0644 bastille-tmpdir.sh ${D}${datadir}/Bastille + install -m 0644 bastille-firewall.cfg ${D}${datadir}/Bastille + install -m 0644 bastille-ipchains ${D}${datadir}/Bastille + install -m 0644 bastille-netfilter ${D}${datadir}/Bastille + install -m 0644 bastille-firewall-early.sh ${D}${datadir}/Bastille + install -m 0644 bastille-firewall-pre-audit.sh ${D}${datadir}/Bastille + install -m 0644 complete.xbm ${D}${datadir}/Bastille + install -m 0644 incomplete.xbm ${D}${datadir}/Bastille + install -m 0644 disabled.xpm ${D}${datadir}/Bastille + install -m 0644 ifup-local ${D}${datadir}/Bastille + install -m 0644 hosts.allow ${D}${datadir}/Bastille + + install -m 0644 Bastille/AccountSecurity.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/Apache.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/API.pm ${D}${libdir}/Bastille + install -m 0644 ${WORKDIR}/AccountPermission.pm ${D}${libdir}/Bastille/API + install -m 0644 ${WORKDIR}/FileContent.pm ${D}${libdir}/Bastille/API + install -m 0644 ${WORKDIR}/HPSpecific.pm ${D}${libdir}/Bastille/API + install -m 0644 ${WORKDIR}/ServiceAdmin.pm ${D}${libdir}/Bastille/API + install -m 0644 ${WORKDIR}/Miscellaneous.pm ${D}${libdir}/Bastille/API + install -m 0644 Bastille/BootSecurity.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/ConfigureMiscPAM.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/DisableUserTools.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/DNS.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/FilePermissions.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/FTP.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/Firewall.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/OSX_API.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/LogAPI.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/HP_UX.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/IOLoader.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/Patches.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/Logging.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/MiscellaneousDaemons.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/PatchDownload.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/Printing.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/PSAD.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/RemoteAccess.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/SecureInetd.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/Sendmail.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/TestDriver.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/TMPDIR.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_AccountSecurity.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_Apache.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_DNS.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_FTP.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_HP_UX.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_MiscellaneousDaemons.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_Patches.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_SecureInetd.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_Sendmail.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_BootSecurity.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_DisableUserTools.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_FilePermissions.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_Logging.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/test_Printing.pm ${D}${libdir}/Bastille + install -m 0644 Bastille/IPFilter.pm ${D}${libdir}/Bastille + install -m 0644 Bastille_Curses.pm ${D}${libdir}/perl5/site_perl + install -m 0644 Bastille_Tk.pm ${D}${libdir}/perl5/site_perl + install -m 0644 Curses/Widgets.pm ${D}${libdir}/perl5/site_perl/Curses + + install -m 0644 OSMap/LINUX.bastille ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/LINUX.system ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/LINUX.service ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/HP-UX.bastille ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/HP-UX.system ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/HP-UX.service ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/OSX.bastille ${D}${datadir}/Bastille/OSMap + install -m 0644 OSMap/OSX.system ${D}${datadir}/Bastille/OSMap + + install -m 0777 ${WORKDIR}/config ${D}${sysconfdir}/Bastille/config + + for file in `cat Modules.txt` ; do + install -m 0644 Questions/$file.txt ${D}${datadir}/Bastille/Questions + done + + ${THISDIR}/files/set_required_questions.py ${D}${sysconfdir}/Bastille/config ${D}${datadir}/Bastille/Questions + + ln -s RevertBastille ${D}${sbindir}/UndoBastille +} + +FILES_${PN} += "${datadir}/Bastille ${libdir}/Bastille ${libdir}/perl* ${sysconfdir}/*" diff --git a/meta-security/recipes-security/bastille/files/API.pm b/meta-security/recipes-security/bastille/files/API.pm new file mode 100644 index 000000000..5060f52a4 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/API.pm @@ -0,0 +1,2528 @@ +# 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; + diff --git a/meta-security/recipes-security/bastille/files/AccountPermission.pm b/meta-security/recipes-security/bastille/files/AccountPermission.pm new file mode 100644 index 000000000..cfbaab1d9 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/AccountPermission.pm @@ -0,0 +1,1060 @@ +package Bastille::API::AccountPermission; +use strict; + +use Bastille::API; + +use Bastille::API::HPSpecific; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw( +B_chmod +B_chmod_if_exists +B_chown +B_chown_link +B_chgrp +B_chgrp_link +B_userdel +B_groupdel +B_remove_user_from_group +B_check_owner_group +B_is_unowned_file +B_is_ungrouped_file +B_check_permissions +B_permission_test +B_find_homes +B_is_executable +B_is_suid +B_is_sgid +B_get_user_list +B_get_group_list +B_remove_suid +); +our @EXPORT = @EXPORT_OK; + +########################################################################### +# &B_chmod ($mode, $file) sets the mode of $file to $mode. $mode must +# be stored in octal, so if you want to give mode 700 to /etc/aliases, +# you need to use: +# +# &B_chmod ( 0700 , "/etc/aliases"); +# +# where the 0700 denotes "octal 7-0-0". +# +# &B_chmod ($mode_changes,$file) also respects the symbolic methods of +# changing file permissions, which are often what question authors are +# really seeking. +# +# &B_chmod ("u-s" , "/bin/mount") +# or +# &B_chmod ("go-rwx", "/bin/mount") +# +# +# &B_chmod respects GLOBAL_LOGONLY and uses +# &B_revert_log used to insert a shell command that will return +# the permissions to the pre-Bastille state. +# +# B_chmod allow for globbing now, as of 1.2.0. JJB +# +########################################################################## + + +sub B_chmod($$) { + my ($new_perm,$file_expr)=@_; + my $old_perm; + my $old_perm_raw; + my $new_perm_formatted; + my $old_perm_formatted; + + my $retval=1; + + my $symbolic = 0; + my ($chmod_noun,$add_remove,$capability) = (); + # Handle symbolic possibilities too + if ($new_perm =~ /([ugo]+)([+-]{1})([rwxst]+)/) { + $symbolic = 1; + $chmod_noun = $1; + $add_remove = $2; + $capability = $3; + } + + my $file; + my @files = glob ($file_expr); + + foreach $file (@files) { + + # Prepend global prefix, but save the original filename for B_backup_file + my $original_file=$file; + + # Store the old permissions so that we can log them. + unless (stat $file) { + &B_log("ERROR","Couldn't stat $original_file from $old_perm to change permissions\n"); + next; + } + + $old_perm_raw=(stat(_))[2]; + $old_perm= (($old_perm_raw/512) % 8) . + (($old_perm_raw/64) % 8) . + (($old_perm_raw/8) % 8) . + ($old_perm_raw % 8); + + # If we've gone symbolic, calculate the new permissions in octal. + if ($symbolic) { + # + # We calculate the new permissions by applying a bitmask to + # the current permissions, by OR-ing (for +) or XOR-ing (for -). + # + # We create this mask by first calculating a perm_mask that forms + # the right side of this, then multiplying it by 8 raised to the + # appropriate power to affect the correct digit of the octal mask. + # This means that we raise 8 to the power of 0,1,2, or 3, based on + # the noun of "other","group","user", or "suid/sgid/sticky". + # + # Actually, we handle multiple nouns by summing powers of 8. + # + # The only tough part is that we have to handle suid/sgid/sticky + # differently. + # + + # We're going to calculate a mask to OR or XOR with the current + # file mode. This mask is $mask. We calculate this by calculating + # a sum of powers of 8, corresponding to user/group/other, + # multiplied with a $premask. The $premask is simply the + # corresponding bitwise expression of the rwx bits. + # + # To handle SUID, SGID or sticky in the simplest way possible, we + # simply add their values to the $mask first. + + my $perm_mask = 00; + my $mask = 00; + + # Check for SUID, SGID or sticky as these are exceptional. + if ($capability =~ /s/) { + if ($chmod_noun =~ /u/) { + $mask += 04000; + } + if ($chmod_noun =~ /g/) { + $mask += 02000; + } + } + if ($capability =~ /t/) { + $mask += 01000; + } + + # Now handle the normal attributes + if ($capability =~ /[rwx]/) { + if ($capability =~ /r/) { + $perm_mask |= 04; + } + if ($capability =~ /w/) { + $perm_mask |= 02; + } + if ($capability =~ /x/) { + $perm_mask |= 01; + } + + # Now figure out which 3 bit octal digit we're affecting. + my $power = 0; + if ($chmod_noun =~ /u/) { + $mask += $perm_mask * 64; + } + if ($chmod_noun =~ /g/) { + $mask += $perm_mask * 8; + } + if ($chmod_noun =~ /o/) { + $mask += $perm_mask * 1; + } + } + # Now apply the mask to get the new permissions + if ($add_remove eq '+') { + $new_perm = $old_perm_raw | $mask; + } + elsif ($add_remove eq '-') { + $new_perm = $old_perm_raw & ( ~($mask) ); + } + } + + # formating for simple long octal output of the permissions in string form + $new_perm_formatted=sprintf "%5lo",$new_perm; + $old_perm_formatted=sprintf "%5lo",$old_perm_raw; + + &B_log("ACTION","change permissions on $original_file from $old_perm_formatted to $new_perm_formatted\n"); + + &B_log("ACTION", "chmod $new_perm_formatted,\"$original_file\";\n"); + + # Change the permissions on the file + + if ( -e $file ) { + unless ($GLOBAL_LOGONLY) { + $retval=chmod $new_perm,$file; + if($retval){ + # if the distribution is HP-UX then the modifications should + # also be made to the IPD (installed product database) + if(&GetDistro =~ "^HP-UX"){ + &B_swmodify($file); + } + # making changes revert-able + &B_revert_log(&getGlobal('BIN', "chmod") . " $old_perm $file\n"); + } + } + unless ($retval) { + &B_log("ERROR","Couldn't change permissions on $original_file from $old_perm_formatted to $new_perm_formatted\n"); + $retval=0; + } + } + else { + &B_log("ERROR", "chmod: File $original_file doesn't exist!\n"); + $retval=0; + } + } + + $retval; + +} + +########################################################################### +# &B_chmod_if_exists ($mode, $file) sets the mode of $file to $mode *if* +# $file exists. $mode must be stored in octal, so if you want to give +# mode 700 to /etc/aliases, you need to use: +# +# &B_chmod_if_exists ( 0700 , "/etc/aliases"); +# +# where the 0700 denotes "octal 7-0-0". +# +# &B_chmod_if_exists respects GLOBAL_LOGONLY and uses +# &B_revert_log to reset the permissions of the file. +# +# B_chmod_if_exists allow for globbing now, as of 1.2.0. JJB +# +########################################################################## + + +sub B_chmod_if_exists($$) { + my ($new_perm,$file_expr)=@_; + # If $file_expr has a glob character, pass it on (B_chmod won't complain + # about nonexistent files if given a glob pattern) + if ( $file_expr =~ /[\*\[\{]/ ) { # } just to match open brace for vi + &B_log("ACTION","Running chmod $new_perm $file_expr"); + return(&B_chmod($new_perm,$file_expr)); + } + # otherwise, test for file existence + if ( -e $file_expr ) { + &B_log("ACTION","File exists, running chmod $new_perm $file_expr"); + return(&B_chmod($new_perm,$file_expr)); + } +} + +########################################################################### +# &B_chown ($uid, $file) sets the owner of $file to $uid, like this: +# +# &B_chown ( 0 , "/etc/aliases"); +# +# &B_chown respects $GLOBAL_LOGONLY and uses +# &B_revert_log to insert a shell command that will return +# the file/directory owner to the pre-Bastille state. +# +# Unlike Perl, we've broken the chown function into B_chown/B_chgrp to +# make error checking simpler. +# +# As of 1.2.0, this now supports file globbing. JJB +# +########################################################################## + + +sub B_chown($$) { + my ($newown,$file_expr)=@_; + my $oldown; + my $oldgown; + + my $retval=1; + + my $file; + my @files = glob($file_expr); + + foreach $file (@files) { + + # Prepend prefix, but save original filename + my $original_file=$file; + + $oldown=(stat $file)[4]; + $oldgown=(stat $file)[5]; + + &B_log("ACTION","change ownership on $original_file from $oldown to $newown\n"); + &B_log("ACTION","chown $newown,$oldgown,\"$original_file\";\n"); + if ( -e $file ) { + unless ($GLOBAL_LOGONLY) { + # changing the files owner using perl chown function + $retval = chown $newown,$oldgown,$file; + if($retval){ + # if the distribution is HP-UX then the modifications should + # also be made to the IPD (installed product database) + if(&GetDistro =~ "^HP-UX"){ + &B_swmodify($file); + } + # making ownership change revert-able + &B_revert_log(&getGlobal('BIN', "chown") . " $oldown $file\n"); + } + } + unless ($retval) { + &B_log("ERROR","Couldn't change ownership to $newown on file $original_file\n"); + } + } + else { + &B_log("ERROR","chown: File $original_file doesn't exist!\n"); + $retval=0; + } + } + + $retval; +} + +########################################################################### +# &B_chown_link just like &B_chown but one exception: +# if the input file is a link it will not change the target's ownship, it only change the link itself's ownship +########################################################################### +sub B_chown_link($$){ + my ($newown,$file_expr)=@_; + my $chown = &getGlobal("BIN","chown"); + my @files = glob($file_expr); + my $retval = 1; + + foreach my $file (@files) { + # Prepend prefix, but save original filename + my $original_file=$file; + my $oldown=(stat $file)[4]; + my $oldgown=(stat $file)[5]; + + &B_log("ACTION","change ownership on $original_file from $oldown to $newown\n"); + &B_log("ACTION","chown -h $newown,\"$original_file\";\n"); + if ( -e $file ) { + unless ($GLOBAL_LOGONLY) { + `$chown -h $newown $file`; + $retval = ($? >> 8); + if($retval == 0 ){ + # if the distribution is HP-UX then the modifications should + # also be made to the IPD (installed product database) + if(&GetDistro =~ "^HP-UX"){ + &B_swmodify($file); + } + # making ownership change revert-able + &B_revert_log("$chown -h $oldown $file\n"); + } + } + unless ( ! $retval) { + &B_log("ERROR","Couldn't change ownership to $newown on file $original_file\n"); + } + } + else { + &B_log("ERROR","chown: File $original_file doesn't exist!\n"); + $retval=0; + } + } +} + + +########################################################################### +# &B_chgrp ($gid, $file) sets the group owner of $file to $gid, like this: +# +# &B_chgrp ( 0 , "/etc/aliases"); +# +# &B_chgrp respects $GLOBAL_LOGONLY and uses +# &B_revert_log to insert a shell command that will return +# the file/directory group to the pre-Bastille state. +# +# Unlike Perl, we've broken the chown function into B_chown/B_chgrp to +# make error checking simpler. +# +# As of 1.2.0, this now supports file globbing. JJB +# +########################################################################## + + +sub B_chgrp($$) { + my ($newgown,$file_expr)=@_; + my $oldown; + my $oldgown; + + my $retval=1; + + my $file; + my @files = glob($file_expr); + + foreach $file (@files) { + + # Prepend global prefix, but save original filename for &B_backup_file + my $original_file=$file; + + $oldown=(stat $file)[4]; + $oldgown=(stat $file)[5]; + + &B_log("ACTION", "Change group ownership on $original_file from $oldgown to $newgown\n"); + &B_log("ACTION", "chown $oldown,$newgown,\"$original_file\";\n"); + if ( -e $file ) { + unless ($GLOBAL_LOGONLY) { + # changing the group for the file/directory + $retval = chown $oldown,$newgown,$file; + if($retval){ + # if the distribution is HP-UX then the modifications should + # also be made to the IPD (installed product database) + if(&GetDistro =~ "^HP-UX"){ + &B_swmodify($file); + } + &B_revert_log(&getGlobal('BIN', "chgrp") . " $oldgown $file\n"); + } + } + unless ($retval) { + &B_log("ERROR","Couldn't change ownership to $newgown on file $original_file\n"); + } + } + else { + &B_log("ERROR","chgrp: File $original_file doesn't exist!\n"); + $retval=0; + } + } + + $retval; +} + +########################################################################### +# &B_chgrp_link just like &B_chgrp but one exception: +# if the input file is a link +# it will not change the target's ownship, it only change the link itself's ownship +########################################################################### +sub B_chgrp_link($$) { + my ($newgown,$file_expr)=@_; + my $chgrp = &getGlobal("BIN","chgrp"); + my @files = glob($file_expr); + my $retval=1; + + foreach my $file (@files) { + # Prepend prefix, but save original filename + my $original_file=$file; + my $oldgown=(stat $file)[5]; + + &B_log("ACTION","change group ownership on $original_file from $oldgown to $newgown\n"); + &B_log("ACTION","chgrp -h $newgown \"$original_file\";\n"); + if ( -e $file ) { + unless ($GLOBAL_LOGONLY) { + # do not follow link with option -h + `$chgrp -h $newgown $file`; + $retval = ($? >> 8); + if($retval == 0 ){ + # if the distribution is HP-UX then the modifications should + # also be made to the IPD (installed product database) + if(&GetDistro =~ "^HP-UX"){ + &B_swmodify($file); + } + # making ownership change revert-able + &B_revert_log("$chgrp" . " -h $oldgown $file\n"); + } + } + unless (! $retval) { + &B_log("ERROR","Couldn't change group ownership to $newgown on file $original_file\n"); + } + } + else { + &B_log("ERROR","chgrp: File $original_file doesn't exist!\n"); + $retval=0; + } + } +} + +########################################################################### +# B_userdel($user) removes $user from the system, chmoding her home +# directory to 000, root:root owned, and removes the user from all +# /etc/passwd, /etc/shadow and /etc/group lines. +# +# In the future, we may also choose to make a B_lock_account routine. +# +# This routine depends on B_remove_user_from_group. +########################################################################### + +sub B_userdel($) { + + my $user_to_remove = $_[0]; + + if (&GetDistro =~ /^HP-UX/) { + return 0; + + # Not yet suported on HP-UX, where we'd need to support + # the TCB files and such. + } + + # + # First, let's chmod/chown/chgrp the user's home directory. + # + + # Get the user's home directory from /etc/passwd + if (open PASSWD,&getGlobal('FILE','passwd')) { + my @lines=<PASSWD>; + close PASSWD; + + # Get the home directory + my $user_line = grep '^\s*$user_to_remove\s*:',@lines; + my $home_directory = (split /\s*:\s*/,$user_line)[5]; + + # Chmod that home dir to 0000,owned by uid 0, gid 0. + if (&B_chmod_if_exists(0000,$home_directory)) { + &B_chown(0,$home_directory); + &B_chgrp(0,$home_directory); + } + } + else { + &B_log('ERROR',"B_userdel couldn't open the passwd file to remove a user."); + return 0; + } + + # + # Next find out what groups the user is in, so we can call + # B_remove_user_from_group($user,$group) + # + # TODO: add this to the helper functions for the test suite. + # + + my @groups = (); + + # Parse /etc/group, looking for our user. + if (open GROUP,&getGlobal('FILE','group')) { + my @lines = <GROUP>; + close GROUP; + + foreach my $line (@lines) { + + # Parse the line -- first field is group, last is users in group. + if ($line =~ /([^\#^:]+):[^:]+:[^:]+:(.*)/) { + my $group = $1; + my $users_section = $2; + + # Get the user list and check if our user is in it. + my @users = split /\s*,\s*/,$users_section; + foreach my $user (@users) { + if ($user_to_remove eq $user) { + push @groups,$group; + last; + } + } + } + } + } + + # Now remove the user from each of those groups. + foreach my $group (@groups) { + &B_remove_user_from_group($user_to_remove,$group); + } + + # Remove the user's /etc/passwd and /etc/shadow lines + &B_delete_line(&getGlobal('FILE','passwd'),"^$user_to_remove\\s*:"); + &B_delete_line(&getGlobal('FILE','shadow'),"^$user_to_remove\\s*:"); + + + # + # We should delete the user's group as well, if it's a single-user group. + # + if (open ETCGROUP,&getGlobal('FILE','group')) { + my @group_lines = <ETCGROUP>; + close ETCGROUP; + chomp @group_lines; + + if (grep /^$user_to_remove\s*:[^:]*:[^:]*:\s*$/,@group_lines > 0) { + &B_groupdel($user_to_remove); + } + } + +} + +########################################################################### +# B_groupdel($group) removes $group from /etc/group. +########################################################################### + +sub B_groupdel($) { + + my $group = $_[0]; + + # First read /etc/group to make sure the group is in there. + if (open GROUP,&getGlobal('FILE','group')) { + my @lines=<GROUP>; + close GROUP; + + # Delete the line in /etc/group if present + if (grep /^$group:/,@lines > 0) { + # The group is named in /etc/group + &B_delete_line(&getGlobal('FILE','group'),"^$group:/"); + } + } + +} + + +########################################################################### +# B_remove_user_from_group($user,$group) removes $user from $group, +# by modifying $group's /etc/group line, pulling the user out. This +# uses B_chunk_replace thrice to replace these patterns: +# +# ":\s*$user\s*," --> ":" +# ",\s*$user" -> "" +# +########################################################################### + +sub B_remove_user_from_group($$) { + + my ($user_to_remove,$group) = @_; + + # + # We need to find the line from /etc/group that defines the group, parse + # it, and put it back together without this user. + # + + # Open the group file + unless (open GROUP,&getGlobal('FILE','group')) { + &B_log('ERROR',"&B_remove_user_from_group couldn't read /etc/group to remove $user_to_remove from $group.\n"); + return 0; + } + my @lines = <GROUP>; + close GROUP; + chomp @lines; + + # + # Read through the lines to find the one we care about. We'll construct a + # replacement and then use B_replace_line to make the switch. + # + + foreach my $line (@lines) { + + if ($line =~ /^\s*$group\s*:/) { + + # Parse this line. + my @group_entries = split ':',$line; + my @users = split ',',($group_entries[3]); + + # Now, recreate it. + my $first_user = 1; + my $group_line = $group_entries[0] . ':' . $group_entries[1] . ':' . $group_entries[2] . ':'; + + # Add every user except the one we're removing. + foreach my $user (@users) { + + # Remove whitespace. + $user =~ s/\s+//g; + + if ($user ne $user_to_remove) { + # Add the user to the end of the line, prefacing + # it with a comma if it's not the first user. + + if ($first_user) { + $group_line .= "$user"; + $first_user = 0; + } + else { + $group_line .= ",$user"; + } + } + } + + # The line is now finished. Replace the original line. + $group_line .= "\n"; + &B_replace_line(&getGlobal('FILE','group'),"^\\s*$group\\s*:",$group_line); + } + + } + return 1; +} + +########################################################################### +# &B_check_owner_group($$$) +# +# Checks if the given file has the given owner and/or group. +# If the given owner is "", checks group only. +# If the given group is "", checks owner only. +# +# return values: +# 1: file has the given owner and/or group +# or file exists, and both the given owner and group are "" +# 0: file does not has the given owner or group +# or file does not exists +############################################################################ + +sub B_check_owner_group ($$$){ + my ($fileName, $owner, $group) = @_; + + if (-e $fileName) { + my @junk=stat ($fileName); + my $uid=$junk[4]; + my $gid=$junk[5]; + + # Check file owner + if ($owner ne "") { + if (getpwnam($owner) != $uid) { + return 0; + } + } + + # Check file group + if ($group ne "") { + if (getgrnam($group) != $gid) { + return 0; + } + } + + return 1; + } + else { + # Something is wrong if the file not exist + return 0; + } +} + +########################################################################## +# this subroutine will test whether the given file is unowned +########################################################################## +sub B_is_unowned_file($) { + my $file =$_; + my $uid = (stat($file))[4]; + my $uname = (getpwuid($uid))[0]; + if ( $uname =~ /.+/ ) { + return 1; + } + return 0; +} + +########################################################################## +# this subroutine will test whether the given file is ungrouped +########################################################################## +sub B_is_ungrouped_file($){ + my $file =$_; + my $gid = (stat($file))[5]; + my $gname = (getgrgid($gid))[0]; + if ( $gname =~ /.+/ ) { + return 1; + } + return 0; +} + + + + +########################################################################### +# &B_check_permissions($$) +# +# Checks if the given file has the given permissions or stronger, where we +# define stronger as "less accessible." The file argument must be fully +# qualified, i.e. contain the absolute path. +# +# return values: +# 1: file has the given permissions or better +# 0: file does not have the given permsssions +# undef: file permissions cannot be determined +########################################################################### + +sub B_check_permissions ($$){ + my ($fileName, $reqdPerms) = @_; + my $filePerms; # actual permissions + + + if (-e $fileName) { + if (stat($fileName)) { + $filePerms = (stat($fileName))[2] & 07777; + } + else { + &B_log ("ERROR", "Can't stat $fileName.\n"); + return undef; + } + } + else { + # If the file does not exist, permissions are as good as they can get. + return 1; + } + + # + # We can check whether the $filePerms are as strong by + # bitwise ANDing them with $reqdPerms and checking if the + # result is still equal to $filePerms. If it is, the + # $filePerms are strong enough. + # + if ( ($filePerms & $reqdPerms) == $filePerms ) { + return 1; + } + else { + return 0; + } + +} + +########################################################################## +# B_permission_test($user, $previlege,$file) +# $user can be +# "owner" +# "group" +# "other" +# $previlege can be: +# "r" +# "w" +# "x" +# "suid" +# "sgid" +# "sticky" +# if previlege is set to suid or sgid or sticky, then $user can be empty +# this sub routine test whether the $user has the specified previlige to $file +########################################################################## + +sub B_permission_test($$$){ + my ($user, $previlege, $file) = @_; + + if (-e $file ) { + my $mode = (stat($file))[2]; + my $bitpos; + # bitmap is | suid sgid sticky | rwx | rwx | rwx + if ($previlege =~ /suid/ ) { + $bitpos = 11; + } + elsif ($previlege =~ /sgid/ ) { + $bitpos = 10; + } + elsif ($previlege =~ /sticky/ ) { + $bitpos = 9; + } + else { + if ( $user =~ /owner/) { + if ($previlege =~ /r/) { + $bitpos = 8; + } + elsif ($previlege =~ /w/) { + $bitpos =7; + } + elsif ($previlege =~ /x/) { + $bitpos =6; + } + else { + return 0; + } + } + elsif ( $user =~ /group/) { + if ($previlege =~ /r/) { + $bitpos =5; + } + elsif ($previlege =~ /w/) { + $bitpos =4; + } + elsif ($previlege =~ /x/) { + $bitpos =3; + } + else { + return 0; + } + } + elsif ( $user =~ /other/) { + if ($previlege =~ /r/) { + $bitpos =2; + } + elsif ($previlege =~ /w/) { + $bitpos =1; + } + elsif ($previlege =~ /x/) { + $bitpos =0; + } + else { + return 0; + } + } + else { + return 0; + } + } + $mode /= 2**$bitpos; + if ($mode % 2) { + return 1; + } + return 0; + } +} + +########################################################################## +# this subroutine will return a list of home directory +########################################################################## +sub B_find_homes(){ + # find loginable homes + my $logins = &getGlobal("BIN","logins"); + my @lines = `$logins -ox`; + my @homes; + foreach my $line (@lines) { + chomp $line; + my @data = split /:/, $line; + if ($data[7] =~ /PS/ && $data[5] =~ /home/) { + push @homes, $data[5]; + } + } + return @homes; +} + + +########################################################################### +# B_is_executable($) +# +# This routine reports on whether a file is executable by the current +# process' effective UID. +# +# scalar return values: +# 0: file is not executable +# 1: file is executable +# +########################################################################### + +sub B_is_executable($) +{ + my $name = shift; + my $executable = 0; + + if (-x $name) { + $executable = 1; + } + return $executable; +} + +########################################################################### +# B_is_suid($) +# +# This routine reports on whether a file is Set-UID and owned by root. +# +# scalar return values: +# 0: file is not SUID root +# 1: file is SUID root +# +########################################################################### + +sub B_is_suid($) +{ + my $name = shift; + + my @FileStatus = stat($name); + my $IsSuid = 0; + + if (-u $name) #Checks existence and suid + { + if($FileStatus[4] == 0) { + $IsSuid = 1; + } + } + + return $IsSuid; +} + +########################################################################### +# B_is_sgid($) +# +# This routine reports on whether a file is SGID and group owned by +# group root (gid 0). +# +# scalar return values: +# 0: file is not SGID root +# 1: file is SGID root +# +########################################################################### + +sub B_is_sgid($) +{ + my $name = shift; + + my @FileStatus = stat($name); + my $IsSgid = 0; + + if (-g $name) #checks existence and sgid + { + if($FileStatus[5] == 0) { + $IsSgid = 1; + } + } + + return $IsSgid; +} + +########################################################################### +# B_get_user_list() +# +# This routine outputs a list of users on the system. +# +########################################################################### + +sub B_get_user_list() +{ + my @users; + open(PASSWD,&getGlobal('FILE','passwd')); + while(<PASSWD>) { + #Get the users + if (/^([^:]+):/) + { + push (@users,$1); + } + } + return @users; +} + +########################################################################### +# B_get_group_list() +# +# This routine outputs a list of groups on the system. +# +########################################################################### + +sub B_get_group_list() +{ + my @groups; + open(GROUP,&getGlobal('FILE','group')); + while(my $group_line = <GROUP>) { + #Get the groups + if ($group_line =~ /^([^:]+):/) + { + push (@groups,$1); + } + } + return @groups; +} + + +########################################################################### +# &B_remove_suid ($file) removes the suid bit from $file if it +# is set and the file exist. If you would like to remove the suid bit +# from /bin/ping then you need to use: +# +# &B_remove_suid("/bin/ping"); +# +# &B_remove_suid respects GLOBAL_LOGONLY. +# &B_remove_suid uses &B_chmod to make the permission changes +# &B_remove_suid allows for globbing. tyler_e +# +########################################################################### + +sub B_remove_suid($) { + my $file_expr = $_[0]; + + &B_log("ACTION","Removing SUID bit from \"$file_expr\"."); + unless ($GLOBAL_LOGONLY) { + my @files = glob($file_expr); + + foreach my $file (@files) { + # check file existence + if(-e $file){ + # stat current file to get raw permissions + my $old_perm_raw = (stat $file)[2]; + # test to see if suidbit is set + my $suid_bit = (($old_perm_raw/2048) % 2); + if($suid_bit == 1){ + # new permission without the suid bit + my $new_perm = ((($old_perm_raw/512) % 8 ) - 4) . + (($old_perm_raw/64) % 8 ) . + (($old_perm_raw/8) % 8 ) . + (($old_perm_raw) % 8 ); + if(&B_chmod(oct($new_perm), $file)){ + &B_log("ACTION","Removed SUID bit from \"$file\"."); + } + else { + &B_log("ERROR","Could not remove SUID bit from \"$file\"."); + } + } # No action if SUID bit is not set + }# No action if file does not exist + }# Repeat for each file in the file glob + } # unless Global_log +} + + + +1; + diff --git a/meta-security/recipes-security/bastille/files/FileContent.pm b/meta-security/recipes-security/bastille/files/FileContent.pm new file mode 100644 index 000000000..0a5d6096c --- /dev/null +++ b/meta-security/recipes-security/bastille/files/FileContent.pm @@ -0,0 +1,1153 @@ +package Bastille::API::FileContent; +use strict; + +use Bastille::API; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw( +B_blank_file +B_insert_line_after +B_insert_line_before +B_insert_line +B_append_line +B_prepend_line +B_replace_line +B_replace_lines +B_replace_pattern +B_match_line +B_match_line_only +B_match_chunk +B_return_matched_lines +B_hash_comment_line +B_hash_uncomment_line +B_delete_line +B_chunk_replace +B_print +B_getValueFromFile +B_getValueFromString + +B_TODO +B_TODOFlags +); +our @EXPORT = @EXPORT_OK; + + + +########################################################################### +# &B_blank_file ($filename,$pattern) blanks the file $filename, unless the +# pattern $pattern is present in the file. This lets us completely redo +# a file, if it isn't the one we put in place on a previous run... +# +# B_blank_file respects $GLOBAL_LOGONLY and uses B_open_plus and B_close_plus +# so that it makes backups and only modifies files when we're not in "-v" +# mode... +# +# If the file does not exist, the function does nothing, and gives an error +# to the Error Log +# +########################################################################### + +sub B_blank_file($$) { + + my ($filename,$pattern) = @_; + my $retval; + + # If this variable is true, we won't blank the file... + + my $found_pattern=0; + + if ($retval=&B_open_plus (*BLANK_NEW,*BLANK_OLD,$filename) ) { + + my @lines; + + while (my $line = <BLANK_OLD>) { + + push @lines,$line; + if ($line =~ $pattern) { + $found_pattern=1; + } + } + + # Only copy the old file if the new one didn't match. + if ($found_pattern) { + while ( my $line = shift @lines ) { + &B_print(*BLANK_NEW,$line); + } + } + else { + &B_log("ACTION","Blanked file $filename\n"); + } + &B_close_plus(*BLANK_NEW,*BLANK_OLD,$filename); + } + else { + &B_log("ERROR","Couldn't blank file $filename since we couldn't open it or its replacement\n"); + } + + return $retval; + +} + +########################################################################### +# &B_insert_line_after ($filename,$pattern,$line_to_insert,$line_to_follow) +# modifies $filename, inserting $line_to_insert unless one or more lines +# in the file matches $pattern. The $line_to_insert will be placed +# immediately after $line_to_follow, if it exists. If said line does not +# exist, the line will not be inserted and this routine will return 0. +# +# B_insert_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here's examples of where you might use this: +# +# You'd like to insert a line in Apache's configuration file, in a +# particular section. +# +########################################################################### + +sub B_insert_line_after($$$$) { + + my ($filename,$pattern,$line_to_insert,$line_to_follow) = @_; + + my @lines; + my $found_pattern=0; + my $found_line_to_follow=0; + + my $retval=1; + + if ( &B_open_plus (*INSERT_NEW,*INSERT_OLD,$filename) ) { + + # Read through the file looking for a match both on the $pattern + # and the line we are supposed to be inserting after... + + my $ctr=1; + while (my $line=<INSERT_OLD>) { + push (@lines,$line); + if ($line =~ $pattern) { + $found_pattern=1; + } + if ( ($found_line_to_follow < 1) and ($line =~ $line_to_follow)) { + $found_line_to_follow=$ctr; + } + $ctr++; + } + + # Log an error if we never found the line we were to insert after + unless ($found_line_to_follow ) { + $retval=0; + &B_log("ERROR","Never found the line that we were supposed to insert after in $filename\n"); + } + + # Now print the file back out, inserting our line if we should... + + $ctr=1; + while (my $line = shift @lines) { + &B_print(*INSERT_NEW,$line); + if ( ($ctr == $found_line_to_follow) and ($found_pattern == 0) ) { + &B_print(*INSERT_NEW,$line_to_insert); + &B_log("ACTION","Inserted the following line in $filename:\n"); + &B_log("ACTION","$line_to_insert"); + } + $ctr++; + } + + &B_close_plus (*INSERT_NEW,*INSERT_OLD,$filename); + + } + else { + $retval=0; + &B_log("ERROR","Couldn't insert line to $filename, since open failed."); + } + + return $retval; + +} +########################################################################### +# &B_insert_line_before ($filename,$pattern,$line_to_insert,$line_to_preceed) +# modifies $filename, inserting $line_to_insert unless one or more lines +# in the file matches $pattern. The $line_to_insert will be placed +# immediately before $line_to_preceed, if it exists. If said line does not +# exist, the line will not be inserted and this routine will return 0. +# +# B_insert_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here's examples of where you might use this: +# +# You'd like to insert a line in Apache's configuration file, in a +# particular section. +# +########################################################################### + +sub B_insert_line_before($$$$) { + + my ($filename,$pattern,$line_to_insert,$line_to_preceed) = @_; + + my @lines; + my $found_pattern=0; + my $found_line_to_preceed=0; + + my $retval=1; + + if ( &B_open_plus (*INSERT_NEW,*INSERT_OLD,$filename) ) { + + # Read through the file looking for a match both on the $pattern + # and the line we are supposed to be inserting after... + + my $ctr=1; + while (my $line=<INSERT_OLD>) { + push (@lines,$line); + if ($line =~ $pattern) { + $found_pattern=1; + } + if ( ($found_line_to_preceed < 1) and ($line =~ $line_to_preceed)) { + $found_line_to_preceed=$ctr; + } + $ctr++; + } + + # Log an error if we never found the line we were to preceed + unless ($found_line_to_preceed ) { + $retval=0; + &B_log("ERROR","Never found the line that we were supposed to insert before in $filename\n"); + } + + # Now print the file back out, inserting our line if we should... + + $ctr=1; + while (my $line = shift @lines) { + if ( ($ctr == $found_line_to_preceed) and ($found_pattern == 0) ) { + &B_print(*INSERT_NEW,$line_to_insert); + &B_log("ACTION","Inserted the following line in $filename:\n"); + &B_log("ACTION","$line_to_insert"); + } + &B_print(*INSERT_NEW,$line); + $ctr++; + } + + &B_close_plus (*INSERT_NEW,*INSERT_OLD,$filename); + + } + else { + $retval=0; + &B_log("ERROR","Couldn't insert line to $filename, since open failed."); + } + + return $retval; + +} + +########################################################################### +# &B_insert_line ($filename,$pattern,$line_to_insert,$line_to_follow) +# +# has been renamed to B_insert_line_after() +# +# This name will continue to work, as a shim for code that has not been +# transitioned. +########################################################################### + +sub B_insert_line($$$$) { + + my $rtn_value = &B_insert_line_after(@_); + + return ($rtn_value); +} + + +########################################################################### +# &B_append_line ($filename,$pattern,$line_to_append) modifies $filename, +# appending $line_to_append unless one or more lines in the file matches +# $pattern. This is an enhancement to the append_line_if_no_such_line_exists +# idea. +# +# Additionally, if $pattern is set equal to "", the line is always appended. +# +# B_append_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here's examples of where you might use this: +# +# You'd like to add a root line to /etc/ftpusers if none exists. +# You'd like to add a Options Indexes line to Apache's config. file, +# after you delete all Options lines from said config file. +# +########################################################################### + +sub B_append_line($$$) { + + my ($filename,$pattern,$line_to_append) = @_; + + my $found_pattern=0; + my $retval=1; + + if ( &B_open_plus (*APPEND_NEW,*APPEND_OLD,$filename) ) { + while (my $line=<APPEND_OLD>) { + &B_print(*APPEND_NEW,$line); + if ($line =~ $pattern) { + $found_pattern=1; + } + } + # Changed != 0 to $pattern so that "" works instead of 0 and perl + # does not give the annoying + # Argument "XX" isn't numeric in ne at ... + if ( $pattern eq "" or ! $found_pattern ) { + &B_print(*APPEND_NEW,$line_to_append); + &B_log("ACTION","Appended the following line to $filename:\n"); + &B_log("ACTION","$line_to_append"); + } + &B_close_plus (*APPEND_NEW,*APPEND_OLD,$filename); + } + else { + $retval=0; + &B_log("ERROR","# Couldn't append line to $filename, since open failed."); + } + + return $retval; + +} + +########################################################################### +# &B_prepend_line ($filename,$pattern,$line_to_prepend) modifies $filename, +# pre-pending $line_to_prepend unless one or more lines in the file matches +# $pattern. This is an enhancement to the prepend_line_if_no_such_line_exists +# idea. +# +# B_prepend_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here's examples of where you might use this: +# +# You'd like to insert the line "auth required pam_deny.so" to the top +# of the PAM stack file /etc/pam.d/rsh to totally deactivate rsh. +# +########################################################################### + +sub B_prepend_line($$$) { + + my ($filename,$pattern,$line_to_prepend) = @_; + + my @lines; + my $found_pattern=0; + my $retval=1; + + if ( &B_open_plus (*PREPEND_NEW,*PREPEND_OLD,$filename) ) { + while (my $line=<PREPEND_OLD>) { + push (@lines,$line); + if ($line =~ $pattern) { + $found_pattern=1; + } + } + unless ($found_pattern) { + &B_print(*PREPEND_NEW,$line_to_prepend); + } + while (my $line = shift @lines) { + &B_print(*PREPEND_NEW,$line); + } + + &B_close_plus (*PREPEND_NEW,*PREPEND_OLD,$filename); + + # Log the action + &B_log("ACTION","Pre-pended the following line to $filename:\n"); + &B_log("ACTION","$line_to_prepend"); + } + else { + $retval=0; + &B_log("ERROR","Couldn't prepend line to $filename, since open failed.\n"); + } + + return $retval; + +} + + +########################################################################### +# &B_replace_line ($filename,$pattern,$line_to_switch_in) modifies $filename, +# replacing any lines matching $pattern with $line_to_switch_in. +# +# It returns the number of lines it replaced (or would have replaced, if +# LOGONLY mode wasn't on...) +# +# B_replace_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here an example of where you might use this: +# +# You'd like to replace any Options lines in Apache's config file with: +# Options Indexes FollowSymLinks +# +########################################################################### + +sub B_replace_line($$$) { + + my ($filename,$pattern,$line_to_switch_in) = @_; + my $retval=0; + + if ( &B_open_plus (*REPLACE_NEW,*REPLACE_OLD,$filename) ) { + while (my $line=<REPLACE_OLD>) { + unless ($line =~ $pattern) { + &B_print(*REPLACE_NEW,$line); + } + else { + # Don't replace the line if it's already there. + unless ($line eq $line_to_switch_in) { + &B_print(*REPLACE_NEW,$line_to_switch_in); + + $retval++; + &B_log("ACTION","File modification in $filename -- replaced line\n" . + "$line\n" . + "with:\n" . + "$line_to_switch_in"); + } + # But if it is there, make sure it stays there! (by Paul Allen) + else { + &B_print(*REPLACE_NEW,$line); + } + } + } + &B_close_plus (*REPLACE_NEW,*REPLACE_OLD,$filename); + } + else { + $retval=0; + &B_log("ERROR","Couldn't replace line(s) in $filename because open failed.\n"); + } + + return $retval; +} + +########################################################################### +# &B_replace_lines ($filename,$patterns_and_substitutes) modifies $filename, +# replacing the line matching the nth $pattern specified in $patterns_and_substitutes->[n]->[0] +# with the corresponding substitutes in $patterns_and_substitutes->[n]->-[1] +# +# It returns the number of lines it replaced (or would have replaced, if +# LOGONLY mode wasn't on...) +# +# B_replace_lines uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here an example of where you might use this: +# +# You'd like to replace /etc/opt/ssh/sshd_config file +# (^#|^)Protocol\s+(.*)\s*$ ==> Protocol 2 +# (^#|^)X11Forwarding\s+(.*)\s*$ ==> X11Forwarding yes +# (^#|^)IgnoreRhosts\s+(.*)\s*$ ==> gnoreRhosts yes +# (^#|^)RhostsAuthentication\s+(.*)\s*$ ==> RhostsAuthentication no +# (^#|^)RhostsRSAAuthentication\s+(.*)\s*$ ==> RhostsRSAAuthentication no +# (^#|^)PermitRootLogin\s+(.*)\s*$ ==> PermitRootLogin no +# (^#|^)PermitEmptyPasswords\s+(.*)\s*$ ==> PermitEmptyPasswords no +# my $patterns_and_substitutes = [ +# [ '(^#|^)Protocol\s+(.*)\s*$' => 'Protocol 2'], +# ['(^#|^)X11Forwarding\s+(.*)\s*$' => 'X11Forwarding yes'], +# ['(^#|^)IgnoreRhosts\s+(.*)\s*$' => 'gnoreRhosts yes'], +# ['(^#|^)RhostsAuthentication\s+(.*)\s*$' => 'RhostsAuthentication no'], +# ['(^#|^)RhostsRSAAuthentication\s+(.*)\s*$' => 'RhostsRSAAuthentication no'], +# ['(^#|^)PermitRootLogin\s+(.*)\s*$' => 'PermitRootLogin no'], +# ['(^#|^)PermitEmptyPasswords\s+(.*)\s*$' => 'PermitEmptyPasswords no'] +#] +# B_replaces_lines($sshd_config,$patterns_and_substitutes); +########################################################################### + +sub B_replace_lines($$){ + my ($filename, $pairs) = @_; + my $retval = 0; + if ( &B_open_plus (*REPLACE_NEW,*REPLACE_OLD,$filename) ) { + while (my $line = <REPLACE_OLD>) { + my $switch; + my $switch_before = $line; + chomp($line); + foreach my $pair (@$pairs) { + $switch = 0; + + my $pattern = $pair->[0] ; + my $replace = $pair->[1]; + my $evalstr = '$line' . "=~ s/$pattern/$replace/"; + eval $evalstr; + if ($@) { + &B_log("ERROR", "eval $evalstr failed.\n"); + } + #if ( $line =~ s/$pair->[0]/$pair->[1]/) { + # $switch = 1; + # last; + #} + } + &B_print(*REPLACE_NEW,"$line\n"); + if ($switch) { + $retval++; + B_log("ACTION","File modification in $filename -- replaced line\n" . + "$switch_before\n" . + "with:\n" . + "$line\n"); + } + } + &B_close_plus (*REPLACE_NEW,*REPLACE_OLD,$filename); + return 1; + } + else { + $retval=0; + &B_log("ERROR","Couldn't replace line(s) in $filename because open failed.\n"); + } +} + +################################################################################################ +# &B_replace_pattern ($filename,$pattern,$pattern_to_remove,$text_to_switch_in) +# modifies $filename, acting on only lines that match $pattern, replacing a +# string that matches $pattern_to_remove with $text_to_switch_in. +# +# Ex: +# B_replace_pattern('/etc/httpd.conf','^\s*Options.*\bIncludes\b','Includes','IncludesNoExec') +# +# replaces all "Includes" with "IncludesNoExec" on Apache Options lines. +# +# It returns the number of lines it altered (or would have replaced, if +# LOGONLY mode wasn't on...) +# +# B_replace_pattern uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +################################################################################################# + +sub B_replace_pattern($$$$) { + + my ($filename,$pattern,$pattern_to_remove,$text_to_switch_in) = @_; + my $retval=0; + + if ( &B_open_plus (*REPLACE_NEW,*REPLACE_OLD,$filename) ) { + while (my $line=<REPLACE_OLD>) { + unless ($line =~ $pattern) { + &B_print(*REPLACE_NEW,$line); + } + else { + my $orig_line =$line; + $line =~ s/$pattern_to_remove/$text_to_switch_in/; + + &B_print(*REPLACE_NEW,$line); + + $retval++; + &B_log("ACTION","File modification in $filename -- replaced line\n" . + "$orig_line\n" . + "via pattern with:\n" . + "$line\n\n"); + } + } + &B_close_plus (*REPLACE_NEW,*REPLACE_OLD,$filename); + } + else { + $retval=0; + &B_log("ERROR","Couldn't pattern-replace line(s) in $filename because open failed.\n"); + } + + return $retval; +} + + +########################################################################### +# &B_match_line($file,$pattern); +# +# This subroutine will return a 1 if the pattern specified can be matched +# against the file specified. It will return a 0 otherwise. +# +# return values: +# 0: pattern not in file or the file is not readable +# 1: pattern is in file +########################################################################### +sub B_match_line($$) { + # file to be checked and pattern to check for. + my ($file,$pattern) = @_; + # if the file is readable then + if(-r $file) { + # if the file can be opened then + if(open FILE,"<$file") { + # look at each line in the file + while (my $line = <FILE>) { + # if a line matches the pattern provided then + if($line =~ $pattern) { + # return the pattern was found + B_log('DEBUG','Pattern: ' . $pattern . ' matched in file: ' . + $file . "\n"); + return 1; + } + } + } + # if the file cann't be opened then + else { + # send a note to that affect to the errorlog + &B_log("ERROR","Unable to open file for read.\n$file\n$!\n"); + } + } + B_log('DEBUG','Pattern: ' . $pattern . ' not matched in file: ' . + $file . "\n"); + # the provided pattern was not matched against a line in the file + return 0; +} + +########################################################################### +# &B_match_line_only($file,$pattern); +# +# This subroutine checks if the specified pattern can be matched and if +# it's the only content in the file. The only content means it's only but +# may have several copies in the file. +# +# return values: +# 0: pattern not in file or pattern is not the only content +# or the file is not readable +# 1: pattern is in file and it's the only content +############################################################################ +sub B_match_line_only($$) { + my ($file,$pattern) = @_; + + # if matched, set to 1 later + my $retval = 0; + + # if the file is readable then + if(-r $file) { + # if the file can be opened then + if(&B_open(*FILED, $file)) { + # pattern should be matched at least once + # pattern can not be mismatched + while (my $line = <FILED>) { + if ($line =~ $pattern) { + $retval = 1; + } + else { + &B_close(*FILED); + return 0; + } + } + } + &B_close(*FILED); + } + + return $retval; +} + +########################################################################### +# &B_return_matched_lines($file,$pattern); +# +# This subroutine returns lines in a file matching a given regular +# expression, when called in the default list mode. When called in scalar +# mode, returns the number of elements found. +########################################################################### +sub B_return_matched_lines($$) +{ + my ($filename,$pattern) = @_; + my @lines = (); + + open(READFILE, $filename); + while (<READFILE>) { + chomp; + next unless /$pattern/; + push(@lines, $_); + } + if (wantarray) + { + return @lines; + } + else + { + return scalar (@lines); + } +} + +########################################################################### +# &B_match_chunk($file,$pattern); +# +# This subroutine will return a 1 if the pattern specified can be matched +# against the file specified on a line-agnostic form. This allows for +# patterns which by necessity must match against a multi-line pattern. +# This is the natural analogue to B_replace_chunk, which was created to +# provide multi-line capability not provided by B_replace_line. +# +# return values: +# 0: pattern not in file or the file is not readable +# 1: pattern is in file +########################################################################### + +sub B_match_chunk($$) { + + my ($file,$pattern) = @_; + my @lines; + my $big_long_line; + my $retval=1; + + open CHUNK_FILE,$file; + + # Read all lines into one scalar. + @lines = <CHUNK_FILE>; + close CHUNK_FILE; + + foreach my $line ( @lines ) { + $big_long_line .= $line; + } + + # Substitution routines get weird unless last line is terminated with \n + chomp $big_long_line; + $big_long_line .= "\n"; + + # Exit if we don't find a match + unless ($big_long_line =~ $pattern) { + $retval = 0; + } + + return $retval; +} + +########################################################################### +# &B_hash_comment_line ($filename,$pattern) modifies $filename, replacing +# any lines matching $pattern with a "hash-commented" version, like this: +# +# +# finger stream tcp nowait nobody /usr/sbin/tcpd in.fingerd +# becomes: +# #finger stream tcp nowait nobody /usr/sbin/tcpd in.fingerd +# +# Also: +# tftp dgram udp wait root /usr/lbin/tftpd tftpd\ +# /opt/ignite\ +# /var/opt/ignite +# becomes: +# #tftp dgram udp wait root /usr/lbin/tftpd tftpd\ +# # /opt/ignite\ +# # /var/opt/ignite +# +# +# B_hash_comment_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +########################################################################### + +sub B_hash_comment_line($$) { + + my ($filename,$pattern) = @_; + my $retval=1; + + if ( &B_open_plus (*HASH_NEW,*HASH_OLD,$filename) ) { + my $line; + while ($line=<HASH_OLD>) { + unless ( ($line =~ $pattern) and ($line !~ /^\s*\#/) ) { + &B_print(*HASH_NEW,$line); + } + else { + &B_print(*HASH_NEW,"#$line"); + &B_log("ACTION","File modification in $filename -- hash commented line\n" . + "$line\n" . + "like this:\n" . + "#$line\n\n"); + # while the line has a trailing \ then we should also comment out the line below + while($line =~ m/\\\n$/) { + if($line=<HASH_OLD>) { + &B_print(*HASH_NEW,"#$line"); + &B_log("ACTION","File modification in $filename -- hash commented line\n" . + "$line\n" . + "like this:\n" . + "#$line\n\n"); + } + else { + $line = ""; + } + } + + } + } + &B_close_plus (*HASH_NEW,*HASH_OLD,$filename); + } + else { + $retval=0; + &B_log("ERROR","Couldn't hash-comment line(s) in $filename because open failed.\n"); + } + + return $retval; +} + + +########################################################################### +# &B_hash_uncomment_line ($filename,$pattern) modifies $filename, +# removing any commenting from lines that match $pattern. +# +# #finger stream tcp nowait nobody /usr/sbin/tcpd in.fingerd +# becomes: +# finger stream tcp nowait nobody /usr/sbin/tcpd in.fingerd +# +# +# B_hash_uncomment_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +########################################################################### + +sub B_hash_uncomment_line($$) { + + my ($filename,$pattern) = @_; + my $retval=1; + + if ( &B_open_plus (*HASH_NEW,*HASH_OLD,$filename) ) { + my $line; + while ($line=<HASH_OLD>) { + unless ( ($line =~ $pattern) and ($line =~ /^\s*\#/) ) { + &B_print(*HASH_NEW,$line); + } + else { + $line =~ /^\s*\#+(.*)$/; + $line = "$1\n"; + + &B_print(*HASH_NEW,"$line"); + &B_log("ACTION","File modification in $filename -- hash uncommented line\n"); + &B_log("ACTION",$line); + # while the line has a trailing \ then we should also uncomment out the line below + while($line =~ m/\\\n$/) { + if($line=<HASH_OLD>) { + $line =~ /^\s*\#+(.*)$/; + $line = "$1\n"; + &B_print(*HASH_NEW,"$line"); + &B_log("ACTION","File modification in $filename -- hash uncommented line\n"); + &B_log("ACTION","#$line"); + &B_log("ACTION","like this:\n"); + &B_log("ACTION","$line"); + } + else { + $line = ""; + } + } + } + } + &B_close_plus (*HASH_NEW,*HASH_OLD,$filename); + } + else { + $retval=0; + &B_log("ERROR","Couldn't hash-uncomment line(s) in $filename because open failed.\n"); + } + + return $retval; +} + + + +########################################################################### +# &B_delete_line ($filename,$pattern) modifies $filename, deleting any +# lines matching $pattern. It uses B_replace_line to do this. +# +# B_replace_line uses B_open_plus and B_close_plus, so that the file +# modified is backed up... +# +# Here an example of where you might use this: +# +# You'd like to remove any timeout= lines in /etc/lilo.conf, so that your +# delay=1 modification will work. + +# +########################################################################### + + +sub B_delete_line($$) { + + my ($filename,$pattern)=@_; + my $retval=&B_replace_line($filename,$pattern,""); + + return $retval; +} + + +########################################################################### +# &B_chunk_replace ($file,$pattern,$replacement) reads $file replacing the +# first occurrence of $pattern with $replacement. +# +########################################################################### + +sub B_chunk_replace($$$) { + + my ($file,$pattern,$replacement) = @_; + + my @lines; + my $big_long_line; + my $retval=1; + + &B_open (*OLDFILE,$file); + + # Read all lines into one scalar. + @lines = <OLDFILE>; + &B_close (*OLDFILE); + foreach my $line ( @lines ) { + $big_long_line .= $line; + } + + # Substitution routines get weird unless last line is terminated with \n + chomp $big_long_line; + $big_long_line .= "\n"; + + # Exit if we don't find a match + unless ($big_long_line =~ $pattern) { + return 0; + } + + $big_long_line =~ s/$pattern/$replacement/s; + + $retval=&B_open_plus (*NEWFILE,*OLDFILE,$file); + if ($retval) { + &B_print (*NEWFILE,$big_long_line); + &B_close_plus (*NEWFILE,*OLDFILE,$file); + } + + return $retval; +} + +########################################################################### +# &B_print ($handle,@list) prints the items of @list to the file handle +# $handle. It logs the action and respects the $GLOBAL_LOGONLY variable. +# +########################################################################### + +sub B_print { + my $handle=shift @_; + + my $result=1; + + unless ($GLOBAL_LOGONLY) { + $result=print $handle @_; + } + + ($handle) = "$handle" =~ /[^:]+::[^:]+::([^:]+)/; + + $result; +} + + +########################################################################## +# &B_getValueFromFile($regex,$file); +# Takes a regex with a single group "()" and returns the unique value +# on any non-commented lines +# This (and B_return_matched_lines are only used in this file, though are +# probably more generally useful. For now, leaving these here serve the following +#functions: +# a) still gets exported/associated as part of the Test_API package, and +# is still availble for a couple operations that can't be deferred to the +# main test loop, as they save values so that individual tests don't have to +# recreate (copy / paste) the logic to get them. +# +# It also avoids the circular "use" if we incldued "use Test API" at the top +# of this file (Test API "uses" this file. +# Returns the uncommented, unique values of a param=value pair. +# +# Return values: +# 'Not Defined' if the value is not present or not uniquely defined. +# $value if the value is present and unique +# +########################################################################### +sub B_getValueFromFile ($$){ + my $inputRegex=$_[0]; + my $file=$_[1]; + my ($lastvalue,$value)=''; + + my @lines=&B_return_matched_lines($file, $inputRegex); + + return &B_getValueFromString($inputRegex,join('/n',@lines)); +} + +########################################################################## +# &B_getValueFromString($param,$string); +# Takes a regex with a single group "()" and returns the unique value +# on any non-commented lines +# This (and B_return_matched_lines are only used in this file, though are +# probably more generally useful. For now, leaving these here serve the following +#functions: +# a) still gets exported/associated as part of the Test_API package, and +# is still availble for a couple operations that can't be deferred to the +# main test loop, as they save values so that individual tests don't have to +# recreate (copy / paste) the logic to get them. +# +# It also avoids the circular "use" if we incldued "use Test API" at the top +# of this file (Test API "uses" this file. +# Returns the uncommented, unique values of a param=value pair. +# +# Return values: +# 'Not Unique' if the value is not uniquely defined. +# undef if the value isn't defined at all +# $value if the value is present and unique +# +########################################################################### +sub B_getValueFromString ($$){ + my $inputRegex=$_[0]; + my $inputString=$_[1]; + my $lastValue=''; + my $value=''; + + my @lines=split(/\n/,$inputString); + + &B_log("DEBUG","B_getvaluefromstring called with regex: $inputRegex and input: " . + $inputString); + foreach my $line (grep(/$inputRegex/,@lines)) { + $line =~ /$inputRegex/; + $value=$1; + if (($lastValue eq '') and ($value ne '')) { + $lastValue = $value; + } elsif (($lastValue ne $value) and ($value ne '')) { + B_log("DEBUG","getvaluefromstring returned Not Unique"); + return 'Not Unique'; + } + } + if ((not(defined($value))) or ($value eq '')) { + &B_log("DEBUG","Could not find regex match in string"); + return undef; + } else { + &B_log("DEBUG","B_getValueFromString Found: $value ; using: $inputRegex"); + return $value; + } +} + +############################################################### +# This function adds something to the To Do List. +# Arguments: +# 1) The string you want to add to the To Do List. +# 2) Optional: Question whose TODOFlag should be set to indicate +# A pending manual action in subsequent reports. Only skip this +# If there's no security-audit relevant action you need the user to +# accomplish +# Ex: +# &B_TODO("------\nInstalling IPFilter\n----\nGo get Ipfilter","IPFilter.install_ipfilter"); +# +# +# Returns: +# 0 - If error condition +# True, if sucess, specifically: +# "appended" if the append operation was successful +# "exists" if no change was made since the entry was already present +############################################################### +sub B_TODO ($;$) { + my $text = $_[0]; + my $FlaggedQuestion = $_[1]; + my $multilineString = ""; + + # trim off any leading and trailing new lines, regexes separated for "clarity" + $text =~ s/^\n+(.*)/$1/; + $text =~ s/(.*)\n+$/$1/; + + if ( ! -e &getGlobal('BFILE',"TODO") ) { + # Make the TODO list file for HP-UX Distro + &B_create_file(&getGlobal('BFILE', "TODO")); + &B_append_line(&getGlobal('BFILE', "TODO"),'a$b', + "Please take the steps below to make your system more secure,\n". + "then delete the item from this file and record what you did along\n". + "with the date and time in your system administration log. You\n". + "will need that information in case you ever need to revert your\n". + "changes.\n\n"); + } + + + if (open(TODO,"<" . &getGlobal('BFILE', "TODO"))) { + while (my $line = <TODO>) { + # getting rid of all meta characters. + $line =~ s/(\\|\||\(|\)|\[|\]|\{|\}|\^|\$|\*|\+|\?|\.)//g; + $multilineString .= $line; + } + chomp $multilineString; + $multilineString .= "\n"; + + close(TODO); + } + else { + &B_log("ERROR","Unable to read TODO.txt file.\n" . + "The following text could not be appended to the TODO list:\n" . + $text . + "End of TODO text\n"); + return 0; #False + } + + my $textPattern = $text; + + # getting rid of all meta characters. + $textPattern =~ s/(\\|\||\(|\)|\[|\]|\{|\}|\^|\$|\*|\+|\?|\.)//g; + + if( $multilineString !~ "$textPattern") { + my $datestamp = "{" . localtime() . "}"; + unless ( &B_append_line(&getGlobal('BFILE', "TODO"), "", $datestamp . "\n" . $text . "\n\n\n") ) { + &B_log("ERROR","TODO Failed for text: " . $text ); + } + #Note that we only set the flag on the *initial* entry in the TODO File + #Not on subsequent detection. This is to avoid the case where Bastille + #complains on a subsequent Bastille run of an already-performed manual + #action that the user neglected to delete from the TODO file. + # It does, however lead to a report of "nonsecure" when the user + #asked for the TODO item, performed it, Bastille detected that and cleared the + # Item, and then the user unperformed the action. I think this is proper behavior. + # rwf 06/06 + + if (defined($FlaggedQuestion)) { + &B_TODOFlags("set",$FlaggedQuestion); + } + return "appended"; #evals to true, and also notes what happened + } else { + return "exists"; #evals to true, and also + } + +} + + +##################################################################### +# &B_TODOFlags() +# +# This is the interface to the TODO flags. Test functions set these when they +# require a TODO item to be completed to get to a "secure" state. +# The prune/reporting function checks these to ensure no flags are set before +# reporting an item "secure" +# "Methods" are load | save | isSet <Question> | set <Question> | unset <Question> +# +###################################################################### + +sub B_TODOFlags($;$) { + my $action = $_[0]; + my $module = $_[1]; + + use File::Spec; + + my $todo_flag = &getGlobal("BFILE","TODOFlag"); + + &B_log("DEBUG","B_TODOFlags action: $action , module: $module"); + + if ($action eq "load") { + if (-e $todo_flag ) { + &B_open(*TODO_FLAGS, $todo_flag); + my @lines = <TODO_FLAGS>; + foreach my $line (@lines) { + chomp($line); + $GLOBAL_CONFIG{"$line"}{"TODOFlag"}="yes"; + } + return (&B_close(*TODO_FLAGS)); #return success of final close + } else { + return 1; #No-op is okay + } + } elsif ($action eq "save") { + # Make sure the file exists, else create + #Note we use open_plus and and create file, so if Bastille is + #reverted, all the flags will self-clear (file deleted) + my $flagNumber = 0; + my $flagData = ''; + foreach my $key (keys %GLOBAL_CONFIG) { + if ($GLOBAL_CONFIG{$key}{"TODOFlag"} eq "yes") { + ++$flagNumber; + $flagData .= "$key\n"; + } + } + if (not( -e $todo_flag)) { + &B_log("DEBUG","Initializing TODO Flag file: $todo_flag"); + &B_create_file($todo_flag); # Make sure it exists + } + &B_blank_file($todo_flag, + "This will not appear in the file; ensures blanking"); + return &B_append_line($todo_flag, "", "$flagData"); #return success of save + } elsif (($action eq "isSet") and ($module ne "")) { + if ($GLOBAL_CONFIG{"$module"}{"TODOFlag"} eq "yes") { + return 1; #TRUE + } else { + return 0; #FALSE + } + } elsif (($action eq "set") and ($module ne "")) { + $GLOBAL_CONFIG{"$module"}{"TODOFlag"} = "yes"; + } elsif (($action eq "clear") and ($module ne "")) { + $GLOBAL_CONFIG{"$module"}{"TODOFlag"} = ""; + } else { + &B_log("ERROR","TODO_Flag Called with invalid parameters: $action , $module". + "audit report may be incorrect."); + return 0; #FALSE + } +} + +1; + + diff --git a/meta-security/recipes-security/bastille/files/HPSpecific.pm b/meta-security/recipes-security/bastille/files/HPSpecific.pm new file mode 100644 index 000000000..7e7d709fb --- /dev/null +++ b/meta-security/recipes-security/bastille/files/HPSpecific.pm @@ -0,0 +1,1983 @@ +package Bastille::API::HPSpecific; + +use strict; +use Bastille::API; +use Bastille::API::FileContent; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw( +getIPFLocation +getGlobalSwlist +B_check_system +B_swmodify +B_load_ipf_rules +B_Schedule +B_ch_rc +B_set_value +B_chperm +B_install_jail +B_list_processes +B_list_full_processes +B_deactivate_inetd_service +B_get_rc +B_set_rc +B_chrootHPapache +isSystemTrusted +isTrustedMigrationAvailable +checkServiceOnHPUX +B_get_path +convertToTrusted +isOKtoConvert +convertToShadow +getSupportedSettings +B_get_sec_value +secureIfNoNameService +isUsingRemoteNameService +remoteServiceCheck +remoteNISPlusServiceCheck +B_create_nsswitch_file +B_combine_service_results + +%priorBastilleNDD +%newNDD +); +our @EXPORT = @EXPORT_OK; + + + +# "Constants" for use both in testing and in lock-down +our %priorBastilleNDD = ( + "ip_forward_directed_broadcasts" =>["ip", "0"], + "ip_forward_src_routed" =>["ip", "0"], + "ip_forwarding" =>["ip", "0"], + "ip_ire_gw_probe" =>["ip", "0"], + "ip_pmtu_strategy" =>["ip", "1"], + "ip_respond_to_echo_broadcast" =>["ip", "0"], + "ip_send_redirects" =>["ip", "0"], + "ip_send_source_quench" =>["ip", "0"], + "tcp_syn_rcvd_max" =>["tcp","1000"], + "tcp_conn_request_max" =>["tcp","4096"] ); + +our %newNDD = ( + "ip_forward_directed_broadcasts" =>["ip", "0"], + "ip_forward_src_routed" =>["ip", "0"], + "ip_forwarding" =>["ip", "0"], + "ip_ire_gw_probe" =>["ip", "0"], + "ip_pmtu_strategy" =>["ip", "1"], + "ip_respond_to_echo_broadcast" =>["ip", "0"], + "ip_send_redirects" =>["ip", "0"], + "ip_send_source_quench" =>["ip", "0"], + "tcp_syn_rcvd_max" =>["tcp","4096"], + "tcp_conn_request_max" =>["tcp","4096"], + "arp_cleanup_interval" =>["arp","60000"], + "ip_respond_to_timestamp" =>["ip", "0"], + "ip_respond_to_timestamp_broadcast" => ["ip","0"] ); + + +#################################################################### +# +# This module makes up the HP-UX specific API routines. +# +#################################################################### +# +# Subroutine Listing: +# &HP_ConfigureForDistro: adds all used file names to global +# hashes and generates a global IPD +# hash for SD modification lookup. +# +# &getGlobalSwlist($): Takes a fully qualified file name +# and returns product:filset info +# for that file. returns undef if +# the file is not present in the IPD +# +# &B_check_system: Runs a series of system queries to +# determine if Bastille can be safely +# ran on the current system. +# +# &B_swmodify($): Takes a file name and runs the +# swmodify command on it so that the +# IPD is updated after changes +# +# &B_System($$): Takes a system command and the system +# command that should be used to revert +# whatever was done. Returns 1 on +# success and 0 on failure +# +# &B_Backtick($) Takes a command to run and returns its stdout +# to be used in place of the prior prevelent use +# of un-error-handled backticks +# +# &B_load_ipf_rules($): Loads a set of ipfrules into ipf, storing +# current rules for later reversion. +# +# &B_Schedule($$): Takes a pattern and a crontab line. +# Adds or replaces the crontab line to +# the crontab file, depending on if a +# line matches the pattern +# +# &B_ch_rc($$): Takes a the rc.config.d flag name and +# new value as well as the init script +# location. This will stop a services +# and set the service so that it will +# not be restarted. +# +# &B_set_value($$$): Takes a param, value, and a filename +# and sets the given value in the file. +# Uses ch_rc, but could be rewritten using +# Bastille API calls to make it work on Linux +# +# &B_TODO($): Appends the give string to the TODO.txt +# file. +# +# &B_chperm($$$$): Takes new perm owner and group of given +# file. TO BE DEPRECATED!!! +# +# &B_install_jail($$): Takes the jail name and the jail config +# script location for a give jail... +# These scripts can be found in the main +# directory e.g. jail.bind.hpux +# +##################################################################### + +############################################################################## +# +# HP-UX Bastille directory structure +# +############################################################################## +# +# /opt/sec_mgmt/bastille/bin/ -- location of Bastille binaries +# /opt/sec_mgmt/bastille/lib/ -- location of Bastille modules +# /opt/sec_mgmt/bastille/doc/ -- location of Bastille doc files +# +# /etc/opt/sec_mgmt/bastille/ -- location of Bastille 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 +# /var/opt/sec_mgmt/bastille/revert/backup -- directory holding the original +# files that Bastille modifies, +# with permissions intact +# +############################################################################## + +sub getIPFLocation () { # Temporary until we get defined search space support + my $ipf=&getGlobal('BIN','ipf_new'); + my $ipfstat=&getGlobal('BIN','ipfstat_new'); + if (not(-e $ipf)) { # Detect if the binaries moved + $ipf = &getGlobal('BIN','ipf'); + $ipfstat=&getGlobal('BIN','ipfstat'); + } + return ($ipf, $ipfstat); +} + +############################################## +# Given a combination of service results, provided +# in an array, this function combines the result into +# a reasonable aggregate result +############################################## + +sub B_combine_service_results(@){ + my @results = @_; + + #TODO: Consider greater sophistication wrt inconsistent, or not installed. + + foreach my $result (@results) { + if (not(($result == SECURE_CAN_CHANGE) or + ($result == SECURE_CANT_CHANGE) or + ($result == NOT_INSTALLED()))) { + return NOTSECURE_CAN_CHANGE(); + } + } + return SECURE_CANT_CHANGE(); +} + +#################################################################### +# &getGlobalSwlist ($file); +# This function returns the product and fileset information for +# a given file or directory if it exists in the IPD otherwise +# it returns undefined "undef" +# +# uses $GLOBAL_SWLIST{"$FILE"} +#################################################################### +sub getGlobalSwlist($){ + no strict; + my $file = $_[0]; + + + if(! %GLOBAL_SWLIST) { + # Generating swlist database for swmodify changes that will be required + # The database will be a hash of fully qualified file names that reference + # the files product name and fileset. These values are required to use + # swmodify... + + # Files tagged 'is_volatile' in the IPD are not entered in the swlist database + # in order to avoid invoking swmodify if the file is changed later. Attempting to + # swmodify 'volatile' files is both unneccessary and complicated since swverify will + # not evaluate volatile files anyway, and adding another value to the swlist database + # would require complex code changes. + + # temp variable to keep swlist command /usr/sbin/swlist + my $swlist = &getGlobal('BIN',"swlist"); + + # listing of each directory and file that was installed by SD on the target machine + my @fileList = `$swlist -a is_volatile -l file`; + + # listing of each patch and the patches that supersede each. + # hash which is indexed by patch.fileset on the system + my %patchSuperseded; + + my @patchList = `${swlist} -l fileset -a superseded_by *.*,c=patch 2>&1`; + # check to see if any patches are present on the system + if(($? >> 8) == 0) { + + # determining patch suppression for swmodify. + foreach my $patchState (@patchList) { + # removing empty lines and commented lines. + if($patchState !~ /^\s*\#/ && $patchState !~ /^\s*$/) { + + # removing leading white space + $patchState =~ s/^\s+//; + my @patches = split /\s+/, $patchState; + if($#patches == 0){ + # patch is not superseded + $patchSuperseded{$patches[0]} = 0; + } + else { + # patch is superseded + $patchSuperseded{$patches[0]} = 1; + } + } + } + } + else { + &B_log("DEBUG","No patches found on the system.\n"); + } + + if($#fileList >= 0){ + # foreach line of swlist output + foreach my $fileEntry ( @fileList ){ + #filter out commented portions + if( $fileEntry !~ /^\s*\#/ ){ + chomp $fileEntry; + # split the output into three fields: product.fileset, filename, flag_isvolatile + my( $productInfo, $file, $is_volatile ) = $fileEntry =~ /^\s*(\S+): (\S+)\t(\S+)/ ; + # do not register volatile files + next if ($is_volatile =~ /true/); # skip to next file entry + $productInfo =~ s/\s+//; + $file =~ s/\s+//; + # if the product is a patch + if($productInfo =~ /PH(CO|KL|NE|SS)/){ + # if the patch is not superseded by another patch + if($patchSuperseded{$productInfo} == 0){ + # add the patch to the list of owner for this file + push @{$GLOBAL_SWLIST{"$file"}}, $productInfo; + } + } + # not a patch. + else { + # add the product to the list of owners for this file + push @{$GLOBAL_SWLIST{"$file"}}, $productInfo; + } + + } + } + } + else{ + # defining GLOBAL_SWLIST in error state. + $GLOBAL_SWLIST{"ERROR"} = "ERROR"; + &B_log("ERROR","Could not execute swlist. Swmodifys will not be attempted"); + } + } + + if(exists $GLOBAL_SWLIST{"$file"}){ + return $GLOBAL_SWLIST{"$file"}; + } + else { + return undef; + } +} + +################################################################### +# &B_check_system; +# This subroutine is called to validate that bastille may be +# safely run on the current system. It will check to insure +# that there is enough file system space, mounts are rw, nfs +# mounts are not mounted noroot, and swinstall, swremove and +# swmodify are not running +# +# uses ErrorLog +# +################################################################## +sub B_check_system { + # exitFlag is one if a conflict with the successful execution + # of bastille is found. + my $exitFlag = 0; + + my $ignoreCheck = &getGlobal("BDIR","config") . "/.no_system_check"; + if( -e $ignoreCheck ) { + return $exitFlag; + } + + # first check for swinstall, swmodify, or swremove processes + my $ps = &getGlobal('BIN',"ps") . " -el"; + my @processTable = `$ps`; + foreach my $process (@processTable) { + if($process =~ /swinstall/ ) { + &B_log("ERROR","Bastille cannot run while a swinstall is in progress.\n" . + "Complete the swinstall operation and then run Bastille.\n\n"); + $exitFlag = 1; + } + + if($process =~ /swremove/ ) { + &B_log("ERROR","Bastille cannot run while a swremove is in progress.\n" . + "Complete the swremove operation and then run Bastille.\n\n"); + $exitFlag = 1; + } + + if($process =~ /swmodify/ ) { + &B_log("ERROR","Bastille cannot run while a swmodify is in progress.\n" . + "Complete the swmodify operation and then run Bastille.\n\n"); + $exitFlag = 1; + } + + } + + # check for root read only mounts for /var /etc /stand / + # Bastille is required to make changes to these file systems. + my $mount = &getGlobal('BIN',"mount"); + my $rm = &getGlobal('BIN',"rm"); + my $touch = &getGlobal('BIN',"touch"); + + my @mnttab = `$mount`; + + if(($? >> 8) != 0) { + &B_log("WARNING","Unable to use $mount to determine if needed partitions\n" . + "are root writable, based on disk mount options.\n" . + "Bastille will continue but note that disk\n" . + "mount checks were skipped.\n\n"); + } + else { + foreach my $record (@mnttab) { + my @fields = split /\s+/, $record; + if ((defined $fields[0]) && (defined $fields[2]) && (defined $fields[3])) { + my $mountPoint = $fields[0]; + my $mountType = $fields[2]; + my $mountOptions = $fields[3]; + + # checks for /stand and /var/* removed + if($mountPoint =~ /^\/$|^\/etc|^\/var$/) { + + if($mountOptions =~ /^ro,|,ro,|,ro$/) { + &B_log("ERROR","$mountPoint is mounted read-only. Bastille needs to make\n" . + "modifications to this file system. Please remount\n" . + "$mountPoint read-write and then run Bastille again.\n\n"); + $exitFlag = 1; + } + # looking for an nfs mounted file system + if($mountType =~/.+:\//){ + my $fileExisted=0; + if(-e "$mountPoint/.bastille") { + $fileExisted=1; + } + + `$touch $mountPoint/.bastille 1>/dev/null 2>&1`; + + if( (! -e "$mountPoint/.bastille") || (($? >> 8) != 0) ) { + &B_log("ERROR","$mountPoint is an nfs mounted file system that does\n" . + "not allow root to write to. Bastille needs to make\n" . + "modifications to this file system. Please remount\n" . + "$mountPoint giving root access and then run Bastille\n" . + "again.\n\n"); + + $exitFlag = 1; + } + # if the file did not exist befor the touch then remove the generated file + if(! $fileExisted) { + `$rm -f $mountPoint/.bastille 1>/dev/null 2>&1`; + } + } + } + } + else { + &B_log("WARNING","Unable to use $mount to determine if needed partitions\n" . + "are root writable, based on disk mount options.\n" . + "Bastille will continue but note that disk\n" . + "mount checks were skipped.\n\n"); + } + } + + } + + # checks for enough disk space in directories that Bastille writes to. + my $bdf = &getGlobal('BIN',"bdf"); + #directories that Bastille writes to => required space in kilobytes. + my %bastilleDirs = ( "/etc/opt/sec_mgmt/bastille" => "4", "/var/opt/sec_mgmt/bastille"=> "1000"); + for my $directory (sort keys %bastilleDirs) { + my @diskUsage = `$bdf $directory`; + + if(($? >> 8) != 0) { + &B_log("WARNING","Unable to use $bdf to determine disk usage for\n" . + "$directory\n" . + "Bastille will continue but note that disk\n" . + "usage checks were skipped.\n\n"); + + } + else { + # removing bdf header line from usage information. + shift @diskUsage; + my $usageString= ""; + + foreach my $usageRecord (@diskUsage) { + chomp $usageRecord; + $usageString .= $usageRecord; + } + + $usageString =~ s/^\s+//; + + my @fields = split /\s+/, $usageString; + if($#fields != 5) { + &B_log("WARNING","Unable to use $bdf to determine disk usage for\n" . + "$directory\n" . + "Bastille will continue but note that disk\n" . + "usage checks were skipped.\n\n"); + } + else { + + my $mountPoint = $fields[5]; + my $diskAvail = $fields[3]; + + if($diskAvail <= $bastilleDirs{"$directory"}) { + &B_log("ERROR","$mountPoint does not contain enough available space\n" . + "for Bastille to run properly. $directory needs\n" . + "at least $bastilleDirs{$directory} kilobytes of space.\n" . + "Please clear at least that amount of space from\n" . + "$mountPoint and run Bastille again.\n" . + "Current Free Space available = ${diskAvail} k\n\n"); + $exitFlag = 1; + } + } + } + } + + # check to make sure that we are in at least run level 2 before we attempt to run + my $who = &getGlobal('BIN', "who") . " -r"; + my $levelInfo = `$who`; + if(($? >> 8) != 0 ) { + &B_log("WARNING","Unable to use \"$who\" to determine system run.\n" . + "level Bastille will continue but note that the run\n" . + "level check was skipped.\n\n"); + } + else { + chomp $levelInfo; + my @runlevel = split /\s+/, $levelInfo; + if ((! defined $runlevel[3]) or ($runlevel[3] < 2)) { + &B_log("WARNING","Bastille requires a run-level of 2 or more to run properly.\n" . + "Please move your system to a higher run level and then\n" . + "run 'bastille -b'.\n\n"); + if(defined $runlevel[3]) { + &B_log("ERROR","Current run-level is '$runlevel[3]'.\n\n"); + $exitFlag=1; + } + else { + &B_log("WARNING","Unable to use \"$who\" to determine system run.\n" . + "level Bastille will continue but note that the run\n" . + "level check was skipped.\n\n"); + } + } + else { + &B_log("DEBUG","System run-level is $runlevel[3]\n"); + } + } + + if($exitFlag) { + exit(1); + } + +} + +################################################################### +# &B_swmodify($file); +# This subroutine is called after a file is modified. It will +# redefine the file in the IPD with it's new properties. If +# the file is not in the IPD it does nothing. +# +# uses B_System to make the swmodifications. +################################################################## +sub B_swmodify($){ + my $file = $_[0]; + if(defined &getGlobalSwlist($file)){ + my $swmodify = &getGlobal('BIN',"swmodify"); + my @productsInfo = @{&getGlobalSwlist($file)}; + # running swmodify on files that were altered by this function but + # were created and maintained by SD + foreach my $productInfo (@productsInfo) { + &B_System("$swmodify -x files='$file' $productInfo", + "$swmodify -x files='$file' $productInfo"); + } + } +} + +#################################################################### +# &B_load_ipf_rules($ipfruleset); +# This function enables an ipfruleset. It's a little more +# specific than most API functions, but necessary because +# ipf doesn't return correct exit codes (syntax error results +# in a 0 exit code) +# +# uses ActionLog and ErrorLog to log +# calls crontab directly (to list and to read in new jobs) +################################################################### +sub B_load_ipf_rules ($) { + my $ipfruleset=$_[0]; + + &B_log("DEBUG","# sub B_load_ipf_rules"); + + # TODO: grab ipf.conf dynamically from the rc.config.d files + my $ipfconf = &getGlobal('FILE','ipf.conf'); + + # file system changes - these are straightforward, and the API + # will take care of the revert + &B_create_file($ipfconf); + &B_blank_file($ipfconf, 'a$b'); + &B_append_line($ipfconf, 'a$b', $ipfruleset); + + # runtime changes + + # define binaries + my $grep = &getGlobal('BIN', 'grep'); + my ($ipf, $ipfstat) = &getIPFLocation; + # create backup rules + # This will exit with a non-zero exit code because of the grep + my @oldrules = `$ipfstat -io 2>&1 | $grep -v empty`; + + my @errors=`$ipf -I -Fa -f $ipfconf 2>&1`; + + if(($? >> 8) == 0) { + + &B_set_rc("IPF_START","1"); + &B_set_rc("IPF_CONF","$ipfconf"); + + # swap the rules in + &B_System("$ipf -s","$ipf -s"); + + # now create a "here" document with the previous version of + # the rules and put it into the revert-actions script + &B_revert_log("$ipf -I -Fa -f - <<EOF\n@{oldrules}EOF"); + + if (@errors) { + &B_log("ERROR","ipfilter produced the following errors when\n" . + " loading $ipfconf. You probably had an invalid\n" . + " rule in ". &getGlobal('FILE','customipfrules') ."\n". + "@errors\n"); + } + + } else { + &B_log("ERROR","Unable to run $ipf\n"); + } + +} + + + +#################################################################### +# &B_Schedule($pattern,$cronjob); +# This function schedules a cronjob. If $pattern exists in the +# crontab file, that job will be replaced. Otherwise, the job +# will be appended. +# +# uses ActionLog and ErrorLog to log +# calls crontab directly (to list and to read in new jobs) +################################################################### +sub B_Schedule ($$) { + my ($pattern,$cronjob)=@_; + $cronjob .= "\n"; + + &B_log("DEBUG","# sub B_Schedule"); + my $crontab = &getGlobal('BIN','crontab'); + + my @oldjobs = `$crontab -l 2>/dev/null`; + my @newjobs; + my $patternfound=0; + + foreach my $oldjob (@oldjobs) { + if (($oldjob =~ m/$pattern/ ) and (not($patternfound))) { + push @newjobs, $cronjob; + $patternfound=1; + &B_log("ACTION","changing existing cron job which matches $pattern with\n" . + "$cronjob"); + } elsif ($oldjob !~ m/$pattern/ ) { + &B_log("ACTION","keeping existing cron job $oldjob"); + push @newjobs, $oldjob; + } #implied: else if pattern matches, but we've + #already replaced one, then toss the others. + } + + unless ($patternfound) { + &B_log("ACTION","adding cron job\n$cronjob\n"); + push @newjobs, $cronjob; + } + + if(open(CRONTAB, "|$crontab - 2> /dev/null")) { + print CRONTAB @newjobs; + + # now create a "here" document with the previous version of + # the crontab file and put it into the revert-actions script + &B_revert_log("$crontab <<EOF\n" . "@oldjobs" . "EOF"); + close CRONTAB; + } + + # Now check to make sure it happened, since cron will exit happily + # (retval 0) with no changes if there are any syntax errors + my @editedjobs = `$crontab -l 2>/dev/null`; + + if (@editedjobs ne @newjobs) { + &B_log("ERROR","failed to add cron job:\n$cronjob\n" . + " You probably had an invalid crontab file to start with."); + } + +} + + +#This function turns off a service, given a service name defined in HP-UX.service + +sub B_ch_rc($) { + + my ($service_name)=@_; + + if (&GetDistro != "^HP-UX") { + &B_log("ERROR","Tried to call ch_rc $service_name on a non-HP-UX\n". + " system! Internal Bastille error."); + return undef; + } + my $configfile=""; + my $command = &getGlobal('BIN', 'ch_rc'); + + my $startup_script=&getGlobal('DIR','initd') . "/". $service_name; + my @rc_parameters= @{ &getGlobal('SERVICE',$service_name) }; + my @rcFiles=@{ &getGlobal('RCCONFIG',$service_name) }; + my $rcFile=''; + if (@rcFiles == 1){ + $rcFile=$rcFiles[0]; + } else { + &B_log("FATAL","Multiple RC Files not yet supported... internal error."); + } + + # if the service-related process is not run, and the control variable is stilll 1 + # there is a inconsistency. in this case we only need to change the control variable + my @psnames=@{ &getGlobal('PROCESS',$service_name)}; + my @processes; + foreach my $psname (@psnames) { + $psname .= '\b'; # avoid embedded match; anchor search pattern to trailing word boundry + my @procList = &isProcessRunning($psname); + if(@procList >= 0){ + splice @processes,$#processes+1,0,@procList; + } + } +#Actually set the rc variable + foreach my $rcVariable (@rc_parameters){ + my $orig_value = &B_get_rc($rcVariable); + if ($orig_value eq "" ) { #If variable not set, used the defined file + $configfile=&getGlobal("DIR","rc.config.d") . "/" . $rcFile; + if (not( -f $configfile )) { + &B_create_file($configfile); + } + } + &B_log("DEBUG","In B_ch_rc (no procs), setting $rcVariable to 0 in $configfile" . + ", with an original value of $orig_value with rcfile: $rcFile"); + if ( ! @processes) { # IF there are no processes we don't neet to perform a "stop" + &B_set_rc($rcVariable, "0", $configfile); + } else { + if ( $orig_value !~ "1" ) { #If param is not already 1, the "stop" script won't work + &B_set_rc($rcVariable, "1",$configfile); + } + &B_System ($startup_script . " stop", #stop service, then restart if the user runs bastille -r + $startup_script . " start"); + # set parameter, so that service will stay off after reboots + &B_set_rc($rcVariable, "0", $configfile); + } + } +} + + +# This routine sets a value in a given file +sub B_set_value($$$) { + my ($param, $value, $file)=@_; + + &B_log("DEBUG","B_set_value: $param, $value, $file"); + if (! -e $file ) { + &B_create_file("$file"); + } + + # If a value is already set to something other than $value then reset it. + #Note that though this tests for "$value ="the whole line gets replaced, so + #any pre-existing values are also replaced. + &B_replace_line($file,"^$param\\s*=\\s*","$param=$value\n"); + # If the value is not already set to something then set it. + &B_append_line($file,"^$param\\s*=\\s*$value","$param=$value\n"); + +} + + +################################################################################## +# &B_chperm($owner,$group,$mode,$filename(s)) +# This function changes ownership and mode of a list of files. Takes four +# arguments first the owner next the group and third the new mode in oct and +# last a list of files that the permissions changes should take affect on. +# +# uses: &swmodify and &B_revert_log +################################################################################## +sub B_chperm($$$$) { + my ($newown, $newgrp, $newmode, $file_expr) = @_; + my @files = glob($file_expr); + + my $return = 1; + + foreach my $file (@files){ + my @filestat = stat $file; + my $oldmode = (($filestat[2]/512) % 8) . + (($filestat[2]/64) % 8) . + (($filestat[2]/8) % 8) . + (($filestat[2]) % 8); + + if((chown $newown, $newgrp, $file) != 1 ){ + &B_log("ERROR","Could not change ownership of $file to $newown:$newgrp\n"); + $return = 0; + } + else{ + &B_log("ACTION","Changed ownership of $file to $newown:$newgrp\n"); + # swmodifying file if possible... + &B_swmodify($file); + &B_revert_log(&getGlobal('BIN',"chown") . " $filestat[4]:$filestat[5] $file\n"); + } + + my $newmode_formatted=sprintf "%5lo",$newmode; + + if((chmod $newmode, $file) != 1){ + &B_log("ERROR","Could not change mode of $file to $newmode_formatted\n"); + $return = 0; + } + else{ + &B_log("ACTION","Changed mode of $file to $newmode_formatted\n"); + &B_revert_log(&getGlobal('BIN',"chmod") . " $oldmode $file\n"); + } + + + } + return $return; +} + +############################################################################ +# &B_install_jail($jailname, $jailconfigfile); +# This function takes two arguments ( jail_name, jail_config ) +# It's purpose is to take read in config files that define a +# chroot jail and then generate it bases on that specification +############################################################################ +sub B_install_jail($$) { + + my $jailName = $_[0]; # Name of the jail e.g bind + my $jailConfig = $_[1]; # Name of the jails configuration file + # create the root directory of the jail if it does not exist + &B_create_dir( &getGlobal('BDIR','jail')); + &B_chperm(0,0,0555,&getGlobal('BDIR','jail')); + + # create the Jail dir if it does not exist + &B_create_dir( &getGlobal('BDIR','jail') . "/" . $jailName); + &B_chperm(0,0,0555,&getGlobal('BDIR','jail') . "/". $jailName); + + + my $jailPath = &getGlobal('BDIR','jail') . "/" . $jailName; + my @lines; # used to store no commented no empty config file lines + # open configuration file for desired jail and parse in commands + if(open(JAILCONFIG,"< $jailConfig")) { + while(my $line=<JAILCONFIG>){ + if($line !~ /^\s*\#|^\s*$/){ + chomp $line; + push(@lines,$line); + } + } + close JAILCONFIG; + } + else{ + &B_log("ERROR","Open Failed on filename: $jailConfig\n"); + return 0; + } + # read through commands and execute + foreach my $line (@lines){ + &B_log("ACTION","Install jail: $line\n"); + my @confCmd = split /\s+/,$line; + if($confCmd[0] =~ /dir/){ # if the command say to add a directory + if($#confCmd == 4) { # checking dir Cmd form + if(! (-d $jailPath . "/" . $confCmd[1])){ + #add a directory and change its permissions according + #to the conf file + &B_create_dir( $jailPath . "/" . $confCmd[1]); + &B_chperm((getpwnam($confCmd[3]))[2], + (getgrnam($confCmd[4]))[2], + oct($confCmd[2]), + $jailPath . "/" . $confCmd[1]); + } + } + else { + &B_log("ERROR","Badly Formed Configuration Line:\n$line\n\n"); + } + } + elsif($confCmd[0] =~ /file/) { + if($#confCmd == 5) { # checking file cmd form + if(&B_cp($confCmd[1],$jailPath . "/" . $confCmd[2])){ + # for copy command cp file and change perms + &B_chperm($confCmd[4],$confCmd[5],oct($confCmd[3]),$jailPath . "/" . $confCmd[2]); + } + else { + &B_log("ERROR","Could not complete copy on specified files:\n" . + "$line\n"); + } + } + else { + &B_log("ERROR","Badly Formed Configuration Line:\n" . + "$line\n\n"); + } + } + elsif($confCmd[0] =~ /slink/) { + if($#confCmd == 2) { # checking file cmd form + if(!(-e $jailPath . "/" . $confCmd[2])){ + #for symlink command create the symlink + &B_symlink($jailPath . "/" . $confCmd[1], $confCmd[2]); + } + } + else { + &B_log("ERROR","Badly Formed Configuration Line:\n" . + "$line\n\n"); + } + } + else { + &B_log("ERROR","Unrecognized Configuration Line:\n" . + "$line\n\n"); + } + } + return 1; +} + + + +########################################################################### +# &B_list_processes($service) # +# # +# This subroutine uses the GLOBAL_PROCESS hash to determine if a # +# service's corresponding processes are running on the system. # +# If any of the processes are found to be running then the process # +# name(s) is/are returned by this subroutine in the form of an list # +# If none of the processes that correspond to the service are running # +# then an empty list is returned. # +########################################################################### +sub B_list_processes($) { + + # service name + my $service = $_[0]; + # list of processes related to the service + my @processes=@{ &getGlobal('PROCESS',$service)}; + + # current systems process information + my $ps = &getGlobal('BIN',"ps"); + my $psTable = `$ps -elf`; + + # the list to be returned from the function + my @running_processes; + + # for every process associated with the service + foreach my $process (@processes) { + # if the process is in the process table then + if($psTable =~ m/$process/) { + # add the process to the list, which will be returned + push @running_processes, $process; + } + + } + + # return the list of running processes + return @running_processes; + +} + +############################################################################# +# &B_list_full_processes($service) # +# # +# This subroutine simply grep through the process table for those matching # +# the input argument TODO: Allow B_list process to levereage this code # +# ... Not done this cycle to avoid release risk (late in cycle) # +############################################################################# +sub B_list_full_processes($) { + + # service name + my $procName = $_[0]; + my $ps = &getGlobal('BIN',"ps"); + my @psTable = split(/\n/,`$ps -elf`); + + # for every process associated with the service + my @runningProcessLines = grep(/$procName/ , @psTable); + # return the list of running processes + return @runningProcessLines; +} + +################################################################################ +# &B_deactivate_inetd_service($service); # +# # +# This subroutine will disable all inetd services associated with the input # +# service name. Service name must be a reference to the following hashes # +# GLOBAL_SERVICE GLOBAL_SERVTYPE and GLOBAL_PROCESSES. If processes are left # +# running it will note these services in the TODO list as well as instruct the# +# user in how they remaining processes can be disabled. # +################################################################################ +sub B_deactivate_inetd_service($) { + my $service = $_[0]; + my $servtype = &getGlobal('SERVTYPE',"$service"); + my $inetd_conf = &getGlobal('FILE',"inetd.conf"); + + # check the service type to ensure that it can be configured by this subroutine. + if($servtype ne 'inet') { + &B_log("ACTION","The service \"$service\" is not an inet service so it cannot be\n" . + "configured by this subroutine\n"); + return 0; + } + + # check for the inetd configuration files existence so it may be configured by + # this subroutine. + if(! -e $inetd_conf ) { + &B_log("ACTION","The file \"$inetd_conf\" cannot be located.\n" . + "Unable to configure inetd\n"); + return 0; + } + + # list of service identifiers present in inetd.conf file. + my @inetd_entries = @{ &getGlobal('SERVICE',"$service") }; + + foreach my $inetd_entry (@inetd_entries) { + &B_hash_comment_line($inetd_conf, "^\\s*$inetd_entry"); + } + + # list of processes associated with this service which are still running + # on the system + my @running_processes = &B_list_processes($service); + + if($#running_processes >= 0) { + my $todoString = "\n" . + "---------------------------------------\n" . + "Deactivating Inetd Service: $service\n" . + "---------------------------------------\n" . + "The following process(es) are associated with the inetd service \"$service\".\n" . + "They are most likely associated with a session which was initiated prior to\n" . + "running Bastille. To disable a process see \"kill(1)\" man pages or reboot\n" . + "the system\n" . + "Active Processes:\n" . + "###################################\n"; + foreach my $running_process (@running_processes) { + $todoString .= "\t$running_process\n"; + } + $todoString .= "###################################\n"; + + &B_TODO($todoString); + } + +} + + +################################################################################ +# B_get_rc($key); # +# # +# This subroutine will use the ch_rc binary to get rc.config.d variables # +# values properly escaped and quoted. # +################################################################################ +sub B_get_rc($) { + + my $key=$_[0]; + my $ch_rc = &getGlobal('BIN',"ch_rc"); + + # get the current value of the given parameter. + my $currentValue=`$ch_rc -l -p $key`; + chomp $currentValue; + + if(($? >> 8) == 0 ) { + # escape all meta characters. + # $currentValue =~ s/([\"\`\$\\])/\\$1/g; + # $currentValue = '"' . $currentValue . '"'; + } + else { + return undef; + } + + return $currentValue; +} + + + +################################################################################ +# B_set_rc($key,$value); # +# # +# This subroutine will use the ch_rc binary to set rc.config.d variables. As # +# well as setting the variable this subroutine will set revert strings. # +# # +################################################################################ +sub B_set_rc($$;$) { + + my ($key,$value,$configfile)=@_; + my $ch_rc = &getGlobal('BIN',"ch_rc"); + + # get the current value of the given parameter. + my $currentValue=&B_get_rc($key); + if(defined $currentValue ) { + if ($currentValue =~ /^\"(.*)\"$/ ) { + $currentValue = '"\"' . $1 . '\""'; + } + if ($value =~ /^\"(.*)\"$/ ) { + $value = '"\"' . $1 . '\""'; + } + if ( &B_System("$ch_rc -a -p $key=$value $configfile", + "$ch_rc -a -p $key=$currentValue $configfile") ) { + #ch_rc success + return 1; + } + else { + #ch_rc failure. + return 0; + } + } + else { + &B_log("ERROR","ch_rc was unable to lookup $key\n"); + return 0; + } + +} + + +################################################################################ +# &ChrootHPApache($chrootScript,$httpd_conf,$httpd_bin, +# $apachectl,$apacheJailDir,$serverString); +# +# This subroutine given an chroot script, supplied by the vendor, a +# httpd.conf file, the binary location of httpd, the control script, +# the jail directory, and the servers identification string, descriptive +# string for TODO etc. It makes modifications to httpd.conf so that when +# Apache starts it will chroot itself into the jail that the above +# mentions script creates. +# +# uses B_replace_line B_create_dir B_System B_TODO +# +############################################################################### +sub B_chrootHPapache($$$$$$) { + + my ($chrootScript,$httpd_conf,$httpd_bin,$apachectl,$apacheJailDir,$serverString)= @_; + + my $exportpath = "export PATH=/usr/bin;"; + my $ps = &getGlobal('BIN',"ps"); + my $isRunning = 0; + my $todo_header = 0; + + # checking for a 2.0 version of the apache chroot script. + if(-e $chrootScript ) { + + if(open HTTPD, $httpd_conf) { + while (my $line = <HTTPD>){ + if($line =~ /^\s*Chroot/) { + &B_log("DEBUG","Apache is already running in a chroot as specified by the following line:\n$line\n" . + "which appears in the httpd.conf file. No Apache Chroot action was taken.\n"); + return; + } + } + close(HTTPD); + } + + if(`$ps -ef` =~ $httpd_bin ) { + $isRunning=1; + &B_System("$exportpath " . $apachectl . " stop","$exportpath " . $apachectl . " start"); + } + &B_replace_line($httpd_conf, '^\s*#\s*Chroot' , + "Chroot " . $apacheJailDir); + if(-d &getGlobal('BDIR',"jail")){ + &B_log("DEBUG","Jail directory already exists. No action taken.\n"); + } + else{ + &B_log("ACTION","Jail directory was created.\n"); + &B_create_dir( &getGlobal('BDIR','jail')); + } + + if(-d $apacheJailDir){ + &B_log("DEBUG","$serverString jail already exists. No action taken.\n"); + } + else{ + &B_System(&getGlobal('BIN',"umask") . " 022; $exportpath " . $chrootScript, + &getGlobal('BIN',"echo") . " \"Your $serverString is now running outside of it's\\n" . + "chroot jail. You must manually migrate your web applications\\n" . + "back to your Apache server's httpd.conf defined location(s).\\n". + "After you have completed this, feel free to remove the jail directories\\n" . + "from your machine. Your apache jail directory is located in\\n" . + &getGlobal('BDIR',"jail") . "\\n\" >> " . &getGlobal('BFILE',"TOREVERT")); + + } + if($isRunning){ + &B_System("$exportpath " . $apachectl . " start","$exportpath " . $apachectl . " stop"); + &B_log("ACTION","$serverString is now running in an chroot jail.\n"); + } + + &B_log("ACTION","The jail is located in " . $apacheJailDir . "\n"); + + if ($todo_header !=1){ + &B_TODO("\n---------------------------------\nApache Chroot:\n" . + "---------------------------------\n"); + } + &B_TODO("$serverString Chroot Jail:\n" . + "httpd.conf contains the Apache dependencies. You should\n" . + "review this file to ensure that the dependencies made it\n" . + "into the jail. Otherwise, you run a risk of your Apache server\n" . + "not having access to all its modules and functionality.\n"); + + + } + +} + + +sub isSystemTrusted { + my $getprdef = &getGlobal('BIN',"getprdef"); + my $definition = &B_Backtick("$getprdef -t 2>&1"); + if($definition =~ "System is not trusted.") { + return 0; + } else { + return 1; + } +} + + +sub isTrustedMigrationAvailable { + my $distroVersion=''; + + if (&GetDistro =~ '^HP-UX11.(\d*)') { + $distroVersion=$1; + if ($distroVersion < 23) { # Not available before 11.23 + return 0; #FALSE + } elsif ($distroVersion >= 31) { #Bundled with 11.31 and after + &B_log('DEBUG','isTrustedMigrationAvailable: HP-UX 11.31 always has trusted mode extensions'); + return 1; + } elsif ($distroVersion == 23) { # Optional on 11.23 if filesets installed + if ( -x &getGlobal('BIN',"userdbget") ) { + &B_log('DEBUG','isTrustedMigrationAvailable: Trusted Extensions Installed'); + return 1; + } else { + &B_log('DEBUG','isTrustedMigrationAvailable: Trusted Extensions Not Installed'); + return 0; #FALSE + } + } else { + &B_log('DEBUG','isTrustedMigrationAvailable: ' . &GetDistro . + ' not currently supported for trusted extentions.'); + return 0; #FALSE + } + } else { + &B_log('WARNING','isTrustedMigrationAvailable: HP-UX routine called on Linux system'); + return 0; #FALSE + } +} + + + +########################################################################### +# &checkServiceOnHPUX($service); +# +# Checks if the given service is running on an HP/UX system. This is +# called by B_is_Service_Off(), which is the function that Bastille +# modules should call. +# +# Return values: +# NOTSECURE_CAN_CHANGE() if the service is on +# SECURE_CANT_CHANGE() if the service is off +# INCONSISTENT() if the state of the service cannot be determined +# NOT_INSTALLED() if the s/w isn't insalled +# +########################################################################### +sub checkServiceOnHPUX($) { + my $service=$_[0]; + + # get the list of parameters which could be used to initiate the service + # (could be in /etc/rc.config.d, /etc/inetd.conf, or /etc/inittab, so we + # check all of them) + my @params= @{ &getGlobal('SERVICE',$service) }; + my $grep =&getGlobal('BIN', 'grep'); + my $inetd=&getGlobal('FILE', 'inetd.conf'); + my $inittab=&getGlobal('FILE', 'inittab'); + my $retVals; + my $startup=&getGlobal('DIR','initd') ; + my @inet_bins= @{ &getGlobal('PROCESS',$service) }; + + my $entry_found = 0; + + &B_log("DEBUG","CheckHPUXservice: $service"); + my $full_initd_path = $startup . "/" . $service; + if ($GLOBAL_SERVTYPE{$service} eq "rc") { # look for the init script in /sbin/init.d + if (not(-e $full_initd_path )) { + return NOT_INSTALLED(); + } + } else { #inet-based service, so look for inetd.conf entries. + &B_log("DEBUG","Checking inet service $service"); + my @inet_entries= @{ &getGlobal('SERVICE',$service) }; + foreach my $service (@inet_entries) { + &B_log('DEBUG',"Checking for inetd.conf entry of $service in checkService on HPUX"); + my $service_regex = '^[#\s]*' . $service . '\s+'; + if ( &B_match_line($inetd, $service_regex) ) { # inet entry search + &B_log('DEBUG',"$service present, entry exists"); + $entry_found = 1 ; + } + } + if ($entry_found == 0 ) { + return NOT_INSTALLED(); + } + } + + foreach my $param (@params) { + &B_log("DEBUG","Checking to see if service $service is off.\n"); + if (&getGlobal('SERVTYPE', $service) =~ /rc/) { + my $ch_rc=&getGlobal('BIN', 'ch_rc'); + my $on=&B_Backtick("$ch_rc -l -p $param"); + + $on =~ s/\s*\#.*$//; # remove end-of-line comments + $on =~ s/^\s*\"(.+)\"\s*$/$1/; # remove surrounding double quotes + $on =~ s/^\s*\'(.+)\'\s*$/$1/; # remove surrounding single quotes + $on =~ s/^\s*\"(.+)\"\s*$/$1/; # just in case someone did '"blah blah"' + + chomp $on; + &B_log("DEBUG","ch_rc returned: $param=$on in checkServiceOnHPUX"); + + if ($on =~ /^\d+$/ && $on != 0) { + # service is on + &B_log("DEBUG","CheckService found $param service is set to \'on\' in scripts."); + return NOTSECURE_CAN_CHANGE(); + } + elsif($on =~ /^\s*$/) { + # if the value returned is an empty string return + # INCONSISTENT(), since we don't know what the hard-coded default is. + return INCONSISTENT(); + } + } else { + # those files which rely on comments to determine what gets + # turned on, such as inetd.conf and inittab + my $inettabs=&B_Backtick("$grep -e '^[[:space:]]*$param' $inetd $inittab"); + if ($inettabs =~ /.+/) { # . matches anything except newlines + # service is not off + &B_log("DEBUG","Checking inetd.conf and inittab; found $inettabs"); + ########################### BREAK out, don't skip question + return NOTSECURE_CAN_CHANGE(); + } + } + } # foreach $param + + # boot-time parameters are not set; check processes + # checkprocs for services returns INCONSISTENT() if a service is found + # since a found-service is inconsistent with the above checks. + B_log("DEBUG","Boot-Parameters not set, checking processes."); + if (&runlevel < 2) { # Below runlevel 2, it is unlikely that + #services will be running, so just check "on-disk" state + &B_log("NOTE","Running during boot sequence, so skipping process checks"); + return SECURE_CANT_CHANGE(); + } else { + return &checkProcsForService($service); + } +} + +sub runlevel { + my $who = &getGlobal("BIN", "who"); + my $runlevel = &B_Backtick("$who -r"); + if ($runlevel =~ s/.* run-level (\S).*/$1/) { + &B_log("DEBUG","Runlevel is: $runlevel"); + return $runlevel; + } else { + &B_log("WARNING","Can not determine runlevel, assuming runlevel 3"); + &B_log("DEBUG","Runlevel command output: $runlevel"); + return "3"; #safer since the who command didn't work, we'll assume + # runlevel 3 since that provides more checks. + } +} + +# +# given a profile file, it will return a PATH array set by the file. +# +sub B_get_path($) { + my $file = $_[0]; + my $sh = &getGlobal("BIN", "sh"); + # use (``)[0] is becuase, signal 0 maybe trapped which will produce some stdout + my $path = (`$sh -c '. $file 1>/dev/null 2>&1 < /dev/null ; echo \$PATH'`)[0]; + my @path_arr = split(":", $path); + my %tmp_path; + my %path; + for my $tmpdir (@path_arr) { + chomp $tmpdir; + if ($tmpdir ne "" && ! $tmp_path{$tmpdir}) { + $tmp_path{$tmpdir}++; + } + } + return keys %tmp_path; +} + +# Convert to trusted mode if it's not already +sub convertToTrusted { + &B_log("DEBUG","# sub convertToTrusted \n"); + if( ! &isSystemTrusted) { + + my ($ok, $message) = &isOKtoConvert; + + my $ts_header="\n---------------------------------\nTrusted Systems:\n" . + "---------------------------------\n"; + + if ($ok) { + # actually do the conversion + if(&B_System(&getGlobal('BIN','tsconvert'), &getGlobal('BIN','tsconvert') . " -r")){ + # adjust change times for user passwords to keep them valid + # default is to expire them when converting to a trusted system, + # which can be problematic, especially since some older versions of + # SecureShell do not allow the user to change the password + &B_System(&getGlobal('BIN','modprpw') . " -V", ""); + + my $getprdef = &getGlobal('BIN','getprdef'); + my $oldsettings = &B_Backtick("$getprdef -m lftm,exptm,mintm,expwarn,umaxlntr"); + $oldsettings =~ s/ //g; + + # remove password lifetime and increasing login tries so they + # don't lock themselves out of the system entirely. + # set default expiration time and the like. + my $newsettings="lftm=0,exptm=0,mintm=0,expwarn=0,umaxlntr=10"; + + &B_System(&getGlobal('BIN','modprdef') . " -m $newsettings", + &getGlobal('BIN','modprdef') . " -m $oldsettings"); + + &B_TODO($ts_header . + "Your system has been converted to a trusted system.\n" . + "You should review the security settings available on a trusted system.\n". + "$message"); + + # to get rid of "Cron: Your job did not contain a valid audit ID." + # error, we re-read the crontab file after converting to trusted mode + # Nothing is necessary in "revert" since we won't be in trusted mode + # at that time. + # crontab's errors can be spurious, and this will report an 'error' + # of the crontab file is missing, so we send stderr to the bit bucket + my $crontab = &getGlobal('BIN',"crontab"); + &B_System("$crontab -l 2>/dev/null | $crontab",""); + } + + } else { + &B_TODO($ts_header . $message); + return 0; # not ok to convert, so we didn't + } + } + else { + &B_log("DEBUG","System is already in trusted mode, no action taken.\n"); + return 1; + } + + # just to make sure + if( &isSystemTrusted ) { + return 1; + } else { + &B_log("ERROR","Trusted system conversion was unsuccessful for an unknown reason.\n" . + " You may try using SAM/SMH to do the conversion instead of Bastille.\n"); + return 0; + } +} + +# isOKtoConvert - check for conflicts between current system state and trusted +# mode +# +# Return values +# 0 - conflict found, see message for details +# 1 - no conflicts, see message for further instructions +# +sub isOKtoConvert { + &B_log("DEBUG","# sub isOKtoConvert \n"); + # initialize text for TODO instructions + my $specialinstructions=" - convert to trusted mode\n"; + + # These are somewhat out-of-place, but only affect the text of the message. + # Each of these messages is repeated in a separate TODO item in the + # appropriate subroutine. + if (&getGlobalConfig("AccountSecurity","single_user_password") eq "Y") { + if (&GetDistro =~ "^HP-UX11.(.*)" and $1<23 ) { + $specialinstructions .= " - set a single user password\n"; + } + } + + if (&getGlobalConfig("AccountSecurity","passwordpolicies") eq "Y") { + $specialinstructions .= " - set trusted mode password policies\n"; + } + + if (&getGlobalConfig("AccountSecurity", "PASSWORD_HISTORY_DEPTHyn") eq "Y") { + $specialinstructions .= " - set a password history depth\n"; + } + + if (&getGlobalConfig("AccountSecurity","system_auditing") eq "Y") { + $specialinstructions .= " - enable auditing\n"; + } + + my $saminstructions= + "The security settings can be modified by running SAM as follows:\n" . + "# sam\n" . + "Next, go to the \"Auditing and Security Area\" and review\n" . + "each sub-section. Make sure that you review all of your\n" . + "settings, as some policies may seem restrictive.\n\n" . + "On systems using the System Management Homepage, you can\n". + "change your settings via the Tools:Security Attributes Configuration\n". + "section. On some systems, you may also have the option of using SMH.\n\n"; + + # First, check for possible conflicts and corner cases + + # check nsswitch for possible conflicts + my $nsswitch = &getGlobal('FILE', 'nsswitch.conf'); + if ( -e $nsswitch) { + open(FILE, $nsswitch); + while (<FILE>) { + if (/nis/ or /compat/ or /ldap/) { + my $message = "Bastille found a possible conflict between trusted mode and\n" . + "$nsswitch. Please remove all references to\n" . + "\"compat\", \"nis\" and \"ldap\" in $nsswitch\n" . + "and rerun Bastille, or use SAM/SMH to\n" . + "$specialinstructions\n". + "$saminstructions"; + close(FILE); + return (0,$message); + } + } + close(FILE); + } + + # check the namesvrs config file for possible NIS conflicts + #Changed to unless "Y AND Y" since question can be skipped when nis is off + # but corner cases can still exist, so check then too. + unless ( &getGlobalConfig('MiscellaneousDaemons','nis_client') eq "Y" and + &getGlobalConfig('MiscellaneousDaemons','nis_server') eq "Y" ) { + my $namesvrs = &getGlobal('FILE', 'namesvrs'); + if (open(FILE, $namesvrs)) { + while (<FILE>) { + if (/^NIS.*=["]?1["]?$/) { + my $message= "Possible conflict between trusted mode and NIS found.\n". + "Please use SAM/SMH to\n" . + " - turn off NIS\n" . + "$specialinstructions\n". + "$saminstructions"; + close(FILE); + return (0,$message); + } + } + close(FILE); + } else { + &B_log("ERROR","Unable to open $namesvrs for reading."); + my $message= "Possible conflict between trusted mode and NIS found.\n". + "Please use SAM/SMH to\n" . + " - turn off NIS\n" . + "$specialinstructions\n". + "$saminstructions"; + return (0,$message); + } + if ( &B_match_line (&getGlobal("FILE","passwd"),"^\+:.*")) { + my $message= '"+" entry found in passwd file. These are not\n' . + "compatible with Trusted Mode. Either remove the entries\n" . + "and re-run Bastille, or re-run Bastille, and direct it to\n" . + "disable NIS client and server.\n"; + return (0,$message); + } + + } + + + # check for conflicts with DCE integrated login + my $authcmd = &getGlobal('BIN','auth.adm'); + if ( -e $authcmd ) { + my $retval = system("PATH=/usr/bin $authcmd -q 1>/dev/null 2>&1"); + if ($retval != 0 and $retval != 1) { + my $message="It appears that DCE integrated login is configured on this system.\n" . + "DCE integrated login is incompatible with trusted systems and\n" . + "auditing. Bastille is unable to\n" . + "$specialinstructions" . + "You will need to configure auditing and password policies using DCE.\n\n"; + return (0,$message); + } + } + + if ( -e &getGlobal('FILE','shadow') ) { + my $message="This system has already been converted to shadow passwords.\n" . + "Shadow passwords are incompatible with trusted mode.\n" . + "Bastille is unable to\n" . + "$specialinstructions" . + "If you desire these features, you should use\n". + "\'pwunconv\' to change back to standard passwords,\n". + "and then rerun Bastille.\n\n"; + return (0,$message); + } + + return (1,$saminstructions); +} + +# This routine allows Bastille to determine trusted-mode extension availability + +sub convertToShadow { + + if (&isSystemTrusted) { + # This is an internal error...Bastille should not call this routine + # in this case. Error is here for robustness against future changes. + &B_log("ERROR","This system is already converted to trusted mode.\n" . + " Converting to shadow passwords will not be attempted.\n"); + return 0; + } + + # configuration files on which shadowed passwords depend + my $nsswitch_conf = &getGlobal('FILE',"nsswitch.conf"); + + # binaries used to convert to a shadowed password + my $pwconv = &getGlobal('BIN',"pwconv"); + my $echo = &getGlobal('BIN','echo'); # the echo is used to pipe a yes into the pwconv program as + # pwconv requires user interaction. + + # the binary used in a system revert. + my $pwunconv = &getGlobal('BIN',"pwunconv"); + #check the password file for nis usage and if the nis client + #or server is running. + if(-e $nsswitch_conf) { + # check the file for nis, nis+, compat, or dce usage. + if(&B_match_line($nsswitch_conf, '^\s*passwd:.+(nis|nisplus|dce|compat)')) { + my $shadowTODO = "\n---------------------------------\nHide encrypted passwords:\n" . + "---------------------------------\n" . + "This version of password shadowing does not support any repository other\n" . + "than files. In order to convert your password database to shadowed passwords\n" . + "there can be no mention of nis, nisplus, compat, or dce in the passwd\n" . + "field of the \"$nsswitch_conf\" file. Please make the necessary edits to\n" . + "the $nsswitch_conf file and run Bastille again using the command:\n" . + "\"bastille -b\"\n"; + # Adding the shadowTODO comment to the TODO list. + &B_TODO("$shadowTODO"); + # Notifing the user that the shadowed password coversion has failed. + &B_log("ERROR","Password Shadowing Conversion Failed\n" . + "$shadowTODO"); + # exiting the subroutine. + return 0; + } + + } + + # convert the password file to a shadowed repository. + if (( -e $pwconv ) and ( -e $pwunconv ) and + ( &B_System("$echo \"yes\" | $pwconv","$pwunconv") ) ){ + &B_TODO( "\n---------------------------------\nShadowing Password File:\n" . + "---------------------------------\n" . + "Your password file has been converted to use password shadowing.\n" . + "This version of password shadowing does not support any repository other\n" . + "than files. There can be no mention of nis, nisplus, compat, or dce\n" . + "in the passwd field of the \"$nsswitch_conf\" file.\n\n" ); + } else { + &B_log("ERROR","Conversion to shadow mode failed. The system may require ". + "a patch to be capable of switching to shadow mode, or the ". + "system my be in a state where conversion is not possible."); + } +} + + + +########################################################################## +# &getSupportedSettings(); +# Manipulates %trustedParameter and %isSupportedSetting, file-scoped variables +# +# Reads the password policy support matrix, which in-turn gives Bastille the +# places it should look for a given password policy setting. + +# Note the file was created like this so if could be maintained in an Excel(tm) +# spreadsheet, to optimize reviewability. TODO: consider other formats + +# File Format: +# HEADERS:<comment>,[<OS Version> <Mode> <Extensions>,]... +# [ +# :<label>:<trusted equivalent>,,,,,,,,,,,,<comment> +# <action> (comment), [<test value>,]... +# ] ... +# Example; +# HEADERS:Information Source (trusted equiv),11.11 Standard no-SMSE,11.11 Trusted no-SMSE,11.11 Shadow no-SMSE,11.23 Standard no-SMSE,11.23 Trusted no-SMSE,11.23 Shadow no-SMSE,11.23 Standard SMSE,11.23 Shadow SMSE,11.23 Trusted SMSE,11.31 Trusted SMSE,11.31 Shadow SMSE,11.31 Standard SMSE,Other Exceptions +#:ABORT_LOGIN_ON_MISSING_HOMEDIR,,,,,,,,,,,,,root +#/etc/security.dsc (search),x,,xx,x,x,x,!,!,!,!,!,!, +#/etc/default/security(search),y,y,y,y,y,y,y,y,y,y,y,y, +#getprdef (execute with <Trusted Equiv> argument),x,x,x,x,x,x,x,x,x,x,x,x, + +########################################################################### +our %trustedParameter = (); +our %isSupportedSetting = (); + +sub getSupportedSettings() { + + my $line; # For a config file line + my $linecount = 0; + my $currentsetting = ""; + my @fields; # Fields in a given line + my @columns; #Column Definitions + + + &B_open(*SETTINGSFILE,&getGlobal('BFILE','AccountSecSupport')); + my @settingLines=<SETTINGSFILE>; + &B_close(*SETTINGSFILE); + + #Remove blank-lines and comments + @settingLines = grep(!/^#/,@settingLines); + @settingLines = grep(!/^(\s*,+)*$/,@settingLines); + + foreach $line (@settingLines) { + ++$linecount; + @fields = split(/,/,$line); + if ($line =~ /^Information Source:/) { #Sets up colums + my $fieldcount = 1; #Skipping first field + while ((defined($fields[$fieldcount])) and + ($fields[$fieldcount] =~ /\d+\.\d+/)){ + my @subfields = split(/ /,$fields[$fieldcount]); + my $fieldsCount = @subfields; + if ($fieldsCount != 3){ + &B_log("ERROR","Invalid subfield count: $fieldsCount in:". + &getGlobal('BFILE','AccountSecSupport') . + " line: $linecount and field: $fieldcount"); + } + $columns[$fieldcount] = {OSVersion => $subfields[0], + Mode => $subfields[1], + Extension => $subfields[2] }; + &B_log("DEBUG","Found Header Column, $columns[$fieldcount]{'OSVersion'}, ". + $columns[$fieldcount]{'Mode'} ." , " . + $columns[$fieldcount]{'Extension'}); + ++$fieldcount; + } # New Account Seting ex: + } elsif ($line =~ /^:([^,:]+)(?::([^,]+))?/) { # :PASSWORD_WARNDAYS:expwarn,,,,,,,,,,,, + $currentsetting = $1; + if (defined($2)) { + $trustedParameter{"$currentsetting"}=$2; + } + &B_log("DEBUG","Found Current Setting: ". $currentsetting . + "/" . $trustedParameter{"$currentsetting"}); + } elsif (($line =~ /(^[^, :\)\(]+)[^,]*,((?:(?:[!y?nx]|!!),)+)/) and #normal line w/ in setting ex: + ($currentsetting ne "")){ # security.dsc (search),x,x,x,x,x,!,!!,!,!,!,!, + my $placeToLook = $1; + my $fieldcount = 1; #Skip the first one, which we used in last line + while (defined($fields[$fieldcount])) { + &B_log("DEBUG","Setting $currentsetting : $columns[$fieldcount]{OSVersion} , ". + "$columns[$fieldcount]{Mode} , ". + "$columns[$fieldcount]{Extension} , ". + "$placeToLook, to $fields[$fieldcount]"); + $isSupportedSetting{"$currentsetting"} + {"$columns[$fieldcount]{OSVersion}"} + {"$columns[$fieldcount]{Mode}"} + {"$columns[$fieldcount]{Extension}"} + {"$placeToLook"} = + $fields[$fieldcount]; + ++$fieldcount; + } + } else { + if ($line !~ /^,*/) { + &B_log("ERROR","Incorrectly Formatted Line at ". + &getGlobal('BFILE','AccountSecSupport') . ": $linecount"); + } + } + } +} + +########################################################################## +# &B_get_sec_value($param); +# This subroutine finds the value for a given user policy parameter. +# Specifically, it supports the parameters listed in the internal data structure + +# Return values: +# 'Not Defined' if the value is not present or not uniquely defined. +# $value if the value is present and unique +# +########################################################################### +sub B_get_sec_value($) { + my $param=$_[0]; + + my $os_version; + if (&GetDistro =~ /^HP-UX\D*(\d+\.\d+)/ ){ + $os_version=$1; + } else { + &B_log("ERROR","B_get_sec_value only supported on HP-UX"); + return undef; + } +# my $sec_dsc = &getGlobal('FILE', 'security.dsc'); + my $sec_file = &getGlobal('FILE', 'security'); + my $getprdef = &getGlobal('BIN','getprdef'); + my $getprpw = &getGlobal('BIN','getprpw'); + my $userdbget = &getGlobal('BIN','userdbget'); + my $passwd = &getGlobal('BIN','passwd'); + + my $sec_flags = ""; + my @sec_settings=(); + my $user_sec_setting=""; + + my $security_mode="Standard"; + my $security_extension="no-SMSE"; + + &B_log("DEBUG","Entering get_sec_value for: $param"); + + sub isok ($) { # Locally-scoped subroutine, takes supported-matrix entry as argument + my $supportedMatrixEntry = $_[0]; + + if ($supportedMatrixEntry =~ /!/) { #Matrix Entry for "Documented and/or tested" + &B_log("DEBUG","isOk TRUE: $supportedMatrixEntry"); + return 1; + } else { + &B_log("DEBUG","isOk FALSE: $supportedMatrixEntry"); + return 0; #FALSE + } + } #end local subroutine + + #Get Top Array item non-destructively + sub getTop (@) { + my @incomingArray = @_; + my $topval = pop(@incomingArray); + push(@incomingArray,$topval); #Probably redundant, but left in just in case. + return $topval; + } + + sub ifExistsPushOnSecSettings($$) { + my $sec_settings = $_[0]; + my $pushval = $_[1]; + + if ($pushval ne ""){ + push (@$sec_settings, $pushval); + } + } + + #prpw and prdef both use "YES" instead of "1" like the other settings. + sub normalizePolicy($){ + my $setting = $_[0]; + + $setting =~ s/YES/1/; + $setting =~ s/NO/1/; + + return $setting; + } + + + + if ((%trustedParameter == ()) or (%isSupportedSetting == ())) { + # Manipulates %trustedParameter and %isSupportedSetting + &getSupportedSettings; + } + + #First determine the security mode + my $shadowFile = &getGlobal("FILE","shadow"); + my $passwdFile = &getGlobal("FILE","passwd"); + + if (&isSystemTrusted) { + $security_mode = 'Trusted'; + } elsif ((-e $shadowFile) and #check file exist, and that passwd has no non-"locked" accounts + (not(&B_match_line($passwdFile,'^[^\:]+:[^:]*[^:*x]')))) { + $security_mode = 'Shadow'; + } else { + $security_mode = 'Standard'; + } + if (&isTrustedMigrationAvailable) { + $security_extension = 'SMSE'; + } else { + $security_extension = 'no-SMSE'; + } + &B_log("DEBUG","Security mode: $security_mode extension: $security_extension"); + # Now look up the value from each applicable database, from highest precedence + # to lowest: + &B_log("DEBUG","Checking $param in userdbget"); + if (&isok($isSupportedSetting{$param}{$os_version}{$security_mode} + {$security_extension}{"userdbget_-a"})) { + &ifExistsPushOnSecSettings(\@sec_settings, + &B_getValueFromString('\w+\s+\w+=(\S+)', + &B_Backtick("$userdbget -a $param"))); + &B_log("DEBUG", $param . ":userdbget setting: ". &getTop(@sec_settings)); + } + &B_log("DEBUG","Checking $param in passwd"); + if (&isok($isSupportedSetting{$param}{$os_version}{$security_mode} + {$security_extension}{"passwd_-sa"})) { + if ($param eq "PASSWORD_MINDAYS") { + &ifExistsPushOnSecSettings(\@sec_settings, + &B_getValueFromString('(?:\w+\s+){2}[\d\/]+\s+(\d+)\s+\d+', + &B_Backtick("$passwd -s -a"))); + } elsif ($param eq "PASSWORD_MAXDAYS") { + &ifExistsPushOnSecSettings(\@sec_settings, + &B_getValueFromString('(?:\w+\s+){2}[\d\/]+\s+\d+\s+(\d+)', + &B_Backtick("$passwd -s -a"))); + } elsif ($param eq "PASSWORD_WARNDAYS") { + &ifExistsPushOnSecSettings(\@sec_settings, + &B_getValueFromString('(?:\w+\s+){2}[\d\/]+(?:\s+\d+){2}\s+(\d+)', + &B_Backtick("$passwd -s -a"))); + } + &B_log("DEBUG", $param . ":passwd -sa setting: ". &getTop(@sec_settings)); + } + &B_log("DEBUG","Checking $param in get prpw"); + if (&isok($isSupportedSetting{$param}{$os_version}{$security_mode} + {$security_extension}{"getprpw"})) { + my $logins = &getGlobal("BIN","logins"); + my @userArray = split(/\n/,`$logins`); + my $userParamVals = ''; + foreach my $rawuser (@userArray) { + $rawuser =~ /^(\S+)/; + my $user = $1; + my $nextParamVal=&B_Backtick("$getprpw -l -m $trustedParameter{$param} $user"); + $nextParamVal =~ s/\w*=(-*[\w\d]*)/$1/; + if ($nextParamVal != -1) { #Don't count users for which the local DB is undefined + $userParamVals .= $user . "::::" . $nextParamVal ."\n"; + } + } #Note getValueFromStrings deals with duplicates, returning "Not Unigue" + my $policySetting = &B_getValueFromString('::::(\S+)',"$userParamVals"); + &ifExistsPushOnSecSettings (\@sec_settings, &normalizePolicy($policySetting)); + &B_log("DEBUG", $param . ":prpw setting: ". &getTop(@sec_settings)); + } + &B_log("DEBUG","Checking $param in get prdef"); + if (&isok($isSupportedSetting{$param}{$os_version}{$security_mode} + {$security_extension}{"getprdef"})) { + $_ = &B_Backtick ("$getprdef -m " . $trustedParameter{$param}); + /\S+=(\S+)/; + my $policySetting = $1; + &ifExistsPushOnSecSettings(\@sec_settings, &normalizePolicy($policySetting)); + &B_log("DEBUG", $param . ":prdef setting: ". &getTop(@sec_settings)); + + } + &B_log("DEBUG","Checking $param in default security"); + if (&isok($isSupportedSetting{$param}{$os_version}{$security_mode} + {$security_extension}{"/etc/default/security"})) { + &ifExistsPushOnSecSettings(\@sec_settings,&B_getValueFromFile('^\s*'. $param . + '\s*=\s*([^\s#]+)\s*$', $sec_file)); + &B_log("DEBUG", $param . ":default setting: ". &getTop(@sec_settings)); + } + #Commented below code in 3.0 release to avoid implication that bastille + #had ever set these values explicitly, and the implications to runnable + #config files where Bastille would then apply the defaults as actual policy + #with possible conversion to shadow or similar side-effect. + +# &B_log("DEBUG","Checking $param in security.dsc"); + #security.dsc, only added in if valid for OS/mode/Extension, and nothing else + #is defined (ie: @sec_settings=0) +# if ((&isok($isSupportedSetting{$param}{$os_version}{$security_mode} +# {$security_extension}{"/etc/security.dsc"})) and (@sec_settings == 0)) { +# &ifExistsPushOnSecSettings(\@sec_settings, &B_getValueFromFile('^' . $param . +# ';(?:[-\w/]*;){2}([-\w/]+);', $sec_dsc)); +# &B_log("DEBUG", $param . ":security.dsc: ". &getTop(@sec_settings)); +# } + + # Return what we found + my $last_setting=undef; + my $current_setting=undef; + while (@sec_settings > 0) { + $current_setting = pop(@sec_settings); + &B_log("DEBUG","Comparing $param configuration for identity: " . + $current_setting); + if ((defined($current_setting)) and ($current_setting ne '')) { + if (not(defined($last_setting))){ + $last_setting=$current_setting; + } elsif (($last_setting ne $current_setting) or + ($current_setting eq 'Not Unique')){ + &B_log("DEBUG","$param setting not unique."); + return 'Not Unique'; # Inconsistent state found, return 'Not Unique' + } + } + } + if ((not(defined($last_setting))) or ($last_setting eq '')) { + return undef; + } else { + return $last_setting; + } + +} #End B_get_sec_value + +sub secureIfNoNameService($){ + my $retval = $_[0]; + + if (&isUsingRemoteNameService) { + return MANUAL(); + } else { + return $retval; + } +} + +#Specifically for cleartext protocols like NIS, which are not "secure" +sub isUsingRemoteNameService(){ + + if (&remoteServiceCheck('nis|nisplus|dce') == SECURE_CAN_CHANGE()){ + return 0; #false + } else { + return 1; + } +} + + + +########################################### +## This is a wrapper for two functions that +## test the existence of nis-like configurations +## It is used by both the front end test and the back-end run +############################################## +sub remoteServiceCheck($){ + my $regex = $_[0]; + + my $nsswitch_conf = &getGlobal('FILE',"nsswitch.conf"); + my $passwd = &getGlobal('FILE',"passwd"); + + # check the file for nis usage. + if (-e $nsswitch_conf) { + if (&B_match_line($nsswitch_conf, '^\s*passwd:.*('. $regex . ')')) { + return NOTSECURE_CAN_CHANGE(); + } elsif ((&B_match_line($nsswitch_conf, '^\s*passwd:.*(compat)')) and + (&B_match_line($passwd, '^\s*\+'))) { + return NOTSECURE_CAN_CHANGE(); # true + } + } elsif ((&B_match_line($passwd, '^\s*\+'))) { + return NOTSECURE_CAN_CHANGE(); + } + + my $oldnisdomain=&B_get_rc("NIS_DOMAIN"); + if ((($oldnisdomain eq "") or ($oldnisdomain eq '""')) and (&checkServiceOnHPUX('nis.client'))){ + return SECURE_CAN_CHANGE(); + } + return NOTSECURE_CAN_CHANGE(); +} + +############################################# +# remoteNISPlusServiceCheck +# test the existence of nis+ configuration +############################################# +sub remoteNISPlusServiceCheck () { + + my $nsswitch_conf = &getGlobal('FILE',"nsswitch.conf"); + + # check the file for nis+ usage. + if (-e $nsswitch_conf) { + if (&B_match_line($nsswitch_conf, 'nisplus')) { + return NOTSECURE_CAN_CHANGE(); + } + } + + return &checkServiceOnHPUX('nisp.client'); +} + + +########################################################################## +# This subroutine creates nsswitch.conf file if the file not exists, +# and then append serveral services into the file if the service not +# exists in the file. +########################################################################## +sub B_create_nsswitch_file ($) { + my $regex = $_[0]; + + my $nsswitch = &getGlobal('FILE',"nsswitch.conf"); + + if( ! -f $nsswitch ) { + &B_create_file($nsswitch); + # we don't need to revert the permissions change because we just + # created the file + chmod(0444, $nsswitch); + + &B_append_line($nsswitch,'\s*passwd:', "passwd: $regex\n"); + &B_append_line($nsswitch,'\s*group:', "group: $regex\n"); + &B_append_line($nsswitch,'\s*hosts:', "hosts: $regex\n"); + &B_append_line($nsswitch,'\s*networks:', "networks: $regex\n"); + &B_append_line($nsswitch,'\s*protocols:', "protocols: $regex\n"); + &B_append_line($nsswitch,'\s*rpc:', "rpc: $regex\n"); + &B_append_line($nsswitch,'\s*publickey:', "publickey: $regex\n"); + &B_append_line($nsswitch,'\s*netgroup:', "netgroup: $regex\n"); + &B_append_line($nsswitch,'\s*automount:', "automount: $regex\n"); + &B_append_line($nsswitch,'\s*aliases:', "aliases: $regex\n"); + &B_append_line($nsswitch,'\s*services:', "services: $regex\n"); + } +} + +1; + diff --git a/meta-security/recipes-security/bastille/files/Miscellaneous.pm b/meta-security/recipes-security/bastille/files/Miscellaneous.pm new file mode 100644 index 000000000..b3bdf10cd --- /dev/null +++ b/meta-security/recipes-security/bastille/files/Miscellaneous.pm @@ -0,0 +1,166 @@ +package Bastille::API::Miscellaneous; +use strict; + +use File::Path; +use Bastille::API; +use Bastille::API::HPSpecific; +use Bastille::API::FileContent; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw( +PrepareToRun +B_is_package_installed +); +our @EXPORT = @EXPORT_OK; + + +########################################################################### +# +# PrepareToRun sets up Bastille to run. It checks the ARGV array for +# special options and runs ConfigureForDistro to set necessary file +# locations and other global variables. +# +########################################################################### + +sub PrepareToRun { + + # Make sure we're root! + if ( $> != 0 ) { + &B_log("ERROR","Bastille must run as root!\n"); + exit(1); + } + + + # Make any directories that don't exist... + foreach my $dir (keys %GLOBAL_BDIR) { + my $BdirPath = $GLOBAL_BDIR{$dir}; + if ( $BdirPath =~ /^\s*\// ) { #Don't make relative directories + mkpath ($BdirPath,0,0700); + } + } + + if(&GetDistro =~ "^HP-UX") { + &B_check_system; + } + + &B_log("ACTION","\n########################################################\n" . + "# Begin Bastille Run #\n" . + "########################################################\n\n"); + + #read sum file if it exists. + &B_read_sums; + + +# No longer necessary as flags are no longer in sum file, and sums are +# are now checked "real time" + + # check the integrity of the files listed +# for my $file (sort keys %GLOBAL_SUM) { +# &B_check_sum($file); +# } + # write out the newly flagged sums +# &B_write_sums; + + +} + + + +########################################################################### +# &B_is_package_installed($package); +# +# This function checks for the existence of the package named. +# +# TODO: Allow $package to be an expression. +# TODO: Allow optional $version, $release, $epoch arguments so we can +# make sure that the given package is at least as recent as some +# given version number. +# +# scalar return values: +# 0: $package is not installed +# 1: $package is installed +########################################################################### + +sub B_is_package_installed($) { + no strict; + my $package = $_[0]; +# Create a "global" variable with values scoped to this function +# We do this to avoid having to repeatedly swlist/rpm +# when we run B_is_package_installed +local %INSTALLED_PACKAGE_LIST; + + my $distro = &GetDistro; + if ($distro =~ /^HP-UX/) { + if (&checkProcsForService('swagent','ignore_warning') == SECURE_CANT_CHANGE()) { + &B_log("WARNING","Software Distributor Agent(swagent) is not running. Can not tell ". + "if package: $package is installed or not. Bastille will assume not. ". + "If the package is actually installed, Bastille may report or configure incorrectly.". + "To use Bastille-results as-is, please check to ensure $package is not installed, ". + "or re-run with the swagent running to get correct results."); + return 0; #FALSE + } + my $swlist=&getGlobal('BIN','swlist'); + if (%INSTALLED_PACKAGE_LIST == () ) { # re-use prior results + if (open(SWLIST, "$swlist -a state -l fileset |")) { + while (my $line = <SWLIST>){ + if ($line =~ /^ {2}\S+\.(\S+)\s*(\w+)/) { + $INSTALLED_PACKAGE_LIST{$1} = $2; + } + } + close SWLIST; + } else { + &B_log("ERROR","B_is_package_installed was unable to run the swlist command: $swlist,\n"); + return FALSE; + } + } + # Now find the entry + if ($INSTALLED_PACKAGE_LIST{$package} == 'configured') { + return TRUE; + } else { + return FALSE; + } + } #End HP-UX Section + # This routine only works on RPM-based distros: Red Hat, Fedora, Mandrake and SuSE + elsif ( ($distro !~ /^RH/) and ($distro !~ /^MN/) and($distro !~ /^SE/) ) { + return 0; + } else { #This is a RPM-based distro + # Run an rpm command -- librpm is extremely messy, dynamic and not + # so much a perl thing. It's actually barely a C/C++ thing... + if (open RPM,"rpm -q $package") { + # We should get only one line back, but let's parse a few + # just in case. + my @lines = <RPM>; + close RPM; + # + # This is what we're trying to parse: + # $ rpm -q jay + # package jay is not installed + # $ rpm -q bash + # bash-2.05b-305.1 + # + + foreach $line (@lines) { + if ($line =~ /^package\s$package\sis\snot\sinstalled/) { + return 0; + } + elsif ($line =~ /^$package\-/) { + return 1; + } + } + + # If we've read every line without finding one of these, then + # our parsing is broken + &B_log("ERROR","B_is_package_installed was unable to find a definitive RPM present or not present line.\n"); + return 0; + } else { + &B_log("ERROR","B_is_package_installed was unable to run the RPM command,\n"); + return 0; + } + } +} + + + +1; + diff --git a/meta-security/recipes-security/bastille/files/ServiceAdmin.pm b/meta-security/recipes-security/bastille/files/ServiceAdmin.pm new file mode 100644 index 000000000..879223a53 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/ServiceAdmin.pm @@ -0,0 +1,690 @@ +package Bastille::API::ServiceAdmin; +use strict; + +use Bastille::API; + +use Bastille::API::HPSpecific; +use Bastille::API::FileContent; + +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw( +B_chkconfig_on +B_chkconfig_off +B_service_start +B_service_stop +B_service_restart +B_is_service_off +checkServiceOnLinux +remoteServiceCheck +remoteNISPlusServiceCheck +B_create_nsswitch_file +); +our @EXPORT = @EXPORT_OK; + + +####### +# &B_chkconfig_on and &B_chkconfig_off() are great for systems that didn't use +# a more modern init system. This is a bit of a problem on Fedora, though, +# which used upstart from Fedora 9 to Fedora 14, then switched to a new +# Red Hat-created system called systemd for Fedora 15 and 16 (so far). +# OpenSUSE also moved to systemd, starting with 12.1. Version 11.4 did not +# use systemd. +# It is also a problem on Ubuntu, starting at version 6.10, where they also +# used upstart. +##### + + + + +########################################################################### +# &B_chkconfig_on ($daemon_name) creates the symbolic links that are +# named in the "# chkconfig: ___ _ _ " portion of the init.d files. We +# need this utility, in place of the distro's chkconfig, because of both +# our need to add revert functionality and our need to harden distros that +# are not mounted on /. +# +# It uses the following global variables to find the links and the init +# scripts, respectively: +# +# &getGlobal('DIR', "rcd") -- directory where the rc_.d subdirs can be found +# &getGlobal('DIR', "initd") -- directory the rc_.d directories link to +# +# Here an example of where you might use this: +# +# You'd like to tell the system to run the firewall at boot: +# B_chkconfig_on("bastille-firewall") +# +########################################################################### + +# PW: Blech. Copied B_chkconfig_off() and changed a few things, +# then changed a few more things.... + +sub B_chkconfig_on { + + my $startup_script=$_[0]; + my $retval=1; + + my $chkconfig_line; + my ($runlevelinfo,@runlevels); + my ($start_order,$stop_order,$filetolink); + + &B_log("ACTION","# chkconfig_on enabling $startup_script\n"); + + # In Debian system there is no chkconfig script, run levels are checked + # one by one (jfs) + if (&GetDistro =~/^DB.*/) { + $filetolink = &getGlobal('DIR', "initd") . "/$startup_script"; + if (-x $filetolink) + { + foreach my $level ("0","1","2","3","4","5","6" ) { + my $link = ''; + $link = &getGlobal('DIR', "rcd") . "/rc" . "$level" . ".d/K50" . "$startup_script"; + $retval=symlink($filetolink,$link); + } + } + return $retval; + } + # + # On SUSE, chkconfig-based rc scripts have been replaced with a whole different + # system. chkconfig on SUSE is actually a shell script that does some stuff and then + # calls insserv, their replacement. + # + + if (&GetDistro =~ /^SE/) { + # only try to chkconfig on if init script is found + if ( -e (&getGlobal('DIR', "initd") . "/$startup_script") ) { + $chkconfig_line=&getGlobal('BIN','chkconfig'); + &B_System("$chkconfig_line $startup_script on", "$chkconfig_line $startup_script off"); + # chkconfig doesn't take affect until reboot, need to restart service also + B_service_restart("$startup_script"); + return 1; #success + } + return 0; #failure + } + + # + # Run through the init script looking for the chkconfig line... + # + $retval = open CHKCONFIG,&getGlobal('DIR', "initd") . "/$startup_script"; + unless ($retval) { + &B_log("ACTION","# Didn't chkconfig_on $startup_script because we couldn't open " . &getGlobal('DIR', "initd") . "/$startup_script\n"); + } + else { + + READ_LOOP: + while (my $line=<CHKCONFIG>) { + + # We're looking for lines like this one: + # # chkconfig: 2345 10 90 + # OR this + # # chkconfig: - 10 90 + + if ($line =~ /^#\s*chkconfig:\s*([-\d]+)\s*(\d+)\s*(\d+)/ ) { + $runlevelinfo = $1; + $start_order = $2; + $stop_order = $3; + # handle a run levels arg of '-' + if ( $runlevelinfo eq '-' ) { + &B_log("ACTION","chkconfig_on saw '-' for run levels for \"$startup_script\", is defaulting to levels 3,4,5\n"); + $runlevelinfo = '345'; + } + @runlevels = split(//,$runlevelinfo); + # make sure the orders have 2 digits + $start_order =~ s/^(\d)$/0$1/; + $stop_order =~ s/^(\d)$/0$1/; + last READ_LOOP; + } + } + close CHKCONFIG; + + # Do we have what we need? + if ( (scalar(@runlevels) < 1) || (! $start_order =~ /^\d{2}$/) || (! $stop_order =~ /^\d{2}$/) ) { + # problem + &B_log("ERROR","# B_chkconfig_on $startup_script failed -- no valid run level/start/stop info found\n"); + return(-1); + } + + # Now, run through creating symlinks... + &B_log("ACTION","# chkconfig_on will use run levels ".join(",",@runlevels)." for \"$startup_script\" with S order $start_order and K order $stop_order\n"); + + $retval=0; + # BUG: we really ought to readdir() on &getGlobal('DIR', "rcd") to get all levels + foreach my $level ( "0","1","2","3","4","5","6" ) { + my $link = ''; + # we make K links in run levels not specified in the chkconfig line + $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/K$stop_order" . $startup_script; + my $klink = $link; + # now we see if this is a specified run level; if so, make an S link + foreach my $markedlevel ( @runlevels ) { + if ( $level == $markedlevel) { + $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/S$start_order" . $startup_script; + } + } + my $target = &getGlobal('DIR', "initd") ."/" . $startup_script; + my $local_return; + + if ( (-e "$klink") && ($klink ne $link) ) { + # there's a K link, but this level needs an S link + unless ($GLOBAL_LOGONLY) { + $local_return = unlink("$klink"); + if ( ! $local_return ) { + # unlinking old, bad $klink failed + &B_log("ERROR","Unlinking $klink failed\n"); + } else { + &B_log("ACTION","Removed link $klink\n"); + # If we removed the link, add a link command to the revert file + &B_revert_log (&getGlobal('BIN','ln') . " -s $target $klink\n"); + } # close what to do if unlink works + } # if not GLOBAL_LOGONLY + } # if $klink exists and ne $link + + # OK, we've disposed of any old K links, make what we need + if ( (! ( -e "$link" )) && ($link ne '') ) { + # link doesn't exist and the start/stop number is OK; make it + unless ($GLOBAL_LOGONLY) { + # create the link + $local_return = &B_symlink($target,$link); + if ($local_return) { + $retval++; + &B_log("ACTION","Created link $link\n"); + } else { + &B_log("ERROR","Couldn't create $link when trying to chkconfig on $startup_script\n"); + } + } + + } # link doesn't exist + } # foreach level + + } + + if ($retval < @runlevels) { + $retval=0; + } + + $retval; + +} + + +########################################################################### +# &B_chkconfig_off ($daemon_name) deletes the symbolic links that are +# named in the "# chkconfig: ___ _ _ " portion of the init.d files. We +# need this utility, in place of the distro's chkconfig, because of both +# our need to add revert functionality and our need to harden distros that +# are not mounted on /. +# +# chkconfig allows for a REVERT of its work by writing to an executable +# file &getGlobal('BFILE', "removed-symlinks"). +# +# It uses the following global variables to find the links and the init +# scripts, respectively: +# +# &getGlobal('DIR', "rcd") -- directory where the rc_.d subdirs can be found +# &getGlobal('DIR', "initd") -- directory the rc_.d directories link to +# +# Here an example of where you might use this: +# +# You'd like to tell stop running sendmail in daemon mode on boot: +# B_chkconfig_off("sendmail") +# +########################################################################### + + + +sub B_chkconfig_off { + + my $startup_script=$_[0]; + my $retval=1; + + my $chkconfig_line; + my @runlevels; + my ($start_order,$stop_order,$filetolink); + + if (&GetDistro =~/^DB.*/) { + $filetolink = &getGlobal('DIR', "initd") . "/$startup_script"; + if (-x $filetolink) + { + # Three ways to do this in Debian: + # 1.- have the initd script set to 600 mode + # 2.- Remove the links in rcd (re-installing the package + # will break it) + # 3.- Use update-rc.d --remove (same as 2.) + # (jfs) + &B_chmod(0600,$filetolink); + $retval=6; + + # The second option + #foreach my $level ("0","1","2","3","4","5","6" ) { + #my $link = ''; + #$link = &getGlobal('DIR', "rcd") . "/rc" . "$level" . ".d/K50" . "$startup_script"; + #unlink($link); + #} + } + } + + # + # On SUSE, chkconfig-based rc scripts have been replaced with a whole different + # system. chkconfig on SUSE is actually a shell script that does some stuff and then + # calls insserv, their replacement. + # + elsif (&GetDistro =~ /^SE/) { + # only try to chkconfig off if init script is found + if ( -e (&getGlobal('DIR', "initd") . "/$startup_script") ) { + $chkconfig_line=&getGlobal('BIN','chkconfig'); + &B_System("$chkconfig_line $startup_script on", "$chkconfig_line $startup_script off"); + # chkconfig doesn't take affect until reboot, need to stop service + # since expectation is that the daemons are disabled even without a reboot + B_service_stop("$startup_script"); + return 1; #success + } + return 0; #failure + } + else { + + # Run through the init script looking for the chkconfig line... + + + $retval = open CHKCONFIG,&getGlobal('DIR', "initd") . "/$startup_script"; + unless ($retval) { + &B_log("ACTION","Didn't chkconfig_off $startup_script because we couldn't open " . &getGlobal('DIR', "initd") . "/$startup_script\n"); + } + else { + + READ_LOOP: + while (my $line=<CHKCONFIG>) { + + # We're looking for lines like this one: + # # chkconfig: 2345 10 90 + + if ($line =~ /^#\s*chkconfig:\s*([-\d]+)\s*(\d+)\s*(\d+)/ ) { + @runlevels=split //,$1; + $start_order=$2; + $stop_order=$3; + + + # Change single digit run levels to double digit -- otherwise, + # the alphabetic ordering chkconfig depends on fails. + if ($start_order =~ /^\d$/ ) { + $start_order = "0" . $start_order; + &B_log("ACTION","chkconfig_off converted start order to $start_order\n"); + } + if ($stop_order =~ /^\d$/ ) { + $stop_order = "0" . $stop_order; + &B_log("ACTION","chkconfig_off converted stop order to $stop_order\n"); + } + + last READ_LOOP; + } + } + close CHKCONFIG; + + # If we never found a chkconfig line, can we just run through all 5 + # rcX.d dirs from 1 to 5...? + + # unless ( $start_order and $stop_order ) { + # @runlevels=("1","2","3","4","5"); + # $start_order = "*"; $stop_order="*"; + # } + + # Now, run through removing symlinks... + + + + $retval=0; + + # Handle the special case that the run level specified is solely "-" + if ($runlevels[0] =~ /-/) { + @runlevels = ( "0","1","2","3","4","5","6" ); + } + + foreach my $level ( @runlevels ) { + my $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/S$start_order" . $startup_script; + my $new_link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/K$stop_order" . $startup_script; + my $target = &getGlobal('DIR', "initd") ."/" . $startup_script; + my $local_return; + + + # Replace the S__ link in this level with a K__ link. + if ( -e $link ) { + unless ($GLOBAL_LOGONLY) { + $local_return=unlink $link; + if ($local_return) { + $local_return=symlink $target,$new_link; + unless ($local_return) { + &B_log("ERROR","Linking $target to $new_link failed.\n"); + } + } + else { # unlinking failed + &B_log("ERROR","Unlinking $link failed\n"); + } + + } + if ($local_return) { + $retval++; + &B_log("ACTION","Removed link $link\n"); + + # + # If we removed the link, add a link command to the revert file + # Write out the revert information for recreating the S__ + # symlink and deleting the K__ symlink. + &B_revert_log(&getGlobal('BIN',"ln") . " -s $target $link\n"); + &B_revert_log(&getGlobal('BIN',"rm") . " -f $new_link\n"); + } + else { + &B_log("ERROR","B_chkconfig_off $startup_script failed\n"); + } + + } + } # foreach + + } # else-unless + + } # else-DB + if ($retval < @runlevels) { + $retval=0; + } + + $retval; + +} + + +########################################################################### +# &B_service_start ($daemon_name) +# Starts service on RedHat/SUSE-based Linux distributions which have the +# service command: +# +# service $daemon_name start +# +# Other Linux distros that also support this method of starting +# services can be added to use this function. +# +# Here an example of where you might use this: +# +# You'd like to tell the system to start the vsftpd daemon: +# &B_service_start("vsftpd") +# +# Uses &B_System in HP_API.pm +# To match how the &B_System command works this method: +# returns 1 on success +# returns 0 on failure +########################################################################### + +sub B_service_start { + + my $daemon=$_[0]; + + if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and + (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) { + &B_log("ERROR","Tried to call service_start on a system lacking a service command! Internal Bastille error."); + return undef; + } + + # only start service if init script is found + if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) { + &B_log("ACTION","# service_start enabling $daemon\n"); + + my $service_cmd=&getGlobal('BIN', 'service'); + if ($service_cmd) { + # Start the service, + # Also provide &B_System revert command + + return (&B_System("$service_cmd $daemon start", + "$service_cmd $daemon stop")); + } + } + + # init script not found, do not try to start, return failure + return 0; +} + +########################################################################### +# &B_service_stop ($daemon_name) +# Stops service on RedHat/SUSE-based Linux distributions which have the +# service command: +# +# service $daemon_name stop +# +# Other Linux distros that also support this method of starting +# services can be added to use this function. +# Stops service. +# +# +# Here an example of where you might use this: +# +# You'd like to tell the system to stop the vsftpd daemon: +# &B_service_stop("vsftpd") +# +# Uses &B_System in HP_API.pm +# To match how the &B_System command works this method: +# returns 1 on success +# returns 0 on failure +########################################################################### + +sub B_service_stop { + + my $daemon=$_[0]; + + if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and + (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) { + &B_log("ERROR","Tried to call service_stop on a system lacking a service command! Internal Bastille error."); + return undef; + } + + # only stop service if init script is found + if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) { + &B_log("ACTION","# service_stop disabling $daemon\n"); + + my $service_cmd=&getGlobal('BIN', 'service'); + if ($service_cmd) { + + # Stop the service, + # Also provide &B_System revert command + + return (&B_System("$service_cmd $daemon stop", + "$service_cmd $daemon start")); + } + } + + # init script not found, do not try to stop, return failure + return 0; +} + + +########################################################################### +# &B_service_restart ($daemon_name) +# Restarts service on RedHat/SUSE-based Linux distributions which have the +# service command: +# +# service $daemon_name restart +# +# Other Linux distros that also support this method of starting +# services can be added to use this function. +# +# Here an example of where you might use this: +# +# You'd like to tell the system to restart the vsftpd daemon: +# &B_service_restart("vsftpd") +# +# Uses &B_System in HP_API.pm +# To match how the &B_System command works this method: +# returns 1 on success +# returns 0 on failure +########################################################################### + +sub B_service_restart { + + my $daemon=$_[0]; + + if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and + (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) { + &B_log("ERROR","Tried to call service_restart on a system lacking a service command! Internal Bastille error."); + return undef; + } + + # only restart service if init script is found + if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) { + &B_log("ACTION","# service_restart re-enabling $daemon\n"); + + my $service_cmd=&getGlobal('BIN', 'service'); + if ($service_cmd) { + + # Restart the service + return (&B_System("$service_cmd $daemon restart", + "$service_cmd $daemon restart")); + } + } + + # init script not found, do not try to restart, return failure + return 0; +} + +########################################################################### +# &B_is_service_off($;$) +# +# Runs the specified test to determine whether or not the question should +# be answered. +# +# return values: +# NOTSECURE_CAN_CHANGE()/0: service is on +# SECURE_CANT_CHANGE()/1: service is off +# undef: test is not defined +########################################################################### + +sub B_is_service_off ($){ + my $service=$_[0]; + + if(&GetDistro =~ "^HP-UX"){ + #die "Why do I think I'm on HPUX?!\n"; + return &checkServiceOnHPUX($service); + } + elsif ( (&GetDistro =~ "^RH") || (&GetDistro =~ "^SE") ) { + return &checkServiceOnLinux($service); + } + else { + &B_log("DEBUG","B_is_service off called for unsupported OS"); + # not yet implemented for other distributions of Linux + # when GLOBAL_SERVICE, GLOBAL_SERVTYPE and GLOBAL_PROCESS are filled + # in for Linux, then + # at least inetd and inittab services should be similar to the above, + # whereas chkconfig would be used on some Linux distros to determine + # if non-inetd/inittab services are running at boot time. Looking at + # processes should be similar. + return undef; + } +} + +########################################################################### +# &checkServiceOnLinux($service); +# +# Checks if the given service is running on a Linux system. This is +# called by B_is_Service_Off(), which is the function that Bastille +# modules should call. +# +# Return values: +# NOTSECURE_CAN_CHANGE() if the service is on +# SECURE_CANT_CHANGE() if the service is off +# undef if the state of the service cannot be determined +# +########################################################################### +sub checkServiceOnLinux($) { + my $service=$_[0]; + + # get the list of parameters which could be used to initiate the service + # (could be in /etc/rc.d/rc?.d, /etc/inetd.conf, or /etc/inittab, so we + # check all of them) + + my @params = @{ &getGlobal('SERVICE', $service) }; + my $chkconfig = &getGlobal('BIN', 'chkconfig'); + my $grep = &getGlobal('BIN', 'grep'); + my $inittab = &getGlobal('FILE', 'inittab'); + my $serviceType = &getGlobal('SERVTYPE', $service);; + + # A kludge to get things running because &getGlobal('SERVICE' doesn't + # return the expected values. + @params = (); + push (@params, $service); + + foreach my $param (@params) { + &B_log("DEBUG","Checking to see if service $service is off.\n"); + + if ($serviceType =~ /rc/) { + my $on = &B_Backtick("$chkconfig --list $param 2>&1"); + if ($on =~ /^$param:\s+unknown/) { + # This service isn't installed on the system + return NOT_INSTALLED(); + } + if ($on =~ /^error reading information on service $param: No such file or directory/) { + # This service isn't installed on the system + return NOT_INSTALLED(); + } + if ($on =~ /^error/) { + # This probably + &B_log("DEBUG","chkconfig returned: $param=$on\n"); + return undef; + } + $on =~ s/^$param\s+//; # remove the service name and spaces + $on =~ s/[0-6]:off\s*//g; # remove any runlevel:off entries + $on =~ s/:on\s*//g; # remove the :on from the runlevels + # what remains is a list of runlevels in which the service is on, + # or a null string if it is never turned on + chomp $on; # newline should be gone already (\s) + &B_log("DEBUG","chkconfig returned: $param=$on\n"); + + if ($on =~ /^\d+$/) { + # service is not off + ########################### BREAK out, don't skip question + return NOTSECURE_CAN_CHANGE(); + } + } + elsif ($serviceType =~ /inet/) { + my $on = &B_Backtick("$chkconfig --list $param 2>&1"); + if ($on =~ /^$param:\s+unknown/) { + # This service isn't installed on the system + return NOT_INSTALLED(); + } + if ($on =~ /^error reading information on service $param: No such file or directory/) { + # This service isn't installed on the system + return NOT_INSTALLED(); + } + if ($on =~ /^error/ ) { + # Something else is wrong? + # return undef + return undef; + } + if ($on =~ tr/\n// > 1) { + $on =~ s/^xinetd.+\n//; + } + $on =~ s/^\s*$param:?\s+//; # remove the service name and spaces + chomp $on; # newline should be gone already (\s) + &B_log("DEBUG","chkconfig returned: $param=$on\n"); + + if ($on =~ /^on$/) { + # service is not off + ########################### BREAK out, don't skip question + return NOTSECURE_CAN_CHANGE(); + } + } + else { + # perhaps the service is started by inittab + my $inittabline = &B_Backtick("$grep -E '^[^#].{0,3}:.*:.+:.*$param' $inittab"); + if ($inittabline =~ /.+/) { # . matches anything except newlines + # service is not off + &B_log("DEBUG","Checking inittab; found $inittabline\n"); + ########################### BREAK out, don't skip question + return NOTSECURE_CAN_CHANGE(); + } + } + } # foreach my $param + + + # boot-time parameters are not set; check processes + # Note the checkProcsforService returns INCONSISTENT() if a process is found + # assuming the checks above + return &checkProcsForService($service); +} + +1; + + diff --git a/meta-security/recipes-security/bastille/files/accept_os_flag_in_backend.patch b/meta-security/recipes-security/bastille/files/accept_os_flag_in_backend.patch new file mode 100644 index 000000000..4a438e49f --- /dev/null +++ b/meta-security/recipes-security/bastille/files/accept_os_flag_in_backend.patch @@ -0,0 +1,34 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/BastilleBackEnd +=================================================================== +--- Bastille.orig/BastilleBackEnd 2013-08-21 12:40:54.000000000 -0400 ++++ Bastille/BastilleBackEnd 2013-08-21 12:43:21.895950001 -0400 +@@ -52,11 +52,13 @@ + my $force = 0; + my $debug = 0; + my $alternate_config=undef; ++my $os_version=undef; + + if( Getopt::Long::GetOptions( "n" => \$nodisclaim, + "v" => \$verbose, + "force" => \$force, + "f=s" => \$alternate_config, ++ "os=s" => \$os_version, + "debug" => \$debug) ) { + $error = 0; # no parse error + +@@ -66,7 +68,8 @@ + + &setOptions( + debug => $debug, +- verbose => $verbose); ++ verbose => $verbose, ++ os => $os_version); + &ConfigureForDistro; + + if ( $error ) { # GetOptions couldn't parse all of the args diff --git a/meta-security/recipes-security/bastille/files/allow_os_with_assess.patch b/meta-security/recipes-security/bastille/files/allow_os_with_assess.patch new file mode 100644 index 000000000..e112f907b --- /dev/null +++ b/meta-security/recipes-security/bastille/files/allow_os_with_assess.patch @@ -0,0 +1,43 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/bin/bastille +=================================================================== +--- Bastille.orig/bin/bastille 2013-08-21 08:59:06.647950000 -0400 ++++ Bastille/bin/bastille 2013-08-21 15:55:53.193631711 -0400 +@@ -195,7 +195,6 @@ + systemFileLocations + + isAssessing='no' +-nonXArg='no' + + if [ $PERL_V_MAJ -eq $MIN_V_MAJ -a $PERL_V_MIN -lt $MIN_V_MIN -o $PERL_V_MAJ -lt $MIN_V_MAJ ]; then # invalid Perl + printErr +@@ -316,12 +315,10 @@ + '--os') + options_left="$options_left --os" + optarg='yes' +- nonXArg='yes' + ;; + '-f') + options_left="$options_left -f" + optarg='yes' +- nonXArg='yes' + ;; + # Non-exclusive (undocumented and unsupported) options follow: + # There is no validity/combination checking done with these. +@@ -345,11 +342,6 @@ + fi + done + +-#Detect case where -f or --os attempted use with --assess +- if [ \( x$nonXArg = xyes \) -a \( x$isAssessing = xyes \) ]; then +- printUsage +- exit 2 +- fi + + # We have a valid version of perl! Verify that all the required + # modules can be found. diff --git a/meta-security/recipes-security/bastille/files/call_output_config.patch b/meta-security/recipes-security/bastille/files/call_output_config.patch new file mode 100644 index 000000000..1e898b148 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/call_output_config.patch @@ -0,0 +1,19 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille_Curses.pm +=================================================================== +--- Bastille.orig/Bastille_Curses.pm 2013-08-21 08:58:53.899950000 -0400 ++++ Bastille/Bastille_Curses.pm 2013-08-21 09:20:20.295950005 -0400 +@@ -84,7 +84,7 @@ + } + + # Output answers to the script and display +- &checkAndSaveConfig(&getGlobal('BFILE', "config")); ++ &outputConfig; + + # Run Bastille + diff --git a/meta-security/recipes-security/bastille/files/config b/meta-security/recipes-security/bastille/files/config new file mode 100755 index 000000000..9e5e20658 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/config @@ -0,0 +1,106 @@ +# Q: Would you like to enforce password aging? [Y] +AccountSecurity.passwdage="Y" +# Q: Should Bastille disable clear-text r-protocols that use IP-based authentication? [Y] +AccountSecurity.protectrhost="Y" +# Q: Should we disallow root login on tty's 1-6? [N] +AccountSecurity.rootttylogins="Y" +# Q: What umask would you like to set for users on the system? [077] +AccountSecurity.umask="077" +# Q: Do you want to set the default umask? [Y] +AccountSecurity.umaskyn="Y" +# Q: Would you like to deactivate the Apache web server? [Y] +Apache.apacheoff="Y" +# Q: Would you like to password protect single-user mode? [Y] +BootSecurity.passsum="Y" +# Q: Should we restrict console access to a small group of user accounts? [N] +ConfigureMiscPAM.consolelogin="Y" +# Q: Which accounts should be able to login at console? [root] +ConfigureMiscPAM.consolelogin_accounts="root" +# Q: Would you like to put limits on system resource usage? [N] +ConfigureMiscPAM.limitsconf="Y" +# Q: Would you like to set more restrictive permissions on the administration utilities? [N] +FilePermissions.generalperms_1_1="Y" +# Q: Would you like to disable SUID status for mount/umount? +FilePermissions.suidmount="Y" +# Q: Would you like to disable SUID status for ping? [Y] +FilePermissions.suidping="Y" +# Q: Would you like to disable SUID status for traceroute? [Y] +FilePermissions.suidtrace="Y" +# Q: Do you need the advanced networking options? +Firewall.ip_advnetwork="Y" +# Q: Should Bastille run the firewall and enable it at boot time? [N] +Firewall.ip_enable_firewall="Y" +# Q: Would you like to run the packet filtering script? [N] +Firewall.ip_intro="Y" +# Q: Interfaces for DHCP queries: [ ] +Firewall.ip_s_dhcpiface=" " +# Q: DNS servers: [0.0.0.0/0] +Firewall.ip_s_dns="10.184.9.1" +# Q: ICMP allowed types: [destination-unreachable echo-reply time-exceeded] +Firewall.ip_s_icmpallowed="destination-unreachable echo-reply time-exceeded" +# Q: ICMP services to audit: [ ] +Firewall.ip_s_icmpaudit=" " +# Q: ICMP types to disallow outbound: [destination-unreachable time-exceeded] +Firewall.ip_s_icmpout="destination-unreachable time-exceeded" +# Q: Internal interfaces: [ ] +Firewall.ip_s_internaliface=" " +# Q: TCP service names or port numbers to allow on private interfaces: [ ] +Firewall.ip_s_internaltcp=" " +# Q: UDP service names or port numbers to allow on private interfaces: [ ] +Firewall.ip_s_internaludp=" " +# Q: Masqueraded networks: [ ] +Firewall.ip_s_ipmasq=" " +# Q: Kernel modules to masquerade: [ftp raudio vdolive] +Firewall.ip_s_kernelmasq="ftp raudio vdolive" +# Q: NTP servers to query: [ ] +Firewall.ip_s_ntpsrv=" " +# Q: Force passive mode? [N] +Firewall.ip_s_passiveftp="N" +# Q: Public interfaces: [eth+ ppp+ slip+] +Firewall.ip_s_publiciface="eth+ ppp+ slip+" +# Q: TCP service names or port numbers to allow on public interfaces:[ ] +Firewall.ip_s_publictcp=" " +# Q: UDP service names or port numbers to allow on public interfaces:[ ] +Firewall.ip_s_publicudp=" " +# Q: Reject method: [DENY] +Firewall.ip_s_rejectmethod="DENY" +# Q: Enable source address verification? [Y] +Firewall.ip_s_srcaddr="Y" +# Q: TCP services to audit: [telnet ftp imap pop3 finger sunrpc exec login linuxconf ssh] +Firewall.ip_s_tcpaudit="telnet ftp imap pop3 finger sunrpc exec login linuxconf ssh" +# Q: TCP services to block: [2049 2065:2090 6000:6020 7100] +Firewall.ip_s_tcpblock="2049 2065:2090 6000:6020 7100" +# Q: Trusted interface names: [lo] +Firewall.ip_s_trustiface="lo" +# Q: UDP services to audit: [31337] +Firewall.ip_s_udpaudit="31337" +# Q: UDP services to block: [2049 6770] +Firewall.ip_s_udpblock="2049 6770" +# Q: Would you like to add additional logging? [Y] +Logging.morelogging="Y" +# Q: Would you like to set up process accounting? [N] +Logging.pacct="N" +# Q: Do you have a remote logging host? [N] +Logging.remotelog="N" +# Q: Would you like to disable acpid and/or apmd? [Y] +MiscellaneousDaemons.apmd="Y" +# Q: Would you like to deactivate NFS and Samba? [Y] +MiscellaneousDaemons.remotefs="Y" +# Q: Would you like to disable printing? [N] +Printing.printing="Y" +# Q: Would you like to disable printing? [N] +Printing.printing_cups="Y" +# Q: Would you like to display "Authorized Use" messages at log-in time? [Y] +SecureInetd.banners="Y" +# Q: Should Bastille ensure inetd's FTP service does not run on this system? [y] +SecureInetd.deactivate_ftp="Y" +# Q: Should Bastille ensure the telnet service does not run on this system? [y] +SecureInetd.deactivate_telnet="Y" +# Q: Who is responsible for granting authorization to use this machine? +SecureInetd.owner="its owner" +# Q: Would you like to set a default-deny on TCP Wrappers and xinetd? [N] +SecureInetd.tcpd_default_deny="Y" +# Q: Do you want to stop sendmail from running in daemon mode? [Y] +Sendmail.sendmaildaemon="Y" +# Q: Would you like to install TMPDIR/TMP scripts? [N] +TMPDIR.tmpdir="N" diff --git a/meta-security/recipes-security/bastille/files/do_not_apply_config.patch b/meta-security/recipes-security/bastille/files/do_not_apply_config.patch new file mode 100644 index 000000000..574aa9803 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/do_not_apply_config.patch @@ -0,0 +1,40 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille_Curses.pm +=================================================================== +--- Bastille.orig/Bastille_Curses.pm 2013-08-27 16:43:39.130959000 -0400 ++++ Bastille/Bastille_Curses.pm 2013-08-27 16:43:39.794959000 -0400 +@@ -83,11 +83,6 @@ + # Output answers to the script and display + &outputConfig; + +- # Run Bastille +- +- &Run_Bastille_with_Config; +- +- + # Display Credits + + open CREDITS,"/usr/share/Bastille/Credits"; +Index: Bastille/InteractiveBastille +=================================================================== +--- Bastille.orig/InteractiveBastille 2013-08-27 16:43:39.434959000 -0400 ++++ Bastille/InteractiveBastille 2013-08-27 17:18:55.758959000 -0400 +@@ -531,10 +531,10 @@ + " Please address bug reports and suggestions to jay\@bastille-linux.org\n" . + "\n"; + +- $InterfaceEndScreenDescription = "We will now implement the choices you have made here.\n\n" . ++ $InterfaceEndScreenDescription = "We will now record the choices you have made here.\n\n" . + "Answer NO if you want to go back and make changes!\n"; +- $InterfaceEndScreenQuestion = "Are you finished answering the questions, i.e. may we make the changes?"; +- $InterfaceEndScreenNoEpilogue = "Please use Back/Next buttons to move among the questions you wish to\nchange.\n\nChoose YES on this question later to implement your choices.\n"; ++ $InterfaceEndScreenQuestion = "Are you finished answering the questions, i.e. may we record the answers and exit?"; ++ $InterfaceEndScreenNoEpilogue = "Please use Back/Next buttons to move among the questions you wish to\nchange.\n\nChoose YES on this question later to record your choices.\n"; + require Bastille_Curses; + } elsif ($GLOBAL_AUDITONLY) { + diff --git a/meta-security/recipes-security/bastille/files/edit_usage_message.patch b/meta-security/recipes-security/bastille/files/edit_usage_message.patch new file mode 100644 index 000000000..72cdc2ff9 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/edit_usage_message.patch @@ -0,0 +1,32 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/bin/bastille +=================================================================== +--- Bastille.orig/bin/bastille 2013-08-25 14:16:35.614779001 -0400 ++++ Bastille/bin/bastille 2013-08-25 14:16:38.674779000 -0400 +@@ -60,7 +60,7 @@ + printUsage () { + cat >&2 << EOF + $ERRSPACES Usage: bastille [ -b | -c | -x ] [ --os <version>] [ -f <alternate config> ] +-$ERRSPACES bastille [-r | -l | -h | --assess | --assessnobrowser ] ++$ERRSPACES bastille [-r | -l | -h | --assess | --assessnobrowser ] [ --os <version> ] + $ERRSPACES -b : use a saved config file to apply changes + $ERRSPACES directly to system + $ERRSPACES -c : use the Curses (non-X11) GUI, not available on HP-UX +Index: Bastille/Bastille/API.pm +=================================================================== +--- Bastille.orig/Bastille/API.pm 2013-08-25 08:15:40.266779002 -0400 ++++ Bastille/Bastille/API.pm 2013-08-25 14:18:22.750778811 -0400 +@@ -206,7 +206,7 @@ + #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 bastille [ -r | --assess | --assessnobowser ] [ --os <version> ]\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". diff --git a/meta-security/recipes-security/bastille/files/find_existing_config.patch b/meta-security/recipes-security/bastille/files/find_existing_config.patch new file mode 100644 index 000000000..c0758752b --- /dev/null +++ b/meta-security/recipes-security/bastille/files/find_existing_config.patch @@ -0,0 +1,64 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/bin/bastille +=================================================================== +--- Bastille.orig/bin/bastille 2013-06-20 14:58:01.065796000 -0400 ++++ Bastille/bin/bastille 2013-08-20 15:16:18.472378000 -0400 +@@ -102,8 +102,9 @@ + # defines OS specific file locations based on uname + systemFileLocations + ++ config_files=`find $config_repository -type f -name \*config 2>/dev/null` ++ + if [ -f $last_config ]; then +- config_files=`find $config_repository -type f -name \*config 2>/dev/null` + for config_cursor in `echo $config_files` + do + if /usr/bin/diff $last_config $config_cursor >/dev/null 2>&1 +@@ -112,8 +113,8 @@ + fi + done + if [ -n "$match" ]; then +- echo "The last bastille run corresponds to the following profiles:" +- echo "$match" ++ printf "The last Bastille run corresponds to the following profiles:\n" ++ printf "$match" + else + cat >&2 << EOF + NOTE: The last config file applied, +@@ -122,18 +123,28 @@ + $ERRSPACES $config_repository. + $ERRSPACES This probably means that Bastille was last run interactively and + $ERRSPACES changes were made to the config file, but they have not yet been +-$ERRSPACES applied, or that the source config file was moved. If you do have pending ++$ERRSPACES applied, or that the source config file was moved. If you do have pending + $ERRSPACES changes in a config file, you can apply them by running + $ERRSPACES 'bastille -b -f <config file>.' + EOF + + fi + else +- echo "NOTE: The system is in its pre-bastilled state.\n" ++ for config_cursor in `echo $config_files` ++ do ++ match="$match $config_cursor\n" ++ done ++ if [ -n "$match" ]; then ++ printf "The following Bastille profiles were located:\n" ++ printf "$match" ++ else ++ printf "No Bastille profiles were located.\n" ++ fi ++ printf "No log files of profiles from previous executions of Bastille have been found. It is likely that Bastille has not been run on this machine.\n" + fi +- + } + ++ + # First, make sure we're root + if [ `PATH="/usr/bin:/bin"; id -u` -ne 0 ]; then + echo "ERROR: Bastille must be run as root user" >&2 diff --git a/meta-security/recipes-security/bastille/files/fix_missing_use_directives.patch b/meta-security/recipes-security/bastille/files/fix_missing_use_directives.patch new file mode 100644 index 000000000..05f145a3e --- /dev/null +++ b/meta-security/recipes-security/bastille/files/fix_missing_use_directives.patch @@ -0,0 +1,54 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille/Firewall.pm +=================================================================== +--- Bastille.orig/Bastille/Firewall.pm 2008-09-14 19:56:54.000000000 -0400 ++++ Bastille/Bastille/Firewall.pm 2013-08-20 16:28:44.588378000 -0400 +@@ -21,6 +21,7 @@ + package Bastille::Firewall; + + use Bastille::API; ++use Bastille::API::AccountPermission; + use Bastille::API::FileContent; + use Bastille::API::ServiceAdmin; + +Index: Bastille/Bastille/SecureInetd.pm +=================================================================== +--- Bastille.orig/Bastille/SecureInetd.pm 2008-09-14 19:56:58.000000000 -0400 ++++ Bastille/Bastille/SecureInetd.pm 2013-08-20 16:45:02.252378001 -0400 +@@ -12,6 +12,7 @@ + use lib "/usr/lib"; + + use Bastille::API; ++use Bastille::API::AccountPermission; + use Bastille::API::HPSpecific; + use Bastille::API::ServiceAdmin; + use Bastille::API::FileContent; +Index: Bastille/Bastille/ConfigureMiscPAM.pm +=================================================================== +--- Bastille.orig/Bastille/ConfigureMiscPAM.pm 2005-09-12 23:47:28.000000000 -0400 ++++ Bastille/Bastille/ConfigureMiscPAM.pm 2013-08-20 18:36:07.340378001 -0400 +@@ -5,6 +5,7 @@ + use lib "/usr/lib"; + + use Bastille::API; ++use Bastille::API::FileContent; + + # To DO: + # +Index: Bastille/Bastille/Printing.pm +=================================================================== +--- Bastille.orig/Bastille/Printing.pm 2008-09-14 19:56:58.000000000 -0400 ++++ Bastille/Bastille/Printing.pm 2013-08-20 19:05:01.532378002 -0400 +@@ -5,6 +5,7 @@ + use lib "/usr/lib"; + + use Bastille::API; ++use Bastille::API::AccountPermission; + use Bastille::API::HPSpecific; + use Bastille::API::ServiceAdmin; + use Bastille::API::FileContent; diff --git a/meta-security/recipes-security/bastille/files/fix_number_of_modules.patch b/meta-security/recipes-security/bastille/files/fix_number_of_modules.patch new file mode 100644 index 000000000..743e54920 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/fix_number_of_modules.patch @@ -0,0 +1,38 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille_Curses.pm +=================================================================== +--- Bastille.orig/Bastille_Curses.pm 2013-08-24 18:21:54.445288000 -0400 ++++ Bastille/Bastille_Curses.pm 2013-08-24 18:29:16.981288000 -0400 +@@ -36,9 +36,6 @@ + use Curses; + use Curses::Widgets; + +- # Number_Modules is the number of modules loaded in by Load_Questions +- $Number_Modules=0; +- + # + # Highlighted button is the button currently chosen in the button bar + # We preserve this from question to question... +@@ -397,7 +394,7 @@ + my $title; + + if ($module) { +- $title=$module . " of $Number_Modules"; ++ $title=$module; + } + + txt_field( 'window' => $window, +@@ -488,7 +485,7 @@ + my $title; + + if ($module) { +- $title=$module . " of $Number_Modules"; ++ $title=$module; + } + + noecho; diff --git a/meta-security/recipes-security/bastille/files/fix_version_parse.patch b/meta-security/recipes-security/bastille/files/fix_version_parse.patch new file mode 100644 index 000000000..5923c0408 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/fix_version_parse.patch @@ -0,0 +1,27 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/bin/bastille +=================================================================== +--- Bastille.orig/bin/bastille ++++ Bastille/bin/bastille +@@ -162,11 +162,12 @@ fi + # We check that the version is at least the minimum + + PERL_VERSION=`${CURRENT_PERL_PATH}/perl -version | +- head -2 | # the second line contains the version ++ head -n 2 | # the second line contains the version + tr " " "\n" | # split words into separate lines +- sed -e "s/^v//" | # to get rid of the v in v5.6.0 +- grep "^[1-9]\." | # find a "word" that starts with number dot +- sed -e "s/_/./"` # substitute _patchlevel with .patchlevel ++ grep "^(v" | # find a "word" that starts with '(v' ++ sed -e "s/^(v//" -e "s/)//" -e "s/_/./"` ++ # to get rid of the (v in v5.6.0 ++ # substitute _patchlevel with .patchlevel + # (used in 5.005_03 and prior) + + # everything before the first . diff --git a/meta-security/recipes-security/bastille/files/fixed_defined_warnings.patch b/meta-security/recipes-security/bastille/files/fixed_defined_warnings.patch new file mode 100644 index 000000000..e7996e380 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/fixed_defined_warnings.patch @@ -0,0 +1,65 @@ +From c59b84ca3bda8e4244d47901b6966f28dd675434 Mon Sep 17 00:00:00 2001 +From: Andrei Dinu <andrei.adrianx.dinu@intel.com> +Date: Thu, 23 May 2013 15:12:23 +0300 +Subject: [PATCH] added yocto-standard to bastille + +In order to make Bastille functional and avoid errors +regarding distros, if not any given distro is identified, +yocto-standard distro is added to the distro variable +in Bastille. + +Fixed also some warnings regarding defined statements +in API.pm. + +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Andrei Dinu <andrei.adrianx.dinu@intel.com> + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + Bastille/API.pm | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +Index: Bastille/Bastille/API.pm +=================================================================== +--- Bastille.orig/Bastille/API.pm 2008-09-14 19:56:53.000000000 -0400 ++++ Bastille/Bastille/API.pm 2013-08-21 08:55:26.715950001 -0400 +@@ -445,8 +445,8 @@ + $release=`/usr/bin/uname -sr`; + } + else { +- print STDERR "$err Could not determine operating system version!\n"; +- $distro="unknown"; ++ print STDERR "$err Could not determine operating system version!\n"; ++ $distro="unknown" + } + + # Figure out what kind of system we're on. +@@ -1284,7 +1284,7 @@ + + my $sumFile = &getGlobal('BFILE',"sum.csv"); + +- if ( defined %GLOBAL_SUM ) { ++ if ( %GLOBAL_SUM ) { + + open( SUM, "> $sumFile") or &B_log("ERROR","Unable to open $sumFile for write.\n$!\n"); + +@@ -1318,7 +1318,7 @@ + my $file = $_[0]; + my $cksum = &getGlobal('BIN',"cksum"); + +- if (not(defined(%GLOBAL_SUM))) { ++ if (not(%GLOBAL_SUM)) { + &B_read_sums; + } + +@@ -1375,7 +1375,7 @@ + sub B_isFileinSumDB($) { + my $file = $_[0]; + +- if (not(defined(%GLOBAL_SUM))) { ++ if (not(%GLOBAL_SUM)) { + &B_log("DEBUG","Reading in DB from B_isFileinSumDB"); + &B_read_sums; + } diff --git a/meta-security/recipes-security/bastille/files/organize_distro_discovery.patch b/meta-security/recipes-security/bastille/files/organize_distro_discovery.patch new file mode 100644 index 000000000..d64d1e26e --- /dev/null +++ b/meta-security/recipes-security/bastille/files/organize_distro_discovery.patch @@ -0,0 +1,476 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille/API.pm +=================================================================== +--- Bastille.orig/Bastille/API.pm 2013-08-22 04:32:38.269968002 -0400 ++++ Bastille/Bastille/API.pm 2013-08-22 11:29:53.137968002 -0400 +@@ -141,7 +141,7 @@ + checkProcsForService + + +- $GLOBAL_OS $GLOBAL_ACTUAL_OS $CLI ++ $CLI + $GLOBAL_LOGONLY $GLOBAL_VERBOSE $GLOBAL_DEBUG $GLOBAL_AUDITONLY $GLOBAL_AUDIT_NO_BROWSER $errorFlag + %GLOBAL_BIN %GLOBAL_DIR %GLOBAL_FILE + %GLOBAL_BDIR %GLOBAL_BFILE +@@ -198,7 +198,7 @@ + my $err ="ERROR: "; + my $spc =" "; + my $GLOBAL_OS="None"; +-my $GLOBAL_ACTUAL_OS="None"; ++my $GLOBAL_INFERRED_OS="None"; + my %GLOBAL_SUMS=(); + my $CLI=''; + +@@ -306,7 +306,7 @@ + + ########################################################################### + # +-# GetDistro checks to see if the target is a known distribution and reports ++# InferDistro checks to see if the target is a known distribution and reports + # said distribution. + # + # This is used throughout the script, but also by ConfigureForDistro. +@@ -314,205 +314,194 @@ + # + ########################################################################### + +-sub GetDistro() { ++sub InferDistro() { + + 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'; +- } ++ 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 Could not infer 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 Could not infer Immunix version! Setting to 6.2!\n"; ++ $distro="RH6.2"; ++ } ++ else { ++ $distro="RH$1"; + } +- 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"; ++ 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 infer 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 ($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"; ++ elsif ($version =~ /^3\./) { ++ $distro='RHEL3AS'; + } + 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"; ++ print STDERR "$err Could not infer CentOS version! Setting to Red Hat Enterprise 4 AS.\n"; ++ $distro='RHEL4AS'; + } +- 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 { ++ # JJB/HP - Should this be B_log? ++ print STDERR "$err Could not infer 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 Could not infer 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 Could not infer TurboLinux version! Setting to 7.0!\n"; ++ $distro="TB7.0"; ++ } + else { +- # We're either on Mac OS X, HP-UX or an unsupported O/S. +- if ( -x '/usr/bin/uname') { ++ $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"; +- } ++ $release=`/usr/bin/uname -sr`; ++ } ++ else { ++ print STDERR "$err Could not infer operating system version from filesystem context. Setting inferred distro to 'unknown'.\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 ( $release =~ /(^HP-UX)\s*B\.(\d+\.\d+)/ ) { +- $distro="$1$2"; ++ elsif ($1 == 7) { ++ $distro = "OSX10.3"; + } ++ elsif ($1 == 8) { ++ $distro = "OSX10.3"; ++ } + else { +- print STDERR "$err Could not determine operating system version!\n"; +- $distro="unknown"; ++ print STDERR "$err Could not infer operating system version from filesystem context. Setting inferred distro to 'unknown'.\n"; ++ $distro = "unknown"; + } + } ++ elsif ( $release =~ /(^HP-UX)\s*B\.(\d+\.\d+)/ ) { ++ $distro="$1$2"; ++ } ++ else { ++ print STDERR "$err Could not infer operating system version from filesystem context. Setting inferred distro to 'unknown'.\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; # ++# &getInferredDistro; # + # # + # 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 ++sub getInferredDistro { ++ if ($GLOBAL_INFERRED_OS eq "None") { ++ $GLOBAL_INFERRED_OS = &InferDistro; ++ } ++ return $GLOBAL_INFERRED_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; ++sub GetDistro { ++ if ($GLOBAL_OS eq "None") { ++ return &getInferredDistro; + } +- return $GLOBAL_ACTUAL_OS; ++ return $GLOBAL_OS; + } ++ + # These are helper routines which used to be included inside GetDistro + sub is_OS_supported($) { + my $os=$_[0]; +@@ -556,7 +545,8 @@ + "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" ++ "TB7.0", ++ "Yocto" + ], + + "HP-UX" => [ +@@ -882,23 +872,19 @@ + ########################################################################### + 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; ++ my $inferredDistro = &getInferredDistro; ++ ++ if (! ($inferredDistro eq $distro) ) { ++ print STDERR "WARNING: Inferred distro $inferredDistro is not the same as specified distro $distro. Using specified distro.\n"; ++ } ++ + $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"; +- } ++ ++ if (! &is_OS_supported($distro)) { ++ print STDERR "$err '$distro' is not a supported operating system.\n"; ++ + my %supportedOSHash = &getSupportedOSHash; + print STDERR "$spc Valid operating system versions are as follows:\n"; + +@@ -930,7 +916,7 @@ + # intend via setting the Perl umask + umask(077); + +- &getFileAndServiceInfo($distro,$actualDistro); ++ &getFileAndServiceInfo($distro,$distro); + + # &dumpFileInfo; # great for debuging file location issues + # &dumpServiceInfo; # great for debuging service information issues +@@ -942,7 +928,7 @@ + "$spc You must use Bastille\'s -n flag (for example:\n" . + "$spc bastille -f -n) or \'touch $nodisclaim_file \'\n"; + +- return $retval; ++ return 1; + } + + +Index: Bastille/Bastille/LogAPI.pm +=================================================================== +--- Bastille.orig/Bastille/LogAPI.pm 2013-08-22 04:32:38.269968002 -0400 ++++ Bastille/Bastille/LogAPI.pm 2013-08-22 04:32:47.509968002 -0400 +@@ -111,7 +111,7 @@ + # do this here to prevent bootstrapping problem, where we need to + # write an error that the errorlog location isn't defined. + my $logdir="/var/log/Bastille"; +- if(&getActualDistro =~ "^HP-UX"){ ++ if(&getInferredDistro =~ "^HP-UX"){ + $logdir = "/var/opt/sec_mgmt/bastille/log/"; + } + diff --git a/meta-security/recipes-security/bastille/files/remove_questions_text_file_references.patch b/meta-security/recipes-security/bastille/files/remove_questions_text_file_references.patch new file mode 100644 index 000000000..bd094ee25 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/remove_questions_text_file_references.patch @@ -0,0 +1,30 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/OSMap/LINUX.bastille +=================================================================== +--- Bastille.orig/OSMap/LINUX.bastille 2008-01-25 18:31:35.000000000 -0500 ++++ Bastille/OSMap/LINUX.bastille 2013-08-22 04:48:32.677968002 -0400 +@@ -12,7 +12,6 @@ + + bfile,InteractiveBastille,'/usr/sbin/InteractiveBastille' + bfile,BastilleBackEnd,'/usr/sbin/BastilleBackEnd' +-bfile,Questions,'/usr/share/Bastille/Questions.txt' + bfile,QuestionsModules,'/usr/share/Bastille/Modules.txt' + bfile,TODO,'/var/log/Bastille/TODO' + bfile,TODOFlag,'/var/log/Bastille/TODOFlag.txt' +Index: Bastille/OSMap/OSX.bastille +=================================================================== +--- Bastille.orig/OSMap/OSX.bastille 2007-09-11 18:09:26.000000000 -0400 ++++ Bastille/OSMap/OSX.bastille 2013-08-22 04:48:47.245968001 -0400 +@@ -10,7 +10,6 @@ + bdir,share,'/usr/share/Bastille' + + bfile,BastilleBackEnd,'/var/root/Bastille/BastilleBackEnd' +-bfile,Questions,'/usr/share/Bastille/Questions.txt' + bfile,QuestionsModules,'/usr/share/Bastille/Modules.txt' + bfile,TODO,'/var/log/Bastille/TODO' + bfile,TODOFlag,'/var/log/Bastille/TODOFlag.txt' diff --git a/meta-security/recipes-security/bastille/files/set_required_questions.py b/meta-security/recipes-security/bastille/files/set_required_questions.py new file mode 100755 index 000000000..4a28358c3 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/set_required_questions.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +#Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +import argparse, os, shutil, sys, tempfile, traceback +from os import path + + + +def get_config(lines): + """ + From a sequence of lines retrieve the question file name, question identifier + pairs. + """ + for l in lines: + if not l.startswith("#"): + try: + (coord, value) = l.split("=") + try: + (fname, ident) = coord.split(".") + yield fname, ident + except ValueError as e: + raise ValueError("Badly formatted coordinates %s in line %s." % (coord, l.strip())) + except ValueError as e: + raise ValueError("Skipping badly formatted line %s, %s" % (l.strip(), e)) + + + +def check_contains(line, name): + """ + Check if the value field for REQUIRE_DISTRO contains the given name. + @param name line The REQUIRE_DISTRO line + @param name name The name to look for in the value field of the line. + """ + try: + (label, distros) = line.split(":") + return name in distros.split() + except ValueError as e: + raise ValueError("Error splitting REQUIRE_DISTRO line: %s" % e) + + + +def add_requires(the_ident, distro, lines): + + """ + Yield a sequence of lines the same as lines except that where + the_ident matches a question identifier change the REQUIRE_DISTRO so that + it includes the specified distro. + + @param name the_ident The question identifier to be matched. + @param name distro The distribution to added to the questions REQUIRE_DISTRO + field. + @param lines The sequence to be processed. + """ + for l in lines: + yield l + if l.startswith("LABEL:"): + try: + (label, ident) = l.split(":") + if ident.strip() == the_ident: + break + except ValueError as e: + raise ValueError("Unexpected line %s in questions file." % l.strip()) + for l in lines: + if l.startswith("REQUIRE_DISTRO"): + if not check_contains(l, distro): + yield l.rstrip() + " " + distro + "\n" + else: + yield l + break; + else: + yield l + for l in lines: + yield l + + + +def xform_file(qfile, distro, qlabel): + """ + Transform a Questions file. + @param name qfile The designated questions file. + @param name distro The distribution to add to the required distributions. + @param name qlabel The question label for which the distro is to be added. + """ + questions_in = open(qfile) + questions_out = tempfile.NamedTemporaryFile(delete=False) + for l in add_requires(qlabel, distro, questions_in): + questions_out.write(l) + questions_out.close() + questions_in.close() + shutil.copystat(qfile, questions_out.name) + os.remove(qfile) + shutil.move(questions_out.name, qfile) + + + +def handle_args(parser): + parser.add_argument('config_file', + help = "Configuration file path.") + parser.add_argument('questions_dir', + help = "Directory containing Questions files.") + parser.add_argument('--distro', '-d', + help = "The distribution, the default is Yocto.", + default = "Yocto") + parser.add_argument('--debug', '-b', + help = "Print debug information.", + action = 'store_true') + return parser.parse_args() + + + +def check_args(args): + args.config_file = os.path.abspath(args.config_file) + args.questions_dir = os.path.abspath(args.questions_dir) + + if not os.path.isdir(args.questions_dir): + raise ValueError("Specified Questions directory %s does not exist or is not a directory." % args.questions_dir) + + if not os.path.isfile(args.config_file): + raise ValueError("Specified configuration file %s not found." % args.config_file) + + + +def main(): + opts = handle_args(argparse.ArgumentParser(description="A simple script that sets required questions based on the question/answer pairs in a configuration file.")) + + try: + check_args(opts) + except ValueError as e: + if opts.debug: + traceback.print_exc() + else: + sys.exit("Fatal error:\n%s" % e) + + + try: + config_in = open(opts.config_file) + for qfile, qlabel in get_config(config_in): + questions_file = os.path.join(opts.questions_dir, qfile + ".txt") + xform_file(questions_file, opts.distro, qlabel) + config_in.close() + + except IOError as e: + if opts.debug: + traceback.print_exc() + else: + sys.exit("Fatal error reading or writing file:\n%s" % e) + except ValueError as e: + if opts.debug: + traceback.print_exc() + else: + sys.exit("Fatal error:\n%s" % e) + + + +if __name__ == "__main__": + main() diff --git a/meta-security/recipes-security/bastille/files/simplify_B_place.patch b/meta-security/recipes-security/bastille/files/simplify_B_place.patch new file mode 100644 index 000000000..307fdcab0 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/simplify_B_place.patch @@ -0,0 +1,40 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille/API.pm +=================================================================== +--- Bastille.orig/Bastille/API.pm 2013-08-21 08:59:17.939950001 -0400 ++++ Bastille/Bastille/API.pm 2013-08-21 08:59:30.983950001 -0400 +@@ -1679,24 +1679,22 @@ + + 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"); ++ &B_backup_file($target); ++ &B_log("ACTION","About to copy $source to $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"); ++ &B_log("ACTION","placed file $source as $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"); ++ &B_revert_log(&getGlobal('BIN',"rm") . " $target\n"); + } else { +- &B_log("ERROR","Failed to place $original_source as $original_target\n"); ++ &B_log("ERROR","Failed to place $source as $target\n"); + } + + # We add the file to the GLOBAL_SUMS hash if it is not already present diff --git a/meta-security/recipes-security/bastille/files/upgrade_options_processing.patch b/meta-security/recipes-security/bastille/files/upgrade_options_processing.patch new file mode 100644 index 000000000..4093867e9 --- /dev/null +++ b/meta-security/recipes-security/bastille/files/upgrade_options_processing.patch @@ -0,0 +1,91 @@ +Upstream Status: Inappropriate [No upstream maintenance] + +Signed-off-by: Anne Mulhern <mulhern@yoctoproject.org> + +--- + +Index: Bastille/Bastille/API.pm +=================================================================== +--- Bastille.orig/Bastille/API.pm 2013-08-21 11:41:09.235950000 -0400 ++++ Bastille/Bastille/API.pm 2013-08-21 11:41:16.183950000 -0400 +@@ -271,9 +271,15 @@ + # 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) = @_; ++sub setOptions { ++ my %opts = @_; ++ ++ $GLOBAL_DEBUG = $opts{debug}; ++ $GLOBAL_LOGONLY = $opts{logonly}; ++ $GLOBAL_VERBOSE = $opts{verbose}; ++ $GLOBAL_AUDITONLY = $opts{auditonly}; ++ $GLOBAL_AUDIT_NO_BROWSER = $opts{audit_no_browser}; ++ $GLOBAL_OS = $opts{os}; + if ($GLOBAL_AUDIT_NO_BROWSER) { + $GLOBAL_AUDITONLY = 1; + } +Index: Bastille/BastilleBackEnd +=================================================================== +--- Bastille.orig/BastilleBackEnd 2013-08-21 11:41:09.235950000 -0400 ++++ Bastille/BastilleBackEnd 2013-08-21 12:40:54.055950001 -0400 +@@ -50,15 +50,13 @@ + my $nodisclaim = 0; + my $verbose = 0; + my $force = 0; +-my $log_only = 0; + my $debug = 0; + my $alternate_config=undef; + + if( Getopt::Long::GetOptions( "n" => \$nodisclaim, + "v" => \$verbose, + "force" => \$force, +-# "log" => \$log_only, # broken +- "f:s" => \$alternate_config, ++ "f=s" => \$alternate_config, + "debug" => \$debug) ) { + $error = 0; # no parse error + +@@ -66,7 +64,9 @@ + $error = 1; # parse error + } + +-&setOptions($debug,$log_only,$verbose); ++&setOptions( ++ debug => $debug, ++ verbose => $verbose); + &ConfigureForDistro; + + if ( $error ) { # GetOptions couldn't parse all of the args +Index: Bastille/InteractiveBastille +=================================================================== +--- Bastille.orig/InteractiveBastille 2013-08-21 11:41:09.235950000 -0400 ++++ Bastille/InteractiveBastille 2013-08-21 12:40:30.531950001 -0400 +@@ -234,8 +234,8 @@ + "a" => \$audit, + "force" => \$force, + "log" => \$log_only, +- "os:s" => \$os_version, +- "f:s" => \$alternate_config, ++ "os=s" => \$os_version, ++ "f=s" => \$alternate_config, + "debug" => \$debug) ) { + $error = 0; # no parse error + } else { +@@ -293,7 +293,13 @@ + $UseRequiresRules = 'N'; + } + +-&setOptions($debug,$log_only,$verbose,$audit,$auditnobrowser,$os_version); ++&setOptions( ++ debug => $debug, ++ logonly => $log_only, ++ verbose => $verbose, ++ auditonly => $audit, ++ audit_no_browser => $auditnobrowser, ++ os => $os_version); + &ConfigureForDistro; + + # ensuring mutually exclusive options are exclusive |