mirror of
https://git.yoctoproject.org/meta-security
synced 2026-01-11 15:00:34 +00:00
1154 lines
37 KiB
Perl
1154 lines
37 KiB
Perl
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;
|
|
|
|
|