|
Home page HOWTO build a DNS registry Sheets Credits How to contribute Legal notice Whois service |
Printable version
Version française
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:
The solution we suggest is to use a XML file for the database:
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:
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 -o db.tld zone2bind.xslt example-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")
|
Last news Changing the IP address of the TLD name server The whois service Anycast, une nouvelle technique de gestion d'un parc de serveur de noms Setting up a DNS registry with XML and XSL The choices for a nameserver Checking your domaine: why and how Modélisation de données Should you publish social information about you registrants? The zone file generator HOWTO setup a domain registry IDN (Internationalized Domain Names) |
||||
DocBook/XML source of this page
For every question about generic NIC, please ask info@generic-nic.net.
(last rebuild by WML 2.0.8 (30-Oct-2001): Wednesday 9 March 2005)