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

SDLZ API (Simple Dynamically Loadable Zones)

The SDLZ API will be of most interest to driver developers.  It allows all 
interactions between the driver and Bind to be ASCII text.  This 
simplifies development of drivers because data will generally be stored 
and manipulated in ASCII.

The SDLZ API is made available by the SDLZ "filter."  The "filter" makes 
use of the DLZ API.  For a more complete picture of how everything works, 
you may want to read about the DLZ interface (API), but it is not 
necessary.  If you read both you will find some redundancy in the 
documentation.  This was done so that you could read only this document 
and have enough information to implement a complete driver.

The SDLZ API provides the following functions:

   dns_sdlzregister(drivername, methods, driverarg, flags, mctx, sdlzimp);

   dns_sdlzunregister(sdlzimp);

   dns_sdlz_putnamedrr(allnodes, name, type, ttl, data);

   dns_sdlz_putrr(lookup, type, ttl, data);
          
   dns_sdlz_putsoa(lookup, mname, rname, serial);
   
   For more information on these functions, see the sdlz.h file.
   
The SDLZ API provides the following structures:
          
   dns_sdlzmethods {
      create;
      destroy;
      findzone;
      lookup;
      authority;
      allnodes;
      allowzonexfr;
   } dns_sdlzmethods_t;
   
   For more information on this structure, see the sdlz.h file.
   
The SDLZ API provides the following callback functions:
(only findzone and lookup are required)

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

   destroy(driverarg, dbdata);

   findzone(driverarg, dbdata, name);
   
   lookup(zone, name, driverarg, dbdata, lookup);

   allnodes(zone, driverarg, dbdata, allnodes);

   authority(zone, driverarg, dbdata, lookup);

   allowzonexfr(driverarg, dbdata, name, client);
                                                               
   For more information on the callback functions, see the sdlz.h file


ONLY 1 SDLZ (OR DLZ) DRIVER ALLOWED PER VIEW!!!

SDLZ DRIVERS MAY SUPPORT MULTITHREADING, BUT ARE NOT REQUIRED TO!!!

A SDLZ driver must first register itself with Bind via the 
dns_sdlzregister() function.  When a driver is registered, it specifies 
its name, a list of callback functions, flags, 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.

SDLZ supports the following flags:

   DNS_SDLZFLAG_THREADSAFE - This flag indicates the SDLZ driver is 
      thread safe and can support multithreading.  If this flag is not 
      specified, the driver will never have more than one thread using it 
      at a time.
 
   DNS_SDLZFLAG_RELATIVEOWNER - This flag indicates that the SDLZ driver 
      requires the host name passed to the lookup() function be relative to
      the zone. If this flag is not specified, the name passed to lookup()
      will be the full DNS name queried for.  I.E. if we are looking for
      "www" in the zone "domain.com" and this flag is specified, lookup()
      will be passed a name of "www".  If the flag is not specified,
      lookup() will be passed a name of "www.domain.com".  Lookup() is
      always passed a zone of "domain.com" in this example.
 
   DNS_SDLZFLAG_RELATIVERDATA - This flag indicates that the SDLZ driver 
      will return data relative to the zone.  If this flag is not 
      specified, the driver will return absolute data.  This is basically 
      the same as the DNS_SDLZ_RELATIVEOWNER flag, but it is for the data 
      returned by the driver instead of the parameters passed to the 
      driver.  Generally, drivers will specify both flags, or neither 
      flag.  I.E. the driver will operate only on all absolute data, or 
      all relative data.  Absolute data can be returned when using this 
      flag by adding an extra "." to the end of the data.  Continuing with
      the last paragraphs example, when this flag is set, returning
      "www.domain.com" from the driver will cause "domain.com" to be
      appended to it.  This would result in Bind returning
      "www.domain.com.domain.com" This is the correct operation but probably
      not the desired result.  If the driver instead returned "www.domain.com."
      Bind would return "www.domain.com".  This is probably most useful when
      your driver needs to return out of zone data, such as NS or SOA records
      that are not within this zone (i.e. your zone is "domain.com" but your
      name server for "domain.com" is "ns1.otherdomain.com".  If your driver
      returned "ns1.otherdomain.com" Bind would answer with 
      "ns1.otherdomain.com.domain.com" which is again a correct but undesired
      result.  If your driver instead returned "ns1.otherdomain.com." Bind
      would respond "ns1.otherdomain.com" which is the desired result.

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

DRIVERS SHOULD NEVER ACCESS OR MODIFY THE "sdlzimp" POINTER OR ITS DATA.

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

Calls to dns_sdlzregister() and dns_sdlzunregister() (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().
An #include should be added that corresponds 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.  They must also 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 SDLZ 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 SDLZ drivers may be registered with Bind.

(S)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 (s)dlz driver
load_name - the name displayed for any errors during config file parsing
database  - required keyword ((s)dlz requires one, and only one parameter)
driver_name - the name of the (s)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 SDLZ driver.  The first argument in the command line 
MUST be the name of the SDLZ 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 (S)DLZ drivers added to its 
setup() function.  Bind will parse its configuration file (named.conf).  
If a (S)DLZ driver is specified, Bind will search for the specified (S)DLZ 
driver.  If one is not found, it will error and continue loading.  Since 
the (S)DLZ driver could not be loaded, no (S)DLZ data will be available 
for answering DNS queries.

If the SDLZ driver is found, Bind will call its create method.  The create 
method is supplied with 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 
various callback 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 (S)DLZ driver is loaded.  Bind allows one (S)DLZ driver per 
view but supports multiple views.  Therefore, it's possible that a (S)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 one of the supplied SDLZ driver's code.

Once a SDLZ driver has been "created," Bind can make use of its findzone()
and the other callback functions. Findzone() could be called once, many
times, or not at all for each query.  However, findzone() is usually called
for most queries. Therefore, findzone() should operate as fast as possible.

When Bind receives most queries, 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.  Bind will start with the longest possible zone 
name.  If findzone() determines it does not have a match, Bind will ask it 
again, with a shorter zone name.  It will continue asking findzone() if it 
has a match for successively shorter names until the entire queried name 
has been searched, findzone() determines it does have a match, or the 
name would be shorter than the match returned by Bind's in memory table. 
If findzone does have a match, Bind will use the (S)DLZ driver instead of its
in memory database.  The only time Bind will NOT ask findzone() for a match 
is when the in memory database has already provided a "perfect match."  A 
perfect match is where the DNS name query matches a zone's name and there 
is nothing extra left over.

Here's an example.

The DNS server is queried for "my.long.name.com".  Here is one possible 
sequence of events:

   1) Bind checks its in memory tables and determines it has nothing for 
      this domain.

   2) Bind asks findzone() if it has a match for "my.long.name.com". 
      Findzone() does not.

   3) Bind asks findzone() if it has a match for "long.name.com". 
      Findzone() does not.   

   4) Bind asks findzone() if it has a match for "name.com".  
      Findzone() does not.
      
   5) Bind asks findzone() if it has a match for "com".  
      Findzone() does not.

   6) Bind has no information for "my.long.name.com" and answers as such.
   
Here is another possible sequence of events:

   1) Bind checks its in memory tables and determines it has a match for 
      "name.com".
      
   2) Bind asks findzone() if it has a match for "my.long.name.com".  
      Findzone() does not.
            
   3) Bind asks findzone() if it has  match for "long.name.com".  
      Findzone() does.  Bind uses the (s)dlz driver for information about this
      zone.
      
Another possible sequence of events:

   1) Bind checks its in memory tables and determines it has a match for 
      "long.name.com".
      
   2) Bind asks findzone() if it has a match for "my.long.name.com".  
      Findzone does not.  Bind uses the in memory database for information 
      about this zone.

Another possible sequence of events:

   1) Bind checks its in memory tables and determines it has a match for 
      "my.long.name.com".  Bind uses it's in memory database for 
       information about this zone.

When Bind calls findzone(), it supplies driverarg, dbdata and the DNS name 
queried for (as ASCII text).  If findzone() determines that it has a 
match, it should return ISC_R_SUCCESS; if not, it should return 
ISC_R_NOTFOUND.  Any other return indicates an error.  See 
lib/isc/include/isc/result.h for possible return codes.

If findzone() has determined it has a match, Bind will use the (S)DLZ driver
for zone information.  From here the SDLZ API operates almost exactly like the
SDB API.  You can check out the SDB documentation in /doc/misc/sdb and 
look at the SDB drivers in /contrib/sdb for information about the SDB API.

Bind will call the lookup() function to gather information for specific 
records.  Bind will pass the zone and name queried for as ASCII text as 
well as "driverarg" and "dbdata" to lookup.  It will also pass "lookup".  
This is used when calling dns_sdlz_putrr() and dns_sdlz_putsoa().  The 
driver passes data back to Bind through the use of these two functions. 
Don't forget that the name passed to lookup() will either be relative or 
absolute depending upon the flags the driver passed during registration.  

The authority() function is necessary if and only if the lookup() function 
will not add SOA and NS records at the zone apex.    If SOA and NS records 
are provided by the lookup() function, the authority() function should be 
NULL.  Bind will pass the zone (as ASCII text), "driverarg", "dbdata" and 
"lookup".  The sole purpose of authority() is to return SOA and NS records 
to Bind for the specified zone.  Don't forget that any returned data will 
be absolute or relative depending upon the flags the driver passed during 
registration.

The allnodes() and allowzonexfr() functions are only required for the driver
to support zone transfers.  Generally, drivers that dynamically generate
DNS data should not implement these functions because the data they supply
will be immediately out of date.  Bind passes the zone (as ASCII text),
"driverarg", "dbdata" and "allnodes" to the allnodes() function. "allnodes"
is used by dns_sdlz_putnamedrr().  Allnodes() should return all data for the 
specified zone to Bind using the dns_sdlz_putnamedrr() function.

Bind passes the zone (the name parameter as listed in the parameter list) and
client IP address (both as ASCII text) as well as "driverarg" and "dbdata" to
the allowzonexfr() function.  Note that the client IP address could be either
an IPV4 address or an IPV6 address.   Allowzonexfr() should return
ISC_R_NOTFOUND if the zone is not contained in the database for the driver.
If the zone is supported by the database, but zone transfers are not allowed
for the specified client, the method should return ISC_R_NOPERM.  If the zone is
supported by the database and zone transfers are allowed for the client,
allowzonexfr() should return ISC_R_SUCCESS.

Bind will always check its in memory zone table for a zone match first.
If a match is found, Bind will use its normal method of processing to 
determine if a zone transfer is allowed for the client, and if so perform that
operation without interacting with a DLZ driver.  If a match is not found
in Bind's in memory zone table, Bind will ask the allowzonexfr() function
if the zone is supported and a transfer by the client allowed.  Only if
allowzonexfr() returns ISC_R_SUCCESS will the allnodes() function be called.

Note that it is often useful to call the findzone() function from within the
allowzonexfr() function to determine if the zone is supported within the
database, since the findzone() function of a driver already contains the logic
for determining if the zone is supported.  Both the allowzonexfr() and
allnodes() function must be implemented to support zone transfers. Implementing
only one of the functions will result in any DLZ zone transfer attempt failing.

For examples of how to use these functions, look at the SDLZ drivers that 
are included with the package.

When it is time for Bind to shutdown, it will call the SDLZ 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 SDLZ 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 the old and which
is the new.


          

