#!/usr/bin/perl
# For the "what" command
# "@(#) Bastille version: 2.1.0"
$VERSION="2.1.0";

# Copyright (C) 2000 Jay Beale
# Changes to support Tk interface copyright (C) 2000 by Paul L. Allen
# Further modifications are copyright (C) 2001-2003 Hewlett Packard Company
# Licensed under the GNU General Public License

#
# 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.
#
#  ReadConfig is used to populate answers into the GUI/TUI.
#  if there is an existing config file 
#
#  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
#
# Further modifications are copyright (C) 2001-2003 Hewlett Packard Company

##########################################
## 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 all "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");
}
push (@INC,"/usr/lib/perl5/site_perl/5.8.0/i586-linux-thread-multi");

use Getopt::Long;
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
#
# Note: the API module no longer takes any action other than sanitizing
# the environment.  ConfigureForDistro happens after we read in the
# arguments
require Bastille::API;
import Bastille::API;

# 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.
#
# Process command-line arguments...
my $nodisclaim = 0;
my $verbose = 0;
my $force = 0;
my $log_only = 0;
my $debug = 0;
my $tk_interface = 0;
my $curses_interface = 0;
my $os_version = undef;

if( Getopt::Long::GetOptions( "n"     => \$nodisclaim,
                              "v"     => \$verbose,
                              "x"     => \$tk_interface,
                              "c"     => \$curses_interface,
                              "force" => \$force,
			      "log"   => \$log_only, 
			      "os:s"   => \$os_version, 
			      "debug" => \$debug) ) {
    $error = 0; # no parse error

} else {
    $error = 1; # parse error
}

my $err ="ERROR:  ";
my $spc ="        ";


if( defined $os_version and $os_version eq "") {
    # print out a pretty error message that tells the user to 
    # use an OS which bastille supports
    my @supported_OS = @{&get_supported_OS_list};
    print STDERR "$err '' is not a supported operating system.\n";
    print STDERR "$spc Valid operating system versions are as follows:\n";
    my $os_number = 1;
    print STDERR "$spc ";
    foreach my $os (@supported_OS) {
	print STDERR "'$os' ";
	if ($os_number == 5){
	    print STDERR "\n$spc ";
	    $os_number = 1;
	}
	else {
	    $os_number++;
	}
    }

    print "\n";

    &showUsage();
}
elsif (defined $os_version ) {
    $GLOBAL_OS=$os_version;
    $UseRequiresRules = 'N';
}

&setOptions($debug,$log_only,$verbose);
&ConfigureForDistro;

# ensuring mutually exclusive options are exclusive
if(($tk_interface and $curses_interface) or $error) {
    &showUsage();
}

# setting Interface to use Tk
if($tk_interface) {
    $Interface = "Tk";

    if ( $ENV{DISPLAY} eq '' ) {
	print "\n" . 
	    "ERROR:   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();
    }

} # setting interface to use Curses
elsif($curses_interface) {
    $Interface = "Curses";
} # setting interface to use unknown
else {
    $Interface = '';
    # if no DISPLAY variable is set in the environment hash then 
    if($ENV{DISPLAY} eq '') {
	# try to use the curses interface
	print "\nNOTE:    \$DISPLAY not set.  Attempting Curses interface.\n\n";
	$Interface = "Curses";
    }
    else { # if a DISPLAY variable is set then 
	# try to use the Tk interface
	print"\nNOTE:    Valid display found; defaulting to Tk (X) interface.\n";
	$Interface = "Tk";
    }

}

print "NOTE:    Using $Interface user interface module.\n";
if ( $UseRequiresRules eq 'Y' ) {
	print "NOTE:    Only displaying questions relevant to the current configuration.\n";
} else {
	print "NOTE:    Displaying all questions relevant to " . &GetDistro . 
            "\n         regardless of this system's specific settings.\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;
      };    
   };
};

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

&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.  
#

if (&GetDistro =~ "^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" .
    "       If at any time you would like to save your configuration changes\n" .
    "       goto the 'End Screen' module and answer it 'yes'.  You will then\n" .
    "       be asked if you would like to save the changes made.\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.  You will be asked if you would like to save\n" .
				     "the configuration changes you have made, which will not affect your" . 
				     "system in any way except to write out the Bastille config file.\n" . 
				     "You will then be asked if you would like to apply the configuration to\n" .
				     "your system.  At no point will you be forced to make the configuration\n" .
				     "apply 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 {
        &ErrorLog("ERROR:   Invalid interface $Interface.\n");
	exit 1;
}

#Load in the questions database
$Question{"Title_Screen"}{'proper_parent'}="Title_Screen";
$Question{"Title_Screen"}{'yes_child'}="End_Screen";

$Question{"End_Screen"}{'proper_parent'}="Title_Screen";
$Question{"End_Screen"}{'yes_child'}="End_Screen";

if ( $UseRequiresRules eq 'Y' ) {
  print "NOTE:    Bastille is scanning the system configuration...\n\n";
}

($Number_Modules,$first_question)=&Load_Questions($UseRequiresRules);

# Add first and last questions
&Load_Questions_Wrapper($first_question);

$Question{"Title_Screen"}{'module'} = "Title Screen";
$Question{"End_Screen"}{'module'} = "End";
# Check for an existing config file and populate the default answers from it
if (&ReadConfig) {
   print "NOTE:    Existing config file found.  Populating answers...\n";
} else {
   print "NOTE:    Could not open config file ", 
                   &getGlobal('BFILE',"config"),", defaults used.\n";
}

&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.
#    
##############################################################################

	mkdir $GLOBAL_BDIR{"config"},0700;
        unless (open FORMATTED_CONFIG, "> $GLOBAL_BFILE{'config'}") {
           &ErrorLog("ERROR:   Couldn't write config file: " . $GLOBAL_BFILE{'config'} . "\n");
           exit(1);
        }

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

    while ($index ne "End_Screen") {

	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"} = "";
	    }

	    # 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...
	    
	    $Question{$index}{"confirm_text"} =~ s/\\n/\n/g;

	    # 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 {
                &ErrorLog("ERROR:  Invalid answer on y/n question $index -- answer " . $Question{$index}{"answer"} . "not y/n !\n");
	    }
	}
    }
    close FORMATTED_CONFIG;

}


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

sub showUsage {
    print &getGlobal('ERROR',"usage");
    exit 1;
}
