MRS – Multi-Rule sed (global substitution)

· 568 words · 3 minute read

Note: This article from my archive was written 2015 November 12.

Today in class, the topic was access control lists and entries on Cisco routers. The extended form of an access control entry is like so

>access-list [name] [action] [tcp/udp] [source network] [wildcard mask] [destination network] eq [port number]

The port number can be represented by a pneumonic, such as www instead of 80, however differing versions of Cisco’s IOS have different labels for these ports.

The solution, in a network with varying versions of IOS, therefore is to simply use ports numbers, which will work on every version of IOS.

But what fun would that be? I didn’t learn BASH just so I could type things out by hand.

The idea is to write the configuration as you would, with port numbers instead of keywords, but to filter it against a rule file. The rule file should also be easy to write. As an example

22 ssh
21 ftp
80 http
443 https

As usual, I started simple, with two possible keywords for port 80, as a proof of concept.

if [ "$1" == "-a" ]; then
    sed 's/eq 80/eq www/' skel
    elif [ "$1" == "-b" ]; then
    sed 's/eq 80/eq http/' skel
else
    exit
fi

Obviously that is a horrible bash script (I didn’t even specify the shell), but it gets the job done. I can use this method to transform the skeleton.

At this point, though, I realized I was writing a script that can transform any file against any rule file. Surely such a program already exists, as it would obviously be more useful than just Cisco administration, but who cares about scripts that already exist?

The next step is to write an engine to parse the rule file.

cat rules | while IFS=$(printf " ") read -r -a line; do 
    echo ${line[0]};
    echo ${line[1]};
done

This code, given a file “rules,” in the format specified above, will print each space-separated item on a new line, which isn’t interesting for this purpose, but in doing so it separates the source text and intended text which can be called as “line[0]” and “line[1]”

Combine that logic with the sed method above, and the following script will approximate my intent.

cat rules | while IFS=$(printf " ") read -r -a line; do
    sed s/eq “${line[0]}”/eq “${line[1]}”/ skel
done

Approximations, however, are not solutions. The above would simply spit out multiple versions of the original, each with one change made. A swap file will put an end to that.

cp skel temp_file
cat rules | while IFS=$(printf " ") read -r -a line; do
    sed s/eq “${line[0]}”/eq “${line[1]}”/ temp_file > temp_file
done
cat temp_file
rm temp_file

From there, it’s just cleanup and generalization (after all, what good is a script that requires certain filenames)?

Download 🔗

The final script can be downloaded here.

In the interest of generalization, I’ve removed the specific search pattern for port numbers in Cisco ACEs. If you plan to use this script to replace port numbers with pneumoics in Cisco ACEs, make the following change

- sed -i -e s/${RULE[0]}/${RULE[1]}/g $1
+ sed -i -e s/eq ${RULE[0]}/eq ${RULE[1]}/g $1

Possible Improvements 🔗

  • Make the argument parser more robust

Notes 🔗

  • Sed itself supports supports command files with the -f tag, but the contents of the command file have to be sed instructions, which are less readable than the format expected by mrs.