Firewall.local - quotes or no quotes, that is the question

Continuing the discussion from Deny access to WebUI from BLUE:


Keep in mind you may need to change this slightly since there are spaces in the various $RULE_ strings.

Maybe something like this:

if ! /sbin/iptables -C "$RULE_INPUT" 2> /dev/null; then
    /sbin/iptables "$RULE_INPUT"
fi

If you look through the bash scripts of the Core Devs, many of the strings use the “belts AND suspenders” approach and do this:

if ! /sbin/iptables -C "${RULE_INPUT}" 2> /dev/null; then
    /sbin/iptables "${RULE_INPUT}"
fi

I think is is more for consistency within the code then anything else…

2 Likes

Thank you for your suggestion, Jon. I have revised the code to include curly braces around the variable name, but I opted not to enclose the variable in double quotes. Your point about the potential risks associated with having spaces within variable contents is valid, but in this particular context, using quotes can introduce problems.

The iptables -C command expects its arguments in a specific sequence: the chain name followed by the rule specification. If the variable is quoted, the entirety of its content is interpreted as a single argument. Consequently, iptables would misconstrue the complete rule specification as the chain’s name. This can trigger an error stating that the chain name is too long (exceeding the limit of 28 characters): iptables v1.8.9 (legacy): chain name ' IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT' too long (must be under 29 chars).

Bash scripting presents so many nuanced aspects that makes my head spinning.

1 Like

This doesn’t sound right…

Can you include the entire bash script? With the single quotes, iptables thinks the chain name is really long. So something else seems wrong in the script.

#!/bin/sh
# Used for private firewall rules

RULE_INPUT="-I IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT"
RULE_FORWARD="-I IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT"

# See how we were called.
case "$1" in
  start)
        # add your 'start' rules here
        # start igmpproxy
        if ! pgrep -x "igmpproxy" > /dev/null
        then
            /usr/sbin/igmpproxy /etc/igmpproxy.conf &
        else
            echo "igmpproxy is already running"
        fi

        # Ensure the rules do not already exist
        if ! /sbin/iptables -C ${RULE_INPUT} 2> /dev/null; then
            /sbin/iptables ${RULE_INPUT}
        fi

        if ! /sbin/iptables -C ${RULE_FORWARD} 2> /dev/null; then
            /sbin/iptables ${RULE_FORWARD}
        fi
        ;;
  stop)
        # add your 'stop' rules here

        # Only delete rules if they exist
        if /sbin/iptables -C ${RULE_INPUT} 2> /dev/null; then
            /sbin/iptables -D ${RULE_INPUT}
        fi

        if /sbin/iptables -C ${RULE_FORWARD} 2> /dev/null; then
            /sbin/iptables -D ${RULE_FORWARD}
        fi
	# stop igmpproxy
        if pgrep -x "igmpproxy" > /dev/null
        then
            killall igmpproxy || echo "Failed to kill igmpproxy"
        else
            echo "No igmpproxy process found to kill"
        fi
        ;;
  reload)
        $0 stop
        sleep 1
        $0 start
        # add your 'reload' rules here
        ;;
  *)
        echo "Usage: $0 {start|stop|reload}"
        ;;
esac

In Bash, using single quotes around a variable will prevent the variable from being expanded. I am pretty sure the script is fine.

Your variables RULE_xxx contain the option -I, which is good for the rule generation.
But for check and delete operation the line expands to
iptables -C -I IPTV_xxx ... and
iptables -D -I IPTV_xxx ...
which is wrong.
Defining the variables as

RULE_INPUT="IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT"
RULE_FORWARD="IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT"

and the generation sequence as

        if ! /sbin/iptables -C ${RULE_INPUT} 2> /dev/null; then
            /sbin/iptables -I ${RULE_INPUT}
        fi

        if ! /sbin/iptables -C ${RULE_FORWARD} 2> /dev/null; then
            /sbin/iptables -I ${RULE_FORWARD}
        fi

( similiar for the delete/stop operation ) should do the job.

BTW: for test purposes it helps not pipe the output to /dev/null. :wink:

EDIT: correct expansions :wink:

I should have thought of that. Thanks Bernhard.

of course. I will modify the script and report back if there is any interesting log.

The script with @bbitsch modification works.

A final note, If the stderr is not redirected to /dev/null, the iptables -C test will also generate the error message “Bad rule…”. Below I start with the rule not present in the chain, the test generates the error message, then I create the rule, test again and the error message is not shown (as expected).

This behavior of iptables -C should have been expected, but it surprised me. I was expecting it would just return either 0 if successful or non 0 if unsuccessful, but it first gives the error and then exit with its return value.

[root@ipfire cfusco]# iptables -C IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT && echo "Rule exists" || echo "Rule does not exist"
iptables: Bad rule (does a matching rule exist in that chain?).
Rule does not exist
[root@ipfire cfusco]# iptables -I IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT
[root@ipfire cfusco]# iptables -C IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT && echo "Rule exists" || echo "Rule does not exist"
Rule exists

This behaviour is wanted!
iptables is a CLI command. Entered from a terminal, errors have to be reported on the terminal. Any other behaviour is an error, IMO.

Using the IPTables::IPv4 module in Perl, for example, implements the interface ( return code 0/1, error message ). But IPFire uses the good old CLI interface.

1 Like

@jon , do you think using an array can help manage variables with white spaces more effectively, while avoiding issues caused by the combination of double quotes and iptables -C?

#!/bin/bash
# Used for private firewall rules

RULE_INPUT=(IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT)
RULE_FORWARD=(IPTVFORWARD -i red0 -d 224.0.0.0/4 -j ACCEPT)

# See how we were called.
case "$1" in
  start)
        # add your 'start' rules here
        # begin igmpproxy
        /usr/sbin/igmpproxy /etc/igmpproxy.conf &

        # Ensure the rules when they do not already exist
        if ! /sbin/iptables -C "${RULE_INPUT[@]}" 2>/dev/null; then
            /sbin/iptables -I "${RULE_INPUT[@]}"
        fi

        if ! /sbin/iptables -C "${RULE_FORWARD[@]}" 2>/dev/null; then
            /sbin/iptables -I "${RULE_FORWARD[@]}"
        fi
        # end igmpproxy
        ;;
  stop)
        # add your 'stop' rules here
        # begin igmpproxy
        # Only delete rules if they exist
        if /sbin/iptables -C "${RULE_INPUT[@]}" 2>/dev/null; then
            /sbin/iptables -D "${RULE_INPUT[@]}"
        fi

        if /sbin/iptables -C "${RULE_FORWARD[@]}" 2>/dev/null; then
            /sbin/iptables -D "${RULE_FORWARD[@]}"
        fi

        killall igmpproxy || echo "No igmpproxy process found to kill"
        # end igmpproxy
        ;;
  reload)
        $0 stop
        sleep 1
        $0 start
        ;;
  *)
        echo "Usage: $0 {start|stop|reload}"
        ;;
esac

EDIT: maybe we should split this part of the thread and close the Original one.

IMHO - no.

Are you getting the same chain error after removing the -I from the RULE_?

To avoid the error for the first call of restart ( stop / start sequence ) one could insert a check for existence of the chain. But this is bit too much effort, IMO.

I see your point, Bashism means no portability. Do you see any other reason?

Yes, see for yourself, shown below inline so it is self contained and it can’t be attributed to other parts of the script.

[root@ipfire ~]# RULE_INPUT="IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT";/sbin/iptables -C ${RULE_INPUT} && echo "Yes" || echo "NO"
Yes
[root@ipfire ~]# RULE_INPUT="IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT";/sbin/iptables -C "${RULE_INPUT}"
iptables v1.8.9 (legacy): chain name `IPTVINPUT -i red0 -d 224.0.0.0/4 -j ACCEPT' too long (must be under 29 chars
Try `iptables -h' or 'iptables --help' for more information.
NO

It is pretty clear jon, if you put double quote, the whole thing is seen as the name of the chain (spelled out by the error message), because iptables -C expects the first parameters to be the name, if you put a double quote in the variable, the entire string IS the first parameter. This is a limitation of the double quote, you have to check what the calling program expects to receive. It is nuanced.

1 Like

That is just odd! It seems more like a iptables-ism to me than bash.

1 Like

or redirect stderr to /dev/null :grin:

1 Like