]> git.ipfire.org Git - people/ms/network.git/blobdiff - functions.firewall
Exit if the check functionality of a hook fails.
[people/ms/network.git] / functions.firewall
index 56a840f10221235ffacfabc3873702f990f692d1..3f0d5ff85394e039c222d1add7878a2598ab1a25 100644 (file)
 # High-level function which will create a ruleset for the current firewall
 # configuration and load it into the kernel.
 function firewall_start() {
+       local protocol="${1}"
+       assert isset protocol
+       shift
+
+       # Test mode.
+       local test="false"
+
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       --test)
+                               test="true"
+                               ;;
+                       *)
+                               error "Unrecognized argument: ${1}"
+                               return ${EXIT_ERROR}
+                               ;;
+               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.
-       iptables_init DROP
+       iptables_init "${protocol}" "DROP"
 
        # Add default chains.
-       firewall_tcp_state_flags
-       firewall_connection_tracking
+       firewall_tcp_state_flags "${protocol}"
+       firewall_custom_chains "${protocol}"
+       firewall_connection_tracking "${protocol}"
+       firewall_tcp_clamp_mss "${protocol}"
 
        # Add policies for every zone.
-       policy_add_localhost
+       firewall_localhost_create_chains "${protocol}"
 
        local zone
        for zone in $(zones_get_all); do
-               policy_add_zone ${zone}
+               # Create all needed chains for the zone.
+               firewall_zone_create_chains "${protocol}" "${zone}"
+
+               # After the chains that are always available have been
+               # created, we will add a custom policy to every single
+               # zone.
+
+               policy_zone_add "${protocol}" "${zone}"
        done
 
-       # Commit the new ruleset.
-       iptables_commit
+       # Load the new ruleset.
+       local args
+       if enabled testmode; then
+               list_append args "--test"
+       fi
+       iptables_commit "${protocol}" ${args}
 
        firewall_lock_release
 }
 
 function firewall_stop() {
+       local protocol="${1}"
+       assert isset protocol
+
        firewall_lock_acquire
 
        # Initialize an empty firewall ruleset
        # with default policy ACCEPT.
-       iptables_init ACCEPT
+       iptables_init "${protocol}" ACCEPT
 
-       # Commit it.
-       iptables_commit
+       # Load it.
+       ipables_load "${protocol}"
+
+       firewall_lock_release
+}
+
+function firewall_show() {
+       local protocol="${1}"
+       assert isset protocol
+
+       # Shows the ruleset that is currently loaded.
+       iptables_status "${protocol}"
+
+       return ${EXIT_OK}
+}
+
+function firewall_panic() {
+       local protocol="${1}"
+       assert isset protocol
+       shift
+
+       local admin_hosts="$@"
+
+       firewall_lock_acquire "${protocol}"
+
+       # Drop all communications.
+       iptables_init "${protocol}" 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 "${protocol}" -A INPUT  -s "${admin_host}" -j ACCEPT
+               iptables "${protocol}" -A OUTPUT -d "${admin_host}" -j ACCEPT
+       done
+
+       # Load it.
+       iptables_commit "${protocol}"
 
        firewall_lock_release
 }
@@ -85,75 +162,296 @@ function firewall_lock_release() {
        lock_release ${RUN_DIR}/.firewall_lock
 }
 
+function firewall_custom_chains() {
+       local protocol="${1}"
+       assert isset protocol
+
+       log INFO "Creating CUSTOM* chains..."
+
+       # These chains are intened to be filled with
+       # rules by the user. They are processed at the very
+       # beginning so it is possible to overwrite everything.
+
+       iptables_chain_create "${protocol}" CUSTOMINPUT
+       iptables "${protocol}" -A INPUT -j CUSTOMINPUT
+
+       iptables_chain_create "${protocol}" CUSTOMFORWARD
+       iptables "${protocol}" -A FORWARD -j CUSTOMFORWARD
+
+       iptables_chain_create "${protocol}" CUSTOMOUTPUT
+       iptables "${protocol}" -A OUTPUT -j CUSTOMOUTPUT
+
+       iptables_chain_create "${protocol}" -t nat CUSTOMPREROUTING
+       iptables "${protocol}" -t nat -A PREROUTING -j CUSTOMPREROUTING
+
+       iptables_chain_create "${protocol}" -t nat CUSTOMPOSTROUTING
+       iptables "${protocol}" -t nat -A POSTROUTING -j CUSTOMPOSTROUTING
+
+       iptables_chain_create "${protocol}" -t nat CUSTOMOUTPUT
+       iptables "${protocol}" -t nat -A OUTPUT -j CUSTOMOUTPUT
+}
+
 function firewall_tcp_state_flags() {
+       local protocol="${1}"
+       assert isset protocol
+
        log INFO "Creating TCP State Flags chain..."
-       iptables_chain_create BADTCP_LOG
-       iptables -A BADTCP_LOG -p tcp -j $(iptables_LOG "Illegal TCP state: ")
-       iptables -A BADTCP_LOG -j DROP
-
-       iptables_chain_create BADTCP
-       iptables -A BADTCP -p tcp --tcp-flags ALL NONE -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags SYN,RST SYN,RST -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags FIN,RST FIN,RST -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags ACK,FIN FIN     -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags ACK,PSH PSH     -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags ACK,URG URG     -j BADTCP_LOG
-
-       iptables -A INPUT   -p tcp -j BADTCP
-       iptables -A OUTPUT  -p tcp -j BADTCP
-       iptables -A FORWARD -p tcp -j BADTCP
+
+       iptables_chain_create "${protocol}" BADTCP_LOG
+       iptables "${protocol}" -A BADTCP_LOG -p tcp -j "$(iptables_LOG "Illegal TCP state: ")"
+       iptables "${protocol}" -A BADTCP_LOG -j DROP
+
+       iptables_chain_create "${protocol}" BADTCP
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ALL NONE        -j BADTCP_LOG
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADTCP_LOG
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags SYN,RST SYN,RST -j BADTCP_LOG
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags FIN,RST FIN,RST -j BADTCP_LOG
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ACK,FIN FIN     -j BADTCP_LOG
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ACK,PSH PSH     -j BADTCP_LOG
+       iptables "${protocol}" -A BADTCP -p tcp --tcp-flags ACK,URG URG     -j BADTCP_LOG
+
+       iptables "${protocol}" -A INPUT   -p tcp -j BADTCP
+       iptables "${protocol}" -A OUTPUT  -p tcp -j BADTCP
+       iptables "${protocol}" -A FORWARD -p tcp -j BADTCP
+}
+
+function firewall_tcp_clamp_mss() {
+       # Do nothing if this has been disabled.
+       enabled FIREWALL_CLAMP_PATH_MTU || return ${EXIT_OK}
+
+       local protocol="${1}"
+       assert isset protocol
+
+       log DEBUG "Adding rules to clamp MSS to path MTU..."
+
+       iptables "${protocol}" -t mangle -A FORWARD \
+               -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
 }
 
 function firewall_connection_tracking() {
+       local protocol="${1}"
+       assert isset protocol
+
        log INFO "Creating Connection Tracking chain..."
-       iptables_chain_create CONNTRACK
-       iptables -A CONNTRACK -m state --state ESTABLISHED,RELATED -j ACCEPT
-       iptables -A CONNTRACK -m state --state INVALID -j $(iptables_LOG "INVALID packet: ")
-       iptables -A CONNTRACK -m state --state INVALID -j DROP
-
-       iptables -A INPUT   -j CONNTRACK
-       iptables -A OUTPUT  -j CONNTRACK
-       iptables -A FORWARD -j CONNTRACK
+
+       iptables_chain_create "${protocol}" CONNTRACK
+       iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
+       iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate INVALID -j "$(iptables_LOG "INVALID packet: ")"
+       iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate INVALID -j DROP
+
+       iptables "${protocol}" -A INPUT   -j CONNTRACK
+       iptables "${protocol}" -A OUTPUT  -j CONNTRACK
+       iptables "${protocol}" -A FORWARD -j CONNTRACK
 }
 
-function firewall_import_portfw() {
-       local zone=${1}
+function firewall_localhost_create_chains() {
+       local protocol="${1}"
+       assert isset protocol
+
+       log DEBUG "Creating firewall chains for localhost..."
+
+       # Accept everything on lo
+       iptables "${protocol}" -A INPUT  -i lo -m conntrack --ctstate NEW -j ACCEPT
+       iptables "${protocol}" -A OUTPUT -o lo -m conntrack --ctstate NEW -j ACCEPT
+}
+
+function firewall_zone_create_chains() {
+       local protocol="${1}"
+       assert isset protocol
+
+       local zone="${2}"
+       assert isset zone
+
+       log DEBUG "Creating firewall chains for zone '${zone}'."
+
+       local chain_prefix="ZONE_${zone^^}"
+
+       # Create filter chains.
+       iptables_chain_create "${protocol}" "${chain_prefix}_INPUT"
+       iptables "${protocol}" -A INPUT   -i ${zone} -j "${chain_prefix}_INPUT"
+
+       iptables_chain_create "${protocol}" "${chain_prefix}_OUTPUT"
+       iptables "${protocol}" -A OUTPUT  -o ${zone} -j "${chain_prefix}_OUTPUT"
+
+       # Custom rules.
+       iptables_chain_create "${protocol}" "${chain_prefix}_CUSTOM"
+
+       # Intrusion Prevention System.
+       iptables_chain_create "${protocol}" "${chain_prefix}_IPS"
+
+       # Create a chain for each other zone.
+       # This leaves us with n^2 chains. Duh.
+
+       local other_zone other_chain_prefix
+       for other_zone in $(zones_get_all); do
+               other_chain_prefix="${chain_prefix}_${other_zone^^}"
+               iptables_chain_create "${protocol}" "${other_chain_prefix}"
+
+               # Connect the chain with the FORWARD chain.
+               iptables "${protocol}" -A FORWARD -i "${zone}" -o "${other_zone}" \
+                       -j "${other_chain_prefix}"
+
+               # Handle custom rules.
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${chain_prefix}_CUSTOM"
+
+               # Link IPS.
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${chain_prefix}_IPS"
+
+               # Rules.
+               iptables_chain_create "${protocol}" "${other_chain_prefix}_RULES"
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${other_chain_prefix}_RULES"
+
+               # Policy.
+               iptables_chain_create "${protocol}" "${other_chain_prefix}_POLICY"
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${other_chain_prefix}_POLICY"
+       done
+
+       ## Create mangle chain.
+       #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}"
+       #iptables "${protocol}" -t mangle -A PREROUTING  -i "${zone}" -j "${chain_prefix}"
+       #iptables "${protocol}" -t mangle -A POSTROUTING -o "${zone}" -j "${chain_prefix}"
+
+       ## Quality of Service
+       #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}_QOS_INC"
+       #iptables "${protocol}" -t mangle -A "${chain_prefix}" -i "${zone}" -j "${chain_prefix}_QOS_INC"
+       #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}_QOS_OUT"
+       #iptables "${protocol}" -t mangle -A "${chain_prefix}" -o "${zone}" -j "${chain_prefix}_QOS_OUT"
+
+       # Create NAT chain.
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}"
+       iptables "${protocol}" -t nat -A PREROUTING  -i "${zone}" -j "${chain_prefix}"
+       iptables "${protocol}" -t nat -A POSTROUTING -o "${zone}" -j "${chain_prefix}"
+
+       # Network Address Translation
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}_DNAT"
+       iptables "${protocol}" -t nat -A PREROUTING  -i "${zone}" -j "${chain_prefix}_DNAT"
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}_SNAT"
+       iptables "${protocol}" -t nat -A POSTROUTING -o "${zone}" -j "${chain_prefix}_SNAT"
+
+       # UPnP
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}_UPNP"
+       iptables "${protocol}" -t nat -A "${chain_prefix}" -j "${chain_prefix}_UPNP"
+
+       return ${EXIT_OK}
+}
+
+function firewall_parse_rules() {
+       local file=${1}
+       assert isset file
        shift
 
-       local protocol="ipv6"
-       local chain="filter"
+       # End if no rule file exists.
+       [ -r "${file}" ] || return ${EXIT_OK}
 
-       while [ $# -gt 0 ]; do
-               case "${1}" in
-                       --chain=*)
-                               chain=$(cli_get_val ${1})
-                               ;;
-                       --protocol=*)
-                               protocol=$(cli_get_val ${1})
-                               ;;
-               esac
+       local cmd
+
+       local ${FIREWALL_RULES_CONFIG_PARAMS}
+       local line
+       while read -r line; do
+               # Skip empty lines.
+               [ -n "${line}" ] || continue
+
+               # Skip commented lines.
+               [ "${line:0:1}" = "#" ] && continue
+
+               # Parse the rule.
+               _firewall_parse_rule_line ${line}
+               if [ $? -ne ${EXIT_OK} ]; then
+                       log WARNING "Skipping invalid line: ${line}"
+                       continue
+               fi
+
+               cmd="iptables $@"
+
+               # Source IP address/net.
+               if isset src; then
+                       list_append cmd "-s ${src}"
+               fi
+
+               # Destination IP address/net.
+               if isset dst; then
+                       list_append cmd "-d ${dst}"
+               fi
+
+               # Protocol.
+               if isset proto; then
+                       list_append cmd "-p ${proto}"
+
+                       if list_match ${proto} ${FIREWALL_PROTOCOLS_SUPPORTING_PORTS}; then
+                               if isset sport; then
+                                       list_append cmd "--sport ${sport}"
+                               fi
+
+                               if isset dport; then
+                                       list_append cmd "--dport ${dport}"
+                               fi
+                       fi
+               fi
+
+               # Always append the action.
+               list_append cmd "-j ${action}"
+
+               # Execute command.
+               ${cmd}
+       done < ${file}
+}
+
+function _firewall_parse_rule_line() {
+       local arg
+
+       # Clear all values.
+       for arg in ${FIREWALL_RULES_CONFIG_PARAMS}; do
+               assign "${arg}" ""
        done
 
-       assert isoneof protocol ipv4 ipv6
+       local key val
+       while read -r arg; do
+               key=$(cli_get_key ${arg})
 
-       local allowed_chains="filter"
-       if [ "${protocol}" = "ipv4" ]; then
-               allowed_chains="${allowed_chains} nat"
+               if ! listmatch "${key}" ${FIREWALL_RULES_CONFIG_PARAMS}; then
+                       log WARNING "Unrecognized argument: ${arg}"
+                       return ${EXIT_ERROR}
+               fi
+
+               val=$(cli_get_val ${arg})
+               assign "${key}" "${val}"
+       done <<< "$(args $@)"
+
+       # action must always be set.
+       if ! isset action; then
+               log WARNING "'action' is not set: $@"
+               return ${EXIT_ERROR}
        fi
-       assert isoneof chain ${allowed_chains}
 
-       # XXX TODO
+       for arg in src dst; do
+               isset ${arg} || continue
 
-       local src dst proto
-       while read src dst proto; do
-               case "${chain}" in
-                       filter)
-                               ;;
-                       nat)
-                               ;;
-               esac
-       done < ${FIREWALL_CONFIG_PORTFW}
-}
+               # Check for valid IP addresses.
+               if ! ip_is_valid ${!arg}; then
+                       log WARNING "Invalid IP address for '${arg}=${!arg}': $@"
+                       return ${EXIT_ERROR}
+               fi
+       done
 
+       if isset proto; then
+               # Make lowercase.
+               proto=${proto,,}
 
+               if ! list_match "${proto}" ${FIREWALL_SUPPORTED_PROTOCOLS}; then
+                       log WARNING "Unsupported protocol type 'proto=${proto}': $@"
+                       return ${EXIT_ERROR}
+               fi
+       fi
+
+       for arg in sport dport; do
+               isset ${arg} || continue
+
+               # Check if port is valid.
+               if ! isinteger ${arg}; then
+                       log WARNING "Invalid port '${arg}=${!arg}': $@"
+                       return ${EXIT_ERROR}
+               fi
+       done
+
+       return ${EXIT_OK}
+}