# Copyright (C) 2002-2003 Hewlett Packard
# Licensed under the GNU General Public License

package Bastille::IPFilter;
use Bastille::API;

use Exporter;
@ISA = qw ( Exporter );
@EXPORT = qw( defineTests run );

################################################################################

 #####   ######  ###### #  #    #  ######   #####  ######  ####  #####  ####
 #    #  #       #      #  ##   #  #          #    #      #        #   #
 #    #  #####   #####  #  # #  #  #####      #    #####   ####    #    ####
 #    #  #       #      #  #  # #  #          #    #           #   #        #
 #    #  #       #      #  #   ##  #          #    #      #    #   #   #    #
 #####   ######  #      #  #    #  ######     #    ######  ####    #    ####

################################################################################

################################################################################
# The "defineTests" routine is used by both the front and backend to prune
# the questions appropriately.  If a test returns true, that means skip the
# corresponding question. 
################################################################################
sub defineTests {

  # for efficiency and to avoid undefined stuff on Linux
  if (&GetDistro !~ "^HP-UX") {
    return;
  }

  # Get the current ruleset to compare with the proposed ruleset
  my $ipf = &getGlobal('BIN','ipf');

  # only configure ipfilter if it's installed
  if ( -e $ipf ) {
     &B_log("DEBUG","Defining tests for IPFilter module\n");
     &B_log("DEBUG","Defining install_ipfilter test\n");
     # ipfilter is already installed, so don't ask the question
     # (otherwise, test is undefined and question gets asked)
     # This routine is hardcoded so we don't have to pass in $ipfconf
     $Bastille::TestAPI::GLOBAL_TEST{'IPFilter'}{'install_ipfilter'} = 
        sub { return 1; };

     my $ipfconf = &getGlobal('FILE','ipf.conf');
     open IPFCONF, "<$ipfconf" or &B_log("ERROR","Can't open $ipfconf\n");

     @ipfrules=<IPFCONF>;
     chomp @ipfrules;

     &B_log("DEBUG","Comparing ipf.conf header to proposed Bastille header\n");
     my @header =split('\n',&getRulesHeader);
     my $headerlines = $#header+1;

     my $headermatches=0;
     if ("@header" eq "@ipfrules[0..$#header]") {
        &B_log("DEBUG","ipf.conf header matches\n");
        $headermatches=1;
     }

     &B_log("DEBUG","Comparing ipf.conf footer to proposed Bastille footer\n");
     my @footer =split('\n',&getRulesFooter );
     my $footerstart = $#ipfrules-$#footer;

     my $footermatches=0;
     if ("@footer" eq "@ipfrules[$footerstart..$#ipfrules]") {
        &B_log("DEBUG","ipf.conf footer matches\n");
        $footermatches=1;
     }

     &B_log("DEBUG","match results: header=$headermatches, " .
                              " footer=$footermatches\n");

     my %allowRulesHash = %{&getAllowRulesHash};

     # Define the tests for each "block" question.  If these don't exist,
     # the question WILL be asked
     foreach my $protocol (keys %allowRulesHash) {
        # The routine will return true (skip the block_ question) if the 
        # header and footer parts of the ipf.conf file match our
        # expectations AND the "allow" rule is missing from the ipf.conf file 

        # In other words, the question will be asked if there is an allow
        # rule for this specific item OR we're starting from scratch OR
        # header or footer has changed.

        # SCOPE warning:  This only works because $protocol, $ipfconf,
        # and %allowRulesHash are scoped locally with "my".  The anonymous
        # subroutine here is defined with references to those variables,
        # so if they were scoped globally and changed, the new values
        # would be used when the subroutine gets run, rather than their
        # values when they were defined.  $protocol is especially interesting
        # because a reference to this iteration's copy of the value is passed

        $Bastille::TestAPI::GLOBAL_TEST{"IPFilter"}{"block_" . $protocol} = 
          sub { 
             $headermatches and
             $footermatches and
             ( ! &Bastille::TestAPI::B_match_line(
                     "$ipfconf", 
                     "^$allowRulesHash{$protocol}" ));
          };
     }
  } 
  else {  
      # The ipf binary was found which implies that ipfilter is not installed
      # Don't try to configure it.
      $Bastille::TestAPI::GLOBAL_TEST{'IPFilter'}{'configure_ipfilter'} = 
	  sub { return 1;};

      $Bastille::TestAPI::GLOBAL_TEST{'IPFilter'}{'install_ipfilter'} = 
	  sub { 
	      if(&Bastille::TestAPI::B_match_line(&getGlobal('BFILE',"TODO"),"IP Filter Reminder:")){
		  return 1;
	      }
	      else{
		  return 0;
	      }
	  };
  }
}

################################################################################

                            #####   #    #  #    #
                            #    #  #    #  ##   #
                            #    #  #    #  # #  #
                            #####   #    #  #  # #
                            #   #   #    #  #   ##
                            #    #   ####   #    #

################################################################################

################################################################################
# The "run" routine is the backend implementation
#
#   Adds information to the TODO list on how to obtain ipfilter if not installed
#   Minimal configuration of ipfilter if it is installed
################################################################################
sub run {

    # Installation information
    if(&getGlobalConfig("IPFilter","install_ipfilter") eq "Y"){
	&B_log("DEBUG","# sub IPFilter (install reminder)\n");
	my $IPFilter_text =
	    "IPFilter (a host-based firewall) is currently available on a\n" .
            "HP-UX Application Software CD-ROM and at http://software.hp.com.\n" .
            "Install IPFilter from the source of your choice and then re-run\n" .
            "Bastille to help you configure it.\n";
	&B_TODO("\n---------------------------------\nIP Filter Reminder:\n" . 
		"---------------------------------\n" . 
		$IPFilter_text);
    }

   # Basic default-deny firewall configuration 

   if(&getGlobalConfig("IPFilter","configure_ipfilter") eq "Y"){

     my $ipf = &getGlobal('BIN','ipf');

     # However, we only configure ipfilter if it is installed.  The
     # question may have been skipped either because ipfilter isn't
     # installed or because the configuration is already in place.
     if ( -e $ipf ) {
       &B_log("DEBUG","# sub IPFilter (configure)\n");

       &B_load_ipf_rules( &getRulesHeader . 
                          &getCustomRules . 
                          &getAllowRules . 
                          &getRulesFooter );


       my $ipfconf = &getGlobal('FILE','ipf.conf');
       my $IPFilter_text =
           "A firewall generally makes up your first line of defense against\n" .
           "network attacks.  Based on your choices, Bastille has created\n" .
           "a basic firewall configuration.  You may wish to customize this\n" .
           "configuration with your own rules, which can be placed in\n" .
           "   " . &getGlobal('FILE',"customipfrules") . "\n" .
           "If you add custom rules to this file, please rerun bastille\n" .
           "and answer \"Yes\" to apply the new custom rules.  (Bastille\n" .
           "will ask the rest of the firewall configuration questions too.)\n".
           "Then, verify that the file\n" .
           "   $ipfconf\n" .
           "contains a ruleset which will adequately protect your system\n" .
           "against network attacks.  See ipf(5) for more information.\n\n";
       &B_TODO("\n---------------------------------\n" .
                "Custom IP Filter Configuration:\n" . 
		"---------------------------------\n" .
		$IPFilter_text);

     }
   } elsif(&getGlobalConfig("IPFilter","configure_ipfilter") eq "Y"){
       &B_log("ERROR","IPFilter cannot be configured because it isn't installed!\n");
   }
}

################################################################################
#  HELPER ROUTINES - these routines are used by both the run and defineTests
#                    parts of this file.
################################################################################

################################################################################
# The "get" routines simply store and return data to be used by both "run"
# and "defineTests"
################################################################################
sub getAllowRulesHash {
   # These are macros to shorten the code below so I can line the rules up
   # and still keep everything on a relatively small line
   my $PIQP="pass in quick proto";
   my $FA="from any";
   my $TA="to any";
   my $KS="keep state";
   my $KF="keep frags";
   my $FS="flags S";
   my $PE="port =";

   #############################################################################
   my %allowRulesHash = (
    # encrypted, authenticated management protocols
    'SecureShell'    =>"$PIQP tcp $FA            $TA $PE 22     $FS $KS $KF\n",
    'wbem'           =>"$PIQP tcp $FA            $TA $PE wbem-https $FS $KS $KF\n",

    'webadmin'       =>"$PIQP tcp $FA            $TA $PE 1188   $FS $KS $KF\n",
    'webadminautostart'=>"$PIQP tcp $FA          $TA $PE 1110   $FS $KS $KF\n",

    # HP host-based intrusion detection ports
    'hpidsagent'     =>"$PIQP tcp $FA        $TA $PE hpidsagent $FS $KS $KF\n",
    'hpidsadmin'     =>"$PIQP tcp $FA        $TA $PE hpidsadmin $FS $KS $KF\n",

    # There's no question for this one, but it's the right port according
    # to Craig Rantz at Gates.  isee is Instant Support Enterprise Edition
    'isee'           =>"$PIQP tcp $FA        $TA $PE 2367  $FS $KS $KF\n",

    # DNS server protocols
    'DNSquery'       =>"$PIQP udp $FA            $TA $PE domain     $KS\n",

    # For a DNS zone transfer, tcp is used
    'DNSzonetransfer'=>"$PIQP tcp $FA $TA $PE domain $FS $KS $KF\n",


    # clear-text protocols
    'bootp'          =>"$PIQP udp $FA $PE bootpc $TA $PE bootps     $KS\n",
    'tftp'           =>"$PIQP udp $FA            $TA $PE tftp\n",
    'snmpGetSet'     =>"$PIQP udp $FA            $TA $PE snmp       $KS\n",
    'snmpTraps'      =>"$PIQP udp $FA            $TA $PE snmp-trap  $KS\n",

   );

   return \%allowRulesHash;
}

sub getRulesHeader {

  my $header=<<EOF;
# Copyright 2002, Hewlett Packard Company
#
# WARNING: This file was generated automatically and will be replaced
# the next time you run bastille.  DO NOT EDIT IT DIRECTLY!!!
#
#IPFilter configuration file

# block incoming packets with ip options set
block in log quick all with ipopts

EOF

  return $header;
}

sub getRulesFooter {

  my $footer=<<EOF;
#Block any incoming connections which were not explicitly allowed
block in log all
EOF

  return $footer;
}

sub getCustomRules {

  my $customrulesfile=&getGlobal('FILE',"customipfrules");
  if ( -e $customrulesfile) {
    open CUSTOM, "<$customrulesfile" or &B_log("ERROR","Can't open $customrulesfile");
    my @customrules=<CUSTOM>;

    my $customruleswithcomments=<<EOF;

#####################################################################
# The following rules were inserted from the file
# $customrulesfile
# and should be edited there rather than here.  Re-running bastille
# will create a new ipf.conf file including any custom rules from
# that file.
#
#   DO NOT EDIT THIS FILE DIRECTLY!!!
#
#   RULES INSERTED FROM $customrulesfile 
#   ARE BELOW.  THESE MAY BE OVERWRITTEN IF YOU RERUN BASTILLE!!!
#
#    #      #      #      #      #      #      #      #      #      
#    #      #      #      #      #      #      #      #      #      
#    #      #      #      #      #      #      #      #      #      
#    #      #      #      #      #      #      #      #      #      
#  # # #  # # #  # # #  # # #  # # #  # # #  # # #  # # #  # # #
#   ###    ###    ###    ###    ###    ###    ###    ###    ###  
#    #      #      #      #      #      #      #      #      #      

@customrules

#    #      #      #      #      #      #      #      #      #      
#   ###    ###    ###    ###    ###    ###    ###    ###    ###  
#  # # #  # # #  # # #  # # #  # # #  # # #  # # #  # # #  # # #
#    #      #      #      #      #      #      #      #      #      
#    #      #      #      #      #      #      #      #      #      
#    #      #      #      #      #      #      #      #      #      
#    #      #      #      #      #      #      #      #      #      
#
#   RULES INSERTED FROM $customrulesfile ABOVE
#
#####################################################################

EOF

    return $customruleswithcomments;
  }

}

sub getAllowRules {
    my %allowRulesHash = %{&getAllowRulesHash};
    my $allowrules=<<EOF;
#####################################################################
#  The following rules explicitly allow certain types of connections
#

EOF
    foreach my $protocol (keys %allowRulesHash) {
       if (&getGlobalConfig("IPFilter","block_" . $protocol) eq "N") {
          $allowrules .= "# Allow $protocol incoming connections\n" .
                         $allowRulesHash{$protocol} . "\n";
       } else {
          $allowrules .= "# do NOT allow $protocol incoming connections\n" .
                         "# " . $allowRulesHash{$protocol} . "\n";
       }
    }
    return $allowrules;
}



1;

