#!/bin/bash

#########################################################################
#									#
#	instant-firewall.sh						#
#									#
# 	Pupose: yet another attempt at simplifying ipchains firewall 	#
#	configuration.							#
#									#
#	Author: Simon Brooke <simon@jasmine.org.uk>			#
#	Created: 19th December 1999					#
#	Copyleft: Use it if you like it.				#
#	$Revision: 1.7 $						#
#									#
#########################################################################

# Linux 2.0 kernels provided one method of setting up firewalls:
# ipfwadm(8). Linux 2.2 provides a slightly different method:
# ipchains(8). Linux 2.4 which is almost upon us is going to change
# things yet again. Meantime, for most working sysadmins, setting up a
# firewall is something the one does rarely, and is quite tricky and
# obscure... and which you *have* to get right. There are a number of
# toolkits designed to help this process, but when I tried I found the
# toolkits so hard to understand they weren't worth it.

# The object of this script is not to be perfect but to be simple. I
# intend and hope that it's also fairly safe. It attempts to separate
# the process out into two phases: first, a series of variables 
# which you should modify to describe the policy you want to achieve;
# second, a block of code which converts your policy into a series of
# ipchains instructions, which it then prints on standard out in the
# form of a Sys V style init.d script, allowing you to inspect and
# potentially alter the ipchains instructions before actually
# executing them.

# This script deals *only* with '3-way' firewalls, as described on
# page 73 (fig 4.7) of Chapman, D & Zwicky, E: _Building Internet
# Firewalls_: O'Reilly, 1995 [ISBN 1-56592-124-0]. This is basically
# the same arangement as
# <URL:http://www.linuxdoc.org/HOWTO/IPCHAINS-HOWTO-7.html#ss7.1>
# except that I use the names 'internal perimeter ouside' instead of
# 'GOOD DMZ BAD' Note that both Chapman and Zwicky and the
# IPCHAINS-HOWTO are excellent and highly recommended reading.

# It should be pointed out that I'm not a security expert and this
# script may have flaws; it is, as they say, a quick hack. It is
# distributed WITHOUT ANY WARRANTY AT ALL. You use it ENTIRELY AT YOUR
# OWN RISK.

# Portability: only useful on Linux 2.2.X; only tested in bash.

# $Log: instant-firewall.sh,v $
# Revision 1.7  2000/09/08 09:12:46  simon
# Whoops! Missed a close-quotes on the end of the string on line 309!
#
# Revision 1.6  2000/09/01 15:18:04  simon
# A lot of changes, motivated by the fact our own firewall broke. Most
# important (to us) is the fact that you can now specify ranges of ports,
# using number-colon-number syntax. To make this work I've had to change
# the specific protocol syntax from service-colon-protocol to
# service-exclamation mark-protocol.
#
# I've also implemented named chains as suggested in section 7.1 of the
# IPCHAINS-HOWTO, although frankly I'm not yet making very good use of
# them. I tried very hard to get the thing to work with an input default
# policy of deny, but I didn't succeed. Goals for next revision will be
# to make more intelligent use of named chains, and to tighten up the
# input policy.
#
# Revision 1.5  2000/07/12 11:54:34  simon
# Changed Usage output of generated script to reflect name of generated
# script; thanks to Ganesh Prasad <gprasad@reply2.com.au> for this fix.
#
# Revision 1.4  2000/05/19 13:37:08  simon
# Added rules to deny inbound packets from RFC 1918 reserved networks - these
# are almost certainly dishonest (or someone's network setup is well fscked).
# This feature is not really tested as my setup denies dishonest packets the
# 'better' way - read the source.
#
# Revision 1.3  2000/01/06 18:03:36  simon
# Tidied up some minor infelicities and added an HTML file which
# basivally just contains a sopy of the comments in the script.
#
# Revision 1.2  2000/01/06 17:28:30  simon
# altered output into the form of a script which will work with SysV 
# style init; added source address verification commands.
#

###############################################################################
#############  Configuration starts here: edit these variables  ###############
###############################################################################

# First, describe your network topology. All three networks described
# must be connected to *separate* NICs (ethernet cards, serial ports,
# whatever) on the box doing the routing.

Outside=0.0.0.0/0.0.0.0
Perimeter=123.123.123.128/255.255.255.248
Internal=192.168.1.0/255.255.255.0

# Now describe the devices these networks are connected to
inside_device=eth1
outside_device=eth0
perimeter_device=eth2

# Now list the services you want to permit. With three networks there
# are six potential directions, abbreviated as follows:

# Inside -> Outside, abbreviated as 'io';
# Inside -> Perimeter, 'ip';
# Perimeter -> Inside, 'pi';
# Perimeter -> Outside, 'po';
# Outside -> Inside, 'oi';
# Outside -> Perimeter, 'op'

# Services are listed as space separated lists of either
#	i:   service names from /etc/services
#	ii:  a service name, as above, followed by a exclmation mark, followed by a
#		protocol name, e.g. 'bootps!udp'
#	iii: port numbers
#	iv:  port ranges comprising two numbers separated by a colon
# or the symbols "any" or "none". If not specified the protocol is
# assumed to be tcp.

# Services from the inside to the outside
io_services="any"

# Services from the inside to the perimeter
ip_services="any"

# Services from the perimeter to the inside
pi_services="5432"

# Services from the perimeter to the outside
po_services="any"

# Services from the outside to the perimeter
op_services="ssh nameserver domain domain!udp http pop-3 auth nntp 1024:65536"

# Services from the outside to the inside
oi_services="none"

# Now list the directions in which you wish to masquerade. If your
# internal network doesn't use 'real' IP numbers issued to you by your
# upstream supplier, then you *must* masquerade outgoing traffic to
# the Internet. It's a bit more secure doing this, too.

# Masquerade directions: space separated list of two-character
# direction symbols to be masqueraded. 
masq_directions="io"

# Now list the directions in which you allow ICMP packets. These help
# the network work out how most efficiently to route your traffic, but
# can potentially tell attackers where oyur hidden machines actually
# are.
icmp_directions="io ip pi op po"

# Now list the directions in which you would like to have traffic
# logged. Warning! this generates *copious* logs.

# Logging directions: space separated list of two-character direction
# symbols to be masqueraded
log_directions="io ip op"

###############################################################################
############# You should not need to change anything after here ###############
###############################################################################

# Source and destination symbols for the direction symbols: 
# do *not* change these!
io_source=$Internal
io_dest=$Outside
io_device=$outside_device
ip_source=$Internal
ip_dest=$Perimeter
ip_device=$perimeter_device
po_source=$Perimeter
po_dest=$Outside
po_device=$outside_device
pi_source=$Perimeter
pi_dest=$Internal
pi_device=$inside_device
oi_source=$Outside
oi_dest=$Internal
oi_device=$inside_device
op_source=$Outside
op_dest=$Perimeter
op_device=$perimeter_device

# Initial setup

cat <<EOF
#! /bin/sh
#
# SysV style init script for firewall, generated by instant-firewall.sh
# No copyright. No warranty. Use at your own risk. Comments, criticism 
# and chocolate to simon@jasmine.org.uk
#
# See how we were called.
case "\$1" in
    restart)
        \$0 stop
        \$0 start
        ;;
    stop)
	echo 0 > /proc/sys/net/ipv4/ip_forward
	## clear out standard chains
	for chain in forward input output
	do
	    ipchains --flush \$chain
	    ipchains -I \$chain 1 -j DENY
	done
	## Homebrewed chains -- may not exist, suppress error messages
	for chain in ip io pi po oi op
	do
	    ipchains --flush \$chain 2> /dev/null
	    ipchains --delete-chain \$chain 2> /dev/null
	done;;
    status)
	ipchains --list;;
    start)
        ## Stop dishonest packets (better method)
	if [ -e /proc/sys/net/ipv4/conf/all/rp_filter ]
	then
	    for f in /proc/sys/net/ipv4/conf/*/rp_filter
	    do
		echo 1 > \$f
	    done
	else
	    spoof="do"
	fi
EOF

# Flush all the standard ipchains to make sure they're clean, and
# block them so no baddies can get in whilst we're reconfiguring
echo "        "; echo "        ## Flush and block standard chains"
for chain in forward input output
do
    echo "        ipchains --flush $chain"
    echo "        ipchains --insert $chain 1 -j DENY"
done

# General policy is to accept input and output, deny forward. Several
# people have said it would be better to deny input as well, but I
# haven't made this work, and as firewalls are so critical I can't
# keep tearing mine down to mess with it. If you can see a better way
# of doing this, patch it and mail the fix to me. You will get credit
# (and possibly even a share of the chocolate).

echo "        "; echo "        ## Set general policy"
echo "        ipchains --policy forward DENY"
echo "        ipchains --policy input ACCEPT"
echo "        ipchains --policy output ACCEPT"

# Ensure forwarding is enabled
echo "        ## Enable packet forwarding"
echo "        echo 1 > /proc/sys/net/ipv4/ip_forward"

# Spoof protection: if a packet arrives at one interface claiming to
# come from another network, it's lying. Stop it, and log the attempt.
# NOTE: this is second-line-of-defence spoof protection, and should not
# be necessary if you do Source Address Verification. See
# <URL:http://www.rustcorp.com/linux/ipchains/HOWTO-5.html#ss5.7>

echo ""; echo "        if [ \"\$spoof\" = \"do\" ]"
echo "        then"
echo "            ## Stop dishonest packets (less good method)"
echo "            ipchains --append input -i $inside_device -s ! $Internal -j DENY -l"

echo "            ipchains --append input -i $perimeter_device -s ! $Perimeter -j DENY -l"

# Stop anything inbound claiming to come from RFC 1918 reserved subnets -
# these are often used by script kiddies
for net in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
do
    echo "            ipchains --append input -i $outside_device -s $net -j DENY"
done

echo "        fi"

# If we've asked for any masquerading, turn masquerading on
if [ -n "$masq_directions" ]
then
    echo "        "; echo "        ## Enable masquerading" 
    echo "        ipchains --masquerading --set 0 0 0"
fi

# For each of the directions we're considering...
for direction in io ip pi po op oi
do
    # First, find values for the services to forward, and where to 
    # forward them from and to.
    for var in services source dest device
    do
	varname=`( echo  "\$ $direction _ $var" | sed 's/ //g')`
	value=`eval echo $varname`

	eval "$var=\"$value\""
    done

    # Then, check whether we should masquerade forwards in this direction
    echo $masq_directions | grep $direction > /dev/null
    if [ $? -eq 0 ] 		# found this direction in masq_directions
    then
	jump="MASQ"
    else
	jump="ACCEPT"
    fi

    # create a new chain for this direction
    echo
    echo "        ## Create a new chain for direction $direction"
    echo "        ipchains --new-chain $direction"
    echo "        ipchains --append forward -s $source -d $dest -j $direction"

    # ... and whether we should log in this direction
    log=""			# by default we don't log
    echo $log_directions | grep $direction > /dev/null
    if [ $? -eq 0 ] 		# found this direction in log_directions
    then
	log="-l"
    fi

    # Then, check whether we should allow icmp (ping, traceroute)
    echo $icmp_directions | grep $direction > /dev/null
    if [ $? -eq 0 ] 		# found this direction in icmp_directions
    then
	echo "        "
	echo "        ## Allow ICMP (ping, traceroute) in direction $direction"
	echo "        ipchains --append $direction -s $source -d $dest -p icmp -j $jump"
    fi

    # OK, now set up the individual services 
    echo "        "; echo "        ## Rules for services in direction $direction"
    for service in $services
    do
	protocol=tcp		# default protocol is tcp

	echo $service | grep "!" > /dev/null;
	if [ $? -eq 0 ] 	# found a colon in the service spec
	then			# so separate into service and protocol
	    spec=$service
	    service=`echo $spec | sed 's/!.*$//'`
	    protocol=`echo $spec | sed 's/^[^!]*!//'`
	fi

	case $service in
	    none | NONE | None ) 
		break;;		# jump out of the loop
	    any | ANY | Any )
		echo "        ## Rule for $service in direction $direction";
		echo "        ipchains --append $direction -s $source -d $dest -j $jump $log";;
	    * )
		echo "        ## Rule for $service in direction $direction";
		echo "        ipchains --append $direction -s $source -d $dest $service -p $protocol -j $jump $log";;
	esac
    done

    echo
    echo "        ## Deny and log anything still left in the $direction chain"
    echo "        ipchains --append $direction -j DENY -l" 

done

# Log anything still left in the input chain (should be nothing)
echo
echo "        ## Deny and log anything still left in the input chain"
echo "        ipchains --append forward -j DENY -l" 

# Remove the blocks we inserted earlier
echo "        "; 
echo "        ## Remove the blocks we inserted earlier"
for chain in forward input output
do
    echo "        ipchains --delete $chain 1"
done

cat <<EOF
	;;
    *)
        echo "Usage: \$0 {start|stop|restart|status}"
        exit 1;;
esac
EOF