1package Bastille::API::FileContent;
2use strict;
3
4use Bastille::API;
5
6require Exporter;
7our @ISA = qw(Exporter);
8our @EXPORT_OK = qw(
9B_blank_file
10B_insert_line_after
11B_insert_line_before
12B_insert_line
13B:append_line
14B:prepend_line
15B_replace_line
16B_replace_lines
17B_replace_pattern
18B_match_line
19B_match_line_only
20B_match_chunk
21B_return_matched_lines
22B_hash_comment_line
23B_hash_uncomment_line
24B_delete_line
25B_chunk_replace
26B_print
27B_getValueFromFile
28B_getValueFromString
29
30B_TODO
31B_TODOFlags
32);
33our @EXPORT = @EXPORT_OK;
34
35
36
37###########################################################################
38# &B_blank_file ($filename,$pattern) blanks the file $filename, unless the
39# pattern $pattern is present in the file.  This lets us completely redo
40# a file, if it isn't the one we put in place on a previous run...
41#
42# B_blank_file respects $GLOBAL_LOGONLY and uses B_open_plus and B_close_plus
43# so that it makes backups and only modifies files when we're not in "-v"
44# mode...
45#
46# If the file does not exist, the function does nothing, and gives an error
47# to the Error Log
48#
49###########################################################################
50
51sub B_blank_file($$) {
52
53    my ($filename,$pattern) = @_;
54    my $retval;
55
56    # If this variable is true, we won't blank the file...
57
58    my $found_pattern=0;
59
60    if ($retval=&B_open_plus (*BLANK_NEW,*BLANK_OLD,$filename) ) {
61
62        my @lines;
63
64        while (my $line = <BLANK_OLD>) {
65
66            push @lines,$line;
67            if ($line =~ $pattern) {
68                $found_pattern=1;
69            }
70        }
71
72        # Only copy the old file if the new one didn't match.
73        if ($found_pattern) {
74            while ( my $line = shift @lines ) {
75                &B_print(*BLANK_NEW,$line);
76            }
77        }
78        else {
79            &B_log("ACTION","Blanked file $filename\n");
80        }
81        &B_close_plus(*BLANK_NEW,*BLANK_OLD,$filename);
82    }
83    else {
84        &B_log("ERROR","Couldn't blank file $filename since we couldn't open it or its replacement\n");
85    }
86
87    return $retval;
88
89}
90
91###########################################################################
92# &B_insert_line_after ($filename,$pattern,$line_to_insert,$line_to_follow)
93# modifies $filename, inserting $line_to_insert unless one or more lines
94# in the file matches $pattern.  The $line_to_insert will be placed
95# immediately after $line_to_follow, if it exists.  If said line does not
96# exist, the line will not be inserted and this routine will return 0.
97#
98# B_insert_line uses B_open_plus and B_close_plus, so that the file
99# modified is backed up...
100#
101# Here's examples of where you might use this:
102#
103# You'd like to insert a line in Apache's configuration file, in a
104# particular section.
105#
106###########################################################################
107
108sub B_insert_line_after($$$$) {
109
110    my ($filename,$pattern,$line_to_insert,$line_to_follow) = @_;
111
112    my @lines;
113    my $found_pattern=0;
114    my $found_line_to_follow=0;
115
116    my $retval=1;
117
118    if ( &B_open_plus (*INSERT_NEW,*INSERT_OLD,$filename) ) {
119
120        # Read through the file looking for a match both on the $pattern
121        # and the line we are supposed to be inserting after...
122
123        my $ctr=1;
124        while (my $line=<INSERT_OLD>) {
125            push (@lines,$line);
126            if ($line =~ $pattern) {
127                $found_pattern=1;
128            }
129            if ( ($found_line_to_follow < 1) and ($line =~ $line_to_follow)) {
130                $found_line_to_follow=$ctr;
131            }
132            $ctr++;
133        }
134
135        # Log an error if we never found the line we were to insert after
136        unless ($found_line_to_follow ) {
137            $retval=0;
138            &B_log("ERROR","Never found the line that we were supposed to insert after in $filename\n");
139        }
140
141        # Now print the file back out, inserting our line if we should...
142
143        $ctr=1;
144        while (my $line = shift @lines) {
145            &B_print(*INSERT_NEW,$line);
146            if ( ($ctr == $found_line_to_follow) and ($found_pattern == 0) ) {
147                &B_print(*INSERT_NEW,$line_to_insert);
148                &B_log("ACTION","Inserted the following line in $filename:\n");
149                &B_log("ACTION","$line_to_insert");
150            }
151            $ctr++;
152        }
153
154        &B_close_plus (*INSERT_NEW,*INSERT_OLD,$filename);
155
156    }
157    else {
158        $retval=0;
159        &B_log("ERROR","Couldn't insert line to $filename, since open failed.");
160    }
161
162    return $retval;
163
164}
165###########################################################################
166# &B_insert_line_before ($filename,$pattern,$line_to_insert,$line_to_preceed)
167# modifies $filename, inserting $line_to_insert unless one or more lines
168# in the file matches $pattern.  The $line_to_insert will be placed
169# immediately before $line_to_preceed, if it exists.  If said line does not
170# exist, the line will not be inserted and this routine will return 0.
171#
172# B_insert_line uses B_open_plus and B_close_plus, so that the file
173# modified is backed up...
174#
175# Here's examples of where you might use this:
176#
177# You'd like to insert a line in Apache's configuration file, in a
178# particular section.
179#
180###########################################################################
181
182sub B_insert_line_before($$$$) {
183
184    my ($filename,$pattern,$line_to_insert,$line_to_preceed) = @_;
185
186    my @lines;
187    my $found_pattern=0;
188    my $found_line_to_preceed=0;
189
190    my $retval=1;
191
192    if ( &B_open_plus (*INSERT_NEW,*INSERT_OLD,$filename) ) {
193
194        # Read through the file looking for a match both on the $pattern
195        # and the line we are supposed to be inserting after...
196
197        my $ctr=1;
198        while (my $line=<INSERT_OLD>) {
199            push (@lines,$line);
200            if ($line =~ $pattern) {
201                $found_pattern=1;
202            }
203            if ( ($found_line_to_preceed < 1) and ($line =~ $line_to_preceed)) {
204                $found_line_to_preceed=$ctr;
205            }
206            $ctr++;
207        }
208
209        # Log an error if we never found the line we were to preceed
210        unless ($found_line_to_preceed ) {
211            $retval=0;
212            &B_log("ERROR","Never found the line that we were supposed to insert before in $filename\n");
213        }
214
215        # Now print the file back out, inserting our line if we should...
216
217        $ctr=1;
218        while (my $line = shift @lines) {
219            if ( ($ctr == $found_line_to_preceed) and ($found_pattern == 0) ) {
220                &B_print(*INSERT_NEW,$line_to_insert);
221                &B_log("ACTION","Inserted the following line in $filename:\n");
222                &B_log("ACTION","$line_to_insert");
223            }
224            &B_print(*INSERT_NEW,$line);
225            $ctr++;
226        }
227
228        &B_close_plus (*INSERT_NEW,*INSERT_OLD,$filename);
229
230    }
231    else {
232        $retval=0;
233        &B_log("ERROR","Couldn't insert line to $filename, since open failed.");
234    }
235
236    return $retval;
237
238}
239
240###########################################################################
241# &B_insert_line ($filename,$pattern,$line_to_insert,$line_to_follow)
242#
243#   has been renamed to B_insert_line_after()
244#
245# This name will continue to work, as a shim for code that has not been
246# transitioned.
247###########################################################################
248
249sub B_insert_line($$$$) {
250
251    my $rtn_value = &B_insert_line_after(@_);
252
253    return ($rtn_value);
254}
255
256
257###########################################################################
258# &B_append_line ($filename,$pattern,$line_to_append)  modifies $filename,
259# appending $line_to_append unless one or more lines in the file matches
260# $pattern.  This is an enhancement to the append_line_if_no_such_line_exists
261# idea.
262#
263# Additionally, if $pattern is set equal to "", the line is always appended.
264#
265# B:append_line uses B_open_plus and B_close_plus, so that the file
266# modified is backed up...
267#
268# Here's examples of where you might use this:
269#
270# You'd like to add a   root   line to /etc/ftpusers if none exists.
271# You'd like to add a   Options Indexes  line to Apache's config. file,
272# after you delete all Options lines from said config file.
273#
274###########################################################################
275
276sub B:append_line($$$) {
277
278    my ($filename,$pattern,$line_to_append) = @_;
279
280    my $found_pattern=0;
281    my $retval=1;
282
283    if ( &B_open_plus (*APPEND_NEW,*APPEND_OLD,$filename) ) {
284        while (my $line=<APPEND_OLD>) {
285            &B_print(*APPEND_NEW,$line);
286            if ($line =~ $pattern) {
287                $found_pattern=1;
288            }
289        }
290        # Changed != 0 to $pattern so that "" works instead of 0 and perl
291        # does not give the annoying
292        # Argument "XX" isn't numeric in ne at ...
293        if ( $pattern eq "" or ! $found_pattern ) {
294            &B_print(*APPEND_NEW,$line_to_append);
295            &B_log("ACTION","Appended the following line to $filename:\n");
296            &B_log("ACTION","$line_to_append");
297        }
298        &B_close_plus (*APPEND_NEW,*APPEND_OLD,$filename);
299    }
300    else {
301        $retval=0;
302        &B_log("ERROR","# Couldn't append line to $filename, since open failed.");
303    }
304
305    return $retval;
306
307}
308
309###########################################################################
310# &B_prepend_line ($filename,$pattern,$line_to_prepend)  modifies $filename,
311# pre-pending $line_to:prepend unless one or more lines in the file matches
312# $pattern.  This is an enhancement to the prepend_line_if_no_such_line_exists
313# idea.
314#
315# B:prepend_line uses B_open_plus and B_close_plus, so that the file
316# modified is backed up...
317#
318# Here's examples of where you might use this:
319#
320# You'd like to insert the line "auth   required   pam_deny.so" to the top
321# of the PAM stack file /etc/pam.d/rsh to totally deactivate rsh.
322#
323###########################################################################
324
325sub B:prepend_line($$$) {
326
327    my ($filename,$pattern,$line_to_prepend) = @_;
328
329    my @lines;
330    my $found_pattern=0;
331    my $retval=1;
332
333    if ( &B_open_plus (*PREPEND_NEW,*PREPEND_OLD,$filename) ) {
334        while (my $line=<PREPEND_OLD>) {
335            push (@lines,$line);
336            if ($line =~ $pattern) {
337                $found_pattern=1;
338            }
339        }
340        unless ($found_pattern) {
341            &B_print(*PREPEND_NEW,$line_to_prepend);
342        }
343        while (my $line = shift @lines) {
344            &B_print(*PREPEND_NEW,$line);
345        }
346
347        &B_close_plus (*PREPEND_NEW,*PREPEND_OLD,$filename);
348
349        # Log the action
350        &B_log("ACTION","Pre-pended the following line to $filename:\n");
351        &B_log("ACTION","$line_to:prepend");
352    }
353    else {
354        $retval=0;
355        &B_log("ERROR","Couldn't prepend line to $filename, since open failed.\n");
356    }
357
358    return $retval;
359
360}
361
362
363###########################################################################
364# &B_replace_line ($filename,$pattern,$line_to_switch_in) modifies $filename,
365# replacing any lines matching $pattern with $line_to_switch_in.
366#
367# It returns the number of lines it replaced (or would have replaced, if
368# LOGONLY mode wasn't on...)
369#
370# B_replace_line uses B_open_plus and B_close_plus, so that the file
371# modified is backed up...
372#
373# Here an example of where you might use this:
374#
375# You'd like to replace any Options lines in Apache's config file with:
376#            Options Indexes FollowSymLinks
377#
378###########################################################################
379
380sub B_replace_line($$$) {
381
382    my ($filename,$pattern,$line_to_switch_in) = @_;
383    my $retval=0;
384
385    if ( &B_open_plus (*REPLACE_NEW,*REPLACE_OLD,$filename) ) {
386        while (my $line=<REPLACE_OLD>) {
387            unless ($line =~ $pattern) {
388                &B_print(*REPLACE_NEW,$line);
389            }
390            else {
391                # Don't replace the line if it's already there.
392                unless ($line eq $line_to_switch_in) {
393                    &B_print(*REPLACE_NEW,$line_to_switch_in);
394
395                    $retval++;
396                    &B_log("ACTION","File modification in $filename -- replaced line\n" .
397                           "$line\n" .
398                           "with:\n" .
399                           "$line_to_switch_in");
400                }
401                # But if it is there, make sure it stays there! (by Paul Allen)
402                else {
403                    &B_print(*REPLACE_NEW,$line);
404                }
405            }
406        }
407        &B_close_plus (*REPLACE_NEW,*REPLACE_OLD,$filename);
408    }
409    else {
410        $retval=0;
411        &B_log("ERROR","Couldn't replace line(s) in $filename because open failed.\n");
412    }
413
414    return $retval;
415}
416
417###########################################################################
418# &B_replace_lines ($filename,$patterns_and_substitutes) modifies $filename,
419# replacing the line matching the nth $pattern specified in $patterns_and_substitutes->[n]->[0]
420# with the corresponding substitutes in $patterns_and_substitutes->[n]->-[1]
421#
422# It returns the number of lines it replaced (or would have replaced, if
423# LOGONLY mode wasn't on...)
424#
425# B_replace_lines uses B_open_plus and B_close_plus, so that the file
426# modified is backed up...
427#
428# Here an example of where you might use this:
429#
430# You'd like to replace /etc/opt/ssh/sshd_config file
431# (^#|^)Protocol\s+(.*)\s*$                             ==>                Protocol 2
432# (^#|^)X11Forwarding\s+(.*)\s*$                  ==>                X11Forwarding yes
433# (^#|^)IgnoreRhosts\s+(.*)\s*$                     ==>                gnoreRhosts yes
434# (^#|^)RhostsAuthentication\s+(.*)\s*$         ==>                RhostsAuthentication no
435# (^#|^)RhostsRSAAuthentication\s+(.*)\s*$   ==>               RhostsRSAAuthentication no
436# (^#|^)PermitRootLogin\s+(.*)\s*$                 ==>              PermitRootLogin no
437# (^#|^)PermitEmptyPasswords\s+(.*)\s*$      ==>              PermitEmptyPasswords no
438# my $patterns_and_substitutes = [
439#           [ '(^#|^)Protocol\s+(.*)\s*$'                             =>                'Protocol 2'],
440#           ['(^#|^)X11Forwarding\s+(.*)\s*$'                  =>                'X11Forwarding yes'],
441#           ['(^#|^)IgnoreRhosts\s+(.*)\s*$'                     =>                'gnoreRhosts yes'],
442#           ['(^#|^)RhostsAuthentication\s+(.*)\s*$'         =>                'RhostsAuthentication no'],
443#           ['(^#|^)RhostsRSAAuthentication\s+(.*)\s*$'   =>               'RhostsRSAAuthentication no'],
444#           ['(^#|^)PermitRootLogin\s+(.*)\s*$'                 =>              'PermitRootLogin no'],
445#          ['(^#|^)PermitEmptyPasswords\s+(.*)\s*$'      =>              'PermitEmptyPasswords no']
446#]
447# B_replaces_lines($sshd_config,$patterns_and_substitutes);
448###########################################################################
449
450sub B_replace_lines($$){
451    my ($filename, $pairs) = @_;
452    my $retval = 0;
453    if ( &B_open_plus (*REPLACE_NEW,*REPLACE_OLD,$filename) ) {
454        while (my $line = <REPLACE_OLD>) {
455            my $switch;
456            my $switch_before = $line;
457            chomp($line);
458            foreach my $pair (@$pairs) {
459                $switch = 0;
460
461                my $pattern = $pair->[0] ;
462                my $replace = $pair->[1];
463                my $evalstr = '$line'  . "=~ s/$pattern/$replace/";
464                eval $evalstr;
465                if ($@) {
466                    &B_log("ERROR", "eval $evalstr failed.\n");
467                }
468                #if ( $line =~ s/$pair->[0]/$pair->[1]/) {
469                #    $switch = 1;
470                #    last;
471                #}
472            }
473            &B_print(*REPLACE_NEW,"$line\n");
474            if ($switch) {
475                $retval++;
476                B_log("ACTION","File modification in $filename -- replaced line\n" .
477                      "$switch_before\n" .
478                      "with:\n" .
479                      "$line\n");
480            }
481        }
482        &B_close_plus (*REPLACE_NEW,*REPLACE_OLD,$filename);
483        return 1;
484    }
485    else {
486        $retval=0;
487        &B_log("ERROR","Couldn't replace line(s) in $filename because open failed.\n");
488    }
489}
490
491################################################################################################
492# &B_replace_pattern ($filename,$pattern,$pattern_to_remove,$text_to_switch_in)
493# modifies $filename, acting on only lines that match $pattern, replacing a
494# string that matches $pattern_to_remove with $text_to_switch_in.
495#
496# Ex:
497#  B_replace_pattern('/etc/httpd.conf','^\s*Options.*\bIncludes\b','Includes','IncludesNoExec')
498#
499#   replaces all "Includes" with "IncludesNoExec" on Apache Options lines.
500#
501# It returns the number of lines it altered (or would have replaced, if
502# LOGONLY mode wasn't on...)
503#
504# B_replace_pattern uses B_open_plus and B_close_plus, so that the file
505# modified is backed up...
506#
507#################################################################################################
508
509sub B_replace_pattern($$$$) {
510
511    my ($filename,$pattern,$pattern_to_remove,$text_to_switch_in) = @_;
512    my $retval=0;
513
514    if ( &B_open_plus (*REPLACE_NEW,*REPLACE_OLD,$filename) ) {
515        while (my $line=<REPLACE_OLD>) {
516            unless ($line =~ $pattern) {
517                &B_print(*REPLACE_NEW,$line);
518            }
519            else {
520                my $orig_line =$line;
521                $line =~ s/$pattern_to_remove/$text_to_switch_in/;
522
523                &B_print(*REPLACE_NEW,$line);
524
525                $retval++;
526                &B_log("ACTION","File modification in $filename -- replaced line\n" .
527                       "$orig_line\n" .
528                       "via pattern with:\n" .
529                       "$line\n\n");
530            }
531        }
532        &B_close_plus (*REPLACE_NEW,*REPLACE_OLD,$filename);
533    }
534    else {
535        $retval=0;
536        &B_log("ERROR","Couldn't pattern-replace line(s) in $filename because open failed.\n");
537    }
538
539    return $retval;
540}
541
542
543###########################################################################
544# &B_match_line($file,$pattern);
545#
546# This subroutine will return a 1 if the pattern specified can be matched
547# against the file specified.  It will return a 0 otherwise.
548#
549# return values:
550# 0:     pattern not in file or the file is not readable
551# 1:     pattern is in file
552###########################################################################
553sub B_match_line($$) {
554    # file to be checked and pattern to check for.
555    my ($file,$pattern) = @_;
556    # if the file is readable then
557    if(-r $file) {
558        # if the file can be opened then
559        if(open FILE,"<$file") {
560            # look at each line in the file
561            while (my $line = <FILE>) {
562                # if a line matches the pattern provided then
563                if($line =~ $pattern) {
564                    # return the pattern was found
565                    B_log('DEBUG','Pattern: ' . $pattern . ' matched in file: ' .
566                    $file . "\n");
567                    return 1;
568                }
569            }
570        }
571        # if the file cann't be opened then
572        else {
573            # send a note to that affect to the errorlog
574            &B_log("ERROR","Unable to open file for read.\n$file\n$!\n");
575        }
576    }
577    B_log('DEBUG','Pattern: ' . $pattern . ' not matched in file: ' .
578          $file . "\n");
579    # the provided pattern was not matched against a line in the file
580    return 0;
581}
582
583###########################################################################
584# &B_match_line_only($file,$pattern);
585#
586# This subroutine checks if the specified pattern can be matched and if
587# it's the only content in the file. The only content means it's only but
588# may have several copies in the file.
589#
590# return values:
591# 0:     pattern not in file or pattern is not the only content
592#        or the file is not readable
593# 1:     pattern is in file and it's the only content
594############################################################################
595sub B_match_line_only($$) {
596    my ($file,$pattern) = @_;
597
598    # if matched, set to 1 later
599    my $retval = 0;
600
601    # if the file is readable then
602    if(-r $file) {
603        # if the file can be opened then
604        if(&B_open(*FILED, $file)) {
605            # pattern should be matched at least once
606            # pattern can not be mismatched
607            while (my $line = <FILED>) {
608                if ($line =~ $pattern) {
609                    $retval = 1;
610                }
611                else {
612                    &B_close(*FILED);
613                    return 0;
614                }
615            }
616        }
617        &B_close(*FILED);
618    }
619
620    return $retval;
621}
622
623###########################################################################
624# &B_return_matched_lines($file,$pattern);
625#
626# This subroutine returns lines in a file matching a given regular
627# expression, when called in the default list mode.  When called in scalar
628# mode, returns the number of elements found.
629###########################################################################
630sub B_return_matched_lines($$)
631{
632    my ($filename,$pattern) = @_;
633    my @lines = ();
634
635    open(READFILE, $filename);
636    while (<READFILE>) {
637        chomp;
638        next unless /$pattern/;
639        push(@lines, $_);
640    }
641    if (wantarray)
642    {
643        return @lines;
644    }
645    else
646    {
647        return scalar (@lines);
648    }
649}
650
651###########################################################################
652# &B_match_chunk($file,$pattern);
653#
654# This subroutine will return a 1 if the pattern specified can be matched
655# against the file specified on a line-agnostic form.  This allows for
656# patterns which by necessity must match against a multi-line pattern.
657# This is the natural analogue to B_replace_chunk, which was created to
658# provide multi-line capability not provided by B_replace_line.
659#
660# return values:
661# 0:     pattern not in file or the file is not readable
662# 1:     pattern is in file
663###########################################################################
664
665sub B_match_chunk($$) {
666
667    my ($file,$pattern) = @_;
668    my @lines;
669    my $big_long_line;
670    my $retval=1;
671
672    open CHUNK_FILE,$file;
673
674    # Read all lines into one scalar.
675    @lines = <CHUNK_FILE>;
676    close CHUNK_FILE;
677
678    foreach my $line ( @lines ) {
679        $big_long_line .= $line;
680    }
681
682    # Substitution routines get weird unless last line is terminated with \n
683    chomp $big_long_line;
684    $big_long_line .= "\n";
685
686    # Exit if we don't find a match
687    unless ($big_long_line =~ $pattern) {
688        $retval = 0;
689    }
690
691    return $retval;
692}
693
694###########################################################################
695# &B_hash_comment_line ($filename,$pattern) modifies $filename, replacing
696# any lines matching $pattern with a "hash-commented" version, like this:
697#
698#
699#        finger  stream  tcp     nowait  nobody  /usr/sbin/tcpd  in.fingerd
700# becomes:
701#        #finger  stream  tcp     nowait  nobody  /usr/sbin/tcpd  in.fingerd
702#
703# Also:
704#       tftp        dgram  udp wait   root /usr/lbin/tftpd    tftpd\
705#        /opt/ignite\
706#        /var/opt/ignite
707# becomes:
708#       #tftp        dgram  udp wait   root /usr/lbin/tftpd    tftpd\
709#       # /opt/ignite\
710#       # /var/opt/ignite
711#
712#
713# B_hash_comment_line uses B_open_plus and B_close_plus, so that the file
714# modified is backed up...
715#
716###########################################################################
717
718sub B_hash_comment_line($$) {
719
720    my ($filename,$pattern) = @_;
721    my $retval=1;
722
723    if ( &B_open_plus (*HASH_NEW,*HASH_OLD,$filename) ) {
724        my $line;
725        while ($line=<HASH_OLD>) {
726            unless ( ($line =~ $pattern) and ($line !~ /^\s*\#/) ) {
727                &B_print(*HASH_NEW,$line);
728            }
729            else {
730                &B_print(*HASH_NEW,"#$line");
731                &B_log("ACTION","File modification in $filename -- hash commented line\n" .
732                       "$line\n" .
733                       "like this:\n" .
734                       "#$line\n\n");
735                # while the line has a trailing \ then we should also comment out the line below
736                while($line =~ m/\\\n$/) {
737                    if($line=<HASH_OLD>) {
738                        &B_print(*HASH_NEW,"#$line");
739                        &B_log("ACTION","File modification in $filename -- hash commented line\n" .
740                               "$line\n" .
741                               "like this:\n" .
742                               "#$line\n\n");
743                    }
744                    else {
745                        $line = "";
746                    }
747                }
748
749            }
750        }
751        &B_close_plus (*HASH_NEW,*HASH_OLD,$filename);
752    }
753    else {
754        $retval=0;
755        &B_log("ERROR","Couldn't hash-comment line(s) in $filename because open failed.\n");
756    }
757
758    return $retval;
759}
760
761
762###########################################################################
763# &B_hash_uncomment_line ($filename,$pattern) modifies $filename,
764# removing any commenting from lines that match $pattern.
765#
766#        #finger  stream  tcp     nowait  nobody  /usr/sbin/tcpd  in.fingerd
767# becomes:
768#        finger  stream  tcp     nowait  nobody  /usr/sbin/tcpd  in.fingerd
769#
770#
771# B_hash_uncomment_line uses B_open_plus and B_close_plus, so that the file
772# modified is backed up...
773#
774###########################################################################
775
776sub B_hash_uncomment_line($$) {
777
778    my ($filename,$pattern) = @_;
779    my $retval=1;
780
781    if ( &B_open_plus (*HASH_NEW,*HASH_OLD,$filename) ) {
782      my $line;
783        while ($line=<HASH_OLD>) {
784            unless ( ($line =~ $pattern) and ($line =~ /^\s*\#/) ) {
785                &B_print(*HASH_NEW,$line);
786            }
787            else {
788                $line =~ /^\s*\#+(.*)$/;
789                $line = "$1\n";
790
791                &B_print(*HASH_NEW,"$line");
792                &B_log("ACTION","File modification in $filename -- hash uncommented line\n");
793                &B_log("ACTION",$line);
794                # while the line has a trailing \ then we should also uncomment out the line below
795                while($line =~ m/\\\n$/) {
796                    if($line=<HASH_OLD>) {
797                        $line =~ /^\s*\#+(.*)$/;
798                        $line = "$1\n";
799                        &B_print(*HASH_NEW,"$line");
800                        &B_log("ACTION","File modification in $filename -- hash uncommented line\n");
801                        &B_log("ACTION","#$line");
802                        &B_log("ACTION","like this:\n");
803                        &B_log("ACTION","$line");
804                    }
805                    else {
806                        $line = "";
807                    }
808                }
809            }
810        }
811        &B_close_plus (*HASH_NEW,*HASH_OLD,$filename);
812    }
813    else {
814        $retval=0;
815        &B_log("ERROR","Couldn't hash-uncomment line(s) in $filename because open failed.\n");
816    }
817
818    return $retval;
819}
820
821
822
823###########################################################################
824# &B_delete_line ($filename,$pattern) modifies $filename, deleting any
825# lines matching $pattern.  It uses B_replace_line to do this.
826#
827# B_replace_line uses B_open_plus and B_close_plus, so that the file
828# modified is backed up...
829#
830# Here an example of where you might use this:
831#
832# You'd like to remove any timeout=  lines in /etc/lilo.conf, so that your
833# delay=1 modification will work.
834
835#
836###########################################################################
837
838
839sub B_delete_line($$) {
840
841    my ($filename,$pattern)=@_;
842    my $retval=&B_replace_line($filename,$pattern,"");
843
844    return $retval;
845}
846
847
848###########################################################################
849# &B_chunk_replace ($file,$pattern,$replacement) reads $file replacing the
850# first occurrence of $pattern with $replacement.
851#
852###########################################################################
853
854sub B_chunk_replace($$$) {
855
856    my ($file,$pattern,$replacement) = @_;
857
858    my @lines;
859    my $big_long_line;
860    my $retval=1;
861
862    &B_open (*OLDFILE,$file);
863
864    # Read all lines into one scalar.
865    @lines = <OLDFILE>;
866    &B_close (*OLDFILE);
867    foreach my $line ( @lines ) {
868        $big_long_line .= $line;
869    }
870
871    # Substitution routines get weird unless last line is terminated with \n
872    chomp $big_long_line;
873    $big_long_line .= "\n";
874
875    # Exit if we don't find a match
876    unless ($big_long_line =~ $pattern) {
877        return 0;
878    }
879
880    $big_long_line =~ s/$pattern/$replacement/s;
881
882    $retval=&B_open_plus (*NEWFILE,*OLDFILE,$file);
883    if ($retval) {
884        &B_print (*NEWFILE,$big_long_line);
885        &B_close_plus (*NEWFILE,*OLDFILE,$file);
886    }
887
888    return $retval;
889}
890
891###########################################################################
892# &B_print ($handle,@list) prints the items of @list to the file handle
893# $handle.  It logs the action and respects the $GLOBAL_LOGONLY variable.
894#
895###########################################################################
896
897sub B_print {
898   my $handle=shift @_;
899
900   my $result=1;
901
902   unless ($GLOBAL_LOGONLY) {
903       $result=print $handle @_;
904   }
905
906   ($handle) = "$handle" =~ /[^:]+::[^:]+::([^:]+)/;
907
908   $result;
909}
910
911
912##########################################################################
913# &B_getValueFromFile($regex,$file);
914# Takes a regex with a single group "()" and returns the unique value
915# on any non-commented lines
916# This (and B_return_matched_lines are only used in this file, though are
917# probably more generally useful.  For now, leaving these here serve the following
918#functions:
919# a) still gets exported/associated as part of the Test_API package, and
920# is still availble for a couple operations that can't be deferred to the
921# main test loop, as they save values so that individual tests don't have to
922# recreate  (copy / paste) the logic to get them.
923#
924# It also avoids the circular "use" if we incldued "use Test API" at the top
925# of this file (Test API "uses" this file.
926# Returns the uncommented, unique values of a param=value pair.
927#
928# Return values:
929# 'Not Defined' if the value is not present or not uniquely defined.
930# $value if the value is present and unique
931#
932###########################################################################
933sub B_getValueFromFile ($$){
934  my $inputRegex=$_[0];
935  my $file=$_[1];
936  my ($lastvalue,$value)='';
937
938  my @lines=&B_return_matched_lines($file, $inputRegex);
939
940  return &B_getValueFromString($inputRegex,join('/n',@lines));
941}
942
943##########################################################################
944# &B_getValueFromString($param,$string);
945# Takes a regex with a single group "()" and returns the unique value
946# on any non-commented lines
947# This (and B_return_matched_lines are only used in this file, though are
948# probably more generally useful.  For now, leaving these here serve the following
949#functions:
950# a) still gets exported/associated as part of the Test_API package, and
951# is still availble for a couple operations that can't be deferred to the
952# main test loop, as they save values so that individual tests don't have to
953# recreate  (copy / paste) the logic to get them.
954#
955# It also avoids the circular "use" if we incldued "use Test API" at the top
956# of this file (Test API "uses" this file.
957# Returns the uncommented, unique values of a param=value pair.
958#
959# Return values:
960# 'Not Unique' if the value is not uniquely defined.
961# undef if the value isn't defined at all
962# $value if the value is present and unique
963#
964###########################################################################
965sub B_getValueFromString ($$){
966  my $inputRegex=$_[0];
967  my $inputString=$_[1];
968  my $lastValue='';
969  my $value='';
970
971  my @lines=split(/\n/,$inputString);
972
973  &B_log("DEBUG","B_getvaluefromstring called with regex: $inputRegex and input: " .
974         $inputString);
975  foreach my $line (grep(/$inputRegex/,@lines)) {
976    $line =~ /$inputRegex/;
977    $value=$1;
978    if (($lastValue eq '') and ($value ne '')) {
979        $lastValue = $value;
980    } elsif (($lastValue ne $value) and ($value ne '')) {
981        B_log("DEBUG","getvaluefromstring returned Not Unique");
982        return 'Not Unique';
983    }
984  }
985  if ((not(defined($value))) or ($value eq '')) {
986    &B_log("DEBUG","Could not find regex match in string");
987    return undef;
988  } else {
989    &B_log("DEBUG","B_getValueFromString Found: $value ; using:  $inputRegex");
990    return $value;
991  }
992}
993
994###############################################################
995# This function adds something to the To Do List.
996# Arguments:
997# 1) The string you want to add to the To Do List.
998# 2) Optional: Question whose TODOFlag should be set to indicate
999#    A pending manual action in subsequent reports.  Only skip this
1000#    If there's no security-audit relevant action you need the user to
1001#    accomplish
1002# Ex:
1003# &B_TODO("------\nInstalling IPFilter\n----\nGo get Ipfilter","IPFilter.install_ipfilter");
1004#
1005#
1006# Returns:
1007# 0 - If error condition
1008# True, if sucess, specifically:
1009#   "appended" if the append operation was successful
1010#   "exists" if no change was made since the entry was already present
1011###############################################################
1012sub B_TODO ($;$) {
1013    my $text = $_[0];
1014    my $FlaggedQuestion = $_[1];
1015    my $multilineString = "";
1016
1017    # trim off any leading and trailing new lines, regexes separated for "clarity"
1018    $text =~ s/^\n+(.*)/$1/;
1019    $text =~ s/(.*)\n+$/$1/;
1020
1021    if ( ! -e &getGlobal('BFILE',"TODO") ) {
1022	# Make the TODO list file for HP-UX Distro
1023	&B_create_file(&getGlobal('BFILE', "TODO"));
1024	&B_append_line(&getGlobal('BFILE', "TODO"),'a$b',
1025          "Please take the steps below to make your system more secure,\n".
1026          "then delete the item from this file and record what you did along\n".
1027          "with the date and time in your system administration log.  You\n".
1028          "will need that information in case you ever need to revert your\n".
1029          "changes.\n\n");
1030    }
1031
1032
1033    if (open(TODO,"<" . &getGlobal('BFILE', "TODO"))) {
1034	while (my $line = <TODO>) {
1035	    # getting rid of all meta characters.
1036	    $line =~ s/(\\|\||\(|\)|\[|\]|\{|\}|\^|\$|\*|\+|\?|\.)//g;
1037	    $multilineString .= $line;
1038	}
1039	chomp $multilineString;
1040        $multilineString .= "\n";
1041
1042	close(TODO);
1043    }
1044    else {
1045	&B_log("ERROR","Unable to read TODO.txt file.\n" .
1046		  "The following text could not be appended to the TODO list:\n" .
1047		  $text .
1048		  "End of TODO text\n");
1049        return 0; #False
1050    }
1051
1052    my $textPattern = $text;
1053
1054    # getting rid of all meta characters.
1055    $textPattern =~ s/(\\|\||\(|\)|\[|\]|\{|\}|\^|\$|\*|\+|\?|\.)//g;
1056
1057    if( $multilineString !~  "$textPattern") {
1058	my $datestamp = "{" . localtime() . "}";
1059	unless ( &B_append_line(&getGlobal('BFILE', "TODO"), "", $datestamp . "\n" . $text . "\n\n\n") ) {
1060	    &B_log("ERROR","TODO Failed for text: " . $text );
1061	}
1062        #Note that we only set the flag on the *initial* entry in the TODO File
1063        #Not on subsequent detection.  This is to avoid the case where Bastille
1064        #complains on a subsequent Bastille run of an already-performed manual
1065        #action that the user neglected to delete from the TODO file.
1066        # It does, however lead to a report of "nonsecure" when the user
1067        #asked for the TODO item, performed it, Bastille detected that and cleared the
1068        # Item, and then the user unperformed the action.  I think this is proper behavior.
1069        # rwf 06/06
1070
1071        if (defined($FlaggedQuestion)) {
1072            &B_TODOFlags("set",$FlaggedQuestion);
1073        }
1074        return "appended"; #evals to true, and also notes what happened
1075    } else {
1076        return "exists"; #evals to true, and also
1077    }
1078
1079}
1080
1081
1082#####################################################################
1083# &B_TODOFlags()
1084#
1085# This is the interface to the TODO flags.  Test functions set these when they
1086# require a TODO item to be completed to get to a "secure" state.
1087# The prune/reporting function checks these to ensure no flags are set before
1088# reporting an item "secure"
1089# "Methods" are load | save | isSet <Question> | set <Question> | unset <Question>
1090#
1091######################################################################
1092
1093sub B_TODOFlags($;$) {
1094    my $action = $_[0];
1095    my $module = $_[1];
1096
1097    use File::Spec;
1098
1099    my $todo_flag = &getGlobal("BFILE","TODOFlag");
1100
1101    &B_log("DEBUG","B_TODOFlags action: $action , module: $module");
1102
1103    if ($action eq "load") {
1104	if (-e $todo_flag ) {
1105	    &B_open(*TODO_FLAGS, $todo_flag);
1106	    my @lines = <TODO_FLAGS>;
1107	    foreach my $line (@lines) {
1108                chomp($line);
1109		$GLOBAL_CONFIG{"$line"}{"TODOFlag"}="yes";
1110	    }
1111	    return (&B_close(*TODO_FLAGS)); #return success of final close
1112	} else {
1113            return 1; #No-op is okay
1114        }
1115    } elsif ($action eq "save") {
1116	# Make sure the file exists, else create
1117        #Note we use open_plus and and create file, so if Bastille is
1118        #reverted, all the flags will self-clear (file deleted)
1119        my $flagNumber = 0;
1120        my $flagData = '';
1121        foreach my $key (keys %GLOBAL_CONFIG) {
1122            if ($GLOBAL_CONFIG{$key}{"TODOFlag"} eq "yes") {
1123                ++$flagNumber;
1124                $flagData .= "$key\n";
1125	    }
1126	}
1127        if (not( -e $todo_flag)) {
1128                &B_log("DEBUG","Initializing TODO Flag file: $todo_flag");
1129                &B_create_file($todo_flag); # Make sure it exists
1130        }
1131        &B_blank_file($todo_flag,
1132                          "This will not appear in the file; ensures blanking");
1133        return &B_append_line($todo_flag, "", "$flagData"); #return success of save
1134    } elsif (($action eq "isSet") and ($module ne "")) {
1135	if ($GLOBAL_CONFIG{"$module"}{"TODOFlag"} eq "yes") {
1136	    return 1; #TRUE
1137	} else {
1138	    return 0; #FALSE
1139        }
1140    } elsif (($action eq "set") and ($module ne "")) {
1141        $GLOBAL_CONFIG{"$module"}{"TODOFlag"} = "yes";
1142    } elsif (($action eq "clear") and ($module ne "")) {
1143        $GLOBAL_CONFIG{"$module"}{"TODOFlag"} = "";
1144    } else {
1145	&B_log("ERROR","TODO_Flag Called with invalid parameters: $action , $module".
1146	       "audit report may be incorrect.");
1147	return 0; #FALSE
1148    }
1149}
1150
11511;
1152
1153
1154