#! /bin/sh
#
# firewall_ipchains		Load/unload ipchains rulesets
#
# version 0.98-beta2
#
# thanks to David Ranch for ideas implemented in his TrinityOS script
#
# This script is designed to be used as a SysV-style init script.
# 
# Users with dynamically-assigned IP addresses should run this with
# "stop" and then "start" after getting a new address if you're concerned
# about clients on "internal" interfaces bypassing restrictions on 
# internally-exposed services by using the new external address.
# (Systems with only "public" and "trusted" interfaces do not need to
#  worry about this detail.)

PATH=/bin:/sbin:/usr/bin:/usr/sbin
IPCHAINS=/sbin/ipchains

# the configuration values should be whitespace-delimited lists of 
# appropriate values, e.g.
# 	TCP_PUBLIC_SERVICES="80 smtp ssh"
# lists Web (port 80), SMTP mail, and Secure Shell ports
#
# This script is suitable for workstations or simple NAT firewalls,
# though you may want to add more "output" restrictions for serious servers

# 0) DNS servers. You must list your DNS servers here so that
#	the firewall will allow them to service your lookup requests
#
# List of DNS servers/networks to allow "domain" responses from
# List your ISP's nameservers as a list of <ip-address>/32 entries
#DNS_SERVERS="a.b.c.d/32 e.f.g.h/32"	
# If you are running a caching nameserver, you'll need to allow from
# "0.0.0.0/0" so named can query any arbitrary nameserver
#DNS_SERVERS="0.0.0.0/0"
# (To enable a caching nameserver, you will also probably need to
#  add "domain" to the TCP and UDP public service lists.)
# to have the DNS servers parsed from /etc/resolv.conf at runtime,
# leave this variable empty
DNS_SERVERS=""


# 1) define your interfaces
#	Note a "+" acts as a wildcard, e.g. ppp+ would match any PPP 
#	interface
#
# list internal/trusted interfaces
TRUSTED_IFACES="lo"					# MINIMAL/SAFEST
#
# list external/untrusted interfaces
PUBLIC_IFACES="eth+ ppp+ slip+"			# SAFEST
#
# list internal/partially-trusted interfaces
INTERNAL_IFACES=""				# SAFEST

# 2) services for which we want to log access attempts to syslog
#	Note this only audits connection attempts from public interfaces
#
TCP_AUDIT_SERVICES="telnet ftp imap pop-3 finger sunrpc exec login linuxconf ssh" 
# anyone probing for BackOrifice?
UDP_AUDIT_SERVICES="31337"
# how about ICMP?
ICMP_AUDIT_TYPES=""
#ICMP_AUDIT_TYPES="echo-request"	# ping/MS tracert
#
# To enable auditing, you must have syslog configured to log "kern"
# messages of "info" level; typically you'd do this with a line in
# syslog.conf like
#
# kern.info				-/var/log/messages


# 3) services we allow connections to
#
# "public" interface:
# TCP services that "public" hosts should be allowed to connect to
TCP_PUBLIC_SERVICES=""					# MINIMAL/SAFEST
#
# UDP services that "public" hosts should be allowed to connect to
UDP_PUBLIC_SERVICES=""					# MINIMAL/SAFEST
#
# "internal" interface:
# (NB: you will need to repeat the "public" services if you want
#      to allow "internal" hosts to reach those services, too.)
# TCP services that internal clients can connect to
TCP_INTERNAL_SERVICES=""				# MINIMAL/SAFEST
#
# UDP services that internal clients can connect to
UDP_INTERNAL_SERVICES=""				# MINIMAL/SAFEST


# 4) FTP is a firewall nightmare; if you allow "normal" FTP connections,
#	you must be careful to block any TCP services that are listening
#	on high ports; it's safer to require your FTP clients to use
#	"passive" mode
#
FORCE_PASV_FTP="1"					# SAFEST


# 5) Services to explicitly block. See FTP note above
#	Note that ranges of ports are specified with colons, and you
#	can specify an open range by using only one number, e.g.
#	1024: means ports >= 1024 and :6000 means ports <= 6000
#
# TCP services on high ports that should be blocked if not forcing passive FTP
# This should include X (6000:6010) and anything else revealed by 'netstat -an'
#  (this does not matter unless you're not forcing "passive" FTP)
TCP_BLOCKED_SERVICES="2049 2065:2090 6000:6020 7070:7071 7100"
#
# UDP services to block: this should be UDP services on high ports.
# Your only vulnerability from public interfaces are the DNS and
# NTP servers/networks (those with 0.0.0.0 for DNS servers should
# obviously be very careful here!)
UDP_BLOCKED_SERVICES="1024:1025 1066 2049 6770"
#
# types of ICMP packets to allow
#ICMP_ALLOWED_TYPES="destination-unreachable"		# MINIMAL/SAFEST
# the following allows you to ping/traceroute outbound
ICMP_ALLOWED_TYPES="destination-unreachable echo-reply time-exceeded"


# 6) Source Address Verification helps prevent "IP Spoofing" attacks
#
ENABLE_SRC_ADDR_VERIFY="1"				# SAFEST


# 7) IP Masquerading / NAT. List your internal/masq'ed networks here
#
# Set this variable if you're using IP Masq / NAT for a local network
IP_MASQ_NETWORK=""					# DISABLE/SAFEST
#IP_MASQ_NETWORK="10.0.0.0/8"				# example
# 
# Masq modules
# NB: The script will prepend "ip_masq_" to each module name
#IP_MASQ_MODULES="cuseeme ftp irc quake raudio vdolive"	# ALL (?)
IP_MASQ_MODULES="ftp raudio vdolive"			# RECOMMENDED


# 8) How to react to disallowed packets
# whether to "REJECT" or "DENY" disallowed packets; if you're running any
# public services, you probably ought to use "REJECT"; if in serious stealth
# mode, choose "DENY" so the probes don't know if there's anything out there
#	NOTE: disallowed ICMP packets are discarded with "DENY", as
#		it would not make sense to "reject" the packet if you're
#		trying to disallow ping/traceroute
#
REJECT_METHOD="DENY"

# 9) DHCP
#    In case your server needs to get a DHCP address from some other
#    machine (e.g. cable modem)
#
DHCP_IFACES=""				# DISABLED
#DHCP_IFACES="eth0"			# example, to allow you to query on eth0

# 10) more UDP fun. List IP addresses or network space of NTP servers
#
NTP_SERVERS=""				# DISABLE NTP QUERIES / SAFEST
#NTP_SERVERS="a.b.c.d/32 e.f.g.h/32"	# example, to allow querying 2 servers


# 11) more ICMP. Control the outbound ICMP to make yourself invisible to
#     traceroute probes
ICMP_OUTBOUND_DISABLED_TYPES="destination-unreachable time-exceeded"

#
# Computed values
# 
# These things should be queried/computed at run time
#
# LOCAL_ADDRESSES
#
# LOCAL_ADDRESSES lists all IP addresses for this server 
#  (for the INTERNAL_SERVICES rules); if you have virtual 
#  network devices, you may want to hand-code this, e.g.
# LOCAL_ADDRESSES="127.0.0.0/32"
#
# The following makes a list of all current IP addresses
LOCAL_ADDRESSES=`/sbin/ifconfig | /bin/grep "inet addr" | /bin/awk '{print $2}' | /bin/awk ' BEGIN {FS=":"} {print $2"/32"}'`
#
#
# INTERNAL_NETWORKS
#
# INTERNAL_NETWORKS lists the masked networks for the INTERNAL_INTERFACES
# INTERNAL_NETWORKS="10.0.0.0/255.0.0.0"
# The following makes a list of all current internal IP addresses _with netmasks_
for i in ${INTERNAL_IFACES} ; do
	INTERNAL_NETWORKS="${INTERNAL_NETWORKS} `/sbin/ifconfig ${i} | /bin/grep "inet addr" | /bin/awk '{print $2":"$4}' | /bin/awk ' BEGIN {FS=":"} {print $2"/"$4}'`"
done
#
#
# DNS_SERVERS
#
# Derive this from /etc/resolv.conf if it's not set already
if [ -z ${DNS_SERVERS} ]; then
	DNS_SERVERS=`grep nameserver /etc/resolv.conf | awk 'BEGIN {FS="#"} {print $1}' | grep '^nameserver' | awk '{print $2"/32"}' | head -3`
fi
#

# See how we were called.
case "$1" in
  start)
	echo
	#	
	# set default policy to disallow forwarding
	${IPCHAINS} -P forward ${REJECT_METHOD}
	# flush rules
	${IPCHAINS} -F forward
	# default is to disallow incoming traffic	
	${IPCHAINS} -P input ${REJECT_METHOD}
	# flush rules 
	${IPCHAINS} -F input
	# disallow outbound until we set up the explicit outbound rules
	${IPCHAINS} -P output ${REJECT_METHOD}
	# flush rules 
	${IPCHAINS} -F output

	# Auditing must be set up before these packets are blocked!
	for iface in ${PUBLIC_IFACES} ; do
		for service in ${TCP_AUDIT_SERVICES} ; do
			${IPCHAINS} -A input -p tcp -d 0.0.0.0/0 ${service} -y -i ${iface} -l
		done   
		for service in ${UDP_AUDIT_SERVICES} ; do
			${IPCHAINS} -A input -p udp -d 0.0.0.0/0 ${service} -i ${iface} -l
		done   
		for type in ${ICMP_AUDIT_TYPES} ; do
			${IPCHAINS} -A input -p icmp --icmp-type ${type} -d 0.0.0.0/0 -i ${iface} -l 
		done
	done

	# lifted from the ipchains HOWTO, I think
	if [ ${ENABLE_SRC_ADDR_VERIFY} = 1 ]; then
		if [ -e /proc/sys/net/ipv4/conf/all/rp_filter ]; then
        		echo -n "Setting up IP spoofing protection..."
		        for f in /proc/sys/net/ipv4/conf/*/rp_filter; do
            		echo 1 > $f
        		done
        		echo " done."
		else
        		echo "PROBLEMS SETTING UP IP SPOOFING PROTECTION.  BE WORRIED"
		fi
	fi

	# Forwarding
	#
	# IP Masquerading/forwarding
	if [ ! -z ${IP_MASQ_NETWORK} ]; then
		echo -n "Setting up masquerading rules..."	
		# since we've set the default forwarding policy to
		# reject, we can enable forwarding now
		echo 1 > /proc/sys/net/ipv4/ip_forward
		# NetBEUI
		${IPCHAINS} -A forward -p tcp -s 0.0.0.0/0 137:139 -d 0.0.0.0/0 -j ${REJECT_METHOD}
		${IPCHAINS} -A forward -p udp -s 0.0.0.0/0 137:139 -d 0.0.0.0/0 -j ${REJECT_METHOD}
		# set up rules for masqueraded networks
		for net in ${IP_MASQ_NETWORK} ; do
			for pub in ${PUBLIC_IFACES} ; do
				# NAT should be one-way, deny traffic from public
				# interfaces that is addresses to masq'ed networks
				${IPCHAINS} -A input -d ${net} -i ${pub} -j ${REJECT_METHOD}
				# spoofed addreses from outside
				${IPCHAINS} -A input -s ${net} -i ${pub} -j ${REJECT_METHOD}
				# enable forwarding
				${IPCHAINS} -A forward -s ${net} -i ${pub} -j MASQ
			done
		done
		echo " done."
		echo -n "Loading masquerading modules..."	
		for mod in ${IP_MASQ_MODULES} ; do
			/sbin/insmod "ip_masq_${mod}"
		done
		echo " done."
	fi
	# disallow any attempts to get to internal interfaces from outside
	# not good if this is supposed to route between normal networks
	for int in ${INTERNAL_NETWORKS} ; do
		for pub in ${PUBLIC_IFACES} ; do
			# deny traffic from public
			# interfaces that is addresses to internal networks
			${IPCHAINS} -A input -d ${int} -i ${pub} -j ${REJECT_METHOD}
			# spoofed addreses from outside
			${IPCHAINS} -A input -s ${int} -i ${pub} -j ${REJECT_METHOD}
		done
	done
		

	echo -n "Setting up general rules..."	

	# Allow all traffic from trusted interfaces
	for t_iface in ${TRUSTED_IFACES} ; do	
		${IPCHAINS} -A input -i ${t_iface} -d 0.0.0.0/0 -j ACCEPT
	done

	# custom rules -------------------------------------------------
	#	
	#	Put custom rules here before we set up the normal
	#	rules for the public interfaces
	#
	# custom rules -------------------------------------------------

	# DHCP servers (from TrinityOS script)
	for iface in ${DHCP_IFACES} ; do
		${IPCHAINS} -A input -p tcp -s 0.0.0.0/0 bootps -i ${iface}  -d 255.255.255.255/0 bootpc -j ACCEPT 		
		${IPCHAINS} -A input -p udp -s 0.0.0.0/0 bootps -i ${iface}  -d 255.255.255.255/0 bootpc -j ACCEPT 		
	done

	# internal interface rules
	for iface in ${INTERNAL_IFACES} ; do
		for net in ${LOCAL_ADDRESSES} ; do
			# TCP services
			for serv in ${TCP_INTERNAL_SERVICES} ; do
				${IPCHAINS} -A input -p tcp -d ${net} ${serv} -i ${iface} -j ACCEPT 		
			done
			${IPCHAINS} -A input -p tcp -d ${net} ${serv} \! -y -i ${iface} -j ACCEPT
			# UDP services
			for serv in ${UDP_INTERNAL_SERVICES} ; do
				${IPCHAINS} -A input -p udp -d ${net} ${serv} -i ${iface} -j ACCEPT 		
			done
		done
		# ICMP
		# hopefully you don't care about hiding from internal hosts!
		${IPCHAINS} -A input -p icmp -d 0.0.0.0/0 -i ${iface} -j ACCEPT 
		${IPCHAINS} -A output -p icmp -d 0.0.0.0/0 -i ${iface} -j ACCEPT 
		# ...but if you do... try this...
		#for type in ${ICMP_ALLOWED_TYPES} ; do
		#	${IPCHAINS} -A input -p icmp --icmp-type ${type} -d 0.0.0.0/0 -i ${iface} -j ACCEPT 
		#done
		#for type in ${ICMP_OUTBOUND_DISABLED_TYPES} ; do
		#	${IPCHAINS} -A output -p icmp --icmp-type ${type} -i ${iface} -j REJECT
		#done
	done
        
	# input rules
	#
	# public interfaces
	for iface in ${PUBLIC_IFACES} ; do

		# --------------------- TCP --------------------------
		for serv in ${TCP_PUBLIC_SERVICES} ; do
			${IPCHAINS} -A input -p tcp -d 0.0.0.0/0 ${serv} -i ${iface} -j ACCEPT 		
		done
		if [ ${FORCE_PASV_FTP} -ne 1 ]; then
			# no point explicitly blocking TCP services unless active FTP is enabled
			# Step 1: block the high port TCP services
			for serv in ${TCP_BLOCKED_SERVICES} ; do
				${IPCHAINS} -A input -p tcp -d 0.0.0.0/0 ${serv} -y -i ${iface} -j ${REJECT_METHOD}
			done
			# Step 2: allow the ftp-data connections
			${IPCHAINS} -A input -p tcp -d 0.0.0.0/0 1024: -s 0.0.0.0/0 ftp-data -i ${iface} -j ACCEPT
		fi
	
		# general response to my TCP requests
		${IPCHAINS} -A input -p tcp \! -y -i ${iface} -j ACCEPT
	
		# no TCP requests to other ports (redundant)
		# ${IPCHAINS} -A input -p tcp -i ${iface} -j ${REJECT_METHOD}
	

		# --------------------- ICMP --------------------------
		for type in ${ICMP_ALLOWED_TYPES} ; do
			${IPCHAINS} -A input -p icmp --icmp-type ${type} -d 0.0.0.0/0 -i ${iface} -j ACCEPT 
		done

		# if you're disallowing ICMP, you may be trying to look 
		# invisible/disable ping, so let's just drop these attempts
		${IPCHAINS} -A input -p icmp -d 0.0.0.0/0 -i ${iface} -j DENY

	
		# --------------------- UDP --------------------------
		for serv in ${UDP_PUBLIC_SERVICES} ; do
			${IPCHAINS} -A input -p udp -d 0.0.0.0/0 ${serv} -i ${iface} -j ACCEPT 		
		done

		# This isn't necessary unless you have DNS_SERVERS or NTP_SERVERS
		# but who wouldn't have DNS servers configured?
		for serv in ${UDP_BLOCKED_SERVICES} ; do
			${IPCHAINS} -A input -p udp -d 0.0.0.0/0 ${serv} -i ${iface} -j ${REJECT_METHOD}
		done

		for dns_net in ${DNS_SERVERS} ; do
			${IPCHAINS} -A input -p udp -s ${dns_net} domain -d 0.0.0.0/0 1024: -i ${iface} -j ACCEPT
		done

		for ntp_net in ${NTP_SERVERS} ; do
			${IPCHAINS} -A input -p udp -s ${ntp_net} ntp -d 0.0.0.0/0 1024: -i ${iface} -j ACCEPT
		done

		# Reject other UDP (redundant)
		#${IPCHAINS} -A input -p udp -i ${iface} -j ${REJECT_METHOD}

		# --------------------- catch-all --------------------------
		# Reject all other traffic (redundant)
		#${IPCHAINS} -A input -i ${iface} -j ${REJECT_METHOD}
	
		# end of loop through public interfaces for input rules
	done
	#
	# now we can deny the attempts from the internal interfaces to this host
	for iface in ${INTERNAL_IFACES} ; do
		for tnet in ${LOCAL_ADDRESSES} ; do
			${IPCHAINS} -A input -i ${iface} -d ${tnet} -j ${REJECT_METHOD}
		done
	done
	# now that we've blocked attempts from the internal interfaces
	# to the IP's on this server, we need to accept other connections
	# so the IP Masq / NAT will function
	for net in ${IP_MASQ_NETWORK} ; do
		${IPCHAINS} -A input -s ${net} -j ACCEPT
	done
	#
       	echo " done."

	echo -n "Setting up outbound rules..."	
	#
	for iface in ${PUBLIC_IFACES} ; do
		# block outbound ICMP types (usu. to hide from traceroute)
		for type in ${ICMP_OUTBOUND_DISABLED_TYPES} ; do
			${IPCHAINS} -A output -p icmp --icmp-type ${type} -i ${iface} -j REJECT
		done
		# 
		# Here you might really lock things down if this is a server,
		# e.g., to keep it from doing anything but connecting to
		# SMTP servers and responding to Web requests, or whatever
		# the specific requirements are. 
		#
		# Such lockdowns are recommended if the situation affords you
		# that flexibility.
		#
	done
	# default is to enable outbound traffic
	# (again, here, for a server you might default to ${REJECT_METHOD} )
	${IPCHAINS} -P output ACCEPT

	echo " done."

	;;
  stop)
	echo
	echo "reverting to default ipchains rulesets"
	if [ ! -z ${IP_MASQ_NETWORK} ]; then
		echo -n "disabling IP forwarding..."	
		echo 0 > /proc/sys/net/ipv4/ip_forward
		echo " done."
		echo -n "unloading masquerading modules..."	
		for mod in ${IP_MASQ_MODULES} ; do
			/sbin/rmmod "ip_masq_${mod}"
		done
		echo " done."
	fi
	# flushing seems to leave the default input at ${REJECT_METHOD}
	echo -n "resetting default input rule to accept..."
	${IPCHAINS} -P input ACCEPT
	echo " done."
	echo -n "resetting default output rule to accept..."
	${IPCHAINS} -P output ACCEPT
	echo " done."
	# likewise, we ought to also disallow forwarding (not specifically
	#  necessary, as we disabled forwarding with the /proc interface)
	echo -n "resetting default forward rule to ${REJECT_METHOD}..."
	${IPCHAINS} -P forward ${REJECT_METHOD}
	echo " done."
	for chain in input output forward ; do
		echo -n "flushing ${chain} rules..."	
		${IPCHAINS} -F ${chain}
		echo " done."
	done
	;;
  status)
	${IPCHAINS} -L
	;;
  *)
	echo "Usage: firewall_ipchains {start|stop|status}"
	exit 1
esac

exit 0

