bindir=$(prefix)/bin
sbindir=$(prefix)/sbin
libdir=$(prefix)/lib
+datadir=$(prefix)/share
sysconfdir=/etc
localstatedir=/var
systemdunitdir=$(prefix)/lib/systemd/system
-mkdir -pv $(DESTDIR)$(sbindir)
-mkdir -pv $(DESTDIR)$(systemdunitdir)
-mkdir -pv $(DESTDIR)$(tmpfilesdir)
+ -mkdir -pv $(DESTDIR)$(datadir)/firewall
install -m 755 -v firewall $(DESTDIR)$(sbindir)
install -m 755 -v network $(DESTDIR)$(sbindir)
# Install pppoe-server wrapper.
install -m 755 ppp/pppoe-server $(DESTDIR)$(libdir)/network/
+ # Install the firewall macros.
+ cp -av macros $(DESTDIR)$(datadir)/firewall/
+
# Create the version file.
: > $(VERSION_FILE)
echo "# This file is automatically generated." >> $(VERSION_FILE)
. /usr/lib/network/functions
function cli_start() {
- firewall_start
+ firewall_start $@
}
function cli_stop() {
firewall_stop
}
+function cli_show() {
+ firewall_show $@
+}
+
+function cli_panic() {
+ if cli_help_requested $@; then
+ cli_show_man firewall-panic
+ exit ${EXIT_OK}
+ fi
+
+ local admin_hosts
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ *)
+ if ip_is_valid ${1}; then
+ admin_hosts="${admin_hosts} ${1}"
+ else
+ warning "Invalid IP address: ${1}"
+ fi
+ ;;
+ esac
+ shift
+ done
+
+ firewall_panic ${admin_hosts}
+}
+
function cli_config() {
if cli_help_requested $@; then
cli_usage root-config
cli_stop $@
;;
+ show)
+ cli_show $@
+ ;;
+
+ panic)
+ cli_panic $@
+ ;;
+
config)
cli_config $@
;;
FIREWALL_CONFIG_DIR="/etc/firewall"
FIREWALL_ZONES_DIR="${FIREWALL_CONFIG_DIR}/zones"
FIREWALL_CONFIG_FILE="${FIREWALL_CONFIG_DIR}/config"
-FIREWALL_CONFIG_PORTFW="${FIREWALL_CONFIG_DIR}/portfw"
+FIREWALL_CONFIG_RULES="${FIREWALL_CONFIG_DIR}/rules"
-FIREWALL_CONFIG_PARAMS=""
+FIREWALL_MACROS_DIRS="${FIREWALL_CONFIG_DIR}/macros /usr/share/firewall/macros"
-FIREWALL_LOG_FACILITY="syslog"
+FIREWALL_CONFIG_PARAMS="FIREWALL_LOG_METHOD FIREWALL_NFLOG_THRESHOLD"
+
+FIREWALL_LOG_METHOD="nflog"
+FIREWALL_NFLOG_THRESHOLD=30
# High-level function which will create a ruleset for the current firewall
# configuration and load it into the kernel.
function firewall_start() {
+ # Test mode.
+ local test="false"
+
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ --test)
+ test="true"
+ ;;
+ esac
+ shift
+ done
+
+ if enabled test; then
+ log INFO "Test mode enabled."
+ log INFO "The firewall ruleset will not be loaded."
+ fi
+
firewall_lock_acquire
# Initialize an empty iptables ruleset.
policy_add_zone ${zone}
done
- # Commit the new ruleset.
- iptables_commit
+ # Load the new ruleset.
+ iptables_load ${test}
firewall_lock_release
}
# with default policy ACCEPT.
iptables_init ACCEPT
- # Commit it.
- iptables_commit
+ # Load it.
+ iptables_load
+
+ firewall_lock_release
+}
+
+function firewall_show() {
+ # Shows the ruleset that is currently loaded.
+ iptables_status
+
+ return ${EXIT_OK}
+}
+
+function firewall_panic() {
+ local admin_hosts="$@"
+
+ firewall_lock_acquire
+
+ # Drop all communications.
+ iptables_init DROP
+
+ # If an admin host is provided, some administrative
+ # things will be allowed from there.
+ local admin_host
+ for admin_host in ${admin_hosts}; do
+ iptables -A INPUT -s ${admin_host} -j ACCEPT
+ iptables -A OUTPUT -d ${admin_host} -j ACCEPT
+ done
+
+ # Load it.
+ iptables_load
firewall_lock_release
}
iptables -A FORWARD -j CONNTRACK
}
-function firewall_import_portfw() {
+function firewall_import_rules() {
local zone=${1}
shift
local protocol="ipv6"
- local chain="filter"
+ local table="filter"
while [ $# -gt 0 ]; do
case "${1}" in
- --chain=*)
- chain=$(cli_get_val ${1})
+ --table=*)
+ table=$(cli_get_val ${1})
;;
--protocol=*)
protocol=$(cli_get_val ${1})
done
assert isoneof protocol ipv4 ipv6
-
- local allowed_chains="filter"
- if [ "${protocol}" = "ipv4" ]; then
- allowed_chains="${allowed_chains} nat"
- fi
- assert isoneof chain ${allowed_chains}
+ assert isoneof table $(iptables_table ${protocol})
# XXX TODO
nat)
;;
esac
- done < ${FIREWALL_CONFIG_PORTFW}
+ done < ${FIREWALL_CONFIG_RULES}
}
-
-
listmatch ${proto} ${IP_SUPPORTED_PROTOCOLS}
}
+function ip_is_valid() {
+ local address=${1}
+ assert isset address
+
+ local proto=$(ip_detect_protocol ${address})
+ isset proto && return ${EXIT_TRUE} || return ${EXIT_FALSE}
+}
+
function ip_address_add() {
local device=${1}
local address=${2}
function iptables() {
local arg
local args
- local table
+ local table="filter"
+ local src dst
+
+ # Default is both protocols.
+ local proto="6 4"
# Check if the directory where we put our rules in is set and
# exists.
assert isset IPTABLES_TMPDIR
assert [ -d "${IPTABLES_TMPDIR}" ]
- table=filter
-
# Parsing arguments
while [ $# -gt 0 ]; do
case "${1}" in
+ # Select IPv4 protocol.
+ -4)
+ proto="4"
+ shift
+ ;;
+ # Select IPv6 protocol.
+ -6)
+ proto="6"
+ shift
+ ;;
-t)
table=${2}
shift 2
;;
*)
args="${args} ${1}"
+
+ # Save some values for further processing.
+ case "${1}" in
+ -s)
+ src="${2}"
+ ;;
+ -d)
+ dst="${2}"
+ ;;
+ esac
shift
;;
esac
done
- echo "${args:1:${#args}}" >> ${IPTABLES_TMPDIR}/${table}
+ # Check that the nat table is not used for IPv6.
+ if isoneof 6 ${proto}; then
+ assert [ "${table}" != "nat" ]
+ fi
+
+ # Detect the version of the IP protocol.
+ local src_proto
+ isset src && src_proto=$(ip_detect_protocol ${src})
+
+ local dst_proto
+ isset dst && dst_proto=$(ip_detect_protocol ${dst})
+
+ # Check that the source and destinations are not
+ # using different versions of the IP protocol.
+ if isset src_proto && isset dst_proto; then
+ assert [ "${src_proto}" = "${dst_proto}" ]
+ fi
+
+ local rulesfile
+ local p
+ for p in ${proto}; do
+ case "${p}" in
+ 6)
+ listmatch ipv4 ${src_proto} ${dst_proto} \
+ && continue
+ ;;
+ 4)
+ listmatch ipv6 ${src_proto} ${dst_proto} \
+ && continue
+ ;;
+ esac
+
+ rulesfile=$(iptables_rulesfile ipv${p} ${table})
+ print "${args:1:${#args}}" >> ${rulesfile}
+ done
+}
+
+# Calls the binary iptables command.
+function _iptables() {
+ local iptables_cmd=$(which iptables)
+ assert isset iptables_cmd
+
+ ${iptables_cmd} $@
+}
+
+function iptables_status() {
+ _iptables -L -n -v
+}
+
+# Returns which tables exist for the given protocol.
+function iptables_tables() {
+ local proto=${1}
+ local file
+
+ case "${proto}" in
+ ipv6)
+ file="/proc/net/ip6_tables_names"
+ ;;
+ ipv4)
+ file="/proc/net/ip_tables_names"
+ ;;
+ *)
+ return ${EXIT_ERROR}
+ ;;
+ esac
+
+ assert [ -r "${file}" ]
+
+ print "$(<${file})"
+ return ${EXIT_OK}
+}
+
+function iptables_rulesfile() {
+ local proto=${1}
+ proto=${proto/ipv/}
+
+ local chain=${2}
+ [ -z "${chain}" ] && chain="ruleset"
+
+ print "${IPTABLES_TMPDIR}/${chain}${proto}"
}
function iptables_init() {
local policy=${1}
- assert isoneof policy ACCEPT DROP
+ assert isset policy
+ # Create filter table and initialize chains.
iptables "* filter"
- iptables_chain_create -t filter INPUT ${policy}
- iptables_chain_create -t filter OUTPUT ${policy}
- iptables_chain_create -t filter FORWARD ${policy}
+ iptables_chain_create -t filter INPUT --policy=${policy}
+ iptables_chain_create -t filter OUTPUT --policy=${policy}
+ iptables_chain_create -t filter FORWARD --policy=${policy}
+ # Create mangle table initialize chains.
iptables -t mangle "* mangle"
- iptables_chain_create -t mangle PREROUTING ACCEPT
- iptables_chain_create -t mangle INPUT ACCEPT
- iptables_chain_create -t mangle OUTPUT ACCEPT
- iptables_chain_create -t mangle FORWARD ACCEPT
- iptables_chain_create -t mangle POSTROUTING ACCEPT
+ iptables_chain_create -t mangle PREROUTING --policy=ACCEPT
+ iptables_chain_create -t mangle INPUT --policy=ACCEPT
+ iptables_chain_create -t mangle OUTPUT --policy=ACCEPT
+ iptables_chain_create -t mangle FORWARD --policy=ACCEPT
+ iptables_chain_create -t mangle POSTROUTING --policy=ACCEPT
- iptables -t nat "* nat"
- iptables_chain_create -t nat PREROUTING ACCEPT
- iptables_chain_create -t nat OUTPUT ACCEPT
- iptables_chain_create -t nat POSTROUTING ACCEPT
+ # Add NAT table for IPv4.
+ iptables -4 -t nat "* nat"
+ iptables_chain_create -4 -t nat PREROUTING --policy=ACCEPT
+ iptables_chain_create -4 -t nat OUTPUT --policy=ACCEPT
+ iptables_chain_create -4 -t nat POSTROUTING --policy=ACCEPT
}
-function iptables_commit() {
- local chain
+# Load the created ruleset into the kernel.
+function iptables_load() {
+ # If first argument is present and true, we
+ # run in test mode.
+ local test="${1}"
- # Check if the directory where we put our rules in is set and
- # exists.
- assert isset IPTABLES_TMPDIR
- assert [ -d "${IPTABLES_TMPDIR}" ]
+ local rulesfile
- log INFO "Committing firewall configuration..."
+ # First, commit all tables.
+ _iptables_commit
+
+ # Concat the table rulesets into one big file.
+ local proto
+ for proto in 6 4; do
+ rulesfile=$(iptables_rulesfile ipv${proto})
+
+ local table
+ local tablefile
+ for table in $(iptables_tables ipv${proto}); do
+ tablefile=$(iptables_rulesfile ipv${proto} ${table})
+ print "$(<${tablefile})"
+ done > ${rulesfile}
+ done
+
+ local error="false"
+ local ret
+
+ # First check if everything is correctly formatted.
+ for proto in 6 4; do
+ rulesfile=$(iptables_rulesfile ipv${proto})
+
+ _iptables_load ipv${proto} ${rulesfile} true
+ if [ $? -ne ${EXIT_OK} ]; then
+ log CRITICAL "Ruleset load check failed for IPv${proto}"
+ error="true"
+ fi
+ done
+
+ # Check if there has been an error in the load check.
+ if enabled error; then
+ iptables_dump CRITICAL
+
+ log CRITICAL "New firewall rules could not be loaded."
+ return ${EXIT_ERROR}
+ fi
+
+ # Dump the data, we are going to load.
+ iptables_dump
+
+ # If we are running in test mode, we are done here.
+ enabled test && return ${EXIT_OK}
+
+ # If we got until here, everything is fine to load the ruleset.
+ for proto in 6 4; do
+ rulesfile=$(iptables_rulesfile ipv${proto})
+
+ _iptables_load ipv${proto} ${rulesfile}
+ done
+ return ${EXIT_OK}
+}
+
+# Commit all tables.
+function _iptables_commit() {
iptables -t filter "COMMIT"
iptables -t mangle "COMMIT"
- iptables -t nat "COMMIT"
- local iptables_ruleset="${IPTABLES_TMPDIR}/commit"
- : > ${iptables_ruleset}
+ # Commit NAT chain for IPv4.
+ iptables -4 -t nat "COMMIT"
+}
+
+function _iptables_load() {
+ local proto=${1}
+ local file=${2}
+ local testmode=${3}
- # Concat the rules for every chain into one file.
- local table
- for table in filter mangle nat; do
- cat ${IPTABLES_TMPDIR}/${table} \
- >> ${iptables_ruleset} 2>/dev/null
- done
+ local command
+ case "${proto}" in
+ ipv6)
+ command="ip6tables-restore"
+ ;;
+ ipv4)
+ command="iptables-restore"
+ ;;
+ esac
+ assert isset command
+
+ if enabled testmode; then
+ command="${command} --test"
+ fi
+
+ local time_started=$(date -u "+%s")
+
+ cmd ${command} < ${file}
+ local ret=$?
+
+ case "${ret}" in
+ ${EXIT_OK})
+ local time_finished=$(date -u "+%s")
+ time_finished=$(( ${time_finished} - ${time_started} ))
+ log INFO \
+ "Successfully loaded new firewall ruleset for IPv${proto/ipv/} in ${time_finished}s!"
+ ;;
+ *)
+ if ! enabled testmode; then
+ log CRITICAL "Error loading firewall ruleset for IPv${proto/ipv/}!"
+ fi
+ ;;
+ esac
+
+ return ${ret}
+}
+
+function iptables_dump() {
+ local log_facility=${1-DEBUG}
+
+ # Here is nothing to do, if we are not running in
+ # debug mode.
+ enabled DEBUG || return ${EXIT_OK}
- log DEBUG "Dumping iptables ruleset"
- local counter=1
+ local rulesfile
+ local counter
local line
- while read line; do
- line=$(printf "%4d | %s\n" "${counter}" "${line}")
- log DEBUG "${line}"
- counter=$(( $counter + 1 ))
- done < ${iptables_ruleset}
-
- iptables-restore < ${iptables_ruleset}
+ local proto
+ for proto in 6 4; do
+ rulesfile=$(iptables_rulesfile ipv${proto})
+ [ -e "${rulesfile}" ] || continue
+
+ log ${log_facility} "Firewall ruleset for IPv${proto}:"
+
+ counter=1
+ while read line; do
+ line=$(print "%4d | %s" "${counter}" "${line}")
+ log ${log_facility} "${line}"
+
+ counter=$(( $counter + 1 ))
+ done < ${rulesfile}
+ done
}
function iptables_chain_create() {
+ local chain
+ local table="filter"
+ local policy="-"
+ local proto
local args
- if [ "${1}" = "-t" ]; then
- args="${1} ${2}"
- shift 2
- fi
+ while [ $# -gt 0 ]; do
+ case "${1}" in
+ -6|-4)
+ proto=${1}
+ ;;
+ -t)
+ table=${2}
+ shift
+ ;;
+ --policy=*)
+ policy=$(cli_get_val ${1})
+ ;;
+ *)
+ chain=${1}
+ ;;
+ esac
+ shift
+ done
+
+ assert isset chain
+ assert isset table
+ assert isoneof policy ACCEPT DROP "-"
- iptables ${args} ":$1 ${2--} [0:0]"
+ iptables ${proto} -t ${table} ":${chain} ${policy} [0:0]"
}
function iptables_LOG() {
local prefix=${1}
+ local ret
- if [ "${FIREWALL_LOG_FACILITY}" = "syslog" ]; then
- echo -n "LOG"
- [ -n "$prefix" ] && echo -n " --log-prefix \"$prefix\""
- else
- echo -n "NFLOG"
- [ -n "$prefix" ] && echo -n " --nflog-prefix \"$prefix\""
- echo -n " --nflog-threshold 30"
- fi
- echo
+ case "${FIREWALL_LOG_METHOD}" in
+ nflog)
+ ret="NFLOG --nflog-threshold ${FIREWALL_NFLOG_THRESHOLD}"
+ isset prefix && ret="${ret} --nflog-prefix \"$prefix\""
+ ;;
+ syslog)
+ ret="LOG"
+ isset prefix && ret="${ret} --log-prefix \"$prefix\""
+ ;;
+ esac
+
+ print "${ret}"
}
function iptables_protocol() {
--- /dev/null
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2012 IPFire Network Development Team #
+# #
+# 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 3 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, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+function macro_file() {
+ local macro=${1}
+ assert isset macro
+
+ # Make the name uppercase.
+ macro=${macro^^}
+
+ local file
+ local dir
+ for dir in ${FIREWALL_MACROS_DIRS}; do
+ file="${dir}/${macro}"
+
+ if [ -r "${file}" ]; then
+ print "${file}"
+ return ${EXIT_OK}
+ fi
+ done
+
+ return ${EXIT_ERROR}
+}
+
+function macro_exists() {
+ local macro=${1}
+ assert isset macro
+
+ macro_file ${macro} &>/dev/null \
+ && return ${EXIT_TRUE} || return ${EXIT_FALSE}
+}
+
+function macro_read() {
+ local macro=${1}
+ assert isset macro
+
+ # Make the name uppercase.
+ macro=${macro^^}
+
+ local file=$(macro_file ${macro})
+ assert isset file
+
+ log DEBUG "Parsing macro '${macro}' (${file})."
+
+ local src dst
+ local sport dport
+ local proto
+ local var
+ local line
+
+ while read src dst proto sport dport; do
+ # Skip lines that start with a "#".
+ [ "${src:0:1}" = "#" ] && continue
+
+ if [ "${src}" = "INCLUDE" ]; then
+ macro_read ${dst}
+ continue
+ fi
+
+ line=""
+
+ # Remove all the dashes.
+ for var in src dst proto sport dport; do
+ [ "${!var}" = "-" ] && continue
+
+ line="${line} ${var}=\"${!var}\""
+ done
+
+ line="$(echo ${line})"
+ print "${line}"
+ done < ${file}
+}
iptables_chain_create ${chain}_IPS
iptables -A ${chain} -i ${zone} -j ${chain}_IPS
- # Port forwarding chain.
- iptables_chain_create ${chain}_PORTFW
- iptables -A ${chain} -i ${zone} -j ${chain}_PORTFW
+ # Rules for incoming packets.
+ iptables_chain_create ${chain}_RULES_INC
+ iptables -A ${chain} -i ${zone} -j ${chain}_RULES_INC
- # Outgoing firewall
- iptables_chain_create ${chain}_OUTFW
- iptables -A ${chain} -o ${zone} -j ${chain}_OUTFW
+ # Rules for outgoing packets.
+ iptables_chain_create ${chain}_RULES_OUT
+ iptables -A ${chain} -o ${zone} -j ${chain}_RULES_OUT
# Policy rules
iptables_chain_create ${chain}_POLICY
iptables -t mangle -A ${chain} -o ${zone} -j ${chain}_QOS_OUT
# Create NAT chain.
- iptables_chain_create -t nat ${chain}
- iptables -t nat -A PREROUTING -i ${zone} -j ${chain}
- iptables -t nat -A POSTROUTING -o ${zone} -j ${chain}
+ iptables_chain_create -4 -t nat ${chain}
+ iptables -4 -t nat -A PREROUTING -i ${zone} -j ${chain}
+ iptables -4 -t nat -A POSTROUTING -o ${zone} -j ${chain}
# Network Address Translation
- iptables_chain_create -t nat ${chain}_NAT
- iptables -t nat -A ${chain} -i ${zone} -j ${chain}_NAT
+ iptables_chain_create -4 -t nat ${chain}_NAT
+ iptables -4 -t nat -A ${chain} -i ${zone} -j ${chain}_NAT
- # Portforwarding
- iptables_chain_create -t nat ${chain}_PORTFW
- iptables -t nat -A ${chain} -i ${zone} -j ${chain}_PORTFW
+ # Port forwarding
+ iptables_chain_create -4 -t nat ${chain}_PORTFW
+ iptables -4 -t nat -A ${chain} -i ${zone} -j ${chain}_PORTFW
# UPNP
- iptables_chain_create -t nat ${chain}_UPNP
- iptables -t nat -A ${chain} -j ${chain}_UPNP
+ iptables_chain_create -4 -t nat ${chain}_UPNP
+ iptables -4 -t nat -A ${chain} -j ${chain}_UPNP
# After the chains that are always available have been
# created, we will add a custom policy to every single
else
: # XXX TODO
fi
+
+ # Import all configured rules and those things.
+ policy_import_all_rules ${zone} ${chain}
}
function policy_add_localhost() {
# Just accept everything.
iptables -A ${chain}_POLICY -j ACCEPT
}
+
+function policy_drop_all() {
+ # Nothing to do here, because that is the
+ # default policy of the INPUT/OUTPUT/FORWARD chain.
+ :
+}
+
+function policy_import_all_rules() {
+ # This will populate all chains with the rules
+ # for the given zone.
+
+ local zone=${1}
+ assert isset zone
+
+ local chain=${2}
+ assert isset chain
+
+ # XXX TODO
+}
--- /dev/null
+# IPFire Macro
+# This macro handles the dynamic host configuration protocol.
+# SRC DST PROTO LOCAL_PORT REMOTE_PORT
+- - tcp 68 67
+- - udp 68 67
--- /dev/null
+# IPFire Macro
+# This macro handles plaintext HTTP (WWW) traffic.
+# SRC DST PROTO LOCAL_PORT REMOTE_PORT
+- - tcp - 80
--- /dev/null
+# IPFire Macro
+# This macro handles secure HTTP (WWW) traffic.
+# SRC DST PROTO LOCAL_PORT REMOTE_PORT
+- - tcp - 443
--- /dev/null
+# IPFire Macro
+# This macro handles WWW traffic.
+# SRC DST PROTO SRC_PORT DST_PORT
+INCLUDE HTTP
+INCLUDE HTTPS