There’s nothing special about the Bourne shell. Other command interpreters or even a mix of them, typically
shell and something else, can be used. Naturally adding languages does result in a more complex quoting;
each level of interpretation adds another level of quoting.
As an example we create a small script that is used to find the IP addresses from where an invalid login
attempt has been attempted. The output should have iptables format. As a script that could be called onitself, the task is almost trivial. The script could look like:
#!/bin/bash lastb -i -f /var/log/btmp | awk ' $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ } END { for (ip in cnt) if (cnt[ip] > '"$NUM"') print ip "\t" cnt[ip] } ' | ( while read foo bar; do if ! grep -q " $foo/" /etc/sysconfig/iptables; then if [ $VERBOSE == "Y" ]; then echo "# $foo $bar"; fi echo "-A INPUT -s $foo/32 -j DROP" echo "-A OUTPUT -d $foo/32 -j DROP" fi; done ) | sort
The script assumes that two variables have been set: NUM and VERBOSE. The first variable defines a threshold.
The script detects invalid login attempts, and if the the number of attempts from a single IP address exceeds
the threshold, the IP address will be reported. The VERBOSE variable lets the script write some extra output. For the example we assume that those variables are defined within the scheduling system.
The first part of the script uses awk to count the number of break-in attempts per IP address. It prints both
the IP address and the number of attempts. This again is read by the shell, which then checks if the IP
address isn’t already blocked. If that’s not the case, it prints the desired output.
This script, albeit somewhat more sophisticated, is actually used to maintain firewall rules on several systems. Today’s output (for one system) looks like this:
172.58.99.157 20 77.247.110.118 50 -A INPUT -s 172.58.99.157/32 -j DROP -A INPUT -s 77.247.110.118/32 -j DROP -A OUTPUT -d 172.58.99.157/32 -j DROP -A OUTPUT -d 77.247.110.118/32 -j DROP
The first two lines show the number of break-in attempts, 20 and 50, from each of the sources. The next 4
lines show the lines that can be added to the iptables configuration in order to block all traffic from those
sources.
Let’s try to convert the script into a run program. A good way to start is to just copy the script and then
to add the extra level of quoting. Hence the first step, but syntactically incorrect, is:
run program = /bin/bash -c ' lastb -i -f /var/log/btmp | awk ' $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ } END { for (ip in cnt) if (cnt[ip] > '"$NUM"') print ip "\t" cnt[ip] } ' | ( while read foo bar; do if ! grep -q " $foo/" /etc/sysconfig/iptables; then if [ $VERBOSE == "Y" ]; then echo "# $foo $bar"; fi echo "-A INPUT -s $foo/32 -j DROP" echo "-A OUTPUT -d $foo/32 -j DROP" fi; done ) | sort '
If we look closely, we find that it is mainly the awk code that causes a problem, since it is enclosed in single
quotes. In order to increase the complexity it also addresses the NUM parameter, which has to be resolved by
the scheduling server.
We’d like to end up with a string that contains the script that has to be executed by the shell. So we could
just as well temporarily use /bin/echo instead of /bin/sh. When the job is run, it will output the script to
be executed, instead of executing the script. This allows us to test the run program without actually running
the ”hot” code.
The second thing to do is to change the single quote following awk and the quote following the awk-code, so
that a single quote remains. This can be achieved by terminating the single quoted string and concatenating
a single quote that is the content of a double quoted string:
run program = /bin/echo -c ' lastb -i -f /var/log/btmp | awk '"'"' $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ } END { for (ip in cnt) if (cnt[ip] > '"$NUM"') print ip "\t" cnt[ip] } '"'"' | ( while read foo bar; do if ! grep -q " $foo/" /etc/sysconfig/iptables; then if [ $VERBOSE == "Y" ]; then echo "# $foo $bar"; fi echo "-A INPUT -s $foo/32 -j DROP" echo "-A OUTPUT -d $foo/32 -j DROP" fi; done ) | sort '
When we run this, the results will make us happy:
-c lastb -i -f /var/log/btmp | awk ' $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ } END { for (ip in cnt) if (cnt[ip] > 25) print ip "\t" cnt[ip] } ' | ( while read foo bar; do if ! grep -q " $foo/" /etc/sysconfig/iptables; then if [ $VERBOSE == "Y" ]; then echo "# $foo $bar"; fi echo "-A INPUT -s $foo/32 -j DROP" echo "-A OUTPUT -d $foo/32 -j DROP" fi; done ) | sort
The NUM parameter has already been correctly substituted, only the VERBOSE parameter isn’t treated correctly
yet. By adding some quotes following the same principle as in the case of the awk script part, we end up
with the working run program:
if ! grep -q " $foo/" /etc/sysconfig/iptables; then if [ "'$VERBOSE'" == "Y" ]; then echo "# $foo $bar"; fi echo "-A INPUT -s $foo/32 -j DROP" echo "-A OUTPUT -d $foo/32 -j DROP" fi; done ) | sort '
And if the Job is run (NUM is set to 19, VERBOSE is set to Y), it reports:
103.147.3.118 20 118.100.180.76 20 155.94.145.191 20 160.124.49.170 20 164.90.133.183 20 185.235.43.158 20 187.170.254.186 20 39.118.192.132 20 58.96.209.38 20 81.70.149.90 20 95.210.130.95 20 -A INPUT -s 103.147.3.118/32 -j DROP -A INPUT -s 118.100.180.76/32 -j DROP -A INPUT -s 155.94.145.191/32 -j DROP -A INPUT -s 160.124.49.170/32 -j DROP -A INPUT -s 164.90.133.183/32 -j DROP -A INPUT -s 185.235.43.158/32 -j DROP -A INPUT -s 187.170.254.186/32 -j DROP -A INPUT -s 39.118.192.132/32 -j DROP -A INPUT -s 58.96.209.38/32 -j DROP -A INPUT -s 81.70.149.90/32 -j DROP -A INPUT -s 95.210.130.95/32 -j DROP -A OUTPUT -d 103.147.3.118/32 -j DROP -A OUTPUT -d 118.100.180.76/32 -j DROP -A OUTPUT -d 155.94.145.191/32 -j DROP -A OUTPUT -d 160.124.49.170/32 -j DROP -A OUTPUT -d 164.90.133.183/32 -j DROP -A OUTPUT -d 185.235.43.158/32 -j DROP -A OUTPUT -d 187.170.254.186/32 -j DROP -A OUTPUT -d 39.118.192.132/32 -j DROP -A OUTPUT -d 58.96.209.38/32 -j DROP -A OUTPUT -d 81.70.149.90/32 -j DROP -A OUTPUT -d 95.210.130.95/32 -j DROP
Part 1: Introduction and simple usage
Part 2: Advanced usage
Part 3: Backticks
Part 4: Other interpreters
Part 5: Circumventing limitations