#!/usr/bin/perl
# For the "what" command
# "@(#) Bastille version: 2.0.2.%06"
$VERSION="2.0.2";
#
# These new modifications were made in Bastille 1.2.0, starting in February 
# of 2001, by Jay Beale.  Tk-specific code is maintained solely by Paul,
# while the remainder is generally maintained by Jay and worked on by
# Jay, Pete Watkins and Paul.
#
#
# The new modifications implement "requirements" so that non-applicable
# questions aren't asked.  Thanks to Lee Brotzman here.  
#
# When we want to skip a question, if YES_CHILD is equal to NO_CHILD, we
# simply go to YES_CHILD.  When they are not equal, we skip to SKIP_CHILD.
#
# The new tags are in implemented in Questions.txt like this:
#
#	REQUIRE_IS_SUID     - question isn't asked if file isn't Set-UID
#	REQUIRE_FILE_EXISTS - question isn't asked if file doesn't exists
#	REQUIRE_DISTRO	    - question isn't asked if distro isn't foo
#
# Each of these are read into the corresponding 
# $Question{question_name}{tag} entry.
#
# The argument list for the first two are lists of files, rather hash entries 
# in $GLOBAL_FILE{}.  The argument for the last is a list of  distributions
# that this question applies to, in the same format as found in API.pm.
#
#
########################################################################
#
# This version of the Bastille 1.1.1 InteractiveBastille.pl script was
# modified by Paul Allen in early December, 2000.  The modifications
# allow the "plugging in" of user interface modules.  
#
# Changes in this file:
#
#  The original Curses interface code has been moved into the module 
#  Bastille_Curses.pm.  If InteractiveBastille.pl is invoked without
#  arguments, the Curses module is used.
#
#  A new module Bastille_Tk.pm has been created to implement a perl/Tk
#  user interface.  If InteractiveBastille.pl is invoked with the
#  switch "-x", the Tk module is used.  For consistency, a "-c"
#  switch selects the Curses module. The script defaults to using the
#  Tk interface if you seem to be running X (if $DISPLAY is set)
#  and have Perl/Tk installed.
#
#  Most of the main-line logic of this file has moved out into the
#  do_Bastille function provided by the user interface modules.  The
#  main-line logic now simply loads the appropriate interface module
#  and calls its do_Bastille function.  The remainder of this file
#  consists of functions not related to the user interface.
#
#  There is now a Populate_Answers function here that can be used to
#  check for an existing config file and use it to fill in the default
#  answers in the Questions database.  It is only called by the Tk 
#  interface code at this point, although the Curses code could call
#  it.  The Curses code was left pristine so that people choosing to
#  use that interface would not be surprised by new behavior.
#
#  I'm not sure the "special test mode" code works any more.
#
#  I hope I haven't screwed up indentation, but I may have.  Sorry.
#
# My changes are copyrighted (C) 2000 by Paul L. Allen and licensed under
# the GNU General Public License.
#
# Most of the real code here retains Jay's original copyright:
#  
# Copyright (C) 2000 Jay Beale
# Licensed under the GNU General Public License

##########################################
## Bastille Linux Text User Interface   ##
##########################################

#
# TO DO:
#
#  1) Use the newly-release Curses::Widgets 1.1 to get better text windows
#  2) Finish documenting program
#  3) Rewrite &Ask subroutine: either use Curses::Forms or just make cleaner

##########################################################################
# This is the Text User Interface for Bastille Linux Architecture 1,     #
# which is used in Bastille Linux 1.0-1.2.                               #
#                                                                        #
# Bastille Linux's first architecture was primarily streams-based, as it #
# was intended to be a fully interactive Perl script.  Since then, I've  #
# understood that this original interface could be drastically improved, #
# and tried to create a text-based interface which is motivated by RH's  #
# setup utility.                                                         #
#                                                                        #
#                              DATA STRUCTURE                            #
#                                                                        #
# The data structure used to hold the questions is a hash of records,    #
# which are implemented as a secondary hash.  The important part to      #
# understand well is the parent-child relationships between records.     #
# Each question has one or two children.  If it is a Yes/No question and #
# there are questions that should only be asked if the answer was, say,  #
# Yes, then the yes_child will differ from the no_child.  Each question  #
# has a proper_parent item, though.  The easiest way to make sense of    #
# this is by example:                                                    #
#                                                                        #
#  Q1:  Do you wanna answer IPCHAINS questions?                          #
#            yes_child= Q1a   no_child= Q2                               #
#                                                                        #
#     Q1a: ipchains question...  yes_child=no_child=Q1b   parent= Q1     #
#     Q1b: ipchains question 2   yes_child=no_child=Q2    parent= Q1a    #
#                                                                        #
#  Q2:  Wanna talk about sendmail?  proper_parent=Q1                     #
#                                                                        #
#                                                                        #
# OK, so the data structure goes like this...  Each question is indexed  #
# in the outer hash by a short name.  Remember, we're dealing with a     #
# hash of references to hashes, which behaves (thanks Larry) like a      #
# hash of hashes.  If you don't know what a reference is, don't worry    #
# as Perl kinda does something pretty intuitive here (thanks Larry).     #
# Within each question are the following fields:                         #
#                                                                        #
#                              RECORD FIELDS                             #
#                                                                        #
# question        --  question text (ex: wanna do this?)                 #
# short_exp       --  explanation text, at lower detail.                 #
# long_exp        --  explanation text, at high detail. (optional)       #
# default_ans     --  a default answer for the question (optional)       #
# toggle_yn       --  1 if a Yes/No question, 0 if fill-in-the-blank     #
# yes_epilogue    --  epilogue to display after question is asked (opt)  #
# no_epilogue     --  same as above, but displayed if this is y/n        #
#                     question and selected answer was no (optional)     #
# yes_child       --  index of next question, if this is not a Y/N ques. #
#                     or if this is a Y/N ques. and answer=Y             #
# no_child        --  index of next question, if Y/N ques. and answer=N  #
# proper_parent   --  index of "proper" previous question.  This is      #
#                     explained above, but, to recap: this is used so    #
#                     1+-> 1a -> 1b -> 1c -> 2, while 2's parent is 1    # 
#                      +-> 2                                             #
#                                                                        #
#                     where threading is due to 1 being a Y/N question   #
#                                                                        #
# confirm_text    --  this is text added to the end of an answer to      #
#                     maintain compatibility with the 1.0-1.2 ipchains   #
#                     script, which requires that most (but not all) of  #
#                     the user's answers be confirmed with a Y\n         #
#                                                                        #
# answer          --  answer chosen -- this is initially populated by    #
#                     the default_answer field's contents                #
# module          --  associated Bastille modules (automatically set     #
#                         by Load_Questions)                              #
#                                                                        #
#                                                                        #
#                              BASIC OPERATION                           #
#                                                                        #
# The TUI is basically composed of three big subroutines:                #
#                                                                        #
#   Load_Questions -- reads in the question data structure from a flat   #
#                     text file (Questions.txt) and does some sanity     #
#                     checking.                                          #
#                                                                        #
#   Ask            -- goes through all the business of asking a question #
#                     including: calling subs to draw the screen,        #
#                     scroll the explanation, and take input.  It has    #
#                     logic to decide what question should be asked next #
#                     based on answers and returns the index of such     #
#                                                                        #
#   Output_Config_Files -- outputs the configuration file for the        #
#                          Bastille script:   config                     #
#                                                                        #
#                                                                        #
#  For more detail, read the Source, Luke.                               #
#                                            - jjb                       #
#                                                                        #
##########################################################################


# By default, we respect the "requires" tags in Questions.txt and test only is not default
$UseRequiresRules = 'Y';
$TEST_ONLY = 0;

# users on x86 Linux who install the Mandrake perl-Curses
# package will have Curses.pm in a non-standard directory;
# this adds that dir to @INC so we can use Curses
if ( -d "/usr/lib/perl5/site_perl/5.6.0/i386-linux" ) {
	push (@INC,"/usr/lib/perl5/site_perl/5.6.0/i386-linux");
}

#
# Remove legacy /usr/lib/perl5/site_perl/Bastille_<ui>.pm files
# These files have moved and the old versions are causing lock-ups
#
if ( -e '/etc/redhat-release') {
    if ( -f '/usr/lib/perl5/site_perl/Bastille_Tk.pm') {
	unlink ('/usr/lib/perl5/site_perl/Bastille_Tk.pm');
    }
    if ( -f '/usr/lib/perl5/site_perl/Bastille_Curses.pm') {
	unlink ('/usr/lib/perl5/site_perl/Bastille_Curses.pm');
    }
}

# Look for -x or -c interface switches, and shift them off.
# If more than one is present, the last one wins.
# This would be a good place to check for a --keep-config switch.
#
if ( $ENV{DISPLAY} ne '' ) {
    $Interface = "Tk";
} else {
    print "\nNOTE:  \$DISPLAY not set.  Attempting Curses interface.\n\n";
    $Interface = "Curses";
}

while ($ARGV[0] =~ /-.*/) {
    if ($ARGV[0] eq "-x") {
	    $Interface = "Tk";
	} elsif ($ARGV[0] eq "-n") {
	    $nodisclaim="1";
	} elsif ($ARGV[0] eq "-c") {
	    $Interface = "Curses";
	} elsif ($ARGV[0] eq "-T") { # Moved '-T' here so this
                                     # opton no longer makes program exit
	    $TEST_ONLY = 1;
	} elsif ($ARGV[0] eq "--norequires") {
	$UseRequiresRules = 'N';
    } else {
	&showUsage();
    }
    shift @ARGV;
}
if ( $Interface eq 'Tk' && $ENV{DISPLAY} eq '' ) {
    print "\nERROR: Cannot use X interface because \$DISPLAY not set.\n".
	  "       Please refer to usage message below for other ways to \n".
	  "       run Bastille:\n\n";
    &showUsage();
}
print "Using $Interface user interface module.\n";
if ( $UseRequiresRules eq 'Y' ) {
	print "Only displaying questions relevant to the current configuration.\n";
} else {
	print "Displaying all questions -- some may not be relevant to the current configuration.\n";
}

for my $interface_module ("Curses", "Tk") {
   if ( $Interface eq $interface_module ) {
      eval "use $interface_module";        
      if ($@) {                    
	  print "\nERROR: Could not load the '${interface_module}.pm' interface module.\n" .
	      "       This may be due to an invalid \$DISPLAY setting,\n".
	      "       or the module not being visible to Perl.\n\n";
	  exit 1;
      };    
   };
};

use Text::Wrap;
use lib "/usr/lib";
push (@INC,"/usr/lib/perl5/site_perl/");
push (@INC,"/usr/lib/Bastille");
# make sure we don't look in the current directory for the modules!
$i = 0;
foreach $d ( @INC ) {
	if ( $d eq '.' ) {
		# remove "." from @INC for security reasons
		splice(@INC, $i, 1);
	}
	++$i;
}
# Now that we've cleaned up @INC, let's load the API module
#
require Bastille::API;
import Bastille::API;

# IOLoader contains all validate and Questions functions.
require Bastille::IOLoader;
import Bastille::IOLoader;

# If this is being run in Test mode (first argument is a T), we only
# test the Questions database
if ($ARGV[0] =~ /^\s*T\s*$/) {
    $TEST_ONLY=1;
}

&showDisclaimer($nodisclaim) ;  #Require User to Accept Disclaimer


# Select the user interface module.  These modules define somewhat
# different versions of &do_Bastille and the rest of the logic for
# each interface.  
#
# chdir "/usr/lib/Bastille" || die "ERROR: /usr/lib/Bastille unreachable: Bastille not correctly installed?\n";
# chdir is not a healthy way to code... this can create strange dependencies

if ($GLOBAL_DISTRO =~ "^HP-UX") {
    my $actionlog=&getGlobal('BFILE','action-log');
    my $errorlog=&getGlobal('BFILE','error-log');
    my $config=&getGlobal('BFILE','config');
                     ################################################################################     
    $SUPPORT_INFO = "       Current support information for HP-UX Bastille is provided on the\n" .
                    "       HP-UX Bastille product page at http://software.hp.com\n\n" .

                    "       HP-UX Bastille has the potential to make changes which will affect\n" .
                    "       the functionality of other software.  If you experience problems after\n" .
                    "       applying Bastille changes to your machine, be sure to inform anyone\n" .
                    "       you ask for help that you have run Bastille on this machine.\n\n" .

                    "       Helpful diagnostic tips:\n" .
                    "       - 'bastille -r' will revert your system to a pre-Bastille state.\n" .
                    "         so you can better track down the cause of the problem\n" .
                    "       - A list of all actions Bastille has taken is located in.\n" .
                    "           $actionlog\n" .
                    "       - If you suspect Bastille, the following files will be\n" .
                    "         helpful to others in diagnosing your problem:\n" .
                    "           $actionlog\n" .
                    "           $errorlog\n" .
                    "           $config\n\n" .

                    "      Available resources include:\n" .
                    "      - the itrc hp-ux security forum at http://www.itrc.hp.com\n" .
                    "      - the Bastille discussion group at\n" .
                    "           bastille-linux-discuss\@lists.sourceforge.net.\n\n";

}
else {
    $SUPPORT_INFO = "       Please address bug reports and suggestions to jay\@bastille-linux.org\n" .
	            "       Bugs in the Tk user interface are the fault of allenp\@nwlink.com.\n";
}

if ($Interface eq "Tk") {
    $InterfaceDescription = 
    "                               (Tk User Interface)\n" .
    " \n" .
    "                                   v$VERSION\n" .
    " \n" .
    " \n".
    "       Please answer all the questions to build a more secure system.\n" .
    " \n" .
    "       The OK and Back buttons navigate forward and backward in the \n" .
    "       questions database.  Changes made in the Answer field are *only*\n" .
    "       saved when you push the Ok button!  The \"modules\" in the\n" .
    "       questions database are listed to the left.  You can jump to\n" .
    "       the start of any module simply by clicking on its name.\n" .
    "\n" .
    "       Some questions have two levels of explanatory text, which you\n" .
    "       can adjust with the Explain Less/More button. \n" .
    "\n" .
    $SUPPORT_INFO .
    "\n";

    $InterfaceEndScreenDescription = "Completing the configuration portion of Bastille will not apply\n" .
	                             "changes to your system.\n\n" . 
				     "If you should choose to apply the configuration to your system then\n" .
				     "Bastille will make changes to your system and create a TODO list in\n" . 
				     &getGlobal('BFILE',"TODO") . " of remaining steps which you should do " . 
				     "to secure your system, based on your answers to the questions.\n" . 
				     "After you have run the Bastille backend, you should review the list\n" . 
				     "and make the necessary changes to your system.  You should also\n" . 
				     "look at the Error log created in " . &getGlobal('BFILE',"error-log") . "\n" . 
				     "to make sure that Bastille did not fail unexpectedly in any of its tasks.\n\n" .
	                             "Answer NO if you want to go back and make changes to the configuration!\n";
    $InterfaceEndScreenQuestion = "Are you finished making changes to your Bastille configuration?";
    $InterfaceEndScreenNoEpilogue = "Please use Back/OK buttons to move among the questions you wish to\n" . 
	                            "change.( Remember, in order to accept an answer change you must use\n" . 
				    "the OK button.)\n\n" . 
				    "Choose YES on this question when you are finished, in order to save\n" . 
				    "your choices to a configuration file.\n";



	require Bastille_Tk;
} elsif ($Interface eq "Curses") {

    $InterfaceDescription = 
    "                            (Text User Interface)                  \n" .
    "\n" .
    "				   v$VERSION\n" .
    "\n" .
    "\n" .
    "       Please answer all the questions to build a more secure system.\n" .
    "       You can use the TAB key to switch among major screen functions,\n" .
    "       like each question's explanation area, input area and button area.\n" .
    "       Within each of the three major areas, use the arrow keys to scroll\n" .
    "       text or switch buttons.\n" .
    "\n" .
    "       Please address bug reports and suggestions to jay\@bastille-linux.org\n" .
    "\n";

    $InterfaceEndScreenDescription = "We will now implement the choices you have made here.\n\n" . 
	"Answer NO if you want to go back and make changes!\n";
    $InterfaceEndScreenQuestion = "Are you finished answering the questions, i.e. may we make the changes?";
    $InterfaceEndScreenNoEpilogue = "Please use Back/Next buttons to move among the questions you wish to\nchange.\n\nChoose YES on this question later to implement your choices.\n";
    require Bastille_Curses;
} else {
	print "Sorry!  Interface $Interface has not been written yet!\n";
	exit;
}


&do_Bastille;


sub Load_Questions_Wrapper($) {
# sub Load_Questions creates a data structure called %%Questions
 
    my $first_question = $_[0];
    my $last_question;
    # Set up Title Screen
    $Question{"Title_Screen"}{"question"}="";

    $Question{"Title_Screen"}{"short_exp"}=$InterfaceDescription;

    $Question{"Title_Screen"}{"proper_parent"}="Title_Screen";
    # Pay particularly good attention here! Title_Screen must have the index
    # for the first question...  Load this from the file later!

    $Question{"Title_Screen"}{"yes_child"}="$first_question";
    $Question{$first_question}{"proper_parent"} = "Title_Screen";
    # Last data file question lookup;
    
    for $key (keys %Question){
	if($Question{$key}{"yes_child"} =~ "End_Screen" &&
	   $Question{$key}{"no_child"} =~  "End_Screen" ) {
	    $last_question = $key;
	}
    }

    if ($TEST_ONLY) {
	exit;
    }      

    # Set up last question: "Can we run now?" screen -- careful about Parent!!!
    $Question{"End_Screen"}{"proper_parent"}="$last_question";
    $Question{"End_Screen"}{"short_exp"}= $InterfaceEndScreenDescription;
	$Question{"End_Screen"}{"question"}= $InterfaceEndScreenQuestion;
    $Question{"End_Screen"}{"toggle_yn"}=1;
    $Question{"End_Screen"}{"yes_child"}="RUN_SCRIPT";
    $Question{"End_Screen"}{"no_child"}="End_Screen";
    $Question{"End_Screen"}{"no_epilogue"}= $InterfaceEndScreenNoEpilogue;

    
    return 1;
}


sub Output_Config_Files {

##############################################################################
# %Output_Answers will:
#
# 1) write out a file, which can be read by our parser, containing the full
#    contents of the questions data structure
#
# More information coming here, from design doc...
#
# WEIRD CASES:
#
#  1 If a record has no short_exp explanation, none is shown.  This is bad?
#  2 If a record has no question, no question is asked, but the explanation
#    is still displayed.  If this is the case, the default_answer is still
#    entered into the configuration, if it exists.
#  3 If a question has no answer, it doesn't create any blank lines or such
#    in the output file, as it will be skipped in &Output_Config_Files.  For
#    this reason, &Prompt_Input, which is only called when the record contains
#    a user-answerable question, pads a space to the end of any 0-length input.
#    Not to worry: Output_Config_Files replaces said space with 0-length input
#    NOTE: we couldn't just only print the answer field when a real question
#          existed -- this would improperly handle case 2.
#    
##############################################################################

#    open RAW_CONFIG,"> tui-generated-raw-config";
	mkdir $GLOBAL_BDIR{"config"},0700;
	open FORMATTED_CONFIG, "> $GLOBAL_BFILE{'config'}" or die "Couldn't write config file: " . $GLOBAL_BFILE{'config'};
#    open FORMATTED_CONFIG,"> /etc/Bastille/config" or die "Couldn't write config file /etc/Bastille/config!"; # removed hard coded path

    my $index="Title_Screen";
    my $module="";

    while ($index ne "End_Screen") {

	# We used to generate the module headers for AppConfig
	#if ($Question{$index}{"module"} ne $module ) {
	#    print FORMATTED_CONFIG "[ $module ]\n";
	#}

	if ($Question{$index}{"question"} ne "" && exists $Question{$index}{"answer"}) {

	    # If the answer is just a space (the way the &Prompt_Input sub
	    # designates a blank line, strip it.
	    
	    if ($Question{$index}{"answer"} =~ /^\s+$/) {
		$Question{$index}{"answer"} = "";
	    }

	    # Print the raw input for the Bastille 1.0 script series 
#	    print RAW_CONFIG "### Question index $index\n";
	    if ($Question{$index}{"question"}) {
#		print RAW_CONFIG "### Question text  ";
#		print RAW_CONFIG $Question{$index}{"question"} . "\n";
	    }

	    # The "confirm_text" data element fixes a problem caused by the
	    # different way that IPChains handles input.  In the IPCHAINS 
	    # module, there are two critical items:
	    # 
            #  1) if the user enters an empty string (by hitting enter),
	    #     IPCHAINS replaces this with the default answer.  A true
	    #     "empty" result reqires that the user enter 
	    #     something (ie, a space)
	    #  2) Most (not _all_) of the questions require a Y\n confirmation
	    #
	    # By including confirmation text, we can kill the first difference
	    # by having the confirm text include a " ".  Should the question
	    # also need a confirming Y, this can be included by making the
	    # confirmation text " \nY".
	    # Note: to make this work, we have to fix \n's...
	    
#	    print RAW_CONFIG $Question{$index}{"answer"};
	    $Question{$index}{"confirm_text"} =~ s/\\n/\n/g;
#	    print RAW_CONFIG $Question{$index}{"confirm_text"};
#	    print RAW_CONFIG "\n";

	    # Print formatted file too...
	    my $module = $Question{$index}{"module"};
	    if ($module =~ /^([^.]+).pm/) {
		$module =$1;
	    }
	    print FORMATTED_CONFIG "# Q: " . $Question{$index}{"question"} . "\n";
	    print FORMATTED_CONFIG "$module.";
	    print FORMATTED_CONFIG $index . "=\"";
	    print FORMATTED_CONFIG $Question{$index}{"answer"} . "\"\n";
	    
	}
	if ($Question{$index}{"toggle_yn"} == 0) {
	    $index=$Question{$index}{"yes_child"};
	}
	else {
	    if ($Question{$index}{"answer"} =~ /^\s*Y/i) {
		$index=$Question{$index}{"yes_child"};
	    }
	    elsif ($Question{$index}{"answer"} =~ /^\s*N/i) {
		$index=$Question{$index}{"no_child"};
	    }
	    else {
#		print RAW_CONFIG "Major error on question $index!\n";
		print "Major error on y/n question $index -- answer not y/n !\n";
	    }
	}
    }
    close FORMATTED_CONFIG;
#    close RAW_CONFIG;

}



sub Run_Bastille_with_Config {
    my $bastilleBackend = &getGlobal('BFILE',"BastilleBackEnd");
    system("$bastilleBackend");
}

# A hacked copy of ReadConfig from Bastille/API.pm to populate the
# questions database with  answers from an existing config file.
#
# If the structure of the Questions.txt file changes in a way that
# requires a change in API.pm, this function will probably need
# changing as well.  It might have been better to put this change
# in API.pm, but I wanted to keep the number of changed files to
# a minimum.  (PLA)
#
sub Populate_Answers {

    if (open CONFIG, $GLOBAL_BFILE{"config"}) {
	print "Existing config file found.  Populating answers...\n";
	while (my $line = <CONFIG>) {
    
#	    Skip commented lines...
#
	    unless ($line =~ /^\s*#/) {
		if ($line =~ /^\s*(\w+).(\w+)\s*=\s*\"(.*)\"/ ) {
		    if ($3 eq "") {

#			Null answers need a space in order to show up 
#			in output file.
#
			$Question{$2}{'answer'} = "";
			# generating GLOBAL_CONFIG so we can look for un-answer questions.
			$GLOBAL_CONFIG{$1}{$2} = "";
		    } else {
			$Question{$2}{'answer'} = $3;
			# generating GLOBAL_CONFIG so we can look for un-answer questions.
			$GLOBAL_CONFIG{$1}{$2} = $3;
		    }
		}
	    }
	}
	close CONFIG;
    } else {
	print "Could not open config: ", $GLOBAL_BFILE{"config"},", defaults used.\n";
    }
}

sub showUsage {
    print $GLOBAL_ERROR{"usage"};
    exit 1;
}














