1# Copyright (C) 1999-2007 Jay Beale
2# Copyright (C) 2001-2008 Hewlett-Packard Development Company, L.P.
3# Licensed under the GNU General Public License, version 2
4
5package Bastille::API;
6
7## TO DO:
8#
9#
10#   1) Look for more places to insert error handling...
11#
12#   2) Document this module more
13#
14#
15
16
17##########################################################################
18#
19# This module forms the basis for the v1.1 API.
20#
21 ##########################################################################
22
23#
24# This module forms the initial basis for the Bastille Engine, implemented
25# presently via a Perl API for Perl modules.
26#
27# This is still under construction -- it is very usable, but not very well
28# documented, yet.
29#
30
31##########################################################################
32#
33#                          API Function Listing
34#
35##########################################################################
36# The routines which should be called by Bastille modules are listed here,
37# though they are better documented throughout this file.
38#
39# Distro Specific Stuff:
40#
41#  &GetDistro     - figures out what distro we're running, if it knows it...
42#  &ConfigureForDistro - sets global variables based on the distro
43#  &GetGlobal - returns hash values defined in ConfigureForDistro
44#
45#  &getGlobalConfig - returns value of hash set up by ReadConfig
46#
47# Logging Specific Stuff has moved to LogAPI.pm:
48#
49#  &B_log(type,msg) -- takes care of all logging
50#
51#
52# Input functions for the old input method...
53#
54# File open/close/backup functions
55#
56#  &B_open     * -- opens a file handle and logs the action/error (OLD WAY!)
57#  &B_open_plus  -- opens a pair of file handles for the old and new version
58#                   of a file; respects logonly flag.  (NEW WAY)
59#  &B_close    * -- closes a file handle and logs the action/error (OLD WAY!)
60#  &B_close_plus -- closes a pair of file handles opened by B_open_plus,
61#                   backing up one file and renaming the new file to the
62#                   old one's name, logging actions/errors.  Respects the
63#                   logonly flag -- needs B_backup file.  Finally, sets
64#                   new file's mode,uid,gid to old file's...  (NEW WAY)
65#  &B_backup_file - backs up a file that is being changed/deleted into the
66#                   $GLOBAL_BDIR{"backup"} directory.
67#
68# Non-content file modification functions
69#
70#  &B_delete_file - deletes the named file, backing up a copy
71#  &B_create_file - creates the named file, if it doesn't exist
72#
73#  &B_symlink     	- create a symlink to a file, recording the revert rm
74#
75# More stuff
76#
77#  &B_createdir     - make a directory, if it doesn't exist, record revert rmdir
78#  &B_cp            - copy a file, respecting LOGONLY and revert func.
79#  &B_mknod         - wrap mknod with revert and logonly and prefix functionality
80#
81#  &B_read_sums     - reads sum.csv file and parses input into the GLOBAL_SUM hash
82#  &B_write_sums    - writes sum.csv file from GLOBAL_SUM hash
83#  &B_check_sum($)  - take a file name and compares the stored cksum with the current
84#                     cksum of said file
85#  &B_set_sum($)    - takes a file name and gets that files current cksum then sets
86#                     that sum in the GLOBAL_SUM hash
87#  &B_revert_log - create entry in shell script, executed later by bastille -r
88#  &showDisclaimer  - Print the disclaimer and wait for 5 minutes for acceptance
89###########################################################################
90# Note:  GLOBAL_VERBOSE
91#
92# All logging functions now check GLOBAL_VERBOSE and, if set, will print
93# all the information sent to log files to STDOUT/STDERR as well.
94#
95
96#
97# Note:  GLOBAL_LOGONLY
98#
99# All Bastille API functions now check for the existence of a GLOBAL_LOGONLY
100# variable.  When said variable is set, no function actually modifies the
101# system.
102#
103# Note:  GLOBAL_DEBUG
104#
105# The B_log("DEBUG",...) function now checks GLOBAL_DEBUG and, if set, it will
106# print all the information to a new debug-log file. If GLOBAL_VERBOSE is
107# set it might log to STDOUT/STDERR as well (not yet implemented, pending
108# discussion). Developers should populate appropriate places with &B_log(DEBUG)
109# in order to be able to tell users to use this options and send the logs
110# for inspection and debugging.
111#
112#
113
114
115# Libraries for the Backup_file routine: Cwd and File::Path
116use Cwd;
117use Bastille::OSX_API;
118use Bastille::LogAPI;
119use File::Path;
120use File::Basename;
121
122# Export the API functions listed below for use by the modules.
123
124use Exporter;
125@ISA = qw ( Exporter );
126@EXPORT = qw(
127    setOptions  GetDistro ConfigureForDistro B_log B_revert_log
128    SanitizeEnv
129    B_open B_close B_symlink StopLogging
130    B_open_plus B_close_plus
131    B_isFileinSumDB
132    B_create_file B_read_sums B_check_sum  B_set_sum isSumDifferent listModifiedFiles
133    B_create_dir B_create_log_file
134    B_delete_file
135    B_cp B_place B_mknod
136    showDisclaimer
137    getSupportedOSHash
138    B_Backtick
139    B_System
140    isProcessRunning
141    checkProcsForService
142
143
144    $GLOBAL_OS $GLOBAL_ACTUAL_OS $CLI
145    $GLOBAL_LOGONLY $GLOBAL_VERBOSE $GLOBAL_DEBUG $GLOBAL_AUDITONLY $GLOBAL_AUDIT_NO_BROWSER $errorFlag
146    %GLOBAL_BIN %GLOBAL_DIR %GLOBAL_FILE
147    %GLOBAL_BDIR %GLOBAL_BFILE
148    %GLOBAL_CONFIG %GLOBAL_SUM
149
150    %GLOBAL_SERVICE %GLOBAL_SERVTYPE %GLOBAL_PROCESS %GLOBAL_RC_CONFIG
151    %GLOBAL_TEST
152
153    getGlobal setGlobal getGlobalConfig
154
155
156    B_parse_fstab
157    B_parse_mtab B_is_rpm_up_to_date
158
159    NOTSECURE_CAN_CHANGE SECURE_CANT_CHANGE
160    NOT_INSTALLED  INCONSISTENT MANUAL NOTEST SECURE_CAN_CHANGE
161    STRING_NOT_DEFINED NOT_INSTALLED_NOTSECURE DONT_KNOW
162    RELEVANT_HEADERQ NOTRELEVANT_HEADERQ
163);
164
165
166
167######################################################
168###Testing Functions
169##################################################################
170
171#Define "Constants" for test functions.  Note these constants sometimes get
172#interpreted as literal strings when used as hash references, so you may
173# have to use CONSTANT() to disambiguate, like below.  Sorry, it was either
174# that or create even *more* global variables.
175# See TestDriver.pm for definitions, and test design doc for full explaination
176use constant {
177    NOTSECURE_CAN_CHANGE => 0,
178    SECURE_CANT_CHANGE     => 1,
179    NOT_INSTALLED => 2, # (where the lack makes the system secure, eg telnet)
180    INCONSISTENT => 3,
181    MANUAL => 4,
182    NOTEST => 5,
183    SECURE_CAN_CHANGE => 6,
184    STRING_NOT_DEFINED => 7,
185    NOT_INSTALLED_NOTSECURE => 8, #(Where the missing s/w makes the system less secure eg IPFilter)
186    #Intentional duplicates follow
187    DONT_KNOW => 5,
188    RELEVANT_HEADERQ => 6,
189    NOTRELEVANT_HEADERQ => 0
190};
191
192&SanitizeEnv;
193
194# Set up some common error messages.  These are independent of
195# operating system
196
197# These will allow us to line up the warnings and error messages
198my $err ="ERROR:  ";
199my $spc ="        ";
200my $GLOBAL_OS="None";
201my $GLOBAL_ACTUAL_OS="None";
202my %GLOBAL_SUMS=();
203my $CLI='';
204
205#OS independent Error Messages Follow, normally "bastille" script filters
206#options before interactive or Bastille runs, so this check is often redundant
207$GLOBAL_ERROR{"usage"}="\n".
208    "$spc Usage: bastille [ -b | -c | -x ] [ --os <version> ] [ -f <alternate config> ]\n".
209    "$spc        bastille [ -r | --assess | --assessnobowser ]\n\n".
210    "$spc --assess : check status of system and report in browser\n".
211    "$spc --assessnobrowser : check status of system and list report locations\n".
212    "$spc -b : use a saved config file to apply changes\n".
213    "$spc      directly to system\n".
214    "$spc -c : use the Curses (non-X11) TUI\n".
215    "$spc -f <alternate config>: populate answers with a different config file\n".
216    "$spc -r : revert all Bastille changes to-date\n".
217    "$spc -x : use the Perl/Tk (X11) GUI\n" .
218    "$spc --os <version> : ask all questions for the given operating system\n" .
219    "$spc                version.  e.g. --os RH6.0\n";
220
221# These options don't work universally, so it's best not to
222# document them here (yet).  Hopefully, we'll get them
223# straightened out soon.
224#"$spc --log : log-only option\n".
225#"$spc -v : verbose mode\n".
226#"$spc --debug : debug mode\n";
227
228
229##############################################################################
230#
231#  Directory structure for Bastille Linux v1.2 and up
232#
233##############################################################################
234#
235#  /usr/sbin/          -- location of Bastille binaries
236#  /usr/lib/Bastille   -- location of Bastille modules
237#  /usr/share/Bastille -- location of Bastille data files
238#  /etc/Bastille       -- location of Bastille config files
239#
240#  /var/log/Bastille      -- location of Bastille log files
241#  /var/log/Bastille/revert -- directory holding all Bastille-created revert scripts
242#  /var/log/Bastille/revert/backup -- directory holding the original files that
243#                                   Bastille modifies, with permissions intact
244#
245##############################################################################
246
247##############################################################################
248#
249#  Directory structure for HP-UX Bastille v2.0 and up
250#
251##############################################################################
252#
253#  /opt/sec_mgmt/bastille/bin/  -- location of Bastille binaries
254#  /opt/sec_mgmt/bastille/lib/  -- location of Bastille modules
255#  /etc/opt/sec_mgmt/bastille/  -- location of Bastille data and config files
256#
257#  /var/opt/sec_mgmt/bastille/log/   -- location of Bastille log files
258#  /var/opt/sec_mgmt/bastille/revert -- directory holding all Bastille-created
259#                                       revert scripts and save files
260#
261##############################################################################
262
263
264##############################################################################
265##############################################################################
266##################  Actual functions start here... ###########################
267##############################################################################
268##############################################################################
269
270###########################################################################
271# setOptions takes six arguments, $GLOBAL_DEBUG, $GLOBAL_LOGONLY,
272# $GLOBAL_VERBOSE, $GLOBAL_AUDITONLY, $GLOBAL_AUDIT_NO_BROWSER, and GLOBAL_OS;
273###########################################################################
274sub setOptions($$$$$$) {
275    ($GLOBAL_DEBUG,$GLOBAL_LOGONLY,$GLOBAL_VERBOSE,$GLOBAL_AUDITONLY,
276     $GLOBAL_AUDIT_NO_BROWSER,$GLOBAL_OS) = @_;
277    if ($GLOBAL_AUDIT_NO_BROWSER) {
278	$GLOBAL_AUDITONLY = 1;
279    }
280    if (not(defined($GLOBAL_OS))){
281        $GLOBAL_OS="None";
282    }
283}
284###########################################################################
285#
286# SanitizeEnv load a proper environment so Bastille cannot be tricked
287# and Perl modules work correctly.
288#
289###########################################################################
290sub SanitizeEnv {
291	 delete @ENV{'IFS','CDPATH','ENV','BASH_ENV'};
292	 $ENV{CDPATH}=".";
293	 $ENV{BASH_ENV}= "";
294	 # Bin is needed here or else  /usr/lib/perl5/5.005/Cwd.pm
295	 # will not find `pwd`
296	 # Detected while testing with -w, jfs
297	 $ENV{PATH} = "/bin:/usr/bin";
298	 # Giorgi, is /usr/local/bin needed? (jfs)
299}
300
301###########################################################################
302#
303# GetDistro checks to see if the target is a known distribution and reports
304# said distribution.
305#
306# This is used throughout the script, but also by ConfigureForDistro.
307#
308#
309###########################################################################
310
311sub GetDistro() {
312
313    my ($release,$distro);
314
315    # Only read files for the distro once.
316    # if the --os option was used then
317    if ($GLOBAL_OS eq "None") {
318	if ( -e "/etc/mandrake-release" ) {
319	    open(MANDRAKE_RELEASE,"/etc/mandrake-release");
320	    $release=<MANDRAKE_RELEASE>;
321
322	    if ( ($release =~ /^Mandrake Linux release (\d+\.\d+\w*)/) or ($release =~ /^Linux Mandrake release (\d+\.\d+\w*)/) ) {
323		$distro="MN$1";
324	    }
325	    elsif ( $release =~ /^Mandrakelinux release (\d+\.\d+)\b/ ) {
326                $distro="MN$1";
327            }
328            else {
329		print STDERR "$err Couldn't determine Mandrake/Mandriva version! Setting to 10.1!\n";
330		$distro="MN10.1";
331	    }
332
333	    close(MANDRAKE_RELEASE);
334	}
335	elsif ( -e "/etc/immunix-release" ) {
336	    open(IMMUNIX_RELEASE,"/etc/immunix-release");
337	    $release=<IMMUNIX_RELEASE>;
338	    unless ($release =~ /^Immunix Linux release (\d+\.\d+\w*)/) {
339		print STDERR "$err Couldn't determine Immunix version! Setting to 6.2!\n";
340		$distro="RH6.2";
341	    }
342	    else {
343		$distro="RH$1";
344	    }
345	    close(*IMMUNIX_RELEASE);
346	}
347	elsif ( -e '/etc/fedora-release' ) {
348            open(FEDORA_RELEASE,'/etc/fedora-release');
349            $release=<FEDORA_RELEASE>;
350            close FEDORA_RELEASE;
351            if ($release =~ /^Fedora Core release (\d+\.?\d*)/) {
352                $distro = "RHFC$1";
353            }
354	    elsif ($release =~ /^Fedora release (\d+\.?\d*)/) {
355                $distro = "RHFC$1";
356            }
357            else {
358                print STDERR "$err Could not determine Fedora version! Setting to Fedora Core 8\n";
359                $distro='RHFC8';
360            }
361	}
362	elsif ( -e "/etc/redhat-release" ) {
363	    open(*REDHAT_RELEASE,"/etc/redhat-release");
364	    $release=<REDHAT_RELEASE>;
365	    if ($release =~ /^Red Hat Linux release (\d+\.?\d*\w*)/) {
366		$distro="RH$1";
367	    }
368            elsif ($release =~ /^Red Hat Linux .+ release (\d+)\.?\d*([AEW]S)/) {
369                $distro="RHEL$1$2";
370            }
371	    elsif ($release =~ /^Red Hat Enterprise Linux ([AEW]S) release (\d+)/) {
372		$distro="RHEL$2$1";
373	    }
374	    elsif ($release =~ /^CentOS release (\d+\.\d+)/) {
375		my $version = $1;
376		if ($version =~ /^4\./) {
377		    $distro='RHEL4AS';
378		}
379		elsif ($version =~ /^3\./) {
380		    $distro='RHEL3AS';
381		}
382		else {
383		    print STDERR "$err Could not determine CentOS version! Setting to Red Hat Enterprise 4 AS.\n";
384		    $distro='RHEL4AS';
385                 }
386	    }
387 	    else {
388		# JJB/HP - Should this be B_log?
389		print STDERR "$err Couldn't determine Red Hat version! Setting to 9!\n";
390		$distro="RH9";
391	    }
392	    close(REDHAT_RELEASE);
393
394	}
395	elsif ( -e "/etc/debian_version" ) {
396	    $stable="3.1"; #Change this when Debian stable changes
397	    open(*DEBIAN_RELEASE,"/etc/debian_version");
398	    $release=<DEBIAN_RELEASE>;
399	    unless ($release =~ /^(\d+\.\d+\w*)/) {
400		print STDERR "$err System is not running a stable Debian GNU/Linux version. Setting to $stable.\n";
401		$distro="DB$stable";
402	    }
403	    else {
404		$distro="DB$1";
405	    }
406	    close(DEBIAN_RELEASE);
407	}
408	elsif ( -e "/etc/SuSE-release" ) {
409	    open(*SUSE_RELEASE,"/etc/SuSE-release");
410	    $release=<SUSE_RELEASE>;
411	    if ($release =~ /^SuSE Linux (\d+\.\d+\w*)/i) {
412		$distro="SE$1";
413	    }
414	    elsif ($release =~ /^SUSE LINUX Enterprise Server (\d+\.?\d?\w*)/i) {
415		$distro="SESLES$1";
416	    }
417	    elsif ($release =~ /^SUSE Linux Enterprise Server (\d+\.?\d?\w*)/i) {
418		$distro="SESLES$1";
419	    }
420            elsif ($release =~ /^openSuSE (\d+\.\d+\w*)/i) {
421                $distro="SE$1";
422            }
423	    else {
424		print STDERR "$err Couldn't determine SuSE version! Setting to 10.3!\n";
425		$distro="SE10.3";
426	    }
427	    close(SUSE_RELEASE);
428	}
429	elsif ( -e "/etc/turbolinux-release") {
430	    open(*TURBOLINUX_RELEASE,"/etc/turbolinux-release");
431	    $release=<TURBOLINUX_RELEASE>;
432	    unless ($release =~ /^Turbolinux Workstation (\d+\.\d+\w*)/) {
433		print STDERR "$err Couldn't determine TurboLinux version! Setting to 7.0!\n";
434		$distro="TB7.0";
435	    }
436	    else {
437		$distro="TB$1";
438	    }
439	    close(TURBOLINUX_RELEASE);
440	}
441	else {
442	    # We're either on Mac OS X, HP-UX or an unsupported O/S.
443            if ( -x '/usr/bin/uname') {
444		# uname is in /usr/bin on Mac OS X and HP-UX
445		$release=`/usr/bin/uname -sr`;
446	    }
447	    else {
448	 	print STDERR "$err Could not determine operating system version!\n";
449		$distro="unknown";
450            }
451
452	    # Figure out what kind of system we're on.
453	    if ($release ne "") {
454		if ($release =~ /^Darwin\s+(\d+)\.(\d+)/) {
455		    if ($1 == 6 ) {
456			$distro = "OSX10.2";
457		    }
458		    elsif ($1 == 7) {
459			$distro = "OSX10.3";
460		    }
461                    elsif ($1 == 8) {
462                        $distro = "OSX10.3";
463                    }
464		    else {
465		        $distro = "unknown";
466		    }
467		}
468	        elsif ( $release =~ /(^HP-UX)\s*B\.(\d+\.\d+)/ ) {
469		   $distro="$1$2";
470		}
471		else {
472		   print STDERR "$err Could not determine operating system version!\n";
473	           $distro="unknown";
474		}
475	    }
476	}
477
478	$GLOBAL_OS=$distro;
479    } elsif (not (defined $GLOBAL_OS)) {
480        print "ERROR: GLOBAL OS Scoping Issue\n";
481    } else {
482        $distro = $GLOBAL_OS;
483    }
484
485    return $distro;
486}
487
488###################################################################################
489#   &getActualDistro;                                                             #
490#                                                                                 #
491#    This subroutine returns the actual os version in which is running on.  This  #
492#    os version is independent of the --os switch feed to bastille.               #
493#                                                                                 #
494###################################################################################
495sub getActualDistro {
496    # set local variable to $GLOBAL_OS
497
498    if ($GLOBAL_ACTUAL_OS eq "None") {
499        my $os = $GLOBAL_OS;
500        # undef GLOBAL_OS so that the GetDistro routine will return
501        # the actualDistro, it might otherwise return the distro set
502        # by the --os switch.
503        $GLOBAL_OS = "None";
504        $GLOBAL_ACTUAL_OS = &GetDistro;
505        # reset the GLOBAL_OS variable
506        $GLOBAL_OS = $os;
507    }
508    return $GLOBAL_ACTUAL_OS;
509}
510# These are helper routines which used to be included inside GetDistro
511sub is_OS_supported($) {
512   my $os=$_[0];
513   my $supported=0;
514   my %supportedOSHash = &getSupportedOSHash;
515
516   foreach my $oSType (keys %supportedOSHash) {
517       foreach my $supported_os ( @{$supportedOSHash{$oSType}} ) {
518	   if ( $supported_os eq $os ) {
519	       $supported=1;
520	   }
521       }
522   }
523
524   return $supported;
525}
526
527###############################################################################
528#   getSupportedOSHash
529#
530#   This subrountine returns a hash of supported OSTypes, which point to a
531#   a list of supported distros.  When porting to a new distro, add the
532#   distro id to the hash in its appropriate list.
533###############################################################################
534sub getSupportedOSHash () {
535
536    my %osHash = ("LINUX" => [
537			      "DB2.2", "DB3.0",
538			      "RH6.0","RH6.1","RH6.2","RH7.0",
539			      "RH7.1","RH7.2","RH7.3","RH8.0",
540			      "RH9",
541                              "RHEL5",
542			      "RHEL4AS","RHEL4ES","RHEL4WS",
543			      "RHEL3AS","RHEL3ES","RHEL3WS",
544			      "RHEL2AS","RHEL2ES","RHEL2WS",
545			      "RHFC1","RHFC2","RHFC3","RHFC4",
546			      "RHFC5","RHFC6","RHFC7","RHFC8",
547			      "MN6.0","MN6.1 ","MN7.0","MN7.1",
548			      "MN7.2","MN8.0","MN8.1","MN8.2",
549			      "MN10.1",
550			      "SE7.2","SE7.3", "SE8.0","SE8.1","SE9.0","SE9.1",
551			      "SE9.2","SE9.3","SE10.0","SE10.1","SE10.2","SE10.3",
552			      "SESLES8","SESLES9","SESLES10",
553			      "TB7.0"
554			      ],
555
556		  "HP-UX" => [
557			      "HP-UX11.00","HP-UX11.11",
558			      "HP-UX11.22", "HP-UX11.23",
559			      "HP-UX11.31"
560			      ],
561
562		  "OSX" => [
563			    'OSX10.2','OSX10.3','OSX10.4'
564			    ]
565		  );
566
567  return %osHash;
568
569}
570
571
572###############################################################################
573#  setFileLocations(OSMapFile, currentDistro);
574#
575#  Given a file map location this subroutine will create the GLOBAL_*
576#  hash entries specified within this file.
577###############################################################################
578sub setFileLocations($$) {
579
580    my ($fileInfoFile,$currentDistro) = @_;
581
582    # define a mapping from the first argument to the proper hash
583    my %map = ("BIN"   => \%GLOBAL_BIN,
584	       "FILE"  => \%GLOBAL_FILE,
585	       "BFILE" => \%GLOBAL_BFILE,
586	       "DIR"   => \%GLOBAL_DIR,
587	       "BDIR"  => \%GLOBAL_BDIR
588	       );
589    my @fileInfo = ();
590
591    #  File containing file location information
592    if(open(FILEINFO, "<$fileInfoFile" )) {
593
594	@fileInfo = <FILEINFO>;
595
596	close(FILEINFO);
597
598    }
599    else {
600	print STDERR "$err Unable to find file location information for '$distro'.\n" .
601	    "$spc Contact the Bastille support list for details.\n";
602	exit(1);
603    }
604
605    # Each line of the file map follows the pattern below:
606    # bdir,init.d,'/etc/rc.d/init.d',RH7.2,RH7.3
607    # if the distro information is not available, e.g.
608    # bdir,init.d,'/etc/rc.d/init.d'
609    # then the line applies to all distros under the OSType
610    foreach my $file (@fileInfo) {
611	# Perl comments are allowed within the file but only entire line comments
612	if($file !~ /^\s+\#|^\s+$/) {
613	    chomp $file;
614	    # type relates to the map above, type bin will map to GLOBAL_BIN
615	    # id is the identifier used as the hash key by the GLOBAL hash
616	    # fileLocation is the full path to the file
617	    # distroList is an optional list of distros that this particular
618	    #   file location, if no distro list is presented the file location
619	    #   is considered to apply to all distros
620	    my ($type,$id,$fileLocation,@distroList) = split /\s*,\s*/, $file;
621	    $fileLocation =~ s/^\'(.*)\'$/$1/;
622	    if($#distroList == -1) {
623		$map{uc($type)}->{$id}=$fileLocation;
624	    }
625	    else {
626		foreach my $distro (@distroList) {
627		    # if the current distro matches the distro listed then
628		    # this file location applies
629		    if($currentDistro =~ /$distro/) {
630			$map{uc($type)}->{$id}=$fileLocation;
631		    }
632		}
633	    }
634	}
635    }
636    unless(defined($map{uc("BFILE")}->{"current_config"})) {
637        &setGlobal("BFILE","current_config",&getGlobal("BFILE","config"));
638    }
639}
640
641###############################################################################
642#  setServiceInfo($OSServiceMapFile, $currentDistro
643#
644#  Given the location of an OS Service map file, which describes
645#  a service in terms of configurables, processes and a service type.
646#  The subroutine fills out the GLOBAL_SERVICE, $GLOBAL_RC_CONFIG, GLOBAL_SERVTYPE, and
647#  GLOBAL_PROCESS hashes for a given service ID.
648###############################################################################
649sub setServiceInfo($$) {
650    my ($serviceInfoFile,$currentDistro) = @_;
651    my @serviceInfo = ();
652
653    if(open(SERVICEINFO, "<$serviceInfoFile" )) {
654
655	@serviceInfo = <SERVICEINFO>;
656
657	close(SERVICEINFO);
658
659    }
660    else {
661	print STDERR "$err Unable to find service, service type, and process information\n" .
662	             "$spc for '$distro'.\n" .
663	             "$spc Contact the Bastille support list for details.\n";
664	exit(1);
665    }
666
667
668    # The following loop, parses the entire (YOUR OS).service file
669    # to provide service information for YOUR OS.
670    # The files format is as follows:
671    # serviceID,servType,('service' 'configuration' 'list'),('process' 'list')[,DISTROS]*
672    # if distros are not present then the service is assumed to be
673    # relevant the the current distro
674
675
676#
677# More specifically, this file's format for rc-based daemons is:
678#
679# script_name,rc,(rc-config-file rc-config-file ...),(rc-variable1 rc-variable2 ...),('program_name1 program_name2 ...')
680#
681# ...where script_name is a file in /etc/init.d/ and
682# ...program_nameN is a program launced by the script.
683#
684# This file's format for inet-based daemons is:
685#
686# identifier, inet, line name/file name, program name
687#
688# label,inet,(port1 port2 ...),(daemon1 daemon2 ...)
689#
690# ...where label is arbitrary, portN is one of the ports
691# ...this one listens on, and daemonN is a program launched
692# ...in response to a connection on a port.
693
694    foreach my $service (@serviceInfo) {
695	# This file accepts simple whole line comments perl style
696	if($service !~ /^\s+\#|^\s+$/) {
697	    chomp $service;
698	    my ($serviceID,$servType,$strConfigList,$strServiceList,
699		$strProcessList,@distroList) = split /\s*,\s*/, $service;
700
701            sub MakeArrayFromString($){
702                my $entryString = $_[0];
703                my @destArray = ();
704                if ($entryString =~ /\'\S+\'/) { #Make sure we have something to extract before we try
705                    @destArray = split /\'\s+\'/, $entryString;
706                    $destArray[0] =~ s/^\(\'(.+)$/$1/; # Remove leading quotation mark
707                    $destArray[$#destArray] =~ s/^(.*)\'\)$/$1/; #Remove trailing quotation mark
708                }
709                return @destArray;
710            }
711
712	    # produce a list of configuration files from the files
713	    # format ('configuration' 'files')
714	    my @configList = MakeArrayFromString($strConfigList);
715
716	    # produce a list of service configurables from the files
717	    # format ('service' 'configurable')
718	    my @serviceList = MakeArrayFromString($strServiceList);
719
720	    # produce a list of process names from the files format
721	    # ('my' 'process' 'list')
722	    my @processList = MakeArrayFromString($strProcessList);
723
724	    # if distros were not specified then accept the service information
725	    if($#distroList == -1) {
726		@{$GLOBAL_SERVICE{$serviceID}} = @serviceList;
727		$GLOBAL_SERVTYPE{$serviceID} = $servType;
728		@{$GLOBAL_PROCESS{$serviceID}} = @processList;
729                @{$GLOBAL_RC_CONFIG{$serviceID}} = @configList;
730	    }
731	    else {
732		# only if the current distro matches one of the listed distros
733		# include the service information.
734		foreach my $distro (@distroList) {
735		    if($currentDistro =~ /$distro/) {
736			@{$GLOBAL_SERVICE{$serviceID}} = @serviceList;
737			$GLOBAL_SERVTYPE{$serviceID} = $servType;
738			@{$GLOBAL_PROCESS{$serviceID}} = @processList;
739                        @{$GLOBAL_RC_CONFIG{$serviceID}} = @configList;
740		    }
741		}
742	    }
743	}
744    }
745}
746
747
748
749###############################################################################
750#  getFileAndServiceInfo($distro,$actualDistro)
751#
752#  This subrountine, given distribution information, will import system file
753#  and service information into the GLOBA_* hashes.
754#
755#  NOTE: $distro and $actualDistro will only differ when the --os switch is
756#        used to generate a configuration file for an arbitrary operating
757#        system.
758#
759###############################################################################
760sub getFileAndServiceInfo($$) {
761
762    my ($distro,$actualDistro) = @_;
763
764    # defines the path to the OS map information for any supported OS type.
765    # OS map information is used to determine file locations for a given
766    # distribution.
767    my %oSInfoPath = (
768		       "LINUX" => "/usr/share/Bastille/OSMap/",
769		       "HP-UX" => "/etc/opt/sec_mgmt/bastille/OSMap/",
770		       "OSX" => "/usr/share/Bastille/OSMap/"
771		       );
772
773    # returns the OS, LINUX,  HP-UX, or OSX, associated with this
774    # distribution
775    my $actualOS = &getOSType($actualDistro);
776    my $oS = &getOSType($distro);
777
778    if(defined $actualOS && defined $oS) {
779	my $bastilleInfoFile = $oSInfoPath{$actualOS} . "${actualOS}.bastille";
780	my $systemInfoFile =  $oSInfoPath{$actualOS} . "${oS}.system";
781	my $serviceInfoFile = $oSInfoPath{$actualOS} . "${oS}.service";
782
783	if(-f $bastilleInfoFile) {
784	    &setFileLocations($bastilleInfoFile,$actualDistro);
785	}
786	else {
787	    print STDERR "$err Unable to find bastille file information.\n" .
788		         "$spc $bastilleInfoFile does not exist on the system";
789	    exit(1);
790	}
791
792	if(-f $systemInfoFile) {
793	    &setFileLocations($systemInfoFile,$distro);
794	}
795	else {
796	    print STDERR "$err Unable to find system file information.\n" .
797		         "$spc $systemInfoFile does not exist on the system";
798	    exit(1);
799	}
800	# Service info File is optional
801	if(-f $serviceInfoFile) {
802	    &setServiceInfo($serviceInfoFile,$distro);
803	}
804    }
805    else {
806	print STDERR "$err Unable to determine operating system type\n" .
807	             "$spc for $actualDistro or $distro\n";
808	exit(1);
809    }
810
811}
812
813
814# returns the Operating System type associated with the specified
815# distribution.
816sub getOSType($) {
817
818    my $distro = $_[0];
819
820    my %supportedOSHash = &getSupportedOSHash;
821    foreach my $oSType (keys %supportedOSHash) {
822	foreach my $oSDistro (@{$supportedOSHash{$oSType}}) {
823	    if($distro eq $oSDistro) {
824		return $oSType;
825	    }
826	}
827    }
828
829    return undef;
830
831}
832
833
834# Test subroutine used to debug file location info for new Distributions as
835# they are ported.
836sub dumpFileInfo {
837    print "Dumping File Information\n";
838    foreach my $hashref (\%GLOBAL_BIN,\%GLOBAL_DIR,\%GLOBAL_FILE,\%GLOBAL_BFILE,\%GLOBAL_BDIR) {
839	foreach my $id (keys %{$hashref}) {
840	    print "$id: ${$hashref}{$id}\n";
841	}
842	print "-----------------------\n\n";
843    }
844}
845
846# Test subroutine used to debug service info for new Distributions as
847# they are ported.
848sub dumpServiceInfo {
849    print "Dumping Service Information\n";
850    foreach my $serviceId (keys %GLOBAL_SERVICE) {
851	print "$serviceId:\n";
852	print "Type - $GLOBAL_SERVTYPE{$serviceId}\n";
853	print "Service List:\n";
854	foreach my $service (@{$GLOBAL_SERVICE{$serviceId}}) {
855	    print "$service ";
856	}
857	print "\nProcess List:\n";
858	foreach my $process (@{$GLOBAL_PROCESS{$serviceId}}) {
859	    print "$process ";
860	}
861	print "\n----------------------\n";
862    }
863}
864
865
866###########################################################################
867#
868# &ConfigureForDistro configures the API for a given distribution.  This
869# includes setting global variables that tell the Bastille API about
870# given binaries and directories.
871#
872# WARNING: If a distro is not covered here, Bastille may not be 100%
873#          compatible with it, though 1.1 is written to be much smarter
874#          about unknown distros...
875#
876###########################################################################
877sub ConfigureForDistro {
878
879    my $retval=1;
880
881    # checking to see if the os version given is in fact supported
882    my $distro = &GetDistro;
883
884    # checking to see if the actual os version is in fact supported
885    my $actualDistro = &getActualDistro;
886    $ENV{'LOCALE'}=''; # So that test cases checking for english results work ok.
887    if ((! &is_OS_supported($distro)) or (! &is_OS_supported($actualDistro))  ) {
888	# if either is not supported then print out a list of supported versions
889	if (! &is_OS_supported($distro)) {
890	    print STDERR "$err '$distro' is not a supported operating system.\n";
891	}
892	else {
893	    print STDERR "$err Bastille is unable to operate correctly on this\n";
894	    print STDERR "$spc $distro operating system.\n";
895	}
896	my %supportedOSHash = &getSupportedOSHash;
897	print STDERR "$spc Valid operating system versions are as follows:\n";
898
899	foreach my $oSType (keys %supportedOSHash) {
900
901	    print STDERR "$spc $oSType:\n$spc ";
902
903	    my $os_number = 1;
904	    foreach my $os (@{$supportedOSHash{$oSType}}) {
905		print STDERR "'$os' ";
906		if ($os_number == 5){
907		    print STDERR "\n$spc ";
908		    $os_number = 1;
909		}
910		else {
911		    $os_number++;
912		}
913
914	    }
915	    print STDERR "\n";
916	}
917
918	print "\n" . $GLOBAL_ERROR{"usage"};
919	exit(1);
920    }
921
922    # First, let's make sure that we do not create any files or
923    # directories with more permissive permissions than we
924    # intend via setting the Perl umask
925    umask(077);
926
927    &getFileAndServiceInfo($distro,$actualDistro);
928
929#    &dumpFileInfo;  # great for debuging file location issues
930#    &dumpServiceInfo; # great for debuging service information issues
931
932   # OS dependent error messages (after configuring file locations)
933    my $nodisclaim_file = &getGlobal('BFILE', "nodisclaimer");
934
935    $GLOBAL_ERROR{"disclaimer"}="$err Unable to touch $nodisclaim_file:" .
936	    "$spc You must use Bastille\'s -n flag (for example:\n" .
937	    "$spc bastille -f -n) or \'touch $nodisclaim_file \'\n";
938
939    return $retval;
940}
941
942
943###########################################################################
944###########################################################################
945#                                                                         #
946# The B_<perl_function> file utilities are replacements for their Perl    #
947# counterparts.  These replacements log their actions and their errors,   #
948# but are very similar to said counterparts.                              #
949#                                                                         #
950###########################################################################
951###########################################################################
952
953
954###########################################################################
955# B_open is used for opening a file for reading.  B_open_plus is the preferred
956# function for writing, since it saves a backup copy of the file for
957# later restoration.
958#
959# B_open opens the given file handle, associated with the given filename
960# and logs appropriately.
961#
962###########################################################################
963
964sub B_open {
965   my $retval=1;
966   my ($handle,$filename)=@_;
967
968   unless ($GLOBAL_LOGONLY) {
969       $retval = open $handle,$filename;
970   }
971
972   ($handle) = "$_[0]" =~ /[^:]+::[^:]+::([^:]+)/;
973   &B_log("ACTION","open $handle,\"$filename\";\n");
974   unless ($retval) {
975      &B_log("ERROR","open $handle, $filename failed...\n");
976   }
977
978   return $retval;
979}
980
981###########################################################################
982# B_open_plus is the v1.1 open command.
983#
984# &B_open_plus($handle_file,$handle_original,$file) opens the file $file
985# for reading and opens the file ${file}.bastille for writing.  It is the
986# counterpart to B_close_plus, which will move the original file to
987# $GLOBAL_BDIR{"backup"} and will place the new file ${file}.bastille in its
988# place.
989#
990# &B_open_plus makes the appropriate log entries in the action and error
991# logs.
992###########################################################################
993
994sub B_open_plus {
995
996    my ($handle_file,$handle_original,$file)=@_;
997    my $retval=1;
998    my $return_file=1;
999    my $return_old=1;
1000
1001    my $original_file = $file;
1002
1003    # Open the original file and open a copy for writing.
1004    unless ($GLOBAL_LOGONLY) {
1005	# if the temporary filename already exists then the open operation will fail.
1006        if ( $file eq "" ){
1007            &B_log("ERROR","Internal Error - Attempt Made to Open Blank Filename");
1008            $return_old=0;
1009	    $return_file=0;
1010            return 0; #False
1011        } elsif (-e "${file}.bastille") {
1012            &B_log("ERROR","Unable to open $file as the swap file ".
1013                   "${file}.bastille\" already exists.  Rename the swap ".
1014                   "file to allow Bastille to make desired file modifications.");
1015	    $return_old=0;
1016	    $return_file=0;
1017	}
1018	else {
1019	    $return_old = open $handle_original,"$file";
1020	    $return_file = open $handle_file,("> $file.bastille");
1021	}
1022    }
1023
1024    # Error handling/logging here...
1025    #&B_log("ACTION","# Modifying file $original_file via temporary file $original_file.bastille\n");
1026    unless ($return_file) {
1027	$retval=0;
1028	&B_log("ERROR","open file: \"$original_file.bastille\" failed...\n");
1029    }
1030    unless ($return_old) {
1031	$retval=0;
1032	&B_log("ERROR","open file: \"$original_file\" failed.\n");
1033    }
1034
1035    return $retval;
1036
1037}
1038
1039###########################################################################
1040# B_close was the v1.0 close command.  It is still used in places in the
1041# code.
1042# However the use of B _close_plus, which implements a new, smarter,
1043# backup scheme is preferred.
1044#
1045# B_close closes the given file handle, associated with the given filename
1046# and logs appropriately.
1047###########################################################################
1048
1049
1050sub B_close {
1051   my $retval=1;
1052
1053   unless ($GLOBAL_LOGONLY) {
1054       $retval = close $_[0];
1055   }
1056
1057   &B_log("ACTION", "close $_[0];\n");
1058   unless ($retval) {
1059      &B_log("ERROR", "close $_[0] failed...\n");
1060   }
1061
1062   return $retval;
1063}
1064
1065
1066###########################################################################
1067# B_close_plus is the v1.1 close command.
1068#
1069# &B_close_plus($handle_file,$handle_original,$file) closes the files
1070# $file and ${file}.bastille, backs up $file to $GLOBAL_BDIR{"backup"} and
1071# renames ${file}.bastille to $file.  This backup is made using the
1072# internal API function &B_backup_file.  Further, it sets the new file's
1073# permissions and uid/gid to the same as the old file.
1074#
1075# B_close_plus is the counterpart to B_open_plus, which opened $file and
1076# $file.bastille with the file handles $handle_original and $handle_file,
1077# respectively.
1078#
1079# &B_close_plus makes the appropriate log entries in the action and error
1080# logs.
1081###########################################################################
1082
1083sub B_close_plus {
1084    my ($handle_file,$handle_original,$file)=@_;
1085    my ($mode,$uid,$gid);
1086    my @junk;
1087
1088    my $original_file;
1089
1090    my $retval=1;
1091    my $return_file=1;
1092    my $return_old=1;
1093
1094    # Append the global prefix, but save the original for B_backup_file b/c
1095    # it appends the prefix on its own...
1096
1097    $original_file=$file;
1098
1099    #
1100    # Close the files and prepare for the rename
1101    #
1102
1103    if (($file eq "") or (not(-e $file ))) {
1104        &B_log("ERROR","Internal Error, attempted to close a blank filename ".
1105               "or nonexistent file.");
1106        return 0; #False
1107    }
1108
1109    unless ($GLOBAL_LOGONLY) {
1110	$return_file = close $handle_file;
1111	$return_old = close $handle_original;
1112    }
1113
1114    # Error handling/logging here...
1115    #&B_log("ACTION","#Closing $original_file and backing up to " . &getGlobal('BDIR', "backup"));
1116    #&B_log("ACTION","/$original_file\n");
1117
1118    unless ($return_file) {
1119	$retval=0;
1120	&B_log("ERROR","close $original_file failed...\n");
1121    }
1122    unless ($return_old) {
1123	$retval=0;
1124	&B_log("ERROR","close $original_file.bastille failed.\n");
1125    }
1126
1127    #
1128    # If we've had no errors, backup the old file and put the new one
1129    # in its place, with the Right permissions.
1130    #
1131
1132    unless ( ($retval == 0) or $GLOBAL_LOGONLY) {
1133
1134	# Read the permissions/owners on the old file
1135
1136	@junk=stat ($file);
1137	$mode=$junk[2];
1138	$uid=$junk[4];
1139	$gid=$junk[5];
1140
1141	# Set the permissions/owners on the new file
1142
1143	chmod $mode, "$file.bastille" or &B_log("ERROR","Not able to retain permissions on $original_file!!!\n");
1144	chown $uid, $gid, "$file.bastille" or &B_log("ERROR","Not able to retain owners on $original_file!!!\n");
1145
1146	# Backup the old file and put a new one in place.
1147
1148	&B_backup_file($original_file);
1149	rename "$file.bastille", $file or
1150        &B_log("ERROR","B_close_plus: not able to move $original_file.bastille to $original_file\n");
1151
1152        # We add the file to the GLOBAL_SUMS hash if it is not already present
1153	&B_set_sum($file);
1154
1155    }
1156
1157    return $retval;
1158}
1159
1160###########################################################################
1161# &B_backup_file ($file) makes a backup copy of the file $file in
1162# &getGlobal('BDIR', "backup").  Note that this routine is intended for internal
1163# use only -- only Bastille API functions should call B_backup_file.
1164#
1165###########################################################################
1166
1167sub B_backup_file {
1168
1169    my $file=$_[0];
1170    my $complain = 1;
1171    my $original_file = $file;
1172
1173    my $backup_dir = &getGlobal('BDIR', "backup");
1174    my $backup_file = $backup_dir . $original_file;
1175
1176    my $retval=1;
1177
1178    # First, separate the file into the directory and the relative filename
1179
1180    my $directory ="";
1181    if ($file =~ /^(.*)\/([^\/]+)$/) {
1182	#$relative_file=$2;
1183	$directory = $1;
1184    } else {
1185        $directory=cwd;
1186    }
1187
1188    # Now, if the directory does not exist, create it.
1189    # Later:
1190    #   Try to set the same permissions on the patch directory that the
1191    #   original had...?
1192
1193    unless ( -d ($backup_dir . $directory) ) {
1194	mkpath(( $backup_dir . $directory),0,0700);
1195
1196    }
1197
1198    # Now we backup the file.  If there is already a backup file there,
1199    # we will leave it alone, since it exists from a previous run and
1200    # should be the _original_ (possibly user-modified) distro's version
1201    # of the file.
1202
1203    if ( -e $file ) {
1204
1205	unless ( -e $backup_file ) {
1206	    my $command=&getGlobal("BIN","cp");
1207            &B_Backtick("$command -p $file $backup_file");
1208	    &B_revert_log (&getGlobal("BIN","mv"). " $backup_file $file");
1209	}
1210
1211    } else {
1212	# The file we were trying to backup doesn't exist.
1213
1214	$retval=0;
1215	# This is a non-fatal error, not worth complaining about
1216	$complain = 0;
1217	#&ErrorLog ("# Failed trying to backup file $file -- it doesn't exist!\n");
1218    }
1219
1220    # Check to make sure that the file does exist in the backup location.
1221
1222    unless ( -e $backup_file ) {
1223	$retval=0;
1224	if ( $complain == 1 ) {
1225	    &B_log("ERROR","Failed trying to backup $file -- the copy was not created.\n");
1226	}
1227    }
1228
1229    return $retval;
1230}
1231
1232
1233###########################################################################
1234# &B_read_sums reads in the sum.csv file which contains information
1235#   about Bastille modified files. The file structure is as follows:
1236#
1237#     filename,filesize,cksum
1238#
1239#   It reads the information into the GLOBAL_SUM hash i.e.
1240#      $GLOBAL_SUM{$file}{sum} = $cksum
1241#      $GLOBAL_SUM{$file}{filesize} = $size
1242#   For the first run of Bastille on a given system this subroutine
1243#   is a no-op, and returns "undefined."
1244###########################################################################
1245
1246sub B_read_sums {
1247
1248    my $sumFile = &getGlobal('BFILE',"sum.csv");
1249
1250    if ( -e $sumFile ) {
1251
1252	open( SUM, "< $sumFile") or &B_log("ERROR","Unable to open $sumFile for read.\n$!\n");
1253
1254	while( my $line = <SUM> ) {
1255	    chomp $line;
1256	    my ($file,$filesize,$sum,$flag) = split /,/, $line;
1257	    if(-e $file) {
1258		$GLOBAL_SUM{"$file"}{filesize} = $filesize;
1259		$GLOBAL_SUM{"$file"}{sum} = $sum;
1260	    }
1261	}
1262
1263	close(SUM);
1264    } else {
1265        return undef;
1266    }
1267}
1268
1269
1270###########################################################################
1271# &B_write_sums writes out the sum.csv file which contains information
1272#   about Bastille modified files. The file structure is as follows:
1273#
1274#     filename,filesize,cksum
1275#
1276#   It writes the information from the GLOBAL_SUM hash i.e.
1277#
1278#      $file,$GLOBAL_SUM{$file}{sum},$GLOBAL_SUM{$file}{filesize}
1279#
1280#   This subroutine requires access to the GLOBAL_SUM hash.
1281###########################################################################
1282
1283sub B_write_sums {
1284
1285    my $sumFile = &getGlobal('BFILE',"sum.csv");
1286
1287    if ( %GLOBAL_SUM ) {
1288
1289	open( SUM, "> $sumFile") or &B_log("ERROR","Unable to open $sumFile for write.\n$!\n");
1290
1291	for my $file (sort keys %GLOBAL_SUM) {
1292	    if( -e $file) {
1293		print SUM "$file,$GLOBAL_SUM{\"$file\"}{filesize},$GLOBAL_SUM{\"$file\"}{sum}\n";
1294	    }
1295	}
1296
1297	close(SUM);
1298    }
1299
1300}
1301
1302
1303###########################################################################
1304# &B_check_sum($file) compares the stored cksum and filesize of the given
1305#   file compared to the current cksum and filesize respectively.
1306#   This subroutine also keeps the state of the sum check by setting the
1307#   checked flag which tells the subroutine that on this run this file
1308#   has already been checked.
1309#
1310#     $GLOBAL_SUM{$file}{checked} = 1;
1311#
1312#   This subroutine requires access to the GLOBAL_SUM hash.
1313#
1314#  Returns 1 if sum checks out and 0 if not
1315###########################################################################
1316
1317sub B_check_sum($) {
1318    my $file = $_[0];
1319    my $cksum = &getGlobal('BIN',"cksum");
1320
1321    if (not(%GLOBAL_SUM)) {
1322        &B_read_sums;
1323    }
1324
1325    if(-e $file) {
1326	my ($sum,$size,$ckfile) = split(/\s+/, `$cksum $file`);
1327        my $commandRetVal = ($? >> 8);  # find the command's return value
1328
1329	if($commandRetVal != 0) {
1330	    &B_log("ERROR","$cksum reported the following error:\n$!\n");
1331            return 0;
1332	} else {
1333            if ( exists $GLOBAL_SUM{$file} ) {
1334                # if the file size or file sum differ from those recorded.
1335                if (( $GLOBAL_SUM{$file}{filesize} == $size) and
1336                    ($GLOBAL_SUM{$file}{sum} == $sum )) {
1337                    return 1; #True, since saved state matches up, all is well.
1338                } else {
1339                    return 0; #False, since saved state doesn't match
1340                }
1341            } else {
1342                &B_log("ERROR","File: $file does not exist in sums database.");
1343                return 0;
1344            }
1345        }
1346    } else {
1347        &B_log("ERROR","The file: $file does not exist for comparison in B_check_sum.");
1348        return 0;
1349    }
1350}
1351
1352# Don't think we need this anymore as function now check_sums returns
1353# results directly
1354#sub isSumDifferent($) {
1355#    my $file = $_[0];
1356#    if(exists $GLOBAL_SUM{$file}) {
1357#	return $GLOBAL_SUM{$file}{flag}
1358#    }
1359#}
1360
1361sub listModifiedFiles {
1362    my @listModifiedFiles=sort keys %GLOBAL_SUM;
1363    return @listModifiedFiles;
1364}
1365
1366###########################################################################
1367# &B_isFileinSumDB($file) checks to see if a given file's sum was saved.
1368#
1369#     $GLOBAL_SUM{$file}{filesize} = $size;
1370#     $GLOBAL_SUM{$file}{sum} = $cksum;
1371#
1372#   This subroutine requires access to the GLOBAL_SUM hash.
1373###########################################################################
1374
1375sub B_isFileinSumDB($) {
1376    my $file = $_[0];
1377
1378    if (not(%GLOBAL_SUM)) {
1379        &B_log("DEBUG","Reading in DB from B_isFileinSumDB");
1380        &B_read_sums;
1381    }
1382    if (exists($GLOBAL_SUM{"$file"})){
1383        &B_log("DEBUG","$file is in sum database");
1384        return 1; #true
1385    } else {
1386        &B_log("DEBUG","$file is not in sum database");
1387        return 0; #false
1388    }
1389}
1390
1391###########################################################################
1392# &B_set_sum($file) sets the current cksum and filesize of the given
1393#   file into the GLOBAL_SUM hash.
1394#
1395#     $GLOBAL_SUM{$file}{filesize} = $size;
1396#     $GLOBAL_SUM{$file}{sum} = $cksum;
1397#
1398#   This subroutine requires access to the GLOBAL_SUM hash.
1399###########################################################################
1400
1401sub B_set_sum($) {
1402
1403    my $file = $_[0];
1404    my $cksum = &getGlobal('BIN',"cksum");
1405    if( -e $file) {
1406
1407	my ($sum,$size,$ckfile) = split(/\s+/, `$cksum $file`);
1408        my $commandRetVal = ($? >> 8);  # find the command's return value
1409
1410	if($commandRetVal != 0) {
1411
1412	    &B_log("ERROR","$cksum reported the following error:\n$!\n");
1413
1414	}
1415	else {
1416
1417	    # new file size and sum are added to the hash
1418	    $GLOBAL_SUM{$file}{filesize} = $size;
1419	    $GLOBAL_SUM{$file}{sum} = $sum;
1420	    &B_write_sums;
1421
1422	}
1423    } else {
1424        &B_log("ERROR","Can not save chksum for file: $file since it does not exist");
1425    }
1426}
1427
1428
1429###########################################################################
1430#
1431# &B_delete_file ($file)  deletes the file $file and makes a backup to
1432# the backup directory.
1433#
1434##########################################################################
1435
1436
1437sub B_delete_file($) { #Currently Linux only (TMPDIR)
1438    #consideration: should create clear_sum routine if this is ever used to remove
1439    #               A Bastille-generated file.
1440
1441    #
1442    # This API routine deletes the named file, backing it up first to the
1443    # backup directory.
1444    #
1445
1446    my $filename=shift @_;
1447    my $retval=1;
1448
1449    # We have to append the prefix ourselves since we don't use B_open_plus
1450
1451    my $original_filename=$filename;
1452
1453    &B_log("ACTION","Deleting (and backing-up) file $original_filename\n");
1454    &B_log("ACTION","rm $original_filename\n");
1455
1456    unless ($filename) {
1457	&B_log("ERROR","B_delete_file called with no arguments!\n");
1458    }
1459
1460    unless ($GLOBAL_LOGONLY) {
1461	if ( B_backup_file($original_filename) ) {
1462	    unless ( unlink $filename ) {
1463		&B_log("ERROR","Couldn't unlink file $original_filename");
1464		$retval=0;
1465	    }
1466	}
1467	else {
1468	    $retval=0;
1469	    &B_log("ERROR","B_delete_file did not delete $original_filename since it could not back it up\n");
1470	}
1471    }
1472
1473    $retval;
1474
1475}
1476
1477
1478###########################################################################
1479# &B_create_file ($file) creates the file $file, if it doesn't already
1480# exist.
1481# It will set a default mode of 0700 and a default uid/gid or 0/0.
1482#
1483# &B_create_file, to support Bastille's revert functionality, writes an
1484# rm $file command to the end of the file &getGlobal('BFILE', "created-files").
1485#
1486##########################################################################
1487
1488
1489sub B_create_file($) {
1490
1491    my $file = $_[0];
1492    my $retval=1;
1493
1494    # We have to create the file ourselves since we don't use B_open_plus
1495
1496    my $original_file = $file;
1497
1498    if ($file eq ""){
1499        &B_log("ERROR","Internal Error, attempt made to create blank filename");
1500        return 0; #False
1501    }
1502
1503    unless ( -e $file ) {
1504
1505	unless ($GLOBAL_LOGONLY) {
1506
1507	    # find the directory in which the file is to reside.
1508	    my $dirName = dirname($file);
1509	    # if the directory does not exist then
1510	    if(! -d $dirName) {
1511		# create it.
1512		mkpath ($dirName,0,0700);
1513	    }
1514
1515	    $retval=open CREATE_FILE,">$file";
1516
1517	    if ($retval) {
1518		close CREATE_FILE;
1519		chmod 0700,$file;
1520		# Make the revert functionality
1521		&B_revert_log( &getGlobal('BIN','rm') . " $original_file \n");
1522	    } else {
1523		&B_log("ERROR","Couldn't create file $original_file even though " .
1524			  "it didn't already exist!\n");
1525	    }
1526	}
1527	&B_log("ACTION","Created file $original_file\n");
1528    } else {
1529	&B_log("DEBUG","Didn't create file $original_file since it already existed.\n");
1530	$retval=0;
1531    }
1532
1533    $retval;
1534}
1535
1536
1537###########################################################################
1538# &B_create_dir ($dir) creates the directory $dir, if it doesn't already
1539# exist.
1540# It will set a default mode of 0700 and a default uid/gid or 0/0.
1541#
1542##########################################################################
1543
1544
1545sub B_create_dir($) {
1546
1547    my $dir = $_[0];
1548    my $retval=1;
1549
1550    # We have to append the prefix ourselves since we don't use B_open_plus
1551
1552    my $original_dir=$dir;
1553
1554    unless ( -d $dir ) {
1555	unless ($GLOBAL_LOGONLY) {
1556	    $retval=mkdir $dir,0700;
1557
1558	    if ($retval) {
1559		# Make the revert functionality
1560		&B_revert_log (&getGlobal('BIN','rmdir') . " $original_dir\n");
1561	    }
1562	    else {
1563		&B_log("ERROR","Couldn't create dir $original_dir even though it didn't already exist!");
1564	    }
1565
1566	}
1567	&B_log("ACTION","Created directory $original_dir\n");
1568    }
1569    else {
1570	&B_log("ACTION","Didn't create directory $original_dir since it already existed.\n");
1571	$retval=0;
1572    }
1573
1574    $retval;
1575}
1576
1577
1578
1579###########################################################################
1580# &B_symlink ($original_file,$new_symlink) creates a symbolic link from
1581# $original_file to $new_symlink.
1582#
1583# &B_symlink respects $GLOBAL_LOGONLY.  It supports
1584# the revert functionality that you've come to know and love by adding every
1585# symbolic link it creates to &getGlobal('BFILE', "created-symlinks"), currently set to:
1586#
1587#         /root/Bastille/revert/revert-created-symlinks
1588#
1589# The revert script, if it works like I think it should, will run this file,
1590# which should be a script or rm's...
1591#
1592##########################################################################
1593
1594sub B_symlink($$) {
1595    my ($source_file,$new_symlink)=@_;
1596    my $retval=1;
1597    my $original_source = $source_file;
1598    my $original_symlink = $new_symlink;
1599
1600    unless ($GLOBAL_LOGONLY) {
1601	$retval=symlink $source_file,$new_symlink;
1602	if ($retval) {
1603	    &B_revert_log (&getGlobal('BIN',"rm") .  " $original_symlink\n");
1604	}
1605    }
1606
1607    &B_log("ACTION", "Created a symbolic link called $original_symlink from $original_source\n");
1608    &B_log("ACTION", "symlink \"$original_source\",\"$original_symlink\";\n");
1609    unless ($retval) {
1610	&B_log("ERROR","Couldn't symlink $original_symlink -> $original_source\n");
1611    }
1612
1613    $retval;
1614
1615}
1616
1617
1618sub B_cp($$) {
1619
1620    my ($source,$target)=@_;
1621    my $retval=0;
1622
1623    my $had_to_backup_target=0;
1624
1625    use File::Copy;
1626
1627    my $original_source=$source;
1628    my $original_target=$target;
1629
1630    if( -e $target and -f $target ) {
1631	&B_backup_file($original_target);
1632	&B_log("ACTION","About to copy $original_source to $original_target -- had to backup target\n");
1633	$had_to_backup_target=1;
1634    }
1635
1636    $retval=copy($source,$target);
1637    if ($retval) {
1638	&B_log("ACTION","cp $original_source $original_target\n");
1639
1640	#
1641	# We want to add a line to the &getGlobal('BFILE', "created-files") so that the
1642	# file we just put at $original_target gets deleted.
1643	#
1644	&B_revert_log(&getGlobal('BIN',"rm") . " $original_target\n");
1645    } else {
1646	&B_log("ERROR","Failed to copy $original_source to $original_target\n");
1647    }
1648    # We add the file to the GLOBAL_SUMS hash if it is not already present
1649    &B_set_sum($target);
1650    $retval;
1651}
1652
1653
1654
1655############################################################################
1656# &B_place puts a file in place, using Perl's File::cp.  This file is taken
1657# from &getGlobal('BDIR', "share") and is used to place a file that came with
1658# Bastille.
1659#
1660# This should be DEPRECATED in favor of &B_cp, since the only reason it exists
1661# is because of GLOBAL_PREFIX, which has been broken for quite some time.
1662# Otherwise, the two routines are identical.
1663#
1664# It respects $GLOBAL_LOGONLY.
1665# If $target is an already-existing file, it is backed up.
1666#
1667# revert either appends another "rm $target" to &getGlobal('BFILE', "revert-actions")  or
1668# backs up the file that _was_ there into the &getGlobal('BDIR', "backup"),
1669# appending a "mv" to revert-actions to put it back.
1670#
1671############################################################################
1672
1673sub B_place { # Only Linux references left (Firewall / TMPDIR)
1674
1675    my ($source,$target)=@_;
1676    my $retval=0;
1677
1678    my $had_to_backup_target=0;
1679
1680    use File::Copy;
1681
1682    my $original_source=$source;
1683    $source  = &getGlobal('BDIR', "share") . $source;
1684    my $original_target=$target;
1685
1686    if ( -e $target and -f $target ) {
1687	&B_backup_file($original_target);
1688	&B_log("ACTION","About to copy $original_source to $original_target -- had to backup target\n");
1689	$had_to_backup_target=1;
1690    }
1691    $retval=copy($source,$target);
1692    if ($retval) {
1693	&B_log("ACTION","placed file $original_source  as  $original_target\n");
1694	#
1695	# We want to add a line to the &getGlobal('BFILE', "created-files") so that the
1696	# file we just put at $original_target gets deleted.
1697	&B_revert_log(&getGlobal('BIN',"rm") . " $original_target\n");
1698    } else {
1699	&B_log("ERROR","Failed to place $original_source as $original_target\n");
1700    }
1701
1702    # We add the file to the GLOBAL_SUMS hash if it is not already present
1703    &B_set_sum($target);
1704
1705    $retval;
1706}
1707
1708
1709
1710
1711
1712#############################################################################
1713#############################################################################
1714#############################################################################
1715
1716###########################################################################
1717# &B_mknod ($file) creates the node $file, if it doesn't already
1718# exist.  It uses the prefix and suffix, like this:
1719#
1720#            mknod $prefix $file $suffix
1721#
1722# This is just a wrapper to the mknod program, which tries to introduce
1723# revert functionality, by writing    rm $file     to the end of the
1724# file &getGlobal('BFILE', "created-files").
1725#
1726##########################################################################
1727
1728
1729sub B_mknod($$$) {
1730
1731    my ($prefix,$file,$suffix) = @_;
1732    my $retval=1;
1733
1734    # We have to create the filename ourselves since we don't use B_open_plus
1735
1736    my $original_file = $file;
1737
1738    unless ( -e $file ) {
1739	my $command = &getGlobal("BIN","mknod") . " $prefix $file $suffix";
1740
1741	if ( system($command) == 0) {
1742	    # Since system will return 0 on success, invert the error code
1743	    $retval=1;
1744	}
1745	else {
1746	    $retval=0;
1747	}
1748
1749	if ($retval) {
1750
1751	    # Make the revert functionality
1752	    &B_revert_log(&getGlobal('BIN',"rm") . " $original_file\n");
1753	} else {
1754	    &B_log("ERROR","Couldn't mknod $prefix $original_file $suffix even though it didn't already exist!\n");
1755	}
1756
1757
1758	&B_log("ACTION","mknod $prefix $original_file $suffix\n");
1759    }
1760    else {
1761	&B_log("ACTION","Didn't mknod $prefix $original_file $suffix since $original_file already existed.\n");
1762	$retval=0;
1763    }
1764
1765    $retval;
1766}
1767
1768###########################################################################
1769# &B_revert_log("reverse_command") prepends a command to a shell script.  This
1770# shell script is intended to be run by bastille -r to reverse the changes that
1771# Bastille made, returning the files which Bastille changed to their original
1772# state.
1773###########################################################################
1774
1775sub B_revert_log($) {
1776
1777    my $revert_command = $_[0];
1778    my $revert_actions = &getGlobal('BFILE', "revert-actions");
1779    my $revertdir= &getGlobal('BDIR', "revert");
1780    my @lines;
1781
1782
1783    if (! (-e $revert_actions)) {
1784        mkpath($revertdir); #if this doesn't work next line catches
1785	if (open REVERT_ACTIONS,">" . $revert_actions){ # create revert file
1786	    close REVERT_ACTIONS; # chown to root, rwx------
1787	    chmod 0700,$revert_actions;
1788	    chown 0,0,$revert_actions;
1789	}
1790	else {
1791	    &B_log("FATAL","Can not create revert-actions file: $revert_actions.\n" .
1792		       "         Unable to add the following command to the revert\n" .
1793		       "         actions script: $revert_command\n");
1794	}
1795
1796    }
1797
1798    &B_open_plus (*REVERT_NEW, *REVERT_OLD, $revert_actions);
1799
1800    while (my $line=<REVERT_OLD>) { #copy file into @lines
1801	push (@lines,$line);
1802    }
1803    print REVERT_NEW $revert_command .  "\n";  #make the revert command first in the new file
1804    while (my $line = shift @lines) { #write the rest of the lines of the file
1805	print REVERT_NEW $line;
1806    }
1807    close REVERT_OLD;
1808    close REVERT_NEW;
1809    if (rename "${revert_actions}.bastille", $revert_actions) { #replace the old file with the new file we
1810	chmod 0700,$revert_actions;                # just made / mirrors B_close_plus logic
1811	chown 0,0,$revert_actions;
1812    } else {
1813	&B_log("ERROR","B_revert_log: not able to move ${revert_actions}.bastille to ${revert_actions}!!! $!) !!!\n");
1814    }
1815}
1816
1817
1818###########################################################################
1819# &getGlobalConfig($$)
1820#
1821# returns the requested GLOBAL_CONFIG hash value, ignoring the error
1822# if the value does not exist (because every module uses this to find
1823# out if the question was answered "Y")
1824###########################################################################
1825sub getGlobalConfig ($$) {
1826  my $module = $_[0];
1827  my $key = $_[1];
1828  if (exists $GLOBAL_CONFIG{$module}{$key}) {
1829    my $answer=$GLOBAL_CONFIG{$module}{$key};
1830    &B_log("ACTION","Answer to question $module.$key is \"$answer\".\n");
1831    return $answer;
1832  } else {
1833    &B_log("ACTION","Answer to question $module.$key is undefined.");
1834    return undef;
1835  }
1836}
1837
1838###########################################################################
1839# &getGlobal($$)
1840#
1841# returns the requested GLOBAL_* hash value, and logs an error
1842# if the variable does not exist.
1843###########################################################################
1844sub getGlobal ($$) {
1845  my $type = uc($_[0]);
1846  my $key = $_[1];
1847
1848  # define a mapping from the first argument to the proper hash
1849  my %map = ("BIN"   => \%GLOBAL_BIN,
1850             "FILE"  => \%GLOBAL_FILE,
1851             "BFILE" => \%GLOBAL_BFILE,
1852             "DIR"   => \%GLOBAL_DIR,
1853             "BDIR"  => \%GLOBAL_BDIR,
1854	     "ERROR" => \%GLOBAL_ERROR,
1855	     "SERVICE" => \%GLOBAL_SERVICE,
1856	     "SERVTYPE" => \%GLOBAL_SERVTYPE,
1857	     "PROCESS" => \%GLOBAL_PROCESS,
1858             "RCCONFIG" => \%GLOBAL_RC_CONFIG
1859            );
1860
1861  # check to see if the desired key is in the desired hash
1862  if (exists $map{$type}->{$key}) {
1863    # get the value from the right hash with the key
1864    return $map{$type}->{$key};
1865  } else {
1866    # i.e. Bastille tried to use $GLOBAL_BIN{'cp'} but it does not exist.
1867    # Note that we can't use B_log, since it uses getGlobal ... recursive before
1868    # configureForDistro is run.
1869    print STDERR "ERROR:   Bastille tried to use \$GLOBAL_${type}\{\'$key\'} but it does not exist.\n";
1870    return undef;
1871  }
1872}
1873
1874###########################################################################
1875# &getGlobal($$)
1876#
1877# sets the requested GLOBAL_* hash value
1878###########################################################################
1879sub setGlobal ($$$) {
1880  my $type = uc($_[0]);
1881  my $key = $_[1];
1882  my $input_value = $_[2];
1883
1884  # define a mapping from the first argument to the proper hash
1885  my %map = ("BIN"   => \%GLOBAL_BIN,
1886             "FILE"  => \%GLOBAL_FILE,
1887             "BFILE" => \%GLOBAL_BFILE,
1888             "DIR"   => \%GLOBAL_DIR,
1889             "BDIR"  => \%GLOBAL_BDIR,
1890	     "ERROR" => \%GLOBAL_ERROR,
1891	     "SERVICE" => \%GLOBAL_SERVICE,
1892	     "SERVTYPE" => \%GLOBAL_SERVTYPE,
1893	     "PROCESS" => \%GLOBAL_PROCESS,
1894            );
1895
1896  if ($map{$type}->{$key} = $input_value) {
1897    return 1;
1898  } else {
1899    &B_log('ERROR','Internal Error, Unable to set global config value:' . $type . ", " .$key);
1900    return 0;
1901  }
1902}
1903
1904
1905###########################################################################
1906# &showDisclaimer:
1907# Print the disclaimer and wait for 2 minutes for acceptance
1908# Do NOT do so if any of the following conditions hold
1909# 1. the -n option was used
1910# 2. the file ~/.bastille_disclaimer exists
1911###########################################################################
1912
1913sub showDisclaimer($) {
1914
1915    my $nodisclaim = $_[0];
1916    my $nodisclaim_file = &getGlobal('BFILE', "nodisclaimer");
1917    my $response;
1918    my $WAIT_TIME = 300; # we'll wait for 5 minutes
1919    my $developersAnd;
1920    my $developersOr;
1921    if ($GLOBAL_OS =~ "^HP-UX") {
1922	$developersAnd ="HP AND ITS";
1923	$developersOr ="HP OR ITS";
1924    }else{
1925	$developersAnd ="JAY BEALE, THE BASTILLE DEVELOPERS, AND THEIR";
1926	$developersOr ="JAY BEALE, THE BASTILLE DEVELOPERS, OR THEIR";
1927    }
1928    my $DISCLAIMER =
1929	"\n" .
1930        "Copyright (C) 1999-2006 Jay Beale\n" .
1931        "Copyright (C) 1999-2001 Peter Watkins\n" .
1932        "Copyright (C) 2000 Paul L. Allen\n" .
1933        "Copyright (C) 2001-2007 Hewlett-Packard Development Company, L.P.\n" .
1934        "Bastille is free software; you are welcome to redistribute it under\n" .
1935        "certain conditions.  See the \'COPYING\' file in your distribution for terms.\n\n" .
1936	"DISCLAIMER.  Use of Bastille can help optimize system security, but does not\n" .
1937	"guarantee system security. Information about security obtained through use of\n" .
1938	"Bastille is provided on an AS-IS basis only and is subject to change without\n" .
1939	"notice. Customer acknowledges they are responsible for their system\'s security.\n" .
1940	"TO THE EXTENT ALLOWED BY LOCAL LAW, Bastille (\"SOFTWARE\") IS PROVIDED TO YOU \n" .
1941	"\"AS IS\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN,\n" .
1942	"EXPRESS OR IMPLIED.  $developersAnd SUPPLIERS\n" .
1943	"DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE \n" .
1944	"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.\n" .
1945	"Some countries, states and provinces do not allow exclusions of implied\n" .
1946	"warranties or conditions, so the above exclusion may not apply to you. You may\n" .
1947	"have other rights that vary from country to country, state to state, or province\n" .
1948	"to province.  EXCEPT TO THE EXTENT PROHIBITED BY LOCAL LAW, IN NO EVENT WILL\n" .
1949	"$developersOr SUBSIDIARIES, AFFILIATES OR\n" .
1950	"SUPPLIERS BE LIABLE FOR DIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR OTHER\n" .
1951	"DAMAGES (INCLUDING LOST PROFIT, LOST DATA, OR DOWNTIME COSTS), ARISING OUT OF\n" .
1952	"THE USE, INABILITY TO USE, OR THE RESULTS OF USE OF THE SOFTWARE, WHETHER BASED\n" .
1953	"IN WARRANTY, CONTRACT, TORT OR OTHER LEGAL THEORY, AND WHETHER OR NOT ADVISED\n" .
1954	"OF THE POSSIBILITY OF SUCH DAMAGES. Your use of the Software is entirely at your\n" .
1955	"own risk. Should the Software prove defective, you assume the entire cost of all\n" .
1956	"service, repair or correction. Some countries, states and provinces do not allow\n" .
1957	"the exclusion or limitation of liability for incidental or consequential \n" .
1958	"damages, so the above limitation may not apply to you.  This notice will only \n".
1959        "display on the first run on a given system.\n".
1960        "To suppress the disclaimer on other machines, use Bastille\'s -n flag (example: bastille -n).\n";
1961
1962
1963# If the user has specified not to show the disclaimer, or
1964# the .bastille_disclaimer file already exists, then return
1965    if( ( $nodisclaim ) || -e $nodisclaim_file ) { return 1; }
1966
1967# otherwise, show the disclaimer
1968    print ($DISCLAIMER);
1969
1970# there is a response
1971	my $touch = &getGlobal('BIN', "touch");
1972	my $retVal = system("$touch $nodisclaim_file");
1973	if( $retVal != 0 ) {
1974	    &ErrorLog ( &getGlobal('ERROR','disclaimer'));
1975	}
1976} # showDisclaimer
1977
1978
1979
1980
1981################################################################
1982# &systemCall
1983#Function used by exported methods B_Backtick and B_system
1984#to handle the mechanics of system calls.
1985# This function also manages error handling.
1986# Input: a system call
1987# Output: a list containing the status, sstdout and stderr
1988# of the the system call
1989#
1990################################################################
1991sub systemCall ($){
1992    no strict;
1993    local $command=$_[0];  # changed scoping so eval below can read it
1994
1995    local $SIG{'ALRM'} = sub {  die "timeout" }; # This subroutine exits the "eval" below.  The program
1996    # can then move on to the next operation.  Used "local"
1997    # to avoid name space collision with disclaim alarm.
1998    local $WAIT_TIME=120; # Wait X seconds for system commands
1999    local $commandOutput = '';
2000    my $errOutput = '';
2001    eval{
2002        $errorFile = &getGlobal('BFILE','stderrfile');
2003        unlink($errorFile); #To make sure we don't mix output
2004	alarm($WAIT_TIME); # start a time-out for command to complete.  Some commands hang, and we want to
2005	                   # fail gracefully.  When we call "die" it exits this eval statement
2006	                   # with a value we use below
2007	$commandOutput = `$command 2> $errorFile`; # run the command and gather its output
2008	my $commandRetVal = ($? >> 8);  # find the commands return value
2009	if ($commandRetVal == 0) {
2010	    &B_log("ACTION","Executed Command: " . $command . "\n");
2011	    &B_log("ACTION","Command Output: " . $commandOutput . "\n");
2012	    die "success";
2013	} else {
2014	    die "failure";
2015	};
2016    };
2017
2018    my $exitcode=$@;
2019    alarm(0);  # End of the timed operation
2020
2021    my $cat = &getGlobal("BIN","cat");
2022    if ( -e $errorFile ) {
2023        $errOutput = `$cat $errorFile`;
2024    }
2025
2026    if ($exitcode) {  # The eval command above will exit with one of the 3 values below
2027	if ($exitcode =~ /timeout/) {
2028	    &B_log("WARNING","No response received from $command after $WAIT_TIME seconds.\n" .
2029		   "Command Output: " . $commandOutput . "\n");
2030	    return (0,'','');
2031	} elsif ($exitcode =~ /success/) {
2032	    return (1,$commandOutput,$errOutput);
2033	} elsif ($exitcode =~ /failure/) {
2034	    return (0,$commandOutput,$errOutput);
2035	} else {
2036	    &B_log("FATAL","Unexpected return state from command execution: $command\n" .
2037		   "Command Output: " . $commandOutput . "\n");
2038	}
2039    }
2040}
2041
2042#############################################
2043# Use this **only** for commands used that are
2044# intended to test system state and
2045# not make any system change.  Use this in place of the
2046# prior use of "backticks throughout Bastille
2047# Handles basic output redirection, but not for stdin
2048# Input: Command
2049# Output: Results
2050#############################################
2051
2052sub B_Backtick($) {
2053    my $command=$_[0];
2054    my $combineOutput=0;
2055    my $stdoutRedir = "";
2056    my $stderrRedir = "";
2057    my $echo = &getGlobal('BIN','echo');
2058
2059    if (($command =~ s/2>&1//) or
2060        (s/>&2//)){
2061        $combineOutput=1;
2062    }
2063    if ($command =~ s/>\s*([^>\s])+// ) {
2064        $stdoutRedir = $1;
2065    }
2066    if ($command =~ s/2>\s*([^>\s])+// ) {
2067        $stderrRedir = $1;
2068    }
2069
2070    my ($ranFine, $stdout, $stderr) = &systemCall($command);
2071    if ($ranFine) {
2072        &B_log("DEBUG","Command: $command succeeded for test with output: $stdout , ".
2073               "and stderr: $stderr");
2074    } else {
2075        &B_log("DEBUG","Command: $command failed for test with output: $stdout , ".
2076               "and stderr: $stderr");
2077    }
2078    if ($combineOutput) {
2079        $stdout .= $stderr;
2080        $stderr = $stdout; #these should be the same
2081    }
2082    if ($stdoutRedir ne "") {
2083        system("$echo \'$stdout\' > $stdoutRedir");
2084    }
2085    if ($stderrRedir ne "") {
2086        system("$echo \'$stderr\' > $stderrRedir");
2087    }
2088    return $stdout;
2089}
2090
2091####################################################################
2092#  &B_System($command,$revertcommand);
2093#    This function executes a command, then places the associated
2094#    revert command in revert file. It takes two parameters, the
2095#    command and the command that reverts that command.
2096#
2097#   uses ActionLog and ErrorLog for logging purposes.
2098###################################################################
2099sub B_System ($$) {
2100    my ($command,$revertcmd)=@_;
2101
2102    my ($ranFine, $stdout, $stderr) = &systemCall($command);
2103    if ($ranFine) {
2104        &B_revert_log ("$revertcmd \n");
2105        if ($stderr ne '' ) {
2106                &B_log("ACTION",$command . "suceeded with STDERR: " .
2107                       $stderr . "\n");
2108        }
2109        return 1;
2110    } else {
2111        my $warningString = "Command Failed: " . $command . "\n" .
2112                            "Command Output: " . $stdout . "\n";
2113        if ($stderr ne '') {
2114            $warningString .= "Error message: " . $stderr;
2115        }
2116        &B_log("WARNING", $warningString);
2117        return 0;
2118    }
2119}
2120
2121
2122###########################################################################
2123# &isProcessRunning($procPattern);
2124#
2125# If called in scalar context this subroutine will return a 1 if the
2126# pattern specified can be matched against the process table.  It will
2127# return a 0 otherwise.
2128# If called in the list context this subroutine will return the list
2129# of processes which matched the pattern supplied
2130#
2131# scalar return values:
2132# 0:     pattern not in process table
2133# 1:     pattern is in process table
2134#
2135# list return values:
2136# proc lines from the process table if they are found
2137###########################################################################
2138sub isProcessRunning($) {
2139
2140    my $procPattern= $_[0];
2141    my $ps = &getGlobal('BIN',"ps");
2142
2143    my $isRunning=0;
2144    # process table.
2145    my @psTable = `$ps -elf`;
2146    # list of processes that match the $procPattern
2147    my @procList;
2148    foreach my $process (@psTable) {
2149        if($process =~ $procPattern) {
2150            $isRunning = 1;
2151            push @procList, $process . "\n";
2152        }
2153    }
2154
2155    &B_log("DEBUG","$procPattern search yielded $isRunning\n\n");
2156    # if this subroutine was called in scalar context
2157    if( ! wantarray ) {
2158        return $isRunning;
2159    }
2160
2161    return @procList;
2162}
2163
2164
2165###########################################################################
2166# &checkProcsForService($service);
2167#
2168# Checks if the given service is running by analyzing the process table.
2169# This is a helper function to checkServiceOnLinux and checkServiceOnHP
2170#
2171# Return values:
2172# SECURE_CANT_CHANGE() if the service is off
2173# INCONSISTENT() if the state of the service cannot be determined
2174#
2175# Mostly used in  "check service" direct-return context, but added option use.
2176# to ignore warning if a check for a service ... where a found service doesn't
2177# have direct security problems.
2178#
2179###########################################################################
2180sub checkProcsForService ($;$) {
2181  my $service=$_[0];
2182  my $ignore_warning=$_[1];
2183
2184  my @psnames=@{ &getGlobal('PROCESS',$service)};
2185
2186  my @processes;
2187  # inetd services don't have a separate process
2188  foreach my $psname (@psnames) {
2189    my @procList = &isProcessRunning($psname);
2190    if(@procList >= 0){
2191      splice @processes,$#processes+1,0,@procList;
2192    }
2193  }
2194
2195  if($#processes >= 0){
2196    if ((defined($ignore_warning)) and ($ignore_warning eq "ignore_warning")) {
2197      &B_log("WARNING","The following processes were still running even though " .
2198           "the corresponding service appears to be turned off.  Bastille " .
2199           "question and action will be skipped.\n\n" .
2200           "@processes\n\n");
2201      # processes were still running, service is not off, but we don't know how
2202      # to configure it so we skip the question
2203    return INCONSISTENT();
2204    } else {
2205      return NOTSECURE_CAN_CHANGE(); # In the case we're ignoring the warning,
2206                                     # ie: checking to make *sure* a process
2207                                     # is running, the answer isn't inconsistent
2208    }
2209  } else {
2210    &B_log("DEBUG","$service is off.  Found no processes running on the system.");
2211    # no processes, so service is off
2212    return SECURE_CANT_CHANGE();
2213  }
2214  # Can't determine the state of the service by looking at the processes,
2215  # so return INCONSISTENT().
2216  return INCONSISTENT();
2217}
2218
2219###########################################################################
2220# B_parse_fstab()
2221#
2222# Search the filesystem table for a specific mount point.
2223#
2224# scalar return value:
2225# The line form the table that matched the mount point, or the null string
2226# if no match was found.
2227#
2228# list return value:
2229# A list of parsed values from the line of the table that matched, with
2230# element [3] containing a reference to a hash of the mount options.  The
2231# keys are: acl, dev, exec, rw, suid, sync, or user.  The value of each key
2232# can be either 0 or 1.  To access the hash, use code similar to this:
2233# %HashResult = %{(&B_parse_fstab($MountPoint))[3]};
2234#
2235###########################################################################
2236
2237sub B_parse_fstab($)
2238{
2239    my $name = shift;
2240    my $file = &getGlobal('FILE','fstab');
2241    my ($enable, $disable, $infile);
2242    my @lineopt;
2243    my $retline = "";
2244    my @retlist = ();
2245
2246    unless (open FH, $file) {
2247	&B_log('ERROR',"B_parse_fstab couldn't open fstab file at path $file.\n");
2248	return 0;
2249    }
2250    while (<FH>) {
2251        s/\#.*//;
2252        next unless /\S/;
2253        @retlist = split;
2254        next unless $retlist[1] eq $name;
2255        $retline  .= $_;
2256        if (wantarray) {
2257            my $option = {		# initialize to defaults
2258            acl    =>  0,		# for ext2, etx3, reiserfs
2259            dev    =>  1,
2260            exec   =>  1,
2261            rw     =>  1,
2262            suid   =>  1,
2263            sync   =>  0,
2264            user   =>  0,
2265            };
2266
2267            my @lineopt = split(',',$retlist[3]);
2268            foreach my $entry (@lineopt) {
2269                if ($entry eq 'acl') {
2270                    $option->{'acl'} = 1;
2271                }
2272                elsif ($entry eq 'nodev') {
2273                    $option->{'dev'} = 0;
2274                }
2275                elsif ($entry eq 'noexec') {
2276                    $option->{'exec'} = 0;
2277                }
2278                elsif ($entry eq 'ro') {
2279                    $option->{'rw'} = 0;
2280                }
2281                elsif ($entry eq 'nosuid') {
2282                    $option->{'suid'} = 0;
2283                }
2284                elsif ($entry eq 'sync') {
2285                    $option->{'sync'} = 1;
2286                }
2287                elsif ($entry eq 'user') {
2288                    $option->{'user'} = 1;
2289                }
2290            }
2291            $retlist[3]= $option;
2292        }
2293        last;
2294    }
2295
2296    if (wantarray)
2297    {
2298        return @retlist;
2299    }
2300    else
2301    {
2302        return $retline;
2303    }
2304
2305}
2306
2307
2308###########################################################################
2309# B_parse_mtab()
2310#
2311# This routine returns a hash of devices and their mount points from mtab,
2312# simply so you can get a list of mounted filesystems.
2313#
2314###########################################################################
2315
2316sub B_parse_mtab
2317{
2318    my $mountpoints;
2319    open(MTAB,&getGlobal('FILE','mtab'));
2320    while(my $mtab_line = <MTAB>) {
2321        #test if it's a device
2322        if ($mtab_line =~ /^\//)
2323        {
2324           #parse out device and mount point
2325           $mtab_line =~ /^(\S+)\s+(\S+)/;
2326           $mountpoints->{$1} = $2;
2327        }
2328     }
2329     return $mountpoints;
2330}
2331
2332
2333###########################################################################
2334# B_is_rpm_up_to_date()
2335#
2336#
2337###########################################################################
2338
2339sub B_is_rpm_up_to_date(@)
2340{
2341    my($nameB,$verB,$relB,$epochB) = @_;
2342    my $installedpkg = $nameB;
2343
2344    if ($epochB =~ /(none)/) {
2345	$epochB = 0;
2346    }
2347
2348    my $rpmA   = `rpm -q --qf '%{VERSION}-%{RELEASE}-%{EPOCH}\n' $installedpkg`;
2349    my $nameA  = $nameB;
2350    my ($verA,$relA,$epochA);
2351
2352    my $retval;
2353
2354    # First, if the RPM isn't installed, let's handle that.
2355    if ($rpmA =~ /is not installed/) {
2356	$retval = -1;
2357	return $retval;
2358    }
2359    else {
2360	# Next, let's try to parse the EVR information without as few
2361	# calls as possible to rpm.
2362	if ($rpmA =~ /([^-]+)-([^-]+)-([^-]+)$/) {
2363	    $verA = $1;
2364	    $relA = $2;
2365	    $epochA = $3;
2366	}
2367	else {
2368	    $nameA  = `rpm -q --qf '%{NAME}' $installedpkg`;
2369	    $verA  = `rpm -q --qf '%{VERSION}' $installedpkg`;
2370	    $relA  = `rpm -q --qf '%{RELEASE}' $installedpkg`;
2371	    $epochA  = `rpm -q --qf '%{EPOCH}' $installedpkg`;
2372	}
2373    }
2374
2375    # Parse "none" as 0.
2376    if ($epochA =~ /(none)/) {
2377	$epochA = 0;
2378    }
2379
2380    # Handle the case where only one of them is zero.
2381    if ($epochA == 0 xor $epochB == 0)
2382    {
2383	if ($epochA != 0)
2384	{
2385	    $retval = 1;
2386	}
2387	else
2388	{
2389	    $retval = 0;
2390	}
2391    }
2392    else
2393    {
2394	# ...otherwise they are either both 0 or both non-zero and
2395	# so the situation isn't trivial.
2396
2397	# Check epoch first - highest epoch wins.
2398	my $rpmcmp = &cmp_vers_part($epochA, $epochB);
2399	#print "epoch rpmcmp is $rpmcmp\n";
2400	if ($rpmcmp > 0)
2401	{
2402	    $retval = 1;
2403	}
2404	elsif ($rpmcmp < 0)
2405	{
2406	    $retval = 0;
2407	}
2408	else
2409	{
2410	    # Epochs were the same.  Check Version now.
2411	    $rpmcmp = &cmp_vers_part($verA, $verB);
2412	    #print "epoch rpmcmp is $rpmcmp\n";
2413	    if ($rpmcmp > 0)
2414	    {
2415		$retval = 1;
2416	    }
2417	    elsif ($rpmcmp < 0)
2418	    {
2419		$retval = 0;
2420	    }
2421	    else
2422	    {
2423		# Versions were the same.  Check Release now.
2424		my $rpmcmp = &cmp_vers_part($relA, $relB);
2425		#print "epoch rpmcmp is $rpmcmp\n";
2426		if ($rpmcmp >= 0)
2427		{
2428		    $retval = 1;
2429		}
2430		elsif ($rpmcmp < 0)
2431		{
2432		    $retval = 0;
2433		}
2434	    }
2435	}
2436    }
2437    return $retval;
2438}
2439
2440#################################################
2441#  Helper function for B_is_rpm_up_to_date()
2442#################################################
2443
2444#This cmp_vers_part function taken from Kirk Bauer's Autorpm.
2445# This version comparison code was sent in by Robert Mitchell and, although
2446# not yet perfect, is better than the original one I had. He took the code
2447# from freshrpms and did some mods to it. Further mods by Simon Liddington
2448# <sjl96v@ecs.soton.ac.uk>.
2449#
2450# Splits string into minors on . and change from numeric to non-numeric
2451# characters. Minors are compared from the beginning of the string. If the
2452# minors are both numeric then they are numerically compared. If both minors
2453# are non-numeric and a single character they are alphabetically compared, if
2454# they are not a single character they are checked to be the same if the are not
2455# the result is unknown (currently we say the first is newer so that we have
2456# a choice to upgrade). If one minor is numeric and one non-numeric then the
2457# numeric one is newer as it has a longer version string.
2458# We also assume that (for example) .15 is equivalent to 0.15
2459
2460sub cmp_vers_part($$) {
2461   my($va, $vb) = @_;
2462   my(@va_dots, @vb_dots);
2463   my($a, $b);
2464   my($i);
2465
2466   if ($vb !~ /^pre/ and $va =~ s/^pre(\d+.*)$/$1/) {
2467      if ($va eq $vb) { return -1; }
2468   } elsif ($va !~ /^pre/ and $vb =~ s/^pre(\d+.*)$/$1/) {
2469      if ($va eq $vb) { return 1; }
2470   }
2471
2472   @va_dots = split(/\./, $va);
2473   @vb_dots = split(/\./, $vb);
2474
2475   $a = shift(@va_dots);
2476   $b = shift(@vb_dots);
2477   # We also assume that (for example) .15 is equivalent to 0.15
2478   if ($a eq '' && $va ne '') { $a = "0"; }
2479   if ($b eq '' && $vb ne '') { $b = "0"; }
2480   while ((defined($a) && $a ne '') || (defined($b) && $b ne '')) {
2481      # compare each minor from left to right
2482      if ((not defined($a)) || ($a eq '')) { return -1; } # the longer version is newer
2483      if ((not defined($b)) || ($b eq '')) { return  1; }
2484      if ($a =~ /^\d+$/ && $b =~ /^\d+$/) {
2485         # I have changed this so that when the two strings are numeric, but one or both
2486         # of them start with a 0, then do a string compare - Kirk Bauer - 5/28/99
2487         if ($a =~ /^0/ or $b =~ /^0/) {
2488            # We better string-compare so that netscape-4.6 is newer than netscape-4.08
2489            if ($a ne $b) {return ($a cmp $b);}
2490         }
2491         # numeric compare
2492         if ($a != $b) { return $a <=> $b; }
2493      } elsif ($a =~ /^\D+$/ && $b =~ /^\D+$/) {
2494         # string compare
2495         if (length($a) == 1 && length($b) == 1) {
2496            # only minors with one letter seem to be useful for versioning
2497            if ($a ne $b) { return $a cmp $b; }
2498         } elsif (($a cmp $b) != 0) {
2499            # otherwise we should at least check they are the same and if not say unknown
2500            # say newer for now so at least we get choice whether to upgrade or not
2501            return -1;
2502         }
2503      } elsif ( ($a =~ /^\D+$/ && $b =~ /^\d+$/) || ($a =~ /^\d+$/ && $b =~ /^\D+$/) ) {
2504         # if we get a number in one and a word in another the one with a number
2505         # has a longer version string
2506         if ($a =~ /^\d+$/) { return 1; }
2507         if ($b =~ /^\d+$/) { return -1; }
2508      } else {
2509         # minor needs splitting
2510         $a =~ /\d+/ || $a =~ /\D+/;
2511         # split the $a minor into numbers and non-numbers
2512         my @va_bits = ($`, $&, $');
2513         $b =~ /\d+/ || $b =~ /\D+/;
2514         # split the $b minor into numbers and non-numbers
2515         my @vb_bits = ($`, $&, $');
2516         for ( my $j=2; $j >= 0; $j--) {
2517            if ($va_bits[$j] ne '') { unshift(@va_dots,$va_bits[$j]); }
2518            if ($vb_bits[$j] ne '') { unshift(@vb_dots,$vb_bits[$j]); }
2519         }
2520      }
2521      $a = shift(@va_dots);
2522      $b = shift(@vb_dots);
2523   }
2524   return 0;
2525}
2526
25271;
2528
2529