#!/bin/sh
### BEGIN INIT INFO
# Provides:          firewall
# Required-Start:    $remote_fs $rsyslog
# Required-Stop:     $remote_fs $rsyslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Firewall initscript
# Description:       Firewall script for iptables
#   To test the script you can just use 'test' arg:
# ./firewall test
# service firewall test
# Doc: * http://openvz.org/Using_NAT_for_container_with_private_IPs
#      * ...
### END INIT INFO

# Author: Gardouille

# **********************************************************************************************
#
# Global var
#
# -----------------------------------------------------------
# iptables path
IPT="/sbin/iptables"
# testing for XX seconds
TIME=42

#### Colors definition
export REDB='\033[1;31m'
export GREEN='\033[1;32m'
export WHITEB='\033[1;37m'
export RESET='\033[0m'


fw_init() {

  #############
  ## KERNEL  ##
  #############
  # Enable Cookie TCP SYN protection
  echo 1 > /proc/sys/net/ipv4/tcp_syncookies
  # Enable IP spoofing protection
  # Check the ip source
  for SYS in /proc/sys/net/ipv4/conf/*/rp_filter
  do
  echo 1 > ${SYS}
  done
  # Disable ICMP redirect
  for SYS in /proc/sys/net/ipv4/conf/*/accept_redirects
  do
  echo 0 > ${SYS}
  done
  # Disable source-route packages
  for SYS in /proc/sys/net/ipv4/conf/*/accept_source_route
  do
  echo 0 > ${SYS}
  done
  # Enable ip forwarding
  #echo 1 > /proc/sys/net/ipv4/ip_forward


  #############
  ## POLICY  ##
  #############

  # drop all traffic
  $IPT -P INPUT DROP
  $IPT -P FORWARD DROP
  $IPT -P OUTPUT DROP
  #$IPT -P INPUT ACCEPT
  #$IPT -P FORWARD ACCEPT
  #$IPT -P OUTPUT ACCEPT

  ############
  ##  BASE  ##
  ############

  # Drop all new connections without a syn flag
  $IPT -t filter -A INPUT -j DROP -p tcp ! --syn -m state --state NEW

  # Forbid local connections that doesn't come from localhost
  $IPT -A INPUT -j REJECT ! -i lo -d 127.0.0.1/8 -m comment --comment "Reject lo not from lo"
  # Allow loopback
  $IPT -A INPUT -j ACCEPT -i lo -m comment --comment "Loopback in"
  $IPT -A OUTPUT -j ACCEPT -o lo -m comment --comment "Loopback out"

  ##############
  ##  OUTPUT  ##
  ##############

  #### ICMP request (Ping)
  $IPT -A OUTPUT -j ACCEPT -p icmp -m state --state NEW -m comment --comment "ICMP out"

}

fw_start() {

  #############
  ##  INPUT  ##
  #############
  #### Pour tenter de s'y retrouver avec l'affichage des règles iptables, pour l'écriture des règles,
  #   respecter cet ordre:
  # -t TABLE -A CHAINE -j TARGET -p PROTOCOLE -i ETH_IN -o ETH_OUT -s SOURCE -d DESTINATION --sport PORT_SRC --dport PORT_DST -m state --state LIST_OF_STATE -m comment --comment "tiny DESCRIPTION"
  ####

  #### Ne pas casser les connexions etablies
  $IPT -A INPUT -j ACCEPT -p all -i "${ILAN}" -d "${IPLAN}" -m state --state RELATED,ESTABLISHED
#  $IPT -A FORWARD -j ACCEPT -p all -o "${IVM}" -d "${LANVM}" -m state --state RELATED,ESTABLISHED

  #####
#  $IPT -A INPUT -j ACCEPT -s 192.168.42.166 -d 192.168.42.1 -m comment --comment "TEST Rules"
#  $IPT -A OUTPUT -j ACCEPT -s 192.168.42.1 -d 192.168.42.166 -m comment --comment "TEST Rules"
#
#  $IPT -A INPUT -j ACCEPT -p icmp -s ${LANVM} -d "${IPLAN}" -m comment --comment "ICMP req LANVM"


  #### ICMP (Ping)
  # Accept all ping
  #$IPT -A INPUT -p icmp -j ACCEPT
  # Accept icmp ping from LAN
  #$IPT -A INPUT -j ACCEPT -p icmp -i "${ILAN}" -s ${LAN} -d "${IPLAN}" -m comment --comment "ICMP req LAN"

  if [ $(command -v sshd) ]; then
    #### SSHD
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 22 -m state --state NEW -m comment --comment "New SSH in"
  fi

  ## BackupPC
  $IPT -A INPUT -j ACCEPT -p icmp -i "${ILAN}" -s 192.168.0.3 -d "${IPLAN}" -m comment --comment "ICMP FURY req"
  $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -s 192.168.0.3 -d "${IPLAN}" --dport 22 -m state --state NEW -m comment --comment "New SSH fury in"

  if [ $(command -v apache2) ] || [ $(command -v nginx) ]; then
    #### Web server
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 80 -m state --state NEW -m comment --comment "New HTTP in"
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 443 -m state --state NEW -m comment --comment "New HTTPS in"
  fi

  if [ $(command -v slapd) ]; then
    #### slapd
    #### if 389 is use, ldap connections should be in TLS
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 389 -m state --state NEW -m comment --comment "New LDAP in"
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 636 -m state --state NEW -m comment --comment "New LDAPS in"
  fi

  if [ $(command -v dhcpd) ]; then
    #### dhcpd
    $IPT -A INPUT -j ACCEPT -p udp -i "${ILAN}" -d "${IPLAN}" --sport 67:68 --dport 67:68 -m state --state NEW -m comment --comment "New DHCPD in"
  fi

  if [ -f /etc/init.d/puppetmaster ] || [ -f /etc/systemd/system/puppetmaster.service ]; then
    #### PuppetMaster
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -s "${LAN}" -d "${IPLAN}" --dport 8140 -m state --state NEW -m comment --comment "New Puppet in"
  fi

  if [ $(command -v nfsdcltrack) ]; then
    #### NFS Server
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 111 -m state --state NEW -m comment --comment "NFS in"
    $IPT -A INPUT -j ACCEPT -p udp -i "${ILAN}" -d "${IPLAN}" --dport 111 -m state --state NEW -m comment --comment "NFS in"
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 2049 -m state --state NEW -m comment --comment "NFS "
    $IPT -A INPUT -j ACCEPT -p udp -i "${ILAN}" -d "${IPLAN}" --dport 2049 -m state --state NEW -m comment --comment "NFS in"
    # 32769: rpc.quotad
    # For more informations see: https://wiki.debian.org/SecuringNFS
    $IPT -A INPUT -j ACCEPT -p tcp -i "${ILAN}" -d "${IPLAN}" --dport 32769 -m state --state NEW -m comment --comment "NFS quotad in"
    $IPT -A INPUT -j ACCEPT -p udp -i "${ILAN}" -d "${IPLAN}" --dport 32769 -m state --state NEW -m comment --comment "NFS quotad in"
  fi

  #### tftp allowed
  #$IPT -A INPUT -j ACCEPT -p udp -i "${ILAN}" -d "${IPLAN}" --dport 69 -m state --state NEW -m comment --comment "TFTPD in"

  if [ $(command -v cupsd) ]; then
    #### Printers
    $IPT -A INPUT -j ACCEPT -p udp -i "${ILAN}" -d "${IPLAN}" --sport 161 -m state --state NEW -m comment --comment "SNMP IN"
  fi

  #########################
  ##  {Multi,Broad}cast  ##
  #########################
  #### DROP Multicast & broadcast
  $IPT -t mangle -A PREROUTING -j DROP -p udp -i "${ILAN}" -d 255.255.255.255 -m comment --comment "DROP Broadcast1"
  $IPT -t mangle -A PREROUTING -j DROP -p udp -i "${ILAN}" -d 129.20.27.255 -m comment --comment "DROP Broadcast2"
  $IPT -t mangle -A PREROUTING -j DROP -p udp -i "${ILAN}" -d 129.20.255.255 -m comment --comment "DROP Broadcast3"
  $IPT -t mangle -A PREROUTING -j DROP -p udp -i "${ILAN}" -d 224.0.0.1 -m comment --comment "DROP Multicast1"
  $IPT -t mangle -A PREROUTING -j DROP -p udp -i "${ILAN}" -d 224.0.0.251 -m comment --comment "DROP Multicast2"



  ##############
  ##  OUTPUT  ##
  ##############
  #### Ne pas casser les connexions etablies
  $IPT -A OUTPUT -j ACCEPT -p all -o "${ILAN}" -s "${IPLAN}" -m state --state RELATED,ESTABLISHED,UNTRACKED
#
#  #### ICMP reply (Ping)
#  #$IPT -A OUTPUT -j ACCEPT -p icmp -o "${ILAN}" --icmp-type 0 -s "${IPLAN}" -d 0/0 -m state --state ESTABLISHED,RELATED -m comment --comment "ICMP reply"


  if [ $(command -v ssh) ]; then
    #### SSH
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" --dport 22 -m state --state NEW -m comment --comment "SSH out"
  fi

  #### Mail SMTP
  ## Port 465 (SMTPS SSL) is deprecated)
  $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" --dport 25 -m state --state NEW -m comment --comment "SMTP out"
  $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" --dport 587 -m state --state NEW -m comment --comment "SMTP TLS out"

  #### Mail IMPA
  $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" --dport 143 -m state --state NEW -m comment --comment "IMAP out"
  $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" --dport 993 -m state --state NEW -m comment --comment "IMAPS out"

  #### DNS (résolution de noms de domaines, ... ...)
  $IPT -A OUTPUT -j ACCEPT -p udp -o ${ILAN} --dport 53 -m state --state NEW -m comment --comment "DNS out udp"
  $IPT -A OUTPUT -j ACCEPT -p tcp -o ${ILAN} --dport 53 -m state --state NEW -m comment --comment "DNS out tcp"

  if [ $(command -v dhclient) ]; then
    #### DHCP
    $IPT -A OUTPUT -j ACCEPT -p udp -o ${ILAN} -s "${IPLAN}" --sport 68 -m comment --comment "DHCPREQUEST"
  fi

  #### HTTP (maj, ...)
  $IPT -A OUTPUT -j ACCEPT -p tcp -o ${ILAN} --dport 80 -m state --state NEW -m comment --comment "HTTP out"
  $IPT -A OUTPUT -j ACCEPT -p tcp -o ${ILAN} --dport 443 -m state --state NEW -m comment --comment "HTTPS out"

  if [ $(command -v ntpd) ]; then
    #### NTP
    $IPT -A OUTPUT -j ACCEPT -p udp -o ${ILAN} --dport 123 -m state --state NEW -m comment --comment "NTP out"
  fi

  if [ $(command -v puppet) ]; then
    #### Puppet (connection, ... )
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" --dport 8140 -m state --state NEW -m comment --comment "Puppet out"
  fi

  #### OpenPGP HTTP key server (add key, maj, ...)
  $IPT -A OUTPUT -j ACCEPT -p tcp -o ${ILAN} --dport 11371 -m state --state NEW -m comment --comment "OpenPGP req"

  if [ $(command -v apache2) ] || [ $(command -v nginx) ]; then
    #### Web server
    $IPT -A OUTPUT -j ACCEPT -p tcp -o ${ILAN} --sport 80 -m state --state NEW -m comment --comment "New HTTPD out"
    $IPT -A OUTPUT -j ACCEPT -p tcp -o ${ILAN} --sport 443 -m state --state NEW -m comment --comment "New HTTPDs out"
  fi

  if [ -d /etc/ldap ]; then
    #### ldap connection should be in TLS or at least in LDAPS/SSL
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" -s "${IPLAN}" --dport 389 -m state --state NEW -m comment --comment "LDAP out"
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" -s "${IPLAN}" --dport 636 -m state --state NEW -m comment --comment "LDAPS out"
  fi

  if [ $(command -v dhcpd) ]; then
    #### dhcpd
    $IPT -A OUTPUT -j ACCEPT -p udp -o "${ILAN}" -s "${IPLAN}" --sport 67:68 --dport 67:68 -m state --state NEW -m comment --comment "DHCPD out"
  fi

  if [ $(command -v nfsiostat) ]; then
    #### NFS Client
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" -s "${IPLAN}" --dport 111 -m state --state NEW -m comment --comment "NFS out"
    $IPT -A OUTPUT -j ACCEPT -p udp -o "${ILAN}" -s "${IPLAN}" --dport 111 -m state --state NEW -m comment --comment "NFS out"
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" -s "${IPLAN}" --dport 2049 -m state --state NEW -m comment --comment "NFS out"
    $IPT -A OUTPUT -j ACCEPT -p udp -o "${ILAN}" -s "${IPLAN}" --dport 2049 -m state --state NEW -m comment --comment "NFS out"
    # List of ports to use given by NFS server
    # For more informations see: https://wiki.debian.org/SecuringNFS
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" -s "${IPLAN}" --dport 32767 -m state --state NEW -m comment --comment "NFS mountd out"
    $IPT -A OUTPUT -j ACCEPT -p udp -o "${ILAN}" -s "${IPLAN}" --dport 32767 -m state --state NEW -m comment --comment "NFS mountd out"
  fi

  if [ $(command -v cupsd) ]; then
    #### Printers
    $IPT -A OUTPUT -j ACCEPT -p udp -o "${ILAN}" -s "${IPLAN}" --dport 161 -m state --state NEW -m comment --comment "SNMP OUT"
    #### HP Printers
    $IPT -A OUTPUT -j ACCEPT -p tcp -o "${ILAN}" -s "${IPLAN}" --dport 9100 -m state --state NEW -m comment --comment "HP printer OUT"
  fi

}

# Règles pour de log
fw_log() {

  #############
  ##   LOG   ##
  #############

  # LOG INPUT DROP PAQUET
  $IPT -N INPLOG
  $IPT -A INPUT -j INPLOG
  $IPT -A INPLOG -p tcp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-IN [tcp]: "
  $IPT -A INPLOG -p udp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-IN [udp]: "
  $IPT -A INPLOG -p icmp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-IN [icmp]: "

  # LOG OUTPUT DROP PAQUET
  $IPT -N OUTLOG
  $IPT -A OUTPUT -j OUTLOG
  $IPT -A OUTLOG -p tcp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-OUT [tcp]: "
  $IPT -A OUTLOG -p udp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-OUT [udp]: "
  $IPT -A OUTLOG -p icmp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-OUT [icmp]: "

  # LOG FORWARD DROP PAQUET
  $IPT -N FORLOG
  $IPT -A FORWARD -j FORLOG
  $IPT -A FORLOG -p tcp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-FOR [tcp]: "
  $IPT -A FORLOG -p udp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-FOR [udp]: "
  $IPT -A FORLOG -p icmp -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "Drop-FOR [icmp]: "

}


# Arrêt du firewall
fw_stop() {
  # Supprimer une route ajouter automatiquement
  #ip route del 169.254.0.0/16
  # Vider les tables actuelles
  $IPT -t filter -F
  # Vider les règles personnelles
  $IPT -t filter -X
  $IPT -t nat -F
  $IPT -t nat -X
  $IPT -t mangle -F
  $IPT -t mangle -X
  $IPT -P INPUT ACCEPT
  $IPT -P FORWARD ACCEPT
  $IPT -P OUTPUT ACCEPT
  $IPT -t nat -P PREROUTING ACCEPT
  $IPT -t nat -P OUTPUT ACCEPT
  $IPT -t nat -P POSTROUTING ACCEPT
  $IPT -t mangle -P PREROUTING ACCEPT
  $IPT -t mangle -P INPUT ACCEPT
  $IPT -t mangle -P FORWARD ACCEPT
  $IPT -t mangle -P OUTPUT ACCEPT
  $IPT -t mangle -P POSTROUTING ACCEPT

}

# VPN
fw_vpn() {
  # Allow all traffic throught VPN
  $IPT -I INPUT -j ACCEPT -p all -i "${ILAN}" -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "VPN in"
  $IPT -I OUTPUT -j ACCEPT -p all -o "${ILAN}" -m state --state NEW,RELATED,ESTABLISHED,UNTRACKED -m comment --comment "VPN out"
}

# **********************************************************************************************
#
# Programme principale
#
# -----------------------------------------------------------

case "${1}" in
  start|restart)
    printf '%s\n' "Start firewall …"
    fw_stop
    fw_init
    # List all available interface except localhost
    for PATH_ILAN in $(find /sys/class/net/ ! \( -name lo -o -iname "tun*" \) -type l); do
      # Interface name
      ILAN=$(basename ${PATH_ILAN})
      IS_UP=$(grep 1 ${PATH_ILAN}/carrier)

      # Test if interface is connected
      if [ ${IS_UP} ]; then
        # Interface IP
        IPLAN=$(ip route|grep -v "default"|grep ${ILAN}|grep src|awk '{print $NF}')
        # IP/MASK
        #IPLAN=$(ip a s "${ILAN}"|grep "inet "|awk '{print $2}')
        # Interface LAN
        LAN=$(ip route|grep -v "default"|grep "${IPLAN}"|awk '{print $1}')

        printf '%b' "${WHITEB}${ILAN} ${GREEN}connected${RESET}: \t${IPLAN} \ton ${LAN}\n"

        # Load rules for this interface
        fw_start
      else
        printf '%b' "${WHITEB}${ILAN} ${REDB}disconnected${RESET}\n"
      fi
    done

    # If exist, load local rules file
    if [ -f "${0}.local" ]; then
      printf '%b' "Load local rules file\n"
      ${0}.local
    fi

    fw_log
    ;;
  stop)
    printf '%s\n' "Clean all firewall rules"
    fw_stop
    ;;
  test)
    printf '%b' "Load firewall rules for ${TIME} secondes …\n"

    $0 start
    sleep ${TIME}
    fw_stop
    ;;
  vpn)
    printf '%s\n' "Special rules for VPN interfaces (TUN or TAP)"
    for PATH_ILAN in $(find /sys/class/net/ \( -iname "*tun*" -o -iname "*tap*" \) -type l ); do
      ILAN=$(basename ${PATH_ILAN})
      IS_UP=$(grep 1 ${PATH_ILAN}/carrier)

      if [ ${IS_UP} ]; then
        # IP
        IPLAN=$(ip route|grep -v "default"|grep ${ILAN}|grep src|awk '{print $NF}')
        # IP/MASK
        #IPLAN=$(ip a s "${ILAN}"|grep "inet "|awk '{print $2}')
        LAN=$(ip route|grep -v "default"|grep "${IPLAN}"|awk '{print $1}')

        printf '%b' "${WHITEB}${ILAN} ${GREEN}connected${RESET}: \t${IPLAN} \ton ${LAN}\n"

        # Load special rules for this interface
        fw_vpn
      else
        printf '%b' "${WHITEB}${ILAN} ${REDB}disconnected${RESET}\n"
      fi
    done
    ;;
  *)
    echo "Usage: firewall ({start|stop|restart|test})"
    exit 1
    ;;
esac

# Fin du script
exit 0

# Fin de la boucle principale
# -----------------------------------------------------------
