/*
 * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 * USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
 * conceived and contributed by Rob Butler.
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 * USE OR PERFORMANCE OF THIS SOFTWARE.
 */
 
DLZ File System driver

Before reading this document, you should read the SDLZ API documentation.
While the SDLZ doc was written to help programmers write SDLZ drivers, it
also has an abundance of basic information that anyone using the DLZ
package should understand.  It will give you a good background to better
understand how this driver operates.

This driver allows you to use any file system as a database for storage of DNS
data.  While any file system may be used, either the Reiser or Memory file
systems will provide the best performance.  Obviously the Memory file system
will not provide permanent storage of data.  The memory file system should be
used as a high speed cache when using other means for permanent storage.

This driver has been tested on Windows 2K and Redhat Linux 7.2.  The driver
should build properly on any UN*X system that Bind supports.  Be sure to
specify --with-dlz-filesystem when running configure so that the file system
driver is built with Bind.  By default DLZ and its drivers are not built.
When you specify a DLZ driver the DLZ core is automatically built too.

The file system driver was built to be as flexible as possible, but is not as
flexible as the PostgreSQL or MySQL drivers because of how file systems work.
  
Below is a sample of a proper dlz_filesystem_driver configuration.  This
configuration segment would be contained in Bind's config (named.conf) file.
It is explained more below.  When you are setting up your own file system
driver be sure to pass the following parameters to Bind "-g -d 1".  The
first "-g" tells bind to write all log messages to stdout instead of a log
file.  The second parameter "-d 1" sets Bind's debug level to 1.  The file
system driver will output additional information when the debug level is set
to at least 1.  This can be very helpful while you are setting up the driver.
The additional information will be output only when Bind is trying to answer
DNS queries, not when Bind loads.  Run a few sample DNS queries in order to
see the output.

dlz "file system zone" {
        database "filesystem /dns-root/ .dns .xfr 0 ~";
};  

The first line:
dlz "file system zone" {

This line tells Bind we want to use a DLZ driver.  The word "dlz" is a new Bind
keyword added by the DLZ patch.  The next section "file system zone" is the
label for this configuration segment.  It is used in any error messages Bind
displays while parsing its config file.  The last piece "{" starts the DLZ
configuration section in Bind's config file.

The second line:
database "filesystem /dns-root/ .dns .xfr 0 ~";

This line is indented just to make it easier to read the configuration file.
The keyword "database" is the only parameter that can be specified in a DLZ
configuration segment.  It is required.  The double quote (") begins the
command line that is passed to the DLZ driver--in this case, the filesystem
driver.  The command line could be broken over many lines, but is not necessary
here.  The next piece is the word "filesystem".  This is the official name of
the DLZ filesystem driver.  We are telling Bind that we want to use the
filesystem driver.  The word "filesystem" is located at argv[0].  I.E. This is
the command line array passed to the driver, and the driver name must always be
at argv[0], it is not optional.

Next is "/dns-root/".  This is the directory path where all DNS data is stored.
I call it the dns-root.  The data root is specified at argv[1] immediately
after the "filesystem" driver name, it is not optional.  Also, the directory
path must always end with a path separator.  On Un*x systems this is usually a
"/", on Windows systems a "\".

The next item ".dns" is the zone / host path splitter.  This will be explained
more later in this document.  It should always start with a "." and must always
be at argv[2], it is not optional.

Similarly ".xfr" is the zone / client path splitter.  This will be explained
more later.  It too should always start with a "." and must always be at
argv[3], it is not optional.

A label is the portion of a domain name separated by ".".

For example: www.example.com has 3 labels.  Those labels are:

www
example
com

In the file system driver configuration above the number 0 is the maximum label
length.  When 0 is specified the maximum label length is unlimited.  If the 
maximum label length were 5, labels longer than five characters would be split
up.  So using the example above "example" would be split into 2 parts.  Those
parts would be "examp" and "le".  How the parts are used will be explained more
below.  When the maximum label length is anything but 0 an additional entry is
needed in the file system for each host in order for zone transfers to operate
properly.  We will explain this more later.  This parameter must always be at
argv[4], it is not optional.


The last parameter is "~" this is the data splitter.  Its use will be explained
later.  It must always be at argv[5], it is not optional.  This parameter MUST
always be only 1 character, and should NOT be any character that you expect to
use in your dns data.  Never use "." as the data splitter!

The last characters on the command line are ";
These characters complete and close the command line and are part of Bind's
standard configuration file syntax.
  
Third line:
};

This closes the DLZ configuration section in Bind's config file.  It is part
of Bind's standard configuration file syntax.

How the file system driver works:

The file system driver maps DNS data to file entries.  It does this by checking
file paths created from a combination of the command line parameters and domain
names.  The easiest way to understand how this happens is by demonstrating a
few examples.

Example 1:

For this example let's use the following configuration:

dlz "file system zone" {
        database "filesystem /dns-root/ .dns .xfr 0 ~";
};

When a query for www.example.com comes in Bind will check with the filesystem
driver to see if the zone is supported.  The file system driver will perform a
series of tests to see if the correct file paths exist.  If it does, the zone
is supported, and Bind is authoritative for the zone.  If the path does not
exist, it is not supported and bind is not authoritative for the zone.  Follow
the sequence below for a better understanding.

Query: www.example.com

First the filesystem driver (FSD) will check for the following path:
/dns-root/com/example/www/.dns

As you can see, a combination of data from the DNS query, and the file system
configuration are used.  The first portion "/dns-root/" is the DNS data root we
specified above.  "com/example/www" is the DNS query in reverse order by label.
The DNS query is reverse so we can take advantage of the hierarchal nature of
file systems.  I.E. any domain name ending with ".com" will be in the 
"/dns-root/com" directory.  Similarly any domain name ending with "example.com"
will be in the "/dns-root/com/example" directory.

Now don't forget, at this stage we are checking to see if the ZONE is
supported.  SDLZ drivers always look for a zone match by checking the entire
domain name first, and then trying again with fewer labels in the zone.  We
don't have a ZONE called "www.example.com", we have a ZONE called "example.com"
with a HOST called "www".  So the zone check for a path of 
"/dns-root/com/example/www/.dns" would fail.

Next the driver will check the file system for the following path:
/dns-root/com/example/.dns

This test would succeed.  Next the file system driver would attempt to get a
directory listing of "/dns-root/com/example/.dns/www".  Now you can see why
we need the "zone / host path splitter".  This allows the driver to know that
the path "/dns-root/com/example/.dns/www" is for the HOST "www" in the ZONE
"example.com".  Without the ".dns" in the path the driver would have no way of
figuring this out.  I recommend that your "zone / host path splitter" start
with a "." because the file system driver will prevent any zone or host name
from starting with a ".".

Next the driver would loop through the directory listing parsing FILE NAMES
to get DNS data.  Let me state this again so it is clear. THE FILE SYSTEM
DRIVER USES FILE NAMES FOR DNS DATA NOT THE CONTENTS OF THE FILE!  THE FILES
CAN BE ZERO LENGTH.

The file names have DNS data contained within them.  File names with spaces are
difficult to deal with.  So instead the file system driver allows the use of a
"data splitter" character.  When the file name is parsed, the "data splitter"
character is used to separate each data field in the file name.

Some examples:

A~86400~22.22.22.22     "A" record with ttl of 86400 IP address of 22.22.22.22

SOA~10~ns1.example.com.~root.ns1.example.com.~2~28800~7200~604800~86400

"SOA" record
ttl of 10
primary name server "ns1.example.com"
responsible person "root.ns1.example.com"
serial number 2
refresh 28800
retry 7200
expire 604800
minimum 86400

Data in a file name must be specified in the following order.  Notice that this
order is the same as the data order for PostgreSQL and MySQL drivers EXCEPT FOR
THE FIRST TWO DATA FIELDS.  In this driver type is the first field.  In the SQL
drivers type is the second field, and ttl is the first.

Order |    Name     |  Data type   |           Description
------------------------------------------------------------------------------- 
  1   | type        | string       | DNS data type               
  2   | ttl         | string (num) | Time to live
  3   | host        | string       | Host name or IP address
  4   | mx_priority | string (num) | MX Priority
  5   | data        | string       | IP address / Host name / Full domain name
  6   | primary_ns  | string       | Primary name server for SOA record
  7   | resp_person | string       | Responsible person for SOA record
  8   | serial      | string (num) | Serial # for SOA record
  9   | refresh     | string (num) | Refresh time for SOA record
  10  | retry       | string (num) | Retry time for SOA record
  11  | expire      | string (num) | Expire time for SOA record
  12  | minimum     | string (num) | Min time for SOA record
  
The file system driver also supports wildcard hostnames.  Usually, "*" is used
as the "wild card", but "*" has special meaning in file systems.  Instead the
file system driver uses the hostname "-" as its wildcard.  Like all other DLZ
drivers "@" is used as the hostname at the zone apex.

More examples.  Below is a list of examples of where DNS data should be held
for each host/zone combination using the same DLZ configuration we have been
using all along.

Zone: the zone name
Host: the host name
Path: the correct path to hold DNS data in

Zone: example.com 
Host: www.long
Path: /dns-root/com/example/.dns/long/www

zone: long.example.com
Host: www
Path: /dns-root/com/example/long/.dns/www

Zone: example.com
Host:
Path: /dns-root/com/example/.dns/@

Zone: example.com
Host: * (wildcard)
Path: /dns-root/com/example/.dns/-

The DLZ driver also supports zone transfer.  When looking up clients in the
file system the driver makes use of the "zone / client path splitter".  Here's
how it works.  When an client wishes to make a zone transfer Bind will use the
driver to check if the client is allowed to.  It will use the client's IP
address in the lookup as well has the ZONE.

Examples:

Zone: the zone name
Client: the IP address of the client (could be IPV4 or IPV6 address)
Path: The path checked

Zone: example.com
Client: 127.0.0.1
Path: /dns-root/com/example/.xfr/127.0.0.1

A file named 127.0.0.1 must exist in the path /dns-root/com/example/.xfr

The DLZ file system driver is intended to be used with advanced file systems
like the Reiser file system.  The Reiser file system allows for a large number
of directory entries to exist without any degradation of performance.  If
however you need to use this driver on a file system that cannot support large
numbers of directory entries you can request DLZ to parse up the directories
more.  This is the purpose of the max label length parameter.  If a label
exceeds the allowed length it will be split up.

Let's try a new configuration

dlz "file system zone" {
        database "filesystem /dns-root/ .dns .xfr 5 ~";
};

Notice that this configuration is the same as last time, except we have changed
the max label length to 5.

Some examples:

Zone: example.com
Host: www
Path: /dns-root/com/examp/le/.dns/www

Notice how example was split into "examp/le".

Zone: example.com 
Host: www.long
Path: /dns-root/com/examp/le/.dns/long/www

Zone: example.com 
Host: www.reallylong
Path: /dns-root/com/examp/le/.dns/reall/ylong/www

Some zone transfer examples:

Zone: example.com
Client: 127.0.0.1
Path: /dns-root/com/examp/le/.xfr/127.0.0.1

VERY IMPORTANT!!!!

When the max label length is NOT 0 the file system driver needs additional help
to know what the hostname is for zone transfers.  So, when you use a max label
length other than 0 you should also place a file called ".host~hostname" in
your DNS data directory.

Examples:

Zone: example.com
Host: www
Hostname entry: /dns-root/com/examp/le/.dns/www/.host~www

Zone: example.com
Host: www.long
Hostname entry: /dns-root/com/examp/le/.dns/long/www/.host~www.long

Zone: example.com
Host: www.reallylong
Hostname entry: /dns-root/com/examp/le/.dns/reall/ylong/www/.host~www.reallylong

Here's a complete example:

Configuration:

dlz "file system zone" {
        database "filesystem /dns-root/ .dns .xfr 0 ~";
};

File system view:


$ ls -RA --format=single-column dns-root
dns-root:
com

dns-root/com:
example

dns-root/com/example:
.dns
.xfr

dns-root/com/example/.dns:
-
@
www

dns-root/com/example/.dns/-:
A~86400~1.2.3.4

dns-root/com/example/.dns/@:
SOA~10~ns1~root.ns1~2~28800~7200~604800~86400

dns-root/com/example/.dns/www:
A~86400~77.88.99.10

dns-root/com/example/.xfr:
10.10.10.1
127.0.0.1

Using this data:

zone transfers from 10.10.10.1 and 127.0.0.1 would succeed.
A query for www would return an "A" record with a ttl of 86400 and IP of
77.88.99.10
A query for any other host name in the zone "example.com" would return
an "A" record with a ttl of 86400 and an IP address of 1.2.3.4
The SOA record has a
ttl of 10
primary name server "ns1.example.com"
responsible person "root.ns1.example.com"
serial number 2
refresh 28800
retry 7200
expire 604800
minimum 86400

Another example:
 
Configuration:

dlz "file system zone" {
        database "filesystem /dns-root/ .dns .xfr 5 ~";
};

File system view:

$ ls -RA --format=single-column dns-root
dns-root:
com

dns-root/com:
examp

dns-root/com/examp:
le

dns-root/com/examp/le:
.dns
.xfr

dns-root/com/examp/le/.dns:
-
@
reall
www

dns-root/com/examp/le/.dns/-:
.host~-
A~86400~1.2.3.4

dns-root/com/examp/le/.dns/@:
.host~@
SOA~10~ns1~root.ns1~2~28800~7200~604800~86400

dns-root/com/examp/le/.dns/reall:
ylong

dns-root/com/examp/le/.dns/reall/ylong:
www

dns-root/com/examp/le/.dns/reall/ylong/www:
.host~www.reallylong
A~86400~9.8.7.6

dns-root/com/examp/le/.dns/www:
.host~www
A~86400~77.88.99.10

dns-root/com/examp/le/.xfr:
10.10.10.1
127.0.0.1

Using this data, all previous answers to DNS queries would be answered in the
same way.  I've added another hostname to the listing above.  "www.reallylong"
Notice how the hostname is split up because of the max label lenth.  Also
notice how each directory with a complete hostname has an entry
".host~hostname".  For "www.reallylong" this is ".host~www.reallylong".  This
information is required when max label length is not 0 so that zone transfers
will work properly.  

It may be confusing at first to understand how to properly create the directory
structure for the file system driver.  Read the docs over a few times.  Read 
the docs for DLZ itself, and even for some of the other drivers.  Once you have
things will make more sense.  Also, try things out.  It's rather hard to
explain how to use the file system driver.  But once you start using it you
will see how easy it is to use it.
