<?xml version="1.0"?>

<!-- Simon Chudley (src299) XML Network Manager -->
<!-- Function described using an XST document to take in the zone declarations of a DNS service
     and create the firewall rules that willl be required to allow people to query that DNS
     service. NOTE: This only deals with class C networks! -->

<!-- This function requires various input parameters to control how it behaves. The following XML
     segment demonstrates.

      <fw:FirewallConstruct name="Dynamic DNS Service" 
                            description="Automatically create firewall Rules to allow DNS service operation">

         <CallFunction name="Firewall::Functions:DNSAccess" onPath="$this/DNS(1)/Bindings(1)"> 
            <Parameter name="networks" value="$network,$network_1"/>
            <Parameter name="ips" value="$interface_1_ip,$interface_2_ip"/>
            <Parameter name="interfaces" value="$interface_1,$interface_2"/>
            <Parameter name="ruleid" value="5000"/>
            <Parameter name="port" value="53"/>
         </CallFunction>  

      </fw:FirewallConstruct>
 
     It takes as input the Bindings element defined within the top of a DNS service. This contains the 
     sequence of zone to file mappings. Within this, there will be a reverse lookup for each network
     the DNS service serves, and it is this that is used to calculate the required firewall rules. The
     reverse declarations take the form 2.168.192.in-addr.arpa, hence serving the network 192.168.2.0/24.

     For the function call, you define which networks you wish to allow DNS queries to be made from. Also,
     the interface names and IP addresses are defined, so that rules can be tweeked.

     The first parameter is 'networks'. This should contain a comma separated list of the networks you
     wish to allow DNS queries to be made on, in class C form, hence 192.168.2. The parameter 'ips'
     should contain the IP address of the interface within this machine for each of those networks, in
     the same comma separated order, hence 192.168.2.100. The parameter 'interfaces' should again contain
     a comma separated list, in the same order, with the interface names for those IP's on the local machine.
     The parameter 'ruleid' should contain the first ruleID to start allocating, and 'port' the port that
     DNS queries will be processed, usually 53.

     The function will only look at Zone's for reverse lookups, hence having a 'name' attribute of
     x.x.x.in-addr.arpa. For each of the networks in the 'networks' parameter it will create two rules in
     the firewall configuration. The first serving incomming connections, the second outgoing. 

     NOTE: There should be no spaces within the comma separated lists! -->


<xsl:stylesheet xmlns="http://www.ecs.soton.ac.uk/~src299/xmlnetman" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:dns="http://www.ecs.soton.ac.uk/~src299/xmlnetman/dns" 
                xmlns:fw="http://www.ecs.soton.ac.uk/~src299/xmlnetman/firewall" 
                version="1.0" >

  <!-- XML Indented output -->
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

  <!-- Just switches IP adresses around -->
  <xsl:template name="reverse_v4_add">
    <xsl:param name="addr"/>

    <xsl:choose>
      <xsl:when test="contains($addr,'.')">
        <xsl:call-template name="reverse_v4_add">
          <xsl:with-param name="addr" select="substring-after($addr,'.')"/>
        </xsl:call-template>
        <xsl:text>.</xsl:text> <xsl:value-of select="substring-before($addr,'.')"/> 
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$addr"/> 
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- Extract some network information -->
  <xsl:template name="net_info">
    <xsl:param name="network"/>
    <xsl:param name="networks"/>
    <xsl:param name="ips"/>
    <xsl:param name="interfaces"/>
    <xsl:param name="type"/>
    <xsl:param name="direction"/>

    <!-- If we are on the right element of the network string  -->
    <xsl:choose>

      <!-- If we failed to find the information -->
      <xsl:when test="(string-length($networks) = 0) or (string-length($interfaces) = 0) or (string-length($ips) = 0)"/>

      <!-- When we have found the information we require -->
      <xsl:when test="starts-with($networks, $network)">

        <!-- They want to output interface information -->
        <xsl:if test="$type = 'INTERFACE'">

          <xsl:choose>
            <xsl:when test="contains($interfaces, ',')">

              <xsl:if test="$direction = 'in'">
                <fw:interface direction="in">
                  <xsl:attribute name="recv"> <xsl:value-of select="substring-before($interfaces, ',')"/> </xsl:attribute>
                </fw:interface>            
              </xsl:if>
              <xsl:if test="$direction = 'out'">
                <fw:interface direction="out">
                  <xsl:attribute name="xmit"> <xsl:value-of select="substring-before($interfaces, ',')"/> </xsl:attribute>
                </fw:interface>              
              </xsl:if>

            </xsl:when>
            <xsl:otherwise>

              <xsl:if test="$direction = 'in'">
                <fw:interface direction="in">
                  <xsl:attribute name="recv"> <xsl:value-of select="$interfaces"/> </xsl:attribute>
                </fw:interface>              
              </xsl:if>
              <xsl:if test="$direction = 'out'">
                <fw:interface direction="out">
                  <xsl:attribute name="xmit"> <xsl:value-of select="$interfaces"/> </xsl:attribute>
                </fw:interface>              
              </xsl:if>

            </xsl:otherwise>
          </xsl:choose>

        </xsl:if>

        <!-- They want to output destination address information -->
        <xsl:if test="$type = 'ADDR'">

          <xsl:choose>
            <xsl:when test="contains($ips, ',')">
              <xsl:attribute name="address"> <xsl:value-of select="substring-before($ips, ',')"/> </xsl:attribute>
            </xsl:when>
            <xsl:otherwise>
              <xsl:attribute name="address"> <xsl:value-of select="$ips"/> </xsl:attribute>
            </xsl:otherwise>
          </xsl:choose>

        </xsl:if>

      </xsl:when>

      <!-- Just recurse around for the next element -->
      <xsl:otherwise> 

        <xsl:call-template name="net_info">
          <xsl:with-param name="type" select="$type"/>
          <xsl:with-param name="network" select="$network"/>
          <xsl:with-param name="networks" select="substring-after($networks, ',')"/>
          <xsl:with-param name="ips" select="substring-after($ips, ',')"/>
          <xsl:with-param name="interfaces" select="substring-after($interfaces, ',')"/>
          <xsl:with-param name="direction" select="$direction"/>
        </xsl:call-template>
      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>


  <!-- Match on the incomming bindings -->
  <xsl:template match="/*/dns:Bindings"> 

    <fw:FirewallConstruct name="Function_Dynamic DNS Service Rules" 
                          description="Automatically create firewall Rules to allow DNS service operation">

    <!-- Process all their Zone declarations -->
    <xsl:for-each select="dns:Zone">

      <xsl:variable name="rev_network_num"> 
        <xsl:value-of select="substring-before(@name, '.in-addr.arpa')"/>
      </xsl:variable>

      <!-- If it looks like it contains a network number within it --> 
      <xsl:if test="string-length($rev_network_num) > 0">

        <!-- Work out the real network number now, coz it is currently backwards -->
        <xsl:variable name="network_num">
          <xsl:call-template name="reverse_v4_add">
            <xsl:with-param name="addr"> <xsl:value-of select="$rev_network_num"/> </xsl:with-param>
          </xsl:call-template>
        </xsl:variable>

        <xsl:variable name="wanted_nets">
          <xsl:value-of select="/*/@networks"/> 
        </xsl:variable>

        <!-- Only if they want us to create rules for this network -->
        <xsl:if test="contains($wanted_nets, $network_num)">

          <!-- Nasty way to work out the next rule number to use -->
          <xsl:variable name="id"> <xsl:value-of select="/*/@ruleid + ((position() - 1) * 2)"/> </xsl:variable>

          <!-- The rule for incomming requests -->
          <fw:Rule Desc="Automatically generated DNS access rule. Incomming">
            <xsl:attribute name="RuleID"> <xsl:value-of select="$id"/> </xsl:attribute>

            <fw:action perform="pass"/> 
            <fw:protocol type="other" name="udp"/>

            <fw:src type="ip" mask="255.255.255.0">
              <xsl:attribute name="address"> <xsl:value-of select="$network_num"/>.0</xsl:attribute>
            </fw:src>

            <fw:dst type="ip" mask="255.255.255.255" ports="set">

              <!-- Work out the destination IP address information -->
              <xsl:call-template name="net_info">
                <xsl:with-param name="type">ADDR</xsl:with-param>
                <xsl:with-param name="network" select="$network_num"/>
                <xsl:with-param name="networks" select="/*/@networks"/>
                <xsl:with-param name="ips" select="/*/@ips"/>
                <xsl:with-param name="interfaces" select="/*/@interfaces"/>
                <xsl:with-param name="direction">in</xsl:with-param>
              </xsl:call-template>

              <fw:port-num>
                <xsl:attribute name="port"> <xsl:value-of select="/*/@port"/> </xsl:attribute>
              </fw:port-num>              
            </fw:dst>
  
            <!-- Work out the interface information -->
            <xsl:call-template name="net_info">
              <xsl:with-param name="type">INTERFACE</xsl:with-param>
              <xsl:with-param name="network" select="$network_num"/>
              <xsl:with-param name="networks" select="/*/@networks"/>
              <xsl:with-param name="ips" select="/*/@ips"/>
              <xsl:with-param name="interfaces" select="/*/@interfaces"/>
              <xsl:with-param name="direction">in</xsl:with-param>
            </xsl:call-template>
          </fw:Rule>

          <!-- The rule for outgoing responces -->
          <fw:Rule Desc="Automatically generated DNS access rule. Outgoing">
            <xsl:attribute name="RuleID"> <xsl:value-of select="$id + position() - 1"/> </xsl:attribute>

            <fw:action perform="pass"/> 
            <fw:protocol type="other" name="udp"/>

            <fw:src type="ip" mask="255.255.255.255" ports="set">

              <!-- Work out the source IP address information -->
              <xsl:call-template name="net_info">
                <xsl:with-param name="type">ADDR</xsl:with-param>
                <xsl:with-param name="network" select="$network_num"/>
                <xsl:with-param name="networks" select="/*/@networks"/>
                <xsl:with-param name="ips" select="/*/@ips"/>
                <xsl:with-param name="interfaces" select="/*/@interfaces"/>
              </xsl:call-template>

              <fw:port-num>
                <xsl:attribute name="port"> <xsl:value-of select="/*/@port"/> </xsl:attribute>
              </fw:port-num>              
            </fw:src>

            <fw:dst type="ip" mask="255.255.255.0">
              <xsl:attribute name="address"> <xsl:value-of select="$network_num"/>.0</xsl:attribute>
            </fw:dst>
  
            <!-- Work out the interface information -->
            <xsl:call-template name="net_info">
              <xsl:with-param name="type">INTERFACE</xsl:with-param>
              <xsl:with-param name="network" select="$network_num"/>
              <xsl:with-param name="networks" select="/*/@networks"/>
              <xsl:with-param name="ips" select="/*/@ips"/>
              <xsl:with-param name="interfaces" select="/*/@interfaces"/>
              <xsl:with-param name="direction">out</xsl:with-param>
            </xsl:call-template>
          </fw:Rule>

        </xsl:if>

      </xsl:if>

    </xsl:for-each>

    </fw:FirewallConstruct>

  </xsl:template>

</xsl:stylesheet>
