#!/bin/sh
# @vasm : vfirewall
# @level: root
# @description: Set up a firewall
# 
# (c) Eko M. Budi, 2003
# (c) Vector Linux, 2003
#
# Released under GNU GPL

vdir=$(dirname $0)
source $vdir/vasm-functions

check_root

# Properties
rc_firewall="/etc/rc.d/rc.firewall"

rc_prefix="/etc/rc.d/rc."
inet_prefix="${rc_prefix}inet"
inet_files="${inet_prefix}?"


mk_rc_firewall ()
{

cat <<'EOF'
#!/bin/sh
## /etc/rc.d/rc.firewall
## Setup the firewall system before any network services is up.
## VASM's vfirewall overwrite this script !!!
##
## By default this script work for a workstation that allows :
##  - all outputs from this machine
##  - some inputs to this machine (domain, ssh, http)
##
## If you specify the GREEN_NET, then this script turn this machine
## into a gateway that allows simple masquerading (Internet sharing)
## with the following configuration 
##
## {RED}-----[firewall]------{GREEN}
##
## RED   = The Internet
## GREEN = Your Intranet 
##
## This firewall uses network address based rules.
## Therefore it is independent to interface, and easier to debug.
## Sufficient for home use, serving some casual clients.
## Not for a serious office !!!
## You cannot sue me for whatever reason regarding this script :P.
##
## GNU GPL (c) Eko M. Budi, 2004
##         (c) Vector Linux, 2004

## The Network you want to protect.
## If specified, forwarding will be allowed.
## It the networt is internal, masquerading will be turned on
## Empty means no forwarding/masquerading.
GREEN_NET=""
#GREEN_NET="192.168.0.0/255.255.255.0"
#GREEN_NET="172.16.0.0/255.255.0.0"
#GREEN_NET="10.0.0.0/255.0.0.0"

## The open ports of THIS host.
## see /etc/services for ports definition
## list the ports, space separated. ALL means all ports.
#PORT_IN="ALL"
PORT_IN="domain ssh http https"
#ICMP_IN="ALL"
ICMP_IN="0 3 8 11"

## The traffic that can come out from this machine.
## You may protect it if you are curious some trojans is in your machine.
## list the ports, space separated. ALL allows everything.
PORT_OUT="ALL"
#PORT_OUT="domain http https pop3 pop3s imap ssh ftp ftp-data irc"

## The traffic that can pass from GREEN the RED network (internet).
## list the ports, space separated. ALL allows everything.
#PORT_FORWARD="ALL"
PORT_FORWARD="domain http https pop3 pop3s imap ssh ftp ftp-data irc"
#ICMP_FORWARD="ALL"
ICMP_FORWARD="3 8 11"

########################################################################
# Do the business ...
. /etc/rc.d/functions

## If this configuration file exist, read it
if [ -r /etc/sysconfig/config/firewall.conf ]; then
   . /etc/sysconfig/config/firewall.conf 
fi

########################################################################
# here we go now
IPT="/usr/sbin/iptables"
MODPROBE="/sbin/modprobe"

## load modules
$MODPROBE ip_tables
$MODPROBE iptable_filter
$MODPROBE iptable_nat
$MODPROBE ip_nat_ftp  
$MODPROBE ip_nat_irc 
$MODPROBE ip_conntrack  
$MODPROBE ip_conntrack_ftp  
$MODPROBE ip_conntrack_irc  

# Check if a net is an internal network
is_internal()
{
   IPADDR=$(echo $1 | cut -f 1 -d /)

   NET=$(ipmask 255.255.0.0 $IPADDR | cut -f 2 -d ' ')
   [ "$NET" = "192.168.0.0" ] && return

   NET=$(ipmask 255.240.0.0 $IPADDR | cut -f 2 -d ' ')
   [ "$NET" = "172.16.0.0" ] && return

   NET=$(ipmask 255.0.0.0 $IPADDR | cut -f 2 -d ' ')
   [ "$NET" = "10.0.0.0" ] && return

   false
}

# This script assumes that the innitial state is secure
# as set by the rc.paranoid. We opens the port one by one.
# On restart/reloading, we are trying to not disturbing the 
# ongoing communications

# STANDARD RULES
firewall_basic()
{
  # Enable IP spoofing protection, turn on Source Address Verification
  for f in /proc/sys/net/ipv4/conf/*/rp_filter; do
     echo 1 > $f
  done

  # Disable ICMP Redirect Acceptance
  for f in /proc/sys/net/ipv4/conf/*/accept_redirects; do
     echo 0 > $f
  done

  # Enabling ping of death protection
  # $IPT -A INPUT -p icmp --icmp-type echo-request -m limit ! --limit 1/s -j DROP

  # Enabling Syn flood protection
  #$IPT -A INPUT -p tcp --syn -m limit ! --limit 1/s -j DROP
 
  # Enabling Furtive port scanner protection
  #$IPT -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST RST -m limit ! --limit 1/s -j DROP
  
}

firewall_flush()
{
  ## Flush all rules
  ## This will delay the communications a bit
  $IPT -F 
  $IPT -F -t nat 
  $IPT -F -t mangle 
  $IPT -X
}

## This is really open everything, except IP forwarding
firewall_clear()
{
  ## Disable IP forwarding
  echo "0" > /proc/sys/net/ipv4/ip_forward

  ## Accept all for default
  $IPT -P INPUT ACCEPT
  $IPT -P FORWARD ACCEPT
  $IPT -P OUTPUT ACCEPT

  firewall_flush
}

# Rules for allowing traffic from the GREEN to the RED network
firewall_forward()
{
  ## if no green net, turn off forwarding
  if [ -z "$GREEN_NET" ]; then
     echo "0" > /proc/sys/net/ipv4/ip_forward 
     $IPT -P FORWARD DROP
     return 0   
  fi
  
  ## Turn ON masquerade automatically
  ## People said that we should use SNAT for static IP
  ## But masquerade will do no harm
  if is_internal $GREEN_NET; then
    $IPT -t nat -A POSTROUTING -s $GREEN_NET -d ! $GREEN_NET -j MASQUERADE
  fi

  ## if everything is ALL, just turn ON the FORWARD
  if [ "$PORT_FORWARD" = "ALL" ] && [ "$ICMP_FORWARD" = "ALL" ]; then
    $IPT -P FORWARD ACCEPT
    echo "1" > /proc/sys/net/ipv4/ip_forward
    return 0
  fi
  
  ## ok, complicated settings are needed
  $IPT -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
  $IPT -A FORWARD -f -j ACCEPT

  if [ "$PORT_FORWARD" != "ALL" ]; then
     for PORT in $PORT_FORWARD; do
       $IPT -A FORWARD -s $GREEN_NET -p udp --dport $PORT -j ACCEPT
       $IPT -A FORWARD -s $GREEN_NET -p tcp --dport $PORT -j ACCEPT
     done
  else
     $IPT -A FORWARD -s $GREEN_NET -p tcp -j ACCEPT
     $IPT -A FORWARD -s $GREEN_NET -p udp -j ACCEPT
  fi
  
  if [ "$ICMP_FORWARD" != "ALL" ]; then
     for TYPE in $ICMP_FORWARD; do 
         $IPT -A FORWARD -s $GREEN_NET -p icmp --icmp-type $TYPE -j ACCEPT
     done
  else
     $IPT -A FORWARD -s $GREEN_NET -p icmp -j ACCEPT
  fi     
  $IPT -P FORWARD DROP
  echo "1" > /proc/sys/net/ipv4/ip_forward
}


# Rules for accepting input to this gateway
firewall_input()
{
  ## if ALL, open the default. Save rules and faster !
  if [ "$PORT_IN" = "ALL" ] && [ "$ICMP_IN" = "ALL" ]; then
     $IPT -P INPUT ACCEPT
     return 0
  fi
  
  ## Set a secure input
  ## allow to internal
  $IPT -A INPUT -d 127.0.0.0/8 -j ACCEPT
  ## and established ones
  $IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
  $IPT -A INPUT -f -j ACCEPT
  
  ## Allow some ports
  if [ "$PORT_IN" != "ALL" ]; then
     for PORT in $PORT_IN ; do 
       $IPT -A INPUT -p udp --dport $PORT -j ACCEPT
       $IPT -A INPUT -p tcp --dport $PORT -j ACCEPT
     done
  else
     $IPT -A INPUT -p udp -j ACCEPT
     $IPT -A INPUT -p tcp -j ACCEPT
  fi

  ## Allow ICMPs
  if [ "$ICMP_IN" != "ALL" ]; then
     for TYPE in $ICMP_IN ; do 
       $IPT -A INPUT -p icmp --icmp-type $TYPE -j ACCEPT
     done
  else
     $IPT -A INPUT -p icmp -j ACCEPT
  fi

  ## Drop everything else
  $IPT -P INPUT DROP
}

# Rules for allowing OUTPUT traffic from this machine
firewall_output()
{
  ## if ALL, open the default. Save rules and faster !
  if [ "$PORT_OUT" = "ALL" ]; then
     $IPT -P OUTPUT ACCEPT
     return 0
  fi
  
  ## Hmmm ... the admin does not believe his own computer
  ## But let's the internal output
  $IPT -A OUTPUT -d 127.0.0.0/8 -j ACCEPT 

  ## allow all ICMPs from this host (is it secure enough ?)
  $IPT -A OUTPUT -p icmp -j ACCEPT

  ## allow some ports
  for PORT in $PORT_OUT; do
     $IPT -A OUTPUT -p udp --dport $PORT -j ACCEPT
     $IPT -A OUTPUT -p tcp --dport $PORT -j ACCEPT
  done

  ## Drop everything else by default
  $IPT -P OUTPUT DROP
}

## START IT
firewall_start() {

  firewall_basic
  firewall_flush
  firewall_input
  firewall_output
  firewall_forward
  return 0
}

firewall_stop()
{
  firewall_clear
  return 0
}

case "$1" in
  start)
    echo -n "Starting firewall ..."
    firewall_start
    evaluate_retval
    ;;
  stop)
    echo -n "Stopping firewall ..."
    firewall_stop
    evaluate_retval
    ;;
  restart)
    echo -n "Restarting firewall ..."
    ## Restarting firewall does not stop it
    ## Since stopping opens the ports for a moment
    firewall_start
    evaluate_retval
    ;;
  reload)
    echo -n "Reloading firewall ..."
    firewall_start
    evaluate_retval
    ;;  
  status)
    $IPT -nL
    echo
    $IPT -t nat -nL
    ;;
  *)  
    echo "Usage $0 {start|stop|restart|reload|status}"
esac




EOF

}

# Get the inet values from file
function get_inet()
{
  DEVICE=""
  NETNAME=""
  IPADDR=""
  NETMASK=""
  GATEWAY=""
  PROBE="no"
  inet_file=$1
  eval `grep -e '^DEVICE=' $inet_file`
  eval `grep -e '^IPADDR=' $inet_file`
  eval `grep -e '^NETMASK=' $inet_file`
  eval `grep -e '^GATEWAY=' $inet_file`
  eval `grep -e '^DHCP=' $inet_file`
  eval `grep -e '^PROBE=' $inet_file`
}

function build_inet_menu()
{
   count=0
   for file in $inet_files; do
     if [ -x $file ]; then
       get_inet $file
       inet=${file##${rc_prefix}}
       if [ "$DHCP" != "yes" ]; then
	  echo "$inet \"DEV=$DEVICE, IP=$IPADDR/$NETMASK\" \\"
       fi    	
       let count++
     fi
   done
   if [ $count == 0 ]; then
     errorbox "No network connection. Add one first, mate !"
     clean_exit 1 
   fi
}

# Check if a net is an internal network
is_internal()
{
   IPADDR=$(echo $1 | cut -f 1 -d /)

   NET=$(ipmask 255.255.0.0 $IPADDR | cut -f 2 -d ' ')
   [ "$NET" = "192.168.0.0" ] && return

   NET=$(ipmask 255.240.0.0 $IPADDR | cut -f 2 -d ' ')
   [ "$NET" = "172.16.0.0" ] && return

   NET=$(ipmask 255.0.0.0 $IPADDR | cut -f 2 -d ' ')
   [ "$NET" = "10.0.0.0" ] && return

   false
}


PORT_LIST="ssh domain http https 8080 ftp ftp-data smtp smtps pop3 pop3s imap
nntp irc sunrpc nfsd lockd netbios-ns netbios-dgm netbios-ssn swat xdmcp 6000:6063"

# print menu
port_desc()
{
case $1 in
  ssh) echo 	"Secure Shell Login" ;;
  domain) echo 	"Domain Name Server";;  
  http) echo 	"World Wide Web HTTP";;
  https) echo 	"HTTP secure";;
  8080) echo 	"HTTP-proxy,servlet,JSP";;
  ftp) echo 	"File Transfer Protocol";;
  ftp-data) echo "File Transfer [Default Data]";;
  smtp) echo 	"Simple Mail Transfer";;
  smtps) echo 	"SMTP secure";;
  pop3) echo     "Post Office Protocol";;
  pop3s) echo    "POP3 secure";;
  imap) echo	 "Interim Mail Access Protocol";;
  nntp) echo     "Network News Transfer Protocol";;
  irc ) echo     "Internet Relay Chat Protocol";;
  sunrpc) echo	 "SUN Remote Procedure Call";;
  nfsd) echo     "NFS server daemon";;
  lockd) echo    "NFS lock daemon";;
  netbios-ns) echo	"Samba NETBIOS Name Service";;
  netbios-dgm) echo	"Samba NETBIOS Datagram Service";;
  netbios-ssn) echo	"Samba NETBIOS Session Service";;
  swat) echo		"SAMBA web configuration tool";;
  xdmcp) echo		"Display Manager Control Protocol";;
  "6000:6063") echo	"X Window System";;
esac
}

# print ON if $1 in $OPEN_PORTS
port_state()
{
   if echo $OPEN_PORTS | grep -qw $1 ; then
      echo on   
   else
      echo off
   fi
}

get_firewall()
{
  GREEN_NET=""
  TCP_OUT=""
  TCP_IN=""
  
  eval `grep -e '^GREEN_NET=' $rc_firewall`
  eval `grep -e '^PORT_FORWARD=' $rc_firewall`
  eval `grep -e '^PORT_IN=' $rc_firewall`
}

set_firewall()
{
  cat $rc_firewall | sed "
  s#^GREEN_NET=.*#GREEN_NET='$GREEN_NET'# 
  s#^PORT_FORWARD=.*#PORT_FORWARD='$PORT_FORWARD'# 
  s#^PORT_IN=.*#PORT_IN='$PORT_IN'#" > $rc_firewall
}

menuA1A() {
  if [ ! -f $rc_firewall ]; then
    infobox "No firewall script. Creating a new one"
    mk_rc_firewall > $rc_firewall
  fi

TITLE="OPEN HOST PORTS"
TEXT="\nSelect network ports of this host that you want to open.\n
Sorry, the list is limited. If you want more than this list,\n
then you are an expert and should write your own firewall script ;-)."
DIMENSION="20 78 10"

OPEN_PORTS=$PORT_IN

dbug open=$OPEN_PORTS
echo '$DCMD --backtitle "$BACKTITLE" --title "$TITLE" --checklist "$TEXT" $DIMENSION \' > $fmenu
for port in $PORT_LIST; do
  desc=$(port_desc $port)
  state=$(port_state $port)
  echo $port \"$desc\" $state \\ >> $fmenu
done
echo ' 2> $freply ' >> $fmenu

#cat $fmenu

. $fmenu

status=$?
[ $status != 0 ] && return $status

if [ "$CMD" ]; then
  PORT_IN=$(cat $freply | sed -e 's!/! !g')
else
  PORT_IN=$(cat $freply | sed -e 's/"//g')
fi

}

menuA1B()
{
   infobox "Saving firewal configuration"
   set_firewall
   sleep 2
   clean_exit 0
}


menuA2A()
{

while [ 0 ]; do
DIMENSION="18 60 4"
TITLE="GATEWAY CONFIGURATION"
TEXT="\n
This menu allows you to setup this host as a gateway,\n
possibly for an internet sharing server. You should\n
have INTERNET and INTRANET connections like this:\n
  {INTERNET}---[gateway]---{INTRANET}\n\n

Now tell me which connection is the INTRANET side:"

  echo '$DCMD --backtitle "$BACKTITLE" --title "$TITLE" --menu "$TEXT" $DIMENSION \' > $fmenu
  build_inet_menu >> $fmenu
  echo '2> $freply' >> $fmenu

#  cat $fmenu
  source $fmenu
  
  status=$?
  [ $status != 0 ] && return $status

  inet=$(cat $freply)
  inet_file="${rc_prefix}$inet"
  get_inet $inet_file
  
  if is_internal $IPADDR; then
     return 0     
  fi  

  yesnobox "IP $IPADDR in not an internal network. Use it anyway ?"
  [ $? = 0 ] && return 0

done
}


menuA2B()
{

  GREEN_NET=$(ipmask $NETMASK $IPADDR | cut -f 2 -d ' ')"/$NETMASK"

DIMENSION="20 70 10"
TITLE="GATEWAY CONFIGURATION"
TEXT="\n
You have chosen $inet as the intranet side. The network is:\n
   $GREEN_NET\n
Next, select the ports that are allowed to pass this gateway."

OPEN_PORTS=$PORT_FORWARD

dbug open=$OPEN_PORTS
echo '$WCMD --backtitle "$BACKTITLE" --title "$TITLE" --checklist "$TEXT" $DIMENSION \' > $fmenu
for port in $PORT_LIST; do
  desc=$(port_desc $port)
  state=$(port_state $port)
  echo $port \"$desc\" $state \\ >> $fmenu
done
echo ' 2> $freply ' >> $fmenu

# cat $fmenu

. $fmenu

status=$?
[ $status != 0 ] && return $status

if [ "$CMD" ]; then
  PORT_FORWARD=$(cat $freply | sed -e 's!/! !g')
else
  PORT_FORWARD=$(cat $freply | sed -e 's/"//g')
fi
}

menuA2C()
{
TITLE="DONE"
TEXT="\n
The gateway portion has been setup.\n
For internet sharing to work, you should:\n
- set the GATEWAY of the clients to (IP=$IPADDR).\n
- Optionally, enable DNS service (dnsmasq) on this host\n
  and set the DNS of the clients to (IP=$IPADDR).\n
Enjoy .... !!!"
DIMENSION="12 60"

$DCMD --backtitle "$BACKTITLE" --title "$TITLE" --msgbox "$TEXT" $DIMENSION
  
   clean_exit 0
}

#########################################
# Open ports
menu_open()
{
  
  list_ports
  infobox "Creating a new firewall script"
  if [ -f $rc_firewall ]; then
    cp $rc_firewall ${rc_firewall}.backup
  fi
  mk_rc_firewall > $rc_firewall
  chmod a+x $rc_firewall
  sleep 3
  return 0
}

#########################################
# Create a new firewall script
menu_new()
{
  infobox "Creating a new firewall script"
  if [ -f $rc_firewall ]; then
    cp $rc_firewall ${rc_firewall}.backup
  fi
  mk_rc_firewall > $rc_firewall
  chmod a+x $rc_firewall
  sleep 3
  return 0
}

#########################################
# start firewall
menu_start()
{
  if [ ! -x $rc_firewall ]; then
    errorbox "No firewall script. Create a new one first"
    return 1
  fi
  infobox "Starting the firewall"
  $rc_firewall start &> /dev/null
  sleep 2
  msgbox "Firewall has been started.\nCheck it from a terminal using command 'iptables -L'" "INFO"
  return 0
}

#########################################
# stop firewall
menu_stop()
{
  if [ ! -x $rc_firewall ]; then
    errorbox "No firewall script. Create a new one first"
    return 1
  fi
  infobox "Stopping the firewall"
  $rc_firewall stop &> /dev/null
  sleep 2
  return 0
}

#########################################
# enable firewall
menu_enable()
{
  infobox "Enabling the firewall"
  if [ ! -f $rc_firewall ]; then
    mk_rc_firewall > $rc_firewall
  fi
  chmod a+x $rc_firewall
  sleep 2
  return 0
}

#########################################
# Disable firewall script
menu_disable()
{
  infobox "Disabling firewall script"
  if [ -x $rc_firewall ]; then
    $rc_firewall stop &> /dev/null
    chmod a-x $rc_firewall
  fi
  sleep 2
  return 0
}

##################################################
# Main Menu
menuA()
{
while [ 1 ]; do
TITLE="FIREWALL CONFIGURATION"
TEXT="This configurator setups a simple firewall to protects this host and
enable internet sharing. You should create a NEW firewall first, then
use the OPEN and GATEWAY menu. After that you may START, STOP, ENABLE,
or DISABLE the firewall at any time.\n
Warning: do not use this firewall if you are using other firewall program
such as GuardDog, GShield or portsentry."

DIMENSION="20 74 8"

$DCMD --backtitle "$BACKTITLE" --title "$TITLE" --menu "$TEXT" $DIMENSION \
  "NEW"     "create a new firewall" \
  "OPEN"    "open some ports of this host" \
  "GATEWAY" "setup this host as a gateway" \
  "START"   "Start the firewall" \
  "STOP"    "Stop the firewall" \
  "ENABLE"  "enable the firewall on booting" \
  "DISABLE" "disable firewall on booting" \
  "DONE"    "finish with this menu" \
2> $freply

status=$?
[ $status != 0 ] && return $status

case $(cat $freply) in
    ENABLE)
      menu_enable
      ;;
    DISABLE)
      menu_disable
      ;;
    START)
      menu_start
      ;;
    STOP)
      menu_stop
      ;;
    OPEN)
      $0 --open
      ;;
    GATEWAY)
      $0 --gateway
      ;;
    NEW)
      menu_new
      ;;
    *)
      return 0
      ;;
esac
done

}

######################
#
case $1 in
  --open)
    get_firewall
    wizard menuA1A menuA1B
    ;;
  --gateway)
    get_firewall
    wizard menuA2A menuA2B menuA2C
    ;;
  *)
    wizard menuA
esac

clean_exit

