Developer guide
From DNSdoctor
Table of contents |
Internal design
Localisation
<check name="soa_ns_cname"> <name>SOA master is not an alias</name> <success>The SOA master is not an alias</success> <failure>SOA master is not allowed to point to a CNAME alias</failure> <explanation sameas="shortcut:ns_cname"/> <details><para>The master (<var name="master"/>) is a CNAME alias to <var name="alias"/>.</para></details> </check>
<shortcut> <explanation name="email_sntx"> <src type="ref" from="rfc" fid="rfc1034#p9"> <title>IETF RFC1034 (p.9), RFC1912 (p.3)</title> <para>Email addresses are converted by using the following rule: local-part@mail-domain ==> local-part.mail-domain if local-part contains a dot in should be backslashed (for 'bind').</para> </src> </explanation> </shortcut> <check name="soa_contact_sntx_at"> <name>misused '@' characters in SOA contact name</name> <success>No misuse of '@' character in SOA contact name</success> <failure>The contact name contains the character '@'</failure> <explanation sameas="shortcut:email_sntx"/> <details/> </check>
This is the DTD used to describe the message catalog:
<!ELEMENT msgcat ((shortcut|check|test)*|(section|tag)*)> <!ELEMENT shortcut (explanation|details)*> <!ELEMENT check (name,success,failure,explanation,details)> <!ELEMENT test (name)> <!ELEMENT name (#PCDATA|var|const)*> <!ELEMENT failure (#PCDATA|var|const)*> <!ELEMENT success (#PCDATA|var|const)*> <!ELEMENT explanation (src*)> <!ELEMENT details (para*)> <!ELEMENT src (title,para+)> <!ELEMENT title (#PCDATA|var|const)*> <!ELEMENT para (#PCDATA|var|const|uri)*> <!ELEMENT var EMPTY> <!ELEMENT const EMPTY> <!ELEMENT uri (#PCDATA)> <!ELEMENT tag (#PCDATA)> <!ELEMENT section (tag*)> <!ATTLIST msgcat lang CDATA #REQUIRED> <!ATTLIST check name CDATA #REQUIRED> <!ATTLIST test name CDATA #REQUIRED> <!ATTLIST explanation name CDATA #IMPLIED sameas CDATA #IMPLIED> <!ATTLIST details name CDATA #IMPLIED sameas CDATA #IMPLIED> <!ATTLIST src type CDATA #REQUIRED from CDATA #IMPLIED fid CDATA #IMPLIED> <!ATTLIST uri link CDATA #REQUIRED> <!ATTLIST var name CDATA #REQUIRED> <!ATTLIST var display CDATA #IMPLIED> <!ATTLIST const name CDATA #REQUIRED> <!ATTLIST const display CDATA #IMPLIED> <!ATTLIST section name CDATA #REQUIRED> <!ATTLIST tag name CDATA #REQUIRED>
Extending
Localization
All the localization files are located in the locale directory and the files have for suffix the corresponding langue. For example the main locale file for dnsdoctor will be dnsdoctor.en for a locale set to en_US.UTF-8.
The localization catalogue has been split into several files to make the addition of new input interfaces or tests easier, so the division is as follow:
- DNSdoctor: dnsdoctor.*
- Input interface: cli.* cgi.* inetd.* gtk.*
- Tests: test/soa.* test/ns.* test/interop.* ...
Implementing new tests
Tests are stored in the directory test and are automatically loaded by dnsdoctor.
There are four types of tests, and the type of the test is defined by the module to which it belongs:
- CheckGeneric
- The test doesn't need to know the nameserver nor the IP addresses.
No parameters are used by the method:chk_testname()
- CheckNameServer
- The test need to know the nameserver.
One parameter is used by the method:chk_testname(ns)
- CheckNetworkAddress
- The test need to know the nameserver and the IP address.
Two parameters are used by the method:chk_testname(ns, ip)
- CheckExtra
- The test is not really DNS related and so doesn't need to know the nameserver nor the IP addresses.
No parameters are used by the method:chk_testname()
The method defining a test must also be part of a class, this allows creating intermediate functions that can be used by a set of related tests, and the class should inherit from Test.
There are two types of methods used by the test engine:
- chk_name
- It is the check, if the return value is true the check was succesful, otherwise if the return value is false or an hash or an exception it is a failure. In the case of a hash, the key/value are used to perform a substitution in the message generated for tokent marked as variables; for the exception the message stored in it is displayed without localization as it is generally an error tied to the operating system.
- tst_name
- It is a conditional, the method should return a string that will be used in a case statement.
The corresponding message catalog should be loaded for the set of tests that we are defining, this is done by adding the with_msgcat 'test/catname.%s'
in the class, generally catname is the same name as the class in lowercase.
Below is an example implementing the check soa_ns_cname which check that the master used in the SOA records doesn't resolve into a CNAME, if it is the case the check fails and returns the name of the master and the name of the CNAME.
require 'framework' module CheckNetworkAddress class SOA < Test with_msgcat 'test/soa.%s' def chk_soa_ns_cname(ns, ip) return true unless name = is_cname?(soa(ip).mname, ip) { 'master' => soa(ip).mname, 'alias' => name } end end end
And this is the fragment corresponding to the localization in english:
<check name="soa_ns_cname"> <name>SOA master is not an alias</name> <success>The SOA master is not an alias</success> <failure>SOA master is not allowed to point to a CNAME alias</failure> <explanation sameas="shortcut:ns_cname"/> <details><para>The master (<var name="master"/>) is a CNAME alias to <var name="alias"/>.</para></details> </check>
Adding input interface
New input interfaces should be part of the module Input and the class implementing it should be in uppercase, the file storing it in lowercase and in the directory zc/input, but the name should be the same (except for the case).
- allow_preset
- Returns true if the interface is able to use preset values as stored in the dnsdoctor.conf configuration file.
- initialize
- Initialise the class.
- restart
- Method to call for restarting the input interface (must reinitialize internal values).
- parse(param)
- Fill the parameter holder (param) from the parsing of primary input information.
- interact(param, config, testmanager, io)
- Allow a deeper interaction with the user, the information available are the parameters (param), the configuration (config) provided by dnsdoctor.conf and the test manager (testmanager). And it is still possible to change the parameters
- usage(errcode, io)
- Display usage normally using the io provided and exit if errcode is not nil.
- error(str, errcode, io)
- Display the error provided as a string using the io provided and exit if errcode is not nil.
This is the structure of the class used by the CGI Input (for more information
you can look at the various implementation in zc/input/):
module Input class CGI with_msgcat "cgi.%s" def allow_preset ; false ; end def initialize ; ..... ; end def restart ; ..... ; end def parse(p) ; ..... ; end def interact(p, c, tm, io=$console.stdout) ; ..... ; end def usage(errcode, io=$console.stdout) ; ..... ; end def error(str, errcode=nil, io=$console.stdout) ; ..... ; end end end
Adding publisher
New publisher should be part of the module Publisher and the class implementing it should inherit from Template, the file storing it should be in the zc/publisher directory. Unlike new tests or input interface, new publisher are not automatically loaded and should be added in the param.rb file.
- error(text)
- Print the text in case of error.
- intro(domain)
- Print a summary information about the domain being tested, generally the domain name and its nameservers.
- diag_start()
- This method is called when dnsdoctor will start to send diagnostic messages (ie: calling diagnostic)
- diagnostic1(domainname, i_count, i_unexp, w_count, w_unexp, f_count, f_unexp, res, severity)
- Print a one line (generally two lines) of information about the status of the domain domainname tested informations, warnings and fatals being counted and a flag is set if it was due to an exception (unexpected), res and severity holding information about the more significant message.
- diagnostic(severity, testname, desc, lst)
- According to severity print a informational, warning or fatal message for the test testname, the description being given by desc and the list of host to which it appplies by lst.
- status(domainname, i_count, w_count, f_count)
- Display the final status for the domain domainname informations, warnings and fatals being counted
- setup(domain_name)
- Allow earlier initialisation. For HTML pages it is usefull for setting the page title in the head structure.
This is the structure of the class used by the Text publisher (for more information you can look at the various implementation in zc/publisher/):
module Publisher class Text < Template Mime = "text/plain" def error(text) ; ... ; end def intro(domain) ; ... ; end def diag_start() ; ... ; end def diag_section(title) ; ... ; end def diagnostic1(domainname, i_count, i_unexp, w_count, w_unexp, f_count, f_unexp, res, severity) ; .. ;end def diagnostic(severity, testname, desc, lst) ; ... ; end def status(domainname, i_count, w_count, f_count) ; ... ; end def setup(domain_name) ; ... ; end end end