#!/bin/bash

# Curby's Netfilter Script - gate.curby.net Configuration
RULESVERSION=1.0.8
DATE=2016-02-21

# Copyright (C) 2015  Michael Lee <curby@cur.by>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.



#   ,------------------------------------------------------------------------------------,
#   |   Host-specific Configuration                                                      |
#   '------------------------------------------------------------------------------------'

# Iptables executable (leave empty to auto-locate)
IPTABLES=

# Map network roles to physical interfaces
EXTDEV="eth0"
INTDEV="eth1"
DMZDEV="eth2"

# YES if host is directly accessible from Internet
# NO if host is on a private subnet
EXTDEV_ROUTABLE="YES"

# We can associate traffic from different sources to different public IPs
# Leave empty for default IP of EXTDEV
MY_SNATIP=
INT_SNATIP=
DMZ_SNATIP=



#   ,------------------------------------------------------------------------------------,
#   |   Netfilter Ruleset                                                                |
#   '------------------------------------------------------------------------------------'

generate_rules() {

initialize_ruleset

# ________________________________________________________________________________________
# '-- 1: Stateful Matches ---------------------------------------------------------------'

# Most valid packets match these rules, so we put them first
f_rule "-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"
f_rule "-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"
f_rule "-A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"

# ________________________________________________________________________________________
# '-- 2: General Protections ------------------------------------------------------------'

# BadTraffic handles INVALID packets and other things that just shouldn't exist
f_rule "-A INPUT -j BadTraffic"
f_rule "-A FORWARD -j BadTraffic"
f_rule "-A OUTPUT -j BadTraffic"

# BanHammer handles things like naughty hosts that we want to get rid of ASAP
f_rule "-A INPUT -j BanHammer"
f_rule "-A FORWARD -j BanHammer"
f_rule "-A OUTPUT -j BanHammer"

# All clients get DNS from our DNS proxy instead of NATing through
f_rule "-A FORWARD -p tcp --dport 53 -j REJECT --reject-with icmp-port-unreachable"
f_rule "-A FORWARD -p udp --dport 53 -j REJECT --reject-with icmp-port-unreachable"

# ________________________________________________________________________________________
# '-- 3: Trust Settings: Allow traffic from trusted hosts -------------------------------'

f_rule "-A INPUT -i lo -j ACCEPT"
f_rule "-A OUTPUT -o lo -j ACCEPT"
if [[ $INTNET_EXISTS ]]; then
  # We won't blindly trust INTNET, but will let them go out on the Internet
  #f_rule "-A INPUT -i $INTDEV -j ACCEPT"
  f_rule "-A OUTPUT -o $INTDEV -j ACCEPT"
  f_rule "-A FORWARD -i $INTDEV -j ACCEPT"
fi

# Allow DMZNET hosts to browse the Web
if [[ $DMZNET_EXISTS ]]; then
  f_rule "-A FORWARD -i $DMZDEV -p tcp --dport 80 -j SynDam"
  f_rule "-A FORWARD -i $DMZDEV -p tcp --dport 443 -j SynDam"
fi

# ________________________________________________________________________________________
# '-- 4: INPUT Default Chain ------------------------------------------------------------'

# ICMP Filters
f_rule "-A INPUT -p icmp -j AllowICMP"

# NTP (public, EXT/INT/DMZ)
f_rule "-A INPUT -p udp --dport 123 -j ACCEPT"

# DNS (protected, INT/DMZ)
f_rule "-A INPUT ! -i $EXTDEV -p tcp --dport 53 -j SynDam"
f_rule "-A INPUT ! -i $EXTDEV -p udp --dport 53 -j ACCEPT"

# DHCP (protected, INT/DMZ)
f_rule "-A INPUT ! -i $EXTDEV -p udp --sport 68 --dport 67 -j ACCEPT"

# Web (public, EXT/INT/DMZ)
f_rule "-A INPUT -p tcp --dport 80 -j ACCEPT"

# SSH (protected, INT/DMZ)
f_table ":fail2ban-ssh - [0:0]"
f_table ":fail2ban-sshban - [0:0]"
f_rule "-A INPUT ! -i $EXTDEV -p tcp --dport 22 -j fail2ban-ssh"
f_rule "-A INPUT ! -i $EXTDEV -p tcp --dport 22 -j fail2ban-sshban"
f_rule "-A INPUT -i $INTDEV -p tcp --dport 22 $TRUSTEDSSHLIMIT -j ACCEPT"
f_rule "-A INPUT -i $DMZDEV -p tcp --dport 22 $SSHLIMIT -j ACCEPT"
f_rule "-A INPUT -p tcp --dport 22 $LOGLIMIT -j LOG --log-prefix \"IPT BadSSH IN \""
f_rule "-A INPUT -p tcp --dport 22 -j DROP"

# APCUPSD Sharing (private, INT)
#f_rule "-A INPUT -s 192.168.0.10 -p tcp --dport 3551 -j SynDam"
#f_rule "-A INPUT -s 192.168.0.10 -p udp --dport 3551 -j ACCEPT"

# Inbound UDP traceroutes
f_rule "-A INPUT -p udp --dport 33434:33534 -m ttl --ttl-eq 1 $PINGLIMIT -j REJECT --reject-with icmp-port-unreachable"

# ________________________________________________________________________________________
# '-- 5: FORWARD Default Chain ----------------------------------------------------------'

# XXX DMZ should be able to access INT as if it were EXT

# ICMP Filters
f_rule "-A FORWARD -p icmp -j AllowICMP"

# DNAT
# For each forwarded port, we DNAT the port to the internal machine,
# then poke a hole through the filter table to forward traffic there

# SSH (DNAT target is expected to protect itself appropriately)
n_rule "-A PREROUTING -i $EXTDEV -p tcp --dport 22 -j DNAT --to-destination 192.168.0.14"
f_rule "-A FORWARD -o $INTDEV -p tcp --dport 22 -j SynDam"

# Internal web server (disabled for now)
#n_rule "-A PREROUTING -i $EXTDEV -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 192.168.0.16"
#f_rule "-A FORWARD -o $INTDEV -p tcp -m multiport --dports 80,443 -j SynDam"

# Bittorrent
#   athena
n_rule "-A PREROUTING -i $EXTDEV -p tcp --dport 49152 -j DNAT --to-destination 192.168.0.52"
f_rule "-A FORWARD -o $INTDEV -p tcp --dport 49152 -j SynDam"
n_rule "-A PREROUTING -i $EXTDEV -p udp --dport 49152 -j DNAT --to-destination 192.168.0.52"
f_rule "-A FORWARD -o $INTDEV -p udp --dport 49152 -j ACCEPT"
#   Fog
n_rule "-A PREROUTING -i $EXTDEV -p tcp --dport 28179 -j DNAT --to-destination 192.168.0.123"
f_rule "-A FORWARD -o $INTDEV -p tcp --dport 28179 -j SynDam"
n_rule "-A PREROUTING -i $EXTDEV -p udp --dport 28179 -j DNAT --to-destination 192.168.0.123"
f_rule "-A FORWARD -o $INTDEV -p udp --dport 28179 -j ACCEPT"
#   Ripley
n_rule "-A PREROUTING -i $EXTDEV -p tcp --dport 47255 -j DNAT --to-destination 192.168.0.122"
f_rule "-A FORWARD -o $INTDEV -p tcp --dport 47255 -j SynDam"
n_rule "-A PREROUTING -i $EXTDEV -p udp --dport 47255 -j DNAT --to-destination 192.168.0.122"
f_rule "-A FORWARD -o $INTDEV -p udp --dport 47255 -j ACCEPT"
#   Babymac
n_rule "-A PREROUTING -i $EXTDEV -p tcp --dport 51413 -j DNAT --to-destination 192.168.0.133"
f_rule "-A FORWARD -o $INTDEV -p tcp --dport 51413 -j SynDam"
n_rule "-A PREROUTING -i $EXTDEV -p udp --dport 51413 -j DNAT --to-destination 192.168.0.133"
f_rule "-A FORWARD -o $INTDEV -p udp --dport 51413 -j ACCEPT"

# ________________________________________________________________________________________
# '-- 6: OUTPUT Default Chain -----------------------------------------------------------'

# When possible, restrict outbound traffic to trusted hosts

# ICMP Filters
f_rule "-A OUTPUT -p icmp -j AllowICMP"

# Mail anywhere
f_rule "-A OUTPUT -p tcp --dport 25 -j ACCEPT"

# whois
f_rule "-A OUTPUT -p tcp --dport 43 -j ACCEPT"

# External DNS servers
f_rule "-A OUTPUT -p tcp --dport 53 -j ACCEPT"
f_rule "-A OUTPUT -p udp --dport 53 -j ACCEPT"

# DHCP Replies
f_rule "-A OUTPUT ! -o $EXTDEV -p udp --sport 67 --dport 68 -j ACCEPT"

# HTTP for patches
f_rule "-A OUTPUT -p tcp -m multiport --dports 80,443 -j ACCEPT"

# NTP peers
f_rule "-A OUTPUT -p udp --dport 123 -j ACCEPT"

# Syslog to log server
f_rule "-A OUTPUT -p tcp --dport 514 -j ACCEPT"

# APCUPSD Sharing
#f_rule "-A OUTPUT -d 192.168.0.10 -p tcp --dport 3551 -j ACCEPT"
#f_rule "-A OUTPUT -d 192.168.0.10 -p udp --dport 3551 -j ACCEPT"

# NUT UPS Sharing
f_rule "-A OUTPUT -d 192.168.0.14 -p tcp --dport 3493 -j ACCEPT"

# Allow UDP traceroutes out
f_rule "-A OUTPUT -p udp --dport 33434:33534 -j ACCEPT"

# ________________________________________________________________________________________
# '-- 7: Source NAT ---------------------------------------------------------------------'

if [[ $MY_SNATIP ]]; then
  n_rule "-A OUTPUT -j SNAT --to-source $MY_SNATIP"
  echo -n "SNAT Firewall Traffic to $MY_SNATIP"
  echo_success
fi
if [[ $INTNET_EXISTS ]]; then
  if [[ -z $INT_SNATIP ]]; then
    INT_SNATIP=$EXTIP
  fi
  n_rule "-A POSTROUTING -o $EXTDEV -s $INTNET -j SNAT --to-source $INT_SNATIP"
  echo -n "SNAT INTNET to $INT_SNATIP"
  echo_success
fi
if [[ $DMZNET_EXISTS ]]; then
  if [[ -z $DMZ_SNATIP ]]; then
    DMZ_SNATIP=$EXTIP
  fi
  n_rule "-A POSTROUTING -o $EXTDEV -s $DMZNET -j SNAT --to-source $DMZ_SNATIP"
  echo -n "SNAT DMZNET to $DMZ_SNATIP"
  echo_success
fi

# ________________________________________________________________________________________
# '-- 8: Default Chain Endings ----------------------------------------------------------'

# Silence chatty protocols to reduce log load
# Warning: these may make debugging more difficult by silently dropping traffic
f_table ":LogCleaner - [0:0]"
f_rule "-A INPUT -j LogCleaner"
f_rule "-A FORWARD -j LogCleaner"
f_rule "-A OUTPUT -j LogCleaner"

# Log everything else
f_rule "-A INPUT $LOGLIMIT -j LOG --log-prefix \"IPT EndOfINPUT \""
f_rule "-A FORWARD $LOGLIMIT -j LOG --log-prefix \"IPT EndOfFORWARD \""
f_rule "-A OUTPUT $LOGLIMIT -j LOG --log-prefix \"IPT EndOfOUTPUT \""

# Count everything that doesn't get accepted, silenced, or explicitly dropped
f_rule "-A INPUT -j DROP"
f_rule "-A FORWARD -j DROP"
f_rule "-A OUTPUT -j DROP"

# ________________________________________________________________________________________
# '-- 9a: Custom Chains: Bad Traffic ----------------------------------------------------'

f_table ":BadTraffic - [0:0]"

# Packets marked invalid from connection tracking
#f_rule "-A BadTraffic -m conntrack --ctstate INVALID $LOGLIMIT -j LOG --log-prefix \"IPT INVALID \""
f_rule "-A BadTraffic -m conntrack --ctstate INVALID -j DROP"

# Non-tracked TCP packets without SYN flag
#f_rule "-A BadTraffic -p tcp ! --syn -m conntrack --ctstate NEW $LOGLIMIT -j LOG --log-prefix \"IPT NewNotSYN \""
f_rule "-A BadTraffic -p tcp ! --syn -m conntrack --ctstate NEW -j DROP"

# Packets appearing on the wrong interface
# XXX these shouldn't be necessary with rp_filter on but it still matched packets last time.  keep an eye on it
if [[ "z$EXTDEV_ROUTABLE" == "zYES" ]]; then
  f_rule "-A BadTraffic -i $EXTDEV -s 0.0.0.0 -j MartianDrop"
  f_rule "-A BadTraffic -i $EXTDEV -s 10.0.0.0/8 -j MartianDrop"
  f_rule "-A BadTraffic -i $EXTDEV -s 172.16.0.0/12 -j MartianDrop"
  f_rule "-A BadTraffic -i $EXTDEV -s 192.168.0.0/16 -j MartianDrop"
fi
if [[ "$INTNET_EXISTS" ]]; then
  f_rule "-A BadTraffic -i $INTDEV -s 0.0.0.0 -j RETURN"
  f_rule "-A BadTraffic -i $INTDEV ! -s $INTNET -j MartianDrop"
fi
if [[ "$DMZNET_EXISTS" ]]; then
  f_rule "-A BadTraffic -i $DMZDEV -s 0.0.0.0 -j RETURN"
  f_rule "-A BadTraffic -i $DMZDEV ! -s $DMZNET -j MartianDrop"
fi
f_table ":MartianDrop - [0:0]"
f_rule "-A MartianDrop $LOGLIMIT -j LOG --log-prefix \"IPT Martian \""
f_rule "-A MartianDrop -j DROP"

# ________________________________________________________________________________________
# '-- 9b: Custom Chains: Ban Hammer -----------------------------------------------------'

f_table ":BanHammer - [0:0]"

# Example: Stop all traffic to/from a given host or subnet
#f_rule "-A BanHammer -s 192.246.40.28/32 -j DROP"
#f_rule "-A BanHammer -d 192.246.40.28/32 -j DROP"

# ________________________________________________________________________________________
# '-- 9c: Custom Chains: Allow ICMP -----------------------------------------------------'

f_table ":AllowICMP - [0:0]"

# Allow pings (8)
# Pongs (0) are treated as RELATED traffic and automatically allowed
f_rule "-A AllowICMP -p icmp --icmp-type 8 $PINGLIMIT -j ACCEPT"

# We don't really NEED to log and drop here, but it shortens the path through the chains
f_rule "-A AllowICMP $LOGLIMIT -j LOG --log-prefix \"IPT BadICMP \""
f_rule "-A AllowICMP -j DROP"

# ________________________________________________________________________________________
# '-- 9d: Custom Chains: SYN Flood Protector --------------------------------------------'

f_table ":SynDam - [0:0]"

# Allowed TCP traffic should jump here for SYN flood protection
f_rule "-A SynDam $SYNLIMIT -j ACCEPT"
f_rule "-A SynDam $LOGLIMIT -j LOG --log-prefix \"IPT SynFlood \""
f_rule "-A SynDam -j DROP"

# ________________________________________________________________________________________
# '-- 9z: Custom Chains: Log Cleaner ----------------------------------------------------'

#       25/tcp        SMTP
#       53/udp,tcp    DNS (useless)
#       67/udp        DHCP/BOOTP server (useless)
#       123/udp       NTP (time synchronization)
#       135/tcp       microsoft rpc (useless)
#       137-138/udp   SMB/NetBIOS stuff
#       139,445/tcp   SMB/NetBIOS stuff
#       192/udp       airport broadcasts/communication
#       631/udp,tcp   IPP (printing)
#       1026-1029/udp       Windows Messenger (spam)
#       1080/tcp            mydoom worm
#       1433-1434/udp       MS SQL (worm)
#       1947/udp            some sort of license server?
#       2745,5554,9898/tcp  (trojan)
#       6129,4899/tcp        remote admin tools (backdoor)
#       17500/udp            dropbox

f_rule "-A LogCleaner -p tcp -m multiport --dports 25,53,135,139,445,631,1080,1433,2745,4899,5554,6129,8443,9898 -j DROP"
f_rule "-A LogCleaner -p udp -m multiport --dports 53,67,123,137,138,192,631 -j DROP"
f_rule "-A LogCleaner -p udp -m multiport --dports 1026,1027,1028,1029,1433,1434,1947,17500 -j DROP"
f_rule "-A LogCleaner -p igmp -j DROP"

finalize_ruleset
}
# end of generate_rules()
