/*
 * 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 Berkeley DB 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 Berkeley DB as a database for storage of DNS
data.  It has been tested on Windows 2K and Redhat Linux 8.0.  The driver
should build properly on any UN*X system that Bind & Berkeley DB support. 
Be sure to specify --with-dlz-bdb when running configure so that the
Berkeley DB 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.

BE SURE TO READ THE SPECIAL BUILD INSTRUCTIONS FOR THIS DRIVER THAT ARE
DETAILED LATER IN THIS DOCUMENT!

The Berkeley DB driver was built to be as flexible as possible, but it is not
as flexible as the PostgreSQL, MySQL or file system drivers because of how
Berkeley DB works.
  
Below is a sample of a proper dlz_bdb_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 Berkeley DB
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 Berkely
DB 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 "bdb zone" {
        database "bdb /dns-root dnsdata.db";
};


The first line:
dlz "bdb 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 "bdb 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 "bdb /dns-root dnsdata.db";

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 Berkeley DB
driver.  The command line could be broken over many lines but is not necessary
here.  The next piece is the word "bdb".  This is the official name of the DLZ
Berkeley DB driver.  We are telling Bind that we want to use the Berkeley DB
driver.  The word "bdb" 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".  It should NOT have a "/" at the end.  This is the
directory path where the Berkeley DB environment is stored.  The Berkeley DB
driver operates in "concurrent data store" (CDS) mode, not in the old style
"DBM" mode, and thus requires a Berkeley DB environment.  For more information
about "concurrent data store" operation of the Berkeley DB, see the
documentation provided with that software package.

Basically, CDS allows the database to be "read" by one or more applications
simultaneously, and also allows the database to be updated or "written" to
simultaneously by another application.  Only one application may make updates
to the database at a time.  The Berkeley DB takes care of all synchronization
and locking.  Because of this, it may appear that multiple applications are
writing to the database simultaneously, but that is not the case.  One
application will make its updates, and then the other application will.
Depending upon how the application is written, the updates from the two
programs may actually be inter-weaved.  One program makes an update, then the
second, then the first again, etc.  In order for the Berkeley DB to handle
locking properly, all applications which access the database must use the same
environement and use the correct Berkeley API's to access the database
environment BEFORE attempting to perform any database operations. The Berkeley
DB driver, and the supplied utility (dlzbdb) both make use of the Berkeley
environment properly.  Look at the source code of both to see how to properly
access the Berkeley envionment and database. 

The next item "dnsdata.db" is the path and name of the database file to store
the dns data in.  This "path" is relative to the environment path.  So, using
the configuration as we have it here will create a file called "dnsdata.db" in
the path "/dns-root" for a full path of "/dns-root/dnsdata.db".  You can also
use absolute paths for the database file path and name if you don't want the
database file stored in the same folder as, or relative to the Berkeley DB
environment. I highly recommend that you place the database file in the same
folder as the database environment just for ease of administration.

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 Berkeley DB driver works:

The Berkeley database file actually has 4 "databases" in it.  Berkeley DB 
and "DBM" / "NDBM" / "GDBM" style databases are only simple key = value
databases.  That is to say that each database can only store a key, and the
corresponding value for that key.  In order to perform more complex query
operations using multiple keys you need to use multiple key = value databases
and combine them together in some way.  This is easiest with the Berkeley DB
as it provides API's to do this, so we don't have to do it all manually
(We still do a lot of it manually).  The four databases in the database file
are:

"dns_data" -    The DNS data.  The key is meaningless.  It is only used as a
                means to identify this particular data record.  The key is used
                to lookup this particular data record by other index databases.

"dns_zone" -    The zone index for the DNS data.  The key is a zone name, the
                value is a "dns_data" key.  Multiple matching keys (zone names)
                are allowed.  This database is used to narrow results to a
                particular zone.  Usually used in combination with "dns_host".
                
"dns_host" -    The host index for the DNS data.  The key is a hose name, the
                value is a "dns_data" key.  Multiple matching keys (host names)
                are allowed.  This database is used to narrow results to a
                particular host.  Usually used in combination with "dns_zone".
                
"dns_client" -  The lookup database for zone transfers.  This databases keys
                are zone names, and the values are IP addresses.  Multiple
                matching keys are allowed.  Multiple matching IP addresses are
                allowed.  Only one unique zone & IP address are allowed. I.E
                You may have:
                
                        key             Value
                        
                        example.com     127.0.0.1
                        example.com     192.168.0.1
                        example2.com    127.0.0.1
                        example2.com    192.168.0.1
                        example.com     127.0.0.1       // not allowed!
                
                All the above would be allowed except the last line because
                a key "example.com" already has a value "127.0.0.1".

Data in the "dns_data" value field must be specified in the following order.
Only a single space " " must be between each field, and records of DNS type TXT
should include any necessary quoting.  Any un-needed fields should be ignored.

Order |    Name     |  Data type   |           Description
-------------------------------------------------------------------------------
  1   | zone        | string       | DNS zone
  2   | host        | string       | DNS host name 
  3   | type        | string       | DNS data type               
  4   | ttl         | string (num) | Time to live (string must convert to num)
  5   | mx_priority | string (num) | MX Priority (only for MX DNS types)
  6   | data        | string       | IP address / Host name / Full domain name
  7   | primary_ns  | string       | Primary name server SOA record (SOA ONLY)
  8   | resp_person | string       | Responsible person SOA record  (SOA ONLY)
  9   | serial      | string (num) | Serial # for SOA record        (SOA ONLY)
  10   | refresh     | string (num) | Refresh time for SOA record    (SOA ONLY)
  11  | retry       | string (num) | Retry time for SOA record      (SOA ONLY)
  12  | expire      | string (num) | Expire time for SOA record     (SOA ONLY)
  13  | minimum     | string (num) | Min time for SOA record        (SOA ONLY)
  
Like most other DLZ drivers, the Berkeley DB supports wildcard hostnames using
"*" as the wild card, and the character "@" is used as the hostname at the zone
apex.

Some examples:

example.com @ SOA 10 ns1.example.com. root.example.com. 2 2800 7200 604800 86400
example.com www A 10 192.168.0.1
example.com mail A 10 192.168.0.2
example.com backup A 10 192.168.0.3
example.com @ MX 10 20 mail
example.com @ MX 10 40 backup
example.com www MX 10 20 mail
example.com www MX 10 40 backup
example.com ns1 A 10 192.168.0.4
example.com ns2 A 10 192.168.0.5
example.com @ NS 10 ns1.example.com.
example.com @ NS 10 ns2.example.com.
example.com @ TXT 10 "This is some sample text.  Notice it is inside of quotes"
example.com @ TXT 10 "The quotes are required by Bind so that it returns the"
example.com @ TXT 10 "string as a single entry.  Otherwise it will return each"
example.com @ TXT 10 "word as a entry instead of the entire string."
example.com @ TXT 10 "This text is actually 8 separate text records.  It was"
example.com @ TXT 10 "broken up this way to be easier to read.  You may"
example.com @ TXT 10 "specify long text entries if you want. i.e. this could"
example.com @ TXT 10 "have all been one entry if we wanted to do that."
example.com * A 10 192.168.0.6

Using the dlzbdb utility:

The dlzbdb utility was developed to enable immediate use of the Berkeley DB
driver and serve as an example of how to interact with the database.  The
utility provides only basic operations.

For help at anytime, execute dlzbdb without any command line arguements.  The
program will display a simple help message showing how to use it.

First you must create a database.  The dlzbdb utility can create a database and
add records at the same time.

For example:

dlzbdb -n -a "d example.com www A 10 192.168.0.1" /dns-root dnsdata.db

This command will create a new database and environment if they did not exist.
If a database and environment already exist, they will not be created or
overwritten.  The new record will simply be added to the existing database.

The "-n" option can be used to re-create Berkeley DB environment files if they
are missing after performing a Berkeley DB recover operation.  If the
environment files are gone and the database file is intact, the environment
files will be recreated, and no data in the database will have been lost.

The "a" option indicates that we wish to add a new record to the database.  The
data is contained within the quotes.  The first letter "d" inicates that this
is DNS data.  The rest of the data inside the quotes is the data to be added
to the database.  Notice it follows the format listed above.  The dlzbdb
utility is a bit more forgiving in that if there are extra spaces between the
fields, it will automatically remove them.  To insert text records with quotes,
you will need to escape the quotes in the data record by proceeding each quote
with a "\" character.

For example:

dlzbdb -a "d example.com www TXT 10 \"this is escaped\"" /dns-root dnsdata.db

The dlzbdb utility will automatically create the key for the "dns_data"
database, and add required zone and host entries to the other databases as
well.

In order for zone transfers to be performed, a client must be authorized to do
zone transfers.  This is accomplished by adding an entry to the "dns_client"
database.  To do this:

dlzbdb -a "c example.com 192.168.0.1" /dns-root dnsdata.db

This command is very similar to the previous ones.  The difference is that
instead of a "d" we have a "c" as the first character.  This lets dlzbdb know
we want to add a client to the zone transfer list.  After the "c" character is
"example.com"--this is the zone.  Following that is "192.168.0.1".  This is the
IP address of a machine that we will allow to perform zone transfers.

In all the commands shown thus far, the last two parameters are "/dns-root" and
"dnsdata.db".  The first parameter is the location of the Berkeley DB
environment.  The last is the relative path & name of the database file.  These
two parameters are required for any operation with dlzbdb and should always be
the last two parameters, in that order.

dlzbdb can also perform "bulk" loads of data.  To do that, create a file where
each line is one "command".  Each command should look exactly as the commands
we have listed above.  The file may also include c, c++ and shell comments;
comments are ignored.

Example file:

d example.com @ SOA 10 ns1.example.com. root.example.com. 2 2800 7200 604800 86400
d example.com www A 10 192.168.0.1
d example.com mail A 10 192.168.0.2
d example.com backup A 10 192.168.0.3
d example.com @ MX 10 20 mail
d example.com @ MX 10 40 backup
d example.com www MX 10 20 mail
d example.com www MX 10 40 backup
// This is an example comment.  It is ignored.
// these are our name servers
d example.com ns1 A 10 192.168.0.4
d example.com ns2 A 10 192.168.0.5
d example.com @ NS 10 ns1.example.com.
d example.com @ NS 10 ns2.example.com.
d example.com @ TXT 10 "This is some sample text."
d example.com * A 10 192.168.0.6
c example.com 192.168.0.4   // allow zone transfers to our public DNS servers
c example.com 192.128.0.5

The Berkeley DB does not allow the use of transactions when operating in CDS
mode.  So it is not possible to "rollback" the load if an error occurs on one
line.  Instead, the dlzbdb utility will display an error message indicating any
line which could not be loaded.  You should then copy that line and any others
that failed to load to a new file, repair the errors and load the new file.
This is not the best solution, but it is the only one that works.

The dlzbdb utility also has the ability to list entries in the database and
delete entries from the database.  Execute dlzbdb with no parameters to display
the help.  Those operations should be self explanatory.

BERKELEY DB DRIVER BUILD INSTRUCTIONS:

Sleepycat, the makers of Berkeley DB, changed the api between versions 4.0.X of
and 4.1.X.  The dlz driver was developed to use the newest version of the
driver available at the time (4.1.25 when this driver released).  The version
of Berkeley DB shipped with most Linux and other UN*X compatible distributions
uses the older style of the Berkeley DB API.  To build this driver you must
download and install a new version of Berkeley DB.  Follow the instructions
included with the Berkeley DB software to build it for your platform.

On Redhat, Berkeley DB will build and install, but does not install over the
original Berkeley DB that shipped with redhat.  I expect the same thing will
happen with other distributions.  In order to build this driver properly, you
will need to specify the path to the Berkeley DB that you just built &
installed.  This is done by specifying the path (as shown below) when running
configure:  (Path used here was for Redhat after Berkeley DB build & install)

./configure --with-dlz-bdb=/usr/local/BerkeleyDB.4.1

Bind will now build properly, but when you try to execute named using the BDB
driver, or use dlzbdb, you will get an error about not finding the library
libdb-4.1.so.  This is because the library is not in your "lib" path.  The
easiest way to fix this is to create a link in your lib path that points to
where the Berkeley install script put the libdb-4.1.so file.  For Redhat
this is:

ln /usr/local/BerkeleyDB.4.1/lib/libdb-4.1.so /usr/lib/libdb-4.1.so

This will create a hard link.  If /usr/local and /usr/lib are not on the same
file system, create a symbolic link instead.

ln -s /usr/local/BerkeleyDB.4.1/lib/libdb-4.1.so /usr/lib/libdb-4.1.so

Lastly run ldconfig to reconfigure the dynamic linker run-time bindings.

Now everything should work fine.



