/*
 * 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 API

Most driver developers will find it much easier to use the SDLZ API.
The SDLZ API simplifies interacting with Bind because it uses
ASCII text for all zone and record data.  The SDLZ API makes
use of the DLZ API.  An understanding of the DLZ API will provide
a more complete picture of how everything operates.

The DLZ API provides the following functions:

   dns_dlzregister(drivername, methods, driverarg, mctx, dlzimp);

   void dns_dlzunregister(dlzimp);
   
   For more information on these functions, see the dlz.h file.

The DLZ API provides the following structures:
   
   dns_dlzmethods {
      create;
      destroy;
      findzone;
      allowzonexfr;
   } dns_dlzmethods_t;
  
   For more information on this structure, see the dlz.h file.

The callback functions are as follows.  THEY ALL MUST BE DEFINED!

   - create(mctx, dlzname, argc, argv[], driverarg, dbdata);

   - destroy(driverarg, dbdata);

   - findzone(driverarg, dbdata, mctx, rdclass, name, dbp);

   - allowzonexfr(driverarg, dbdata, mctx, rdclass, name, clientaddr, dbp);
       
   For more information on the callback functions, see the dlz.h file.
    
    
DLZ DRIVERS MUST SUPPORT MULTITHREADING!!!

ONLY 1 DLZ DRIVER ALLOWED PER VIEW!!!

DLZ DRIVERS USUALLY IMPLEMENT MOST OF BIND'S STANDARD DATABASE API, 
NOT JUST THE 6 DLZ METHODS!!!  FOR MORE INFORMATION ON BIND'S STANDARD 
DATABASE API, LOOK AT LIB/DNS/DB.C, LIB/DNS/INCLUDE/DNS/DB.H, 
LIB/DNS/RBTDB.C, /LIB/DNS/RBTDB.H, LIB/DNS/RBTDB64.C, LIB/DNS/RBTDB64.H,
/LIB/DNS/SDB.C, /LIB/DNS/INCLUDE/DNS/SDB.H, /LIB/DNS/SDLZ.C AND 
/LIB/DNS/INCLUDE/DNS/SDLZ.H

A DLZ driver must first register itself with Bind via the dns_dlzregister()
function. When a driver is registered, it specifies its name, a list of
callback functions, and its ISC memory context (ns_g_mctx).  Optionally, 
the driver may also pass an argument to "driverarg".  That argument will 
be returned unmodified to the callback methods.  Generally, this can be 
used to pass a pointer to some driver global memory structure.

The dns_dlzregister() function will pass back a pointer through "dlzimp".
This pointer should be saved by the driver for later use in calling the
dns_dlzunregister() function.

DRIVERS SHOULD NEVER ACCESS OR MODIFY THE "dlzimp" POINTER OR ITS DATA!

A DLZ driver should unregister itself before Bind completely shuts down.

Calls to dns_dlzregister() and dns_dlzunregister() (or your wrapper
functions) must be inserted into the server, in bind9/bin/named/main.c.
Registration should be in setup(), before the call to ns_server_create().
Unregistration should be in cleanup(), after the call to ns_server_destroy().
A #include should be added corresponding to the driver header file.

The driver module and header file must be copied to (or linked into)
the bind9/bin/named and bind9/bin/named/include directories
respectively, and they must be added to the DBDRIVER_OBJS and DBDRIVER_SRCS
lines in bin/named/Makefile.in  If the driver needs additional header
files or libraries in nonstandard places, the DBDRIVER_INCLUDES and
DBDRIVER_LIBS lines should also be updated.

The provided DLZ drivers have been integrated with the configure script
and do not make use of DBDRIVER_SRCS, DBDRIVER_OBJS, DBDRIVER_INCLUDES
or DBDRIVER_LIBS.  Those are only for use when building your own custom
driver.

Multiple DLZ drivers may be registered with Bind.

DLZ adds new syntax for Bind's configuration file.  The new syntax is in
the format:

dlz "load_name" {
  database "driver_name parameter_list {grouped list} more parameters { more grouped }";
};

dlz       - indicates that we wish to load a DLZ driver
load_name - the name displayed for any errors during config file parsing
database  - required keyword (DLZ requires one, and only one parameter)
driver_name - the name of the DLZ driver to use (multiple drivers may be registered)

Everything between the double quotes after database is the command line
to be passed to the DLZ driver.  The first argument in the command line 
MUST be the name of the DLZ driver you want to use.  The command line is 
parsed into argc / argv style (with a twist) and passed to your driver's 
create method.  The "twist" to the argc / argv parsing is parameter 
grouping.  This is explained below.

Normally, you can group words together on a command line using double
quotes.  Since the double quotes already have a specific purpose in the 
config file it is impossible to use them to group words.  Single quotes 
could have been used, but most drivers will probably be developed to work 
with an SQL database.  Single quotes have special meaning to SQL, and use 
of single quotes would make SQL drivers more difficult to implement.
Instead, { and } can be used to group words into a single parameter.
Each opening brace { must have a corresponding closing brace }.  Nesting 
of braces or crossover of braces is not allowed or supported.  White space 
is not trimmed within braces; braces are removed.

All parameters in the command line are passed to the driver, including
the driver_name. As an example, when the sample command line above is 
parsed into argc / argv, it will result in the following:

argc: 0  argv: "driver_name"    (all argv's without quotes)
argc: 1  argv: "parameter_list"
argc: 2  argv: "grouped list"
argc: 3  argv: "more"
argc: 4  argv: "parameters"
argc: 5  argv: " more grouped " (notice the extra white spaces).

Driver lifecycle:

When Bind starts, it will register all DLZ drivers added to its setup()
function.  Bind will parse its configuration file (named.conf).  If a DLZ
driver is specified, Bind will search for the specified DLZ driver.  If one
is not found, it will error and continue loading.  Since the DLZ driver 
could not be loaded, no DLZ data will be available for answering DNS 
queries.

If the DLZ driver is found, Bind will call its create method.  The create 
method is supplied with the current ISC memory context, the DLZ name 
("load_name" from the example above), the argc / argv command line, and 
"driverarg".  Again, "driverarg" can be used during registration to supply 
a pointer to some driver specific global memory structure. "dbdata" is 
un-initialized. "dbdata" may be used to pass back a "view specific" memory 
structure for use later in the findzone(), destroy() and allowzonexfr()
methods.  The create method is responsible for allocating any resources
the driver needs to perform its function.

Some explanation of "view specific" is needed.  Only one copy of each 
registered DLZ driver is loaded.  Bind allows one DLZ driver per view but 
supports multiple views.  Therefore, it's possible that a DLZ driver's 
create method could be called multiple times (once for each view using 
that driver).  Since only one copy of the driver is loaded, you should not 
store anything in the driver's global memory that is only relevant to a 
single view  (i.e. argc / argv parameters, or resources required only for 
that view).  Instead, you should create (and dynamically allocate) the 
appropriate structure to hold any "view specific" data your driver needs, 
and then pass the pointer to that structure back as dbdata.  For a better 
example, look at the SDLZ "driver filter" code.

Once a DLZ driver has been "created" Bind can make use of its findzone() 
and allowzonexfr() functions.  Findzone() is called when Bind performs
most DNS queries.  Therefore, findzone() could be called once, many times,
or not at all for each DNS query. Allowzonexfr() is called when Bind is
asked to perform a zone transfer on a DLZ zone.  Both full (axfr) and
incremental (ixfr) transfer requests are supported; however, the zone
transfer will always be a full (axfr) response.  DLZ is not capable of
supporting incremental (ixfr) responses.

If a DLZ driver is registered for a view, findzone() is usually called at 
least once for every query in that view.  Therefore, findzone() should 
operate as fast as possible.

When Bind receives a query, it will search its in memory table for a 
zone that matches the query.  Generally, Bind will then ask findzone() if 
it has a better match.  If findzone does have a better match, it will 
return a pointer to a database driver, and Bind will use it instead of the 
in memory database.  The only time Bind will NOT ask findzone() for a 
better match is when the in memory database has already provided a 
"perfect match".  A perfect match is where all the labels in the DNS name 
query match a zone, and there are no extra DNS name labels left over (i.e. 
the DNS query was at the zone's apex).  (If you don't understand what this 
means, consider using the SDLZ API instead.)

When Bind calls findzone(), it supplies driverarg, dbdata, the current ISC 
memory context, the views rdclass, and the DNS name queried for.  If 
findzone() determines that it has a better match, it should pass back a 
pointer to a standard Bind database.  Generally, the DLZ driver developer 
will have to implement a large portion of Bind's standard database API to 
support DNS queries.  For more information on Bind's standard database 
interface, look at the references mentioned previously.

Once Bind has determined the appropriate database to use, it will continue 
operation as normal (i.e. it will use the database to find the 
information requested in the DNS query).  For more information on this 
process, look at /bin/named/query.c.

Similarly, when Bind receives a zone transfer query, it will search its in
memory table for a zone that matches the query.  If it does not find one, 
it will then ask allowzonexfr() if it has a match.  If allowzonexfr() does
have a match, and the client that issued the axfr/ixfr query is permitted
to do a transfer, the function will return a pointer to a database driver.
Bind will use that database instead of the in memory database to respond
to the query.  The parameters passed to allowzonexfr() are the same as
those passed to findzone(), with the addition of clientaddr.  Clientaddr
is the IP address of the machine requesting the axfr/ixfr query.  The
IP address may be an IPV4 or an IPV6 address.  Once Bind has determined
the appropriate database to use, it will continue operation as normal.
For more information on this process, look at /bin/named/xfrout.c.

When it is time for Bind to shutdown, it will call the DLZ driver's 
destroy() method.  It is the destroy() methods job to free any resources 
that were allocated by the create method.  A driver's destroy() method 
will be called once for each view that uses that driver.

Finally, Bind will call the driver's unregister() method.  The unregister() 
method is responsible for freeing any of the resources allocated during 
the call to the register() method.

Reload/Reconfig/(possibly restart when it is implemented) considerations:

When Bind reloads or is reconfigured (via rndc reload or rndc reconfig), 
two copies of each view will exist for a period of time.  Bind holds on to 
the old view and destroys it after it loads the new view.  Thus, for a 
brief period of time there will be two instances of a DLZ driver for a 
view: one for the old view and one for the new view.  You should keep 
this in mind when developing drivers for use against databases with 
limited connection resources.  Unfortunately, until Bind calls the destroy() 
method on the old view, there is no way of telling which is old and which
is the new.



