Bagpuss
Bagpuss

Reputation: 13

xmlstarlet - need to selectively replace attribute value based on value of another attribute

Just wondering if anyone can help me. I'm currently building a pfsense firewall, which uses VPN connections to secure the traffic. The VPN provider does provide a port forwarding mechanism, but the incoming port number changes every hour. I have a script which allows me to discover the new port, but I need a scripted way to modify the port forward settings in the firewall to match.

A snippet of the firewall config file that controls this is as follows: input.xml:

<?xml version="1.0"?>
<pfsense>
    <nat>
        <rule>
            <source>
                <any/>
            </source>
            <destination>
                <any/>
                <port>53400</port>
            </destination>
            <protocol>tcp/udp</protocol>
            <target>192.168.0.15</target>
            <local-port>53400</local-port>
            <interface>opt2</interface>
            <descr><![CDATA[Torrent]]></descr>
            <associated-rule-id>nat_52d81d2dc904f5.77023355</associated-rule-id>
            <created>
                <time>1389894957</time>
                <username>[email protected]</username>
            </created>
            <updated>
                <time>1389980696</time>
                <username>[email protected]</username>
            </updated>
        </rule>
    </nat>

    <filter>
        <rule>
            <id/>
            <type>pass</type>
            <interface>opt2</interface>
            <ipprotocol>inet</ipprotocol>
            <tag/>
            <tagged/>
            <max/>
            <max-src-nodes/>
            <max-src-conn/>
            <max-src-states/>
            <statetimeout/>
            <statetype>keep state</statetype>
            <os/>
            <protocol>tcp/udp</protocol>
            <source>
                <any/>
            </source>
            <destination>
                <address>192.168.0.15</address>
                <port>53400</port>
            </destination>
            <log/>
            <descr><![CDATA[NAT Torrent]]></descr>
            <associated-rule-id>nat_52d81d2dc904f5.77023355</associated-rule-id>
            <created>
                <time>1389894957</time>
                <username>NAT Port Forward</username>
            </created>
            <updated>
                <time>1389899075</time>
                <username>[email protected]</username>
            </updated>
        </rule>
    </filter>
</pfsense>

In the XML above, we have the two parts which comprise a port forward rule for pfsense. The part enclosed in the <nat> section is the port forward. The section in the <rule> is an interface specific incoming firewall rule. Both have to be modified for the new port forward setting to be effective.

I was thinking to use xmlstarlet to modify the config file, using the <descr> as my key for identifying which sections to change.

I'm aware that you can have data like:

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>

and modify it with:

xml ed -P -O -L \
    -u '//username/text()' -v 'something' \
    -u '//password/text()' -v 'somethingelse' \
    -u '//dbname/text()'   -v 'somethingdifferent' \
    file.xml

and also that you can have something like: sample.xml:

<objects>
    <object>
        <name>Foo</name>
        <constant1>10</constant1>
        <constant2>20</constant2>
    </object>
    <object>
        <name>Bar</name>
        <constant1>15</constant1>
        <constant2>40</constant2>
    </object>
</objects>

and update attributes with:

xmlstarlet ed -u '//object[name="Foo"]/const1' -v 18 sample.xml

However, I'm struggling to merge the two, so that I have a single statement which matches <descr>="Torrent" and then updates the relevant <port> and <local-port> attributes.

Any help with a suitable xmlstarlet command would be much appreciated.

Upvotes: 1

Views: 1613

Answers (1)

npostavs
npostavs

Reputation: 5027

$ xmlstarlet ed \
    -u '//rule[descr="Torrent"]/destination/port' -v 1111 \
    -u '//rule[descr="Torrent"]/local-port'       -v 2222 \
    input.xml |
    xmlstarlet format --indent-spaces 4 > output.xml
$ diff input.xml output.xml
10c10
<                 <port>53400</port>
---
>                 <port>1111</port>
14c14
<             <local-port>53400</local-port>
---
>             <local-port>2222</local-port>
28d27
<

Upvotes: 4

Related Questions