Copyright © 2003 AFNIC
$Date: 2004-03-14 20:59:18 $
Abstract
This document describes how to set up a small DNS registry using a XML file as the database (both social and technical) and XSLT to produce the zone file.
Table of Contents
Every DNS registry will require a database. For large registries, a real DBMS (DataBase Management System) is mandatory. But for small registries, it can be overkill. These small registries (for instance ccTLDs in developing countries) typically use adhoc techniques that have serious drawbacks:
Some use only the zone file as a database. Social information is made of comments in that zone file. It prevents any whois-like service to be created. And it does not allow automatic processing of the data (for instance, finding all the domains hold by a given entity).
Some use office software like MS-Excel. Since these programs have very poor groupware capabilities, it makes difficult for several people to work on the database.
The solution we suggest is to use a XML file for the database:
Since it is a text file, groupware tools like CVS will be usable to allow group work.
XML is an open format so you do not depend on Microsoft for your data.
XML is suitable for automatic processing: we will mostly use XSLT but many other tools are possible.
![]() | Warning |
|---|---|
This document has been throughly checked but the ideas presented here have never been actually deployed with a real registry. |
In French, you can see the AFNIC training.
One of the funny things about XML is that you can use several different programming languages to write the schema. You have:
DTD (Document Type Definition), a legacy from SGML, widely implemented and recognized. The simplest solution.
XML Schemas, a very verbose language. [1]
Relax, the least common
When you set up a registry, you have to decide on a schema. Wether it is in SQL or written for XML, this is a policy choice. Here, we will skip the political part and I propose the following schema, written using the DTD language.
<!-- A simple schema for an Internet DNS registry --> <!ELEMENT domain (fqdn,nameservers)> <!ATTLIST domain holder IDREF #REQUIRED> <!ATTLIST domain tech_c IDREF #REQUIRED> <!ATTLIST domain admin_c IDREF #REQUIRED> <!ELEMENT nameserver (name,ipaddress?)> <!ELEMENT contact (name,address+,city,country,phone,email)> <!ATTLIST contact handle ID #REQUIRED> <!ELEMENT nameservers (nameserver)+> <!ELEMENT zone (domain|contact)+> <!ELEMENT fqdn (#PCDATA)> <!ELEMENT name (#PCDATA)> <!ELEMENT address (#PCDATA)> <!ELEMENT city (#PCDATA)> <!ELEMENT country (#PCDATA)> <!ELEMENT phone (#PCDATA)> <!ELEMENT email (#PCDATA)> <!ELEMENT ipaddress (#PCDATA)> <!-- $Id: schema.dtd,v 1.1 2003-03-18 13:55:38 bortzmeyer Exp $ -->
With such a schema, you can write a zone file.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE zone SYSTEM "schema.dtd">
<!-- $Id: example-zone.xml,v 1.3 2003-04-22 15:33:27 bortzmeyer Exp $ -->
<zone>
<contact handle="SB1">
<name>Bortzmeyer</name>
<address>Abbey Road</address>
<city>London</city>
<country>uk</country>
<phone>1234</phone>
<email>bortzmeyer@hotmail.com</email>
</contact>
<domain holder="PL1" tech_c="CD1" admin_c="SB1">
<fqdn>foobar.tld</fqdn>
<nameservers>
<nameserver>
<name>ns1.example.com</name>
</nameserver>
<nameserver>
<name>ns2.example.com</name>
</nameserver>
</nameservers>
</domain>
<contact handle="CD1">
<name>Delacourt</name>
<address>Chateau</address>
<city>Cersailles</city>
<country>fr</country>
<phone>1234</phone>
<email>celine@nic.fr</email>
</contact>
<contact handle="PL1">
<name>Lubrano</name>
<address>Immeuble International</address>
<city>Saint-Quentin</city>
<country>fr</country>
<phone>1234</phone>
<email>lulu@nic.fr</email>
</contact>
<domain holder="SB1" tech_c="SB1" admin_c="SB1">
<fqdn>barfoo.tld</fqdn>
<nameservers>
<nameserver>
<name>ns1.barfoo.tld</name>
<ipaddress>10.20.30.40</ipaddress>
</nameserver>
</nameservers>
</domain>
<domain holder="SB1" tech_c="SB1" admin_c="PL1">
<fqdn>dummy.tld</fqdn>
<nameservers>
<nameserver>
<name>ns2.barfoo.tld</name>
<ipaddress>10.20.30.41</ipaddress>
</nameserver>
<nameserver>
<name>ns2.nic.fr</name>
</nameserver>
</nameservers>
</domain>
</zone>
The zone is a simple text file. You can use any editor you want (although I would recommand Emacs with its psgml package, which simplifies XML edition a lot).
To create a zone file suitable for your nameserver (we will use the syntax of BIND and nsd), a XSL stylesheet can be as simple as:
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
version='1.0'>
<!-- $Id: zone2bind.xslt,v 1.2 2003-04-22 15:33:27 bortzmeyer Exp $ -->
<xsl:output method="text"/>
<xsl:preserve-space elements="*"/>
<xsl:param name="tld">tld</xsl:param>
<xsl:param name="soa">1</xsl:param> <!-- The SOA must be provided by the
calling program or we default to 1 -->
<xsl:param name="current_date">(UNSPECIFIED)</xsl:param>
<xsl:param name="current_dir">(UNSPECIFIED)</xsl:param>
<xsl:param name="current_host">(UNSPECIFIED)</xsl:param>
<xsl:param name="zone_file">(UNSPECIFIED)</xsl:param>
<xsl:template match="/">
<xsl:text>; Zone file for the '.</xsl:text><xsl:value-of select="$tld"/><xsl:text>' domain
; Automatically produced by </xsl:text>
<xsl:value-of select="system-property('xsl:vendor')"/><xsl:text> (</xsl:text>
<xsl:value-of select="system-property('xsl:vendor-url')"/><xsl:text>)
: on </xsl:text>
<xsl:choose>
<xsl:when test="function-available('date:date-time')">
<xsl:variable name="now" select="date:date-time()"/>
<xsl:value-of select="date:year($now)"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="date:month-in-year($now)"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="date:day-in-month($now)"/>
<xsl:text> (date obtained by the XSL processor)</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$current_date"/>
<xsl:text> (date obtained from the calling program)</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
; Generated in </xsl:text>
<xsl:value-of select="$current_host"/>
<xsl:text>:</xsl:text>
<xsl:value-of select="$current_dir"/>
<xsl:text>
; from </xsl:text>
<xsl:value-of select="$zone_file"/>
<xsl:text>
; Do not edit by hand
</xsl:text>
$TTL 86400
@ IN SOA ns1.nic.tld. hostmaster.nic.tld. (
<xsl:value-of select="$soa"/> ; Serial
86400 ; Refresh
21600 ; Retry
604800 ; Expire
7200 ) ; Negative Cache TTL
;
@ IN NS ns1
@ IN NS ns2
<xsl:text> </xsl:text>
<xsl:apply-templates select="zone/domain"/>
</xsl:template>
<xsl:template match="/zone/domain">
<!-- TODO: better handling of whitespace to have a prettier zone file -->
<xsl:for-each select="nameservers/nameserver">
<xsl:value-of select="../../fqdn"/>. IN NS <xsl:value-of select="name"/>.
<xsl:variable name="name"><xsl:value-of select='name'/></xsl:variable>
<!-- Add the glue records, if necessary -->
<xsl:if test="substring($name, 2 + string-length($name) - string-length('$tld')) = $tld">
<xsl:text> </xsl:text>
<xsl:value-of select="$name"/>. IN A <xsl:value-of select="ipaddress"/>
</xsl:if>
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
You can apply this stylesheet and automatically get the zone file in the BIND format. Here is an example with libxslt:
%xsltproc -odb.tldzone2bind.xsltexample-zone.xml
But XML allows you much more. Suppose we want to get the information about a contact. we will use the xpath tool, part of Perl XML::XPath module[2]. xpath allows us to use Xpath queries against our data:
%xpath -e'//contact[@handle="SB1"]'./example-zone.xml Found 1 nodes in ./example-zone.xml: -- NODE -- <contact handle="SB1"> <name>Bortzmeyer</name> <address>Abbey Road</address> <city>London</city> <country>uk</country> <phone>1234</phone> <email>bortzmeyer@hotmail.com</email> </contact>%xpath -e'//contact[city="Saint-Quentin"]'./example-zone.xml Found 1 nodes in ./example-zone.xml: -- NODE -- <contact handle="PL1"> <name>Lubrano</name> <address>Immeuble International</address> <city>Saint-Quentin</city> <country>fr</country> <phone>1234</phone> <email>lulu@nic.fr</email> </contact>
The whois protocol is defined in RFC 954 (RFC means Request For Comments. The RFC are available on the IETF server.). You use it to find out social information about domain registrants, or contacts (technical or administrative). An example of use, with the whois program:
% whois -h whois.example.tld linux.tld
All rights reserved.
Copyright TLD-NIC
Domain: linux.tld
Registrant name: Torvalds
...
We present here a very simple whois server, but which is sufficient for our purposes. It opens the XML file, parse it into a DOM object, then waits for requests. When one arrives, it uses Xpath to query the data.
#!/usr/bin/python
# $Id: whoisd.py,v 1.3 2003-04-15 14:28:02 bortzmeyer Exp $
import SocketServer, time, re, sys, os, pwd, syslog, signal, string
from xml.dom import ext
from xml.dom.ext.reader import PyExpat
from xml.xpath import Evaluate
PORT = 43
user = "nobody"
db_file = "example-zone.xml"
InvalidDomain = "This domain name is not a legal one"
legal = """All rights reserved.
Copyright <Your organization>.
"""
def hup_received (signum, frame):
global xml_dom_object
syslog.syslog (syslog.LOG_INFO | syslog.LOG_DAEMON, \
"Reloading database")
xml_dom_object = reader.fromStream(open(db_file))
class ThreadingServer (SocketServer.ThreadingMixIn,SocketServer.TCPServer):
pass
class WhoisRequestHandler (SocketServer.StreamRequestHandler):
def write_contact (self, handle, label):
result = Evaluate("/zone/contact[@handle=\"" + \
handle + "\"]",
xml_dom_object.documentElement)
contact_node = result[0]
contact_name = Evaluate("name/text()", contact_node)[0].nodeValue
self.wfile.write (label + " name: " + contact_name + "\n")
contact_address = Evaluate("address/text()", contact_node)[0].nodeValue
self.wfile.write (label + " address: " + contact_address + "\n")
contact_city = Evaluate("city/text()", contact_node)[0].nodeValue
self.wfile.write (label + " city: " + contact_city + "\n")
contact_email = Evaluate("email/text()", contact_node)[0].nodeValue
self.wfile.write (label + " email: " + contact_email + "\n")
self.wfile.write ("\n")
def handle (self):
domain = string.strip(self.rfile.readline())
# TODO: allow the search of a contact by its handle
syslog.syslog (syslog.LOG_INFO | syslog.LOG_DAEMON, \
"Request for domain \"" + str(domain) + "\" from " +
str(self.client_address))
if domain == "":
raise InvalidDomain
if not re.match('^[A-Za-z0-9\.\-]+$', domain):
raise InvalidDomain
# Use XPath to find the domain
result = Evaluate("/zone/domain[fqdn=\"" + \
string.lower(domain) + "\"]",
xml_dom_object.documentElement)
if not len(result):
self.wfile.write ("No such domain \"" + domain + "\"\n")
return
domain_node = result[0]
domain_name = Evaluate("fqdn/text()", domain_node)[0].nodeValue
self.wfile.write (legal)
self.wfile.write ("Domain: " + domain_name + "\n\n")
domain_holder = Evaluate("@holder", domain_node)[0].nodeValue
self.write_contact(domain_holder, "Registrant")
domain_admin = Evaluate("@admin_c", domain_node)[0].nodeValue
self.write_contact(domain_admin, "Admin. contact")
domain_tech = Evaluate("@tech_c", domain_node)[0].nodeValue
self.write_contact(domain_tech, "Tech. contact")
# TODO: display the nameservers
server = ThreadingServer (("", PORT),
WhoisRequestHandler)
server.allow_reuse_address = 1
if (os.getuid() == 0):
uid = pwd.getpwnam(user)[2]
os.setreuid(uid,uid)
syslog.openlog('whoisd')
reader = PyExpat.Reader()
xml_dom_object = reader.fromStream(open(db_file))
signal.signal(signal.SIGHUP, hup_received)
try:
server.serve_forever()
except KeyboardInterrupt:
syslog.syslog (syslog.LOG_INFO | syslog.LOG_DAEMON, \
"Terminating")
For every question about generic NIC, please ask info@generic-nic.net.
(last rebuild by WML 2.0.11 (19-Aug-2006): Monday 26 October 2009)