diff options
Diffstat (limited to 'pythonmod')
39 files changed, 4812 insertions, 0 deletions
| diff --git a/pythonmod/LICENSE b/pythonmod/LICENSE new file mode 100644 index 000000000000..7b769d091201 --- /dev/null +++ b/pythonmod/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                    Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +    * Redistributions in binary form must reproduce the above copyright +      notice, this list of conditions and the following disclaimer in the +      documentation and/or other materials provided with the distribution. +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/pythonmod/Makefile b/pythonmod/Makefile new file mode 100644 index 000000000000..2a00152418ab --- /dev/null +++ b/pythonmod/Makefile @@ -0,0 +1,58 @@ +# Makefile: tests unbound python module (please edit SCRIPT variable) +# +# Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +#                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) +# +# This software is open source. +#  +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +#  +#    * Redistributions of source code must retain the above copyright notice, +#      this list of conditions and the following disclaimer. +#  +#    * Redistributions in binary form must reproduce the above copyright notice, +#      this list of conditions and the following disclaimer in the documentation +#      and/or other materials provided with the distribution. +#  +#    * Neither the name of the organization nor the names of its +#      contributors may be used to endorse or promote products derived from this +#      software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +SUEXEC  = sudo +UNBOUND = ../unbound +SCRIPT  = ./test-resip.conf + +UNBOUND_OPTS = -dv -c $(SCRIPT) + +.PHONY: test sudo suexec doc + +all: test + +$(UNBOUND): +	make -C .. + +test: $(UNBOUND) +	$(UNBOUND) $(UNBOUND_OPTS) + +sudo: $(UNBOUND) +	sudo $(UNBOUND) $(UNBOUND_OPTS) + +suexec: $(UNBOUND) +	su -c "$(UNBOUND) $(UNBOUND_OPTS)" + +doc: +	$(MAKE) -C doc html diff --git a/pythonmod/doc/_static/readme b/pythonmod/doc/_static/readme new file mode 100644 index 000000000000..db676aebbde9 --- /dev/null +++ b/pythonmod/doc/_static/readme @@ -0,0 +1 @@ +this directory exists to pacify sphinx-build. diff --git a/pythonmod/doc/conf.py b/pythonmod/doc/conf.py new file mode 100644 index 000000000000..bc7a5aba68d5 --- /dev/null +++ b/pythonmod/doc/conf.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# +# Unbound scripting interface documentation build configuration file +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default value; values that are commented out +# serve to show the default value. + +import sys, os + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),'../..'))) +#print sys.path + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General substitutions. +project = 'Unbound scriptable interface' +copyright = '2009, Zdenek Vasicek, Marek Vavrusa' + +# The default replacements for |version| and |release|, also used in various +# other places throughout the built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0.0' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directories, that shouldn't be searched +# for source files. +#exclude_dirs = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'default.css' + +# The name for this set of Sphinx documents.  If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar.  Default is the same as html_title. +#html_short_title = None + +# The name of an image file (within the static path) to place at the top of +# the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/<name>. +html_copy_source = False + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it.  The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'unbound_interface' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ +  ('index', 'Unbound_interface.tex', 'Unbound scriptable interface', +   'Zdenek Vasicek, Marek Vavrusa', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/pythonmod/doc/examples/example0-1.py b/pythonmod/doc/examples/example0-1.py new file mode 100644 index 000000000000..98a9acccdeaf --- /dev/null +++ b/pythonmod/doc/examples/example0-1.py @@ -0,0 +1,37 @@ + +print mod_env.fname   # Print module script name +mod_env.data = "test" # Store global module data + +def init(id, cfg): +   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script)) +   return True + +def deinit(id): +   log_info("pythonmod: deinit called, module id is %d" % id) +   return True + +def inform_super(id, qstate, superqstate, qdata): +   return True + +def operate(id, event, qstate, qdata): +   log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) + +   if event == MODULE_EVENT_NEW: +      qstate.ext_state[id] = MODULE_WAIT_MODULE  +      return True + +   if event == MODULE_EVENT_MODDONE: +      log_info("pythonmod: module we are waiting for is done") +      qstate.ext_state[id] = MODULE_FINISHED  +      return True + +   if event == MODULE_EVENT_PASS: +      log_info("pythonmod: event_pass") +      qstate.ext_state[id] = MODULE_ERROR  +      return True + +   log_err("pythonmod: BAD event") +   qstate.ext_state[id] = MODULE_ERROR +   return True + +log_info("pythonmod: script loaded.") diff --git a/pythonmod/doc/examples/example0.rst b/pythonmod/doc/examples/example0.rst new file mode 100644 index 000000000000..80eca5ea6ebb --- /dev/null +++ b/pythonmod/doc/examples/example0.rst @@ -0,0 +1,129 @@ +.. _example_handler: + +Fundamentals +================ + +This basic example shows how to create simple python module which will pass on the requests to the iterator. + +How to enable python module +---------------------------- +If you look into unbound configuration file, you can find the option `module-config` which specifies the names and the order of modules to be used. +Example configuration:: + +	module-config: "validator python iterator" + +As soon as the DNS query arrives, Unbound calls modules starting from leftmost - the validator *(it is the first module on the list)*. +The validator does not know the answer *(it can only validate)*, thus it will pass on the event to the next module. +Next module is python which can + +	a) generate answer *(response)* +		When python module generates the response unbound calls validator. Validator grabs the answer and determines the security flag. + +	b) pass on the event to the iterator. +		When iterator resolves the query, Unbound informs python module (event :data:`module_event_moddone`). In the end, when the python module is done, validator is called. + +Note that the python module is called with :data:`module_event_pass` event, because new DNS event was already handled by validator. + +Another situation occurs when we use the following configuration:: + +	module-config: "python validator iterator" + +Python module is the first module here, so it's invoked with :data:`module_event_new` event *(new query)*. + +On Python module initialization, module loads script from `python-script` option:: + +	python-script: "/unbound/test/ubmodule.py" + +Simple python module step by step +--------------------------------- + +Script file must contain four compulsory functions: + +.. function:: init(id, cfg) + +   Initialize module internals, like database etc. +   Called just once on module load. + +   :param id: module identifier (integer) +   :param cfg: :class:`config_file` configuration structure + +:: + +   def init(id, cfg): +      log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script)) +      return True + + +.. function:: deinit(id) + +   Deinitialize module internals. +   Called just once on module unload. + +   :param id: module identifier (integer) + +:: + +   def deinit(id): +      log_info("pythonmod: deinit called, module id is %d" % id) +      return True + + +.. function:: inform_super(id, qstate, superqstate, qdata) + +   Inform super querystate about the results from this subquerystate. +   Is called when the querystate is finished. + +   :param id: module identifier (integer) +   :param qstate: :class:`module_qstate` Query state +   :param superqstate: :class:`pythonmod_qstate` Mesh state +   :param qdata: :class:`query_info` Query data + +:: + +   def inform_super(id, qstate, superqstate, qdata): +      return True + + + +.. function:: operate(id, event, qstate, qdata) + +   Perform action on pending query. Accepts a new query, or work on pending query. + +   You have to set qstate.ext_state on exit. +   The state informs unbound about result and controls the following states. + +   :param id: module identifier (integer) +   :param qstate: :class:`module_qstate` query state structure +   :param qdata: :class:`query_info` per query data, here you can store your own data + +:: + +   def operate(id, event, qstate, qdata): +      log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) +      if event == MODULE_EVENT_NEW: +         qstate.ext_state[id] = MODULE_WAIT_MODULE  +         return True + +      if event == MODULE_EVENT_MODDONE: +         qstate.ext_state[id] = MODULE_FINISHED  +         return True + +      if event == MODULE_EVENT_PASS: +         qstate.ext_state[id] = MODULE_ERROR  +         return True + +      log_err("pythonmod: BAD event") +      qstate.ext_state[id] = MODULE_ERROR +      return True + + +Complete source code +-------------------- + +..	literalinclude:: example0-1.py +	:language: python + +As you can see, the source code is much more flexible in contrast to C modules.  +Moreover, compulsory functions called on appropriate module events allows to handle almost +anything from web control to query analysis. + diff --git a/pythonmod/doc/examples/example1.rst b/pythonmod/doc/examples/example1.rst new file mode 100644 index 000000000000..b49e64409255 --- /dev/null +++ b/pythonmod/doc/examples/example1.rst @@ -0,0 +1,42 @@ +.. _log_handler: + +Packet logger +========================= + +This example shows how to log and print details about query and response. +As soon as the ``iterator`` has finished (event is :data:`module_event_moddone`), ``qstate.return_msg`` contains response packet or ``None``. +This packet will be send to a client that asked for it. + +Complete source code +-------------------- + +.. literalinclude:: ../../examples/log.py +   :language: python + +Testing +------------------ +Run the unbound server: + +``root@localhost>unbound -dv -c ./test-log.conf`` + +In case you use own configuration file, don't forget to enable python module: ``module-config: "validator python iterator"`` and use valid script path: ``python-script: "./examples/log.py"``. + +Example of output::	 + +   [1231790168] unbound[7941:0] info: response for <f.gtld-servers.NET. AAAA IN> +   [1231790168] unbound[7941:0] info: reply from <gtld-servers.NET.> 192.5.6.31#53 +   [1231790168] unbound[7941:0] info: query response was ANSWER +   [1231790168] unbound[7941:0] info: pythonmod: operate called, id: 1, event:module_event_moddone +   ---------------------------------------------------------------------------------------------------- +   Query: f.gtld-servers.NET., type: AAAA (28), class: IN (1)  +   ---------------------------------------------------------------------------------------------------- +   Return    reply :: flags: 8080, QDcount: 1, Security:0, TTL=86400 +             qinfo :: qname: ['f', 'gtld-servers', 'NET', ''] f.gtld-servers.NET., qtype: AAAA, qclass: IN +   Reply: +   0 : ['gtld-servers', 'NET', ''] gtld-servers.NET. flags: 0000 type: SOA (6) class: IN (1) +      0 : TTL= 86400 +          0x00 | 00 3A 02 41 32 05 4E 53 54 4C 44 03 43 4F 4D 00 05 | . : . A 2 . N S T L D . C O M . .  +          0x10 | 05 6E 73 74 6C 64 0C 76 65 72 69 73 69 67 6E 2D 67 | . n s t l d . v e r i s i g n - g  +          0x20 | 67 72 73 03 43 4F 4D 00 77 74 2D 64 00 00 0E 10 00 | g r s . C O M . w t - d . . . . .  +          0x30 | 00 00 03 84 00 12 75 00 00 01 51 80                | . . . . . . u . . . Q .  + diff --git a/pythonmod/doc/examples/example2.rst b/pythonmod/doc/examples/example2.rst new file mode 100644 index 000000000000..f00fcc239609 --- /dev/null +++ b/pythonmod/doc/examples/example2.rst @@ -0,0 +1,46 @@ +Response generation +===================== + +This example shows how to handle queries and generate response packet. + +.. note:: +   If the python module is the first module and validator module is enabled (``module-config: "python validator iterator"``), +   a return_msg security flag has to be set at least to 2. Leaving security flag untouched causes that the +   response will be refused by unbound worker as unbound will consider it as non-valid response. + +Complete source code +-------------------- + +.. literalinclude:: ../../examples/resgen.py +   :language: python + +Testing +------- + +Run the unbound server: + +``root@localhost>unbound -dv -c ./test-resgen.conf`` + +Query for a A record ending with .localdomain + +``dig A test.xxx.localdomain @127.0.0.1`` + +Dig produces the following output:: + +	;; global options:  printcmd +	;; Got answer: +	;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48426 +	;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 +	 +	;; QUESTION SECTION: +	;test.xxx.localdomain.		IN	A +	 +	;; ANSWER SECTION: +	test.xxx.localdomain.	10	IN	A	127.0.0.1 +	 +	;; Query time: 2 msec +	;; SERVER: 127.0.0.1#53(127.0.0.1) +	;; WHEN: Mon Jan 01 12:46:02 2009 +	;; MSG SIZE  rcvd: 54 + +As we handle (override) in python module only queries ending with "localdomain.", the unboud can still resolve host names. diff --git a/pythonmod/doc/examples/example3.rst b/pythonmod/doc/examples/example3.rst new file mode 100644 index 000000000000..6213dc188f12 --- /dev/null +++ b/pythonmod/doc/examples/example3.rst @@ -0,0 +1,63 @@ +Response modification +===================== + +This example shows how to modify the response produced by the ``iterator`` module. + +As soon as the iterator module returns the response, we : + +1. invalidate the data in cache +2. modify the response *TTL* +3. rewrite the data in cache +4. return modified packet + +Note that the steps 1 and 3 are neccessary only in case, the python module is the first module in the processing chain. +In other cases, the validator module guarantees updating data which are produced by iterator module. + +Complete source code +-------------------- + +.. literalinclude:: ../../examples/resmod.py +   :language: python + +Testing +------- + +Run Unbound server: + +``root@localhost>unbound -dv -c ./test-resmod.conf`` + +Issue a query for name ending with "nic.cz." + +``>>>dig A @127.0.0.1 www.nic.cz`` + +:: + +	;; global options:  printcmd +	;; Got answer: +	;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48831 +	;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 3, ADDITIONAL: 5 +	 +	;; QUESTION SECTION: +	;www.nic.cz.			IN	A +	 +	;; ANSWER SECTION: +	www.nic.cz.		10	IN	A	217.31.205.50 +	 +	;; AUTHORITY SECTION: +	nic.cz.			10	IN	NS	e.ns.nic.cz. +	nic.cz.			10	IN	NS	a.ns.nic.cz. +	nic.cz.			10	IN	NS	c.ns.nic.cz. +	 +	;; ADDITIONAL SECTION: +	a.ns.nic.cz.		10	IN	A	217.31.205.180 +	a.ns.nic.cz.		10	IN	AAAA	2001:1488:dada:176::180 +	c.ns.nic.cz.		10	IN	A	195.66.241.202 +	c.ns.nic.cz.		10	IN	AAAA	2a01:40:1000::2 +	e.ns.nic.cz.		10	IN	A	194.146.105.38 +	 +	;; Query time: 166 msec +	;; SERVER: 127.0.0.1#53(127.0.0.1) +	;; WHEN: Mon Jan 02 13:39:43 2009 +	;; MSG SIZE  rcvd: 199 + +As you can see, TTL of all the records is set to 10. diff --git a/pythonmod/doc/examples/example4.rst b/pythonmod/doc/examples/example4.rst new file mode 100644 index 000000000000..6cc484797826 --- /dev/null +++ b/pythonmod/doc/examples/example4.rst @@ -0,0 +1,164 @@ +DNS-based language dictionary +=============================== + +This example shows how to create a simple language dictionary based on **DNS** +service within 15 minutes. The translation will be performed using TXT resource records. + +Key parts +----------- + +Initialization +~~~~~~~~~~~~~~~~~~~~~~~ +On **init()** module loads dictionary from a text file containing records in ``word [tab] translation`` format. +:: + +   def init(id, cfg): +      log_info("pythonmod: dict init") +      f = open("examples/dict_data.txt", "r") +      ... + +The suitable file can be found at http://slovnik.zcu.cz + +DNS query and word lookup +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let's define the following format od DNS queries: ``word1[.]word2[.] ... wordN[.]{en,cs}[._dict_.cz.]``. +Word lookup is done by simple ``dict`` lookup from broken DNS request. +Query name is divided into a list of labels. This list is accesible as qname_list attribute. +:: + +   aword = ' '.join(qstate.qinfo.qname_list[0:-4]) #skip last four labels +   adict = qstate.qinfo.qname_list[-4] #get 4th label from the end + +   words = [] #list of words +   if (adict == "en") and (aword in en_dict): +      words = en_dict[aword]  + +   if (adict == "cs") and (aword in cz_dict): +      words = cz_dict[aword] # CS -> EN + +In the first step, we get a string in the form: ``word1[space]word2[space]...word[space]``. +In the second assignment, fourth label from the end is obtained. This label should contains *"cs"* or *"en"*. +This label determines the direction of translation. + + +Forming of a DNS reply +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +DNS reply is formed only on valid match and added as TXT answer. +:: + +	msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_AA) + +	for w in words: +		msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str, w.replace("\"", "\\\""))) + +	if not msg.set_return_msg(qstate): +		qstate.ext_state[id] = MODULE_ERROR  +		return True + +	qstate.return_rcode = RCODE_NOERROR +	qstate.ext_state[id] = MODULE_FINISHED  +	return True + +In the first step, a :class:`DNSMessage` instance is created for a given query *(type TXT)*. +The fourth argument specifies the flags *(authoritative answer)*. +In the second step, we append TXT records containing the translation *(on the right side of RR)*. +Then, the response is finished and ``qstate.return_msg`` contains new response. +If no error, the module sets :attr:`module_qstate.return_rcode` and :attr:`module_qstate.ext_state`. + +**Steps:** + +1. create :class:`DNSMessage` instance +2. append TXT records containing the translation +3. set response to ``qstate.return_msg`` + +Testing +------- + +Run the Unbound server: + +``root@localhost>unbound -dv -c ./test-dict.conf`` + +In case you use own configuration file, don't forget to enable Python module:: + +	module-config: "validator python iterator" + +and use valid script path:: + +	python-script: "./examples/dict.py" + +The translation from english word *"a bar fly"* to Czech can be done by doing: + +``>>>dig TXT @127.0.0.1 a.bar.fly.en._dict_.cz`` + +::	 + +	; (1 server found) +	;; global options:  printcmd +	;; Got answer: +	;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48691 +	;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 +	 +	;; QUESTION SECTION: +	;a.bar.fly.en._dict_.cz.		IN	TXT +	 +	;; ANSWER SECTION: +	a.bar.fly.en._dict_.cz.	300	IN	TXT	"barov\253 povale\232" +	 +	;; Query time: 5 msec +	;; SERVER: 127.0.0.1#53(127.0.0.1) +	;; WHEN: Mon Jan 01 17:44:18 2009 +	;; MSG SIZE  rcvd: 67 +	 +``>>>dig TXT @127.0.0.1 nic.cs._dict_.cz`` +:: +	 +	; <<>> DiG 9.5.0-P2 <<>> TXT @127.0.0.1 nic.cs._dict_.cz +	; (1 server found) +	;; global options:  printcmd +	;; Got answer: +	;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58710 +	;; flags: aa rd ra; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 0 +	 +	;; QUESTION SECTION: +	;nic.cs._dict_.cz.		IN	TXT +	 +	;; ANSWER SECTION: +	nic.cs._dict_.cz.	300	IN	TXT	"aught" +	nic.cs._dict_.cz.	300	IN	TXT	"naught" +	nic.cs._dict_.cz.	300	IN	TXT	"nihil" +	nic.cs._dict_.cz.	300	IN	TXT	"nix" +	nic.cs._dict_.cz.	300	IN	TXT	"nothing" +	nic.cs._dict_.cz.	300	IN	TXT	"zilch" +	 +	;; Query time: 0 msec +	;; SERVER: 127.0.0.1#53(127.0.0.1) +	;; WHEN: Mon Jan 01 17:45:39 2009 +	;; MSG SIZE  rcvd: 143 + +Proof that the unbound still works as resolver. + +``>>>dig A @127.0.0.1 www.nic.cz`` +:: + +	; (1 server found) +	;; global options:  printcmd +	;; Got answer: +	;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19996 +	;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 3, ADDITIONAL: 5 +	 +	;; QUESTION SECTION: +	;www.nic.cz.			IN	A +	 +	;; ANSWER SECTION: +	www.nic.cz.		1662	IN	A	217.31.205.50 +	 +	;; AUTHORITY SECTION: +	... + +Complete source code +-------------------- + +.. literalinclude:: ../../examples/dict.py +   :language: python diff --git a/pythonmod/doc/examples/index.rst b/pythonmod/doc/examples/index.rst new file mode 100644 index 000000000000..6c50225810ef --- /dev/null +++ b/pythonmod/doc/examples/index.rst @@ -0,0 +1,15 @@ +.. _Tutorials: + +============================== +Tutorials +============================== + +Here you can find several tutorials which clarify the usage and capabilities of Unbound scriptable interface.  + +`Tutorials` + +.. toctree:: +	:maxdepth: 2 +	:glob: + +	example* diff --git a/pythonmod/doc/index.rst b/pythonmod/doc/index.rst new file mode 100644 index 000000000000..fe9bcf42b962 --- /dev/null +++ b/pythonmod/doc/index.rst @@ -0,0 +1,34 @@ +Unbound scriptable interface +======================================= + +Python module for **Unbound** provides easy-to-use flexible solution, +for scripting query events and much more! + +Along with extensible **SWIG** interface, it turns **Unbound** into dynamic *DNS* service +designed for rapid development of *DNS* based applications, like detailed *(per query/domain)* statistics, +monitoring with anything Python can offer *(database backend, http server)*. + +**Key features** +   * Rapid dynamic DNS-based application development in **Python** +   * Extensible interface with **SWIG** +   * Easy to use debugging and analysis tool +   * Capable to produce authoritative answers +   * Support for logging or doing detailed statistics +   * Allows to manipulate with content of cache memory + +Contents +-------- +.. toctree:: +   :maxdepth: 2 + +   install +   examples/index +   usecase +   modules/index + +Indices and tables +------------------- + +* :ref:`genindex` +* :ref:`search` + diff --git a/pythonmod/doc/install.rst b/pythonmod/doc/install.rst new file mode 100644 index 000000000000..991e2b4becf9 --- /dev/null +++ b/pythonmod/doc/install.rst @@ -0,0 +1,59 @@ +Installation +=================================== + +**Prerequisites** + +Python 2.4 or higher, SWIG 1.3 or higher, GNU make + +**Download** + +You can download the source codes `here`_. +The latest release is 1.1.1, Jan 15, 2009. + +.. _here: unbound-1.1.1-py.tar.gz + +**Compiling** + +After downloading, you can compile the Unbound library by doing:: + +	> tar -xzf unbound-1.1.1-py.tar.gz +	> cd unbound-1.1.1 +	> ./configure --with-pythonmodule +	> make + +You need GNU make to compile sources. +SWIG and Python devel libraries to compile extension module.  + +**Testing** + +If the compilation is successful, you can test the extension module by:: + +	> cd pythonmod +	> make sudo # or "make test" or "make suexec" + +This will start unbound server with language dictionary service (see :ref:`Tutorials`). +In order to test this service, type:: +   +   > dig TXT @127.0.0.1 aught.en._dict_.cz + +Dig should print this message (czech equivalent of aught):: + +   ; <<>> DiG 9.5.0-P2 <<>> TXT @127.0.0.1 aught.en._dict_.cz +   ; (1 server found) +   ;; global options:  printcmd +   ;; Got answer: +   ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30085 +   ;; flags: aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 +    +   ;; QUESTION SECTION: +   ;aught.en._dict_.cz.		IN	TXT +    +   ;; ANSWER SECTION: +   aught.en._dict_.cz.	300	IN	TXT	"nic" +    +   ;; Query time: 11 msec +   ;; SERVER: 127.0.0.1#53(127.0.0.1) +   ;; WHEN: Thu Jan 10 16:45:58 2009 +   ;; MSG SIZE  rcvd: 52 + +The ``pythonmod/examples`` directory contains simple applications written in Python. diff --git a/pythonmod/doc/modules/config.rst b/pythonmod/doc/modules/config.rst new file mode 100644 index 000000000000..1277bceddf90 --- /dev/null +++ b/pythonmod/doc/modules/config.rst @@ -0,0 +1,350 @@ +Configuration interface +======================= + +Currently passed to Python module in init(module_id, cfg). + +config_file +-------------------- + +.. class:: config_file + +   This class provides these data attributes: + +   .. attribute:: verbosity +    +      Verbosity level as specified in the config file. + +   .. attribute:: stat_interval +    +      Statistics interval (in seconds). +    +   .. attribute:: stat_cumulative +    +      If false, statistics values are reset after printing them. +    +   .. attribute:: stat_extended +    +      If true, the statistics are kept in greater detail. + +   .. attribute:: num_threads +    +      Number of threads to create. + +   .. attribute:: port +    +      Port on which queries are answered. + +   .. attribute:: do_ip4 +    +      Do ip4 query support. + +   .. attribute:: do_ip6 +    +      Do ip6 query support. + +   .. attribute:: do_udp +    +      Do udp query support. +    +   .. attribute:: do_tcp +    +      Do tcp query support. + +   .. attribute:: outgoing_num_ports +    +      Outgoing port range number of ports (per thread). + +   .. attribute:: outgoing_num_tcp +    +      Number of outgoing tcp buffers per (per thread). + +   .. attribute:: incoming_num_tcp +    +      Number of incoming tcp buffers per (per thread). + +   .. attribute:: outgoing_avail_ports +    +      Allowed udp port numbers, array with 0 if not allowed. + +   .. attribute:: msg_buffer_size +    +      Number of bytes buffer size for DNS messages. + +   .. attribute:: msg_cache_size +    +      Size of the message cache. +    +   .. attribute:: msg_cache_slabs +    +      Slabs in the message cache. +    +   .. attribute:: num_queries_per_thread +    +      Number of queries every thread can service. +    +   .. attribute:: jostle_time +    +      Number of msec to wait before items can be jostled out. +    +   .. attribute:: rrset_cache_size +    +      Size of the rrset cache. +    +   .. attribute:: rrset_cache_slabs +    +      Slabs in the rrset cache. +    +   .. attribute:: host_ttl +    +      Host cache ttl in seconds. + +   .. attribute:: lame_ttl +    +      Host is lame for a zone ttl, in seconds. + +   .. attribute:: infra_cache_slabs +    +      Number of slabs in the infra host cache. +    +   .. attribute:: infra_cache_numhosts +    +      Max number of hosts in the infra cache. +    +   .. attribute:: infra_cache_lame_size +    +      Max size of lame zones per host in the infra cache. + +   .. attribute:: target_fetch_policy +    +      The target fetch policy for the iterator. + +   .. attribute:: if_automatic +    +      Automatic interface for incoming messages. Uses ipv6 remapping, +      and recvmsg/sendmsg ancillary data to detect interfaces, boolean. +    +   .. attribute:: num_ifs +    +      Number of interfaces to open. If 0 default all interfaces. +    +   .. attribute:: ifs +    +      Interface description strings (IP addresses). + +   .. attribute:: num_out_ifs +    +      Number of outgoing interfaces to open.  +      If 0 default all interfaces. + +   .. attribute:: out_ifs +    +      Outgoing interface description strings (IP addresses). +       +   .. attribute:: root_hints +    +      The root hints. +    +   .. attribute:: stubs +    +      The stub definitions, linked list. +    +   .. attribute:: forwards +    +      The forward zone definitions, linked list. +    +   .. attribute:: donotqueryaddrs +    +      List of donotquery addresses, linked list. +    +   .. attribute:: acls +    +      List of access control entries, linked list. +    +   .. attribute:: donotquery_localhost +    +      Use default localhost donotqueryaddr entries. + +   .. attribute:: harden_short_bufsize +    +      Harden against very small edns buffer sizes. +    +   .. attribute:: harden_large_queries +    +      Harden against very large query sizes. +    +   .. attribute:: harden_glue +    +      Harden against spoofed glue (out of zone data). +    +   .. attribute:: harden_dnssec_stripped +    +      Harden against receiving no DNSSEC data for trust anchor. +    +   .. attribute:: harden_referral_path +    +      Harden the referral path, query for NS,A,AAAA and validate. +    +   .. attribute:: use_caps_bits_for_id +    +      Use 0x20 bits in query as random ID bits. +    +   .. attribute:: private_address +    +      Strip away these private addrs from answers, no DNS Rebinding. +    +   .. attribute:: private_domain +    +      Allow domain (and subdomains) to use private address space. +    +   .. attribute:: unwanted_threshold +    +      What threshold for unwanted action. + +   .. attribute:: chrootdir +    +      Chrootdir, if not "" or chroot will be done. +    +   .. attribute:: username +    +      Username to change to, if not "". +    +   .. attribute:: directory +    +      Working directory. +    +   .. attribute:: logfile +    +      Filename to log to. +    +   .. attribute:: pidfile +    +      Pidfile to write pid to. + +   .. attribute:: use_syslog +    +      Should log messages be sent to syslogd. + +   .. attribute:: hide_identity +    +      Do not report identity (id.server, hostname.bind). +    +   .. attribute:: hide_version +    +      Do not report version (version.server, version.bind). +    +   .. attribute:: identity +    +      Identity, hostname is returned if "". +    +   .. attribute:: version +    +      Version, package version returned if "". + +   .. attribute:: module_conf +    +      The module configuration string. +    +   .. attribute:: trust_anchor_file_list +    +      Files with trusted DS and DNSKEYs in zonefile format, list. +    +   .. attribute:: trust_anchor_list +    +      List of trustanchor keys, linked list. +    +   .. attribute:: trusted_keys_file_list +    +      Files with trusted DNSKEYs in named.conf format, list. +    +   .. attribute:: dlv_anchor_file +    +      DLV anchor file. +    +   .. attribute:: dlv_anchor_list +    +      DLV anchor inline. + +   .. attribute:: max_ttl +    +      The number of seconds maximal TTL used for RRsets and messages. +    +   .. attribute:: val_date_override +    +      If not 0, this value is the validation date for RRSIGs. +    +   .. attribute:: bogus_ttl  +    +      This value sets the number of seconds before revalidating bogus. +    +   .. attribute:: val_clean_additional +    +      Should validator clean additional section for secure msgs. +    +   .. attribute:: val_permissive_mode +    +      Should validator allow bogus messages to go through. +    +   .. attribute:: val_nsec3_key_iterations +    +      Nsec3 maximum iterations per key size, string. +    +   .. attribute:: key_cache_size +    +      Size of the key cache. +    +   .. attribute:: key_cache_slabs +    +      Slabs in the key cache. +    +   .. attribute:: neg_cache_size +    +      Size of the neg cache. + +    +   .. attribute:: local_zones +    +      Local zones config. +    +   .. attribute:: local_zones_nodefault +    +      Local zones nodefault list. +    +   .. attribute:: local_data +    +      Local data RRs configged. + +   .. attribute:: remote_control_enable +    +      Remote control section. enable toggle. +    +   .. attribute:: control_ifs +    +      The interfaces the remote control should listen on. +    +   .. attribute:: control_port +    +      Port number for the control port. +    +   .. attribute:: server_key_file +    +      Private key file for server. +    +   .. attribute:: server_cert_file +    +      Certificate file for server. +    +   .. attribute:: control_key_file +    +      Private key file for unbound-control. +    +   .. attribute:: control_cert_file +    +      Certificate file for unbound-control. + +   .. attribute:: do_daemonize +    +      Daemonize, i.e. fork into the background. + +   .. attribute:: python_script +    +      Python script file. diff --git a/pythonmod/doc/modules/env.rst b/pythonmod/doc/modules/env.rst new file mode 100644 index 000000000000..42dbbd1cfa3b --- /dev/null +++ b/pythonmod/doc/modules/env.rst @@ -0,0 +1,412 @@ +Global environment +================== + +Global variables +---------------- + +.. envvar:: mod_env + +   Module environment, contains data pointer for module-specific data. +   See :class:`pythonmod_env`. + + +Predefined constants +----------------------- + +Module extended state +~~~~~~~~~~~~~~~~~~~~~~~ + +.. data:: module_state_initial + +   Initial state - new DNS query. + +.. data:: module_wait_reply + +   Waiting for reply to outgoing network query. + +.. data:: module_wait_module + +   Module is waiting for another module. +    +.. data:: module_wait_subquery + +   Module is waiting for sub-query. +    +.. data:: module_error + +   Module could not finish the query. +    +.. data:: module_finished + +   Module is finished with query. + +Module event +~~~~~~~~~~~~~ +.. data:: module_event_new + +   New DNS query. +    +.. data:: module_event_pass + +   Query passed by other module. +    +.. data:: module_event_reply + +   Reply inbound from server. +    +.. data:: module_event_noreply + +   No reply, timeout or other error. +    +.. data:: module_event_capsfail + +   Reply is there, but capitalisation check failed. +    +.. data:: module_event_moddone + +   Next module is done, and its reply is awaiting you. +    +.. data:: module_event_error + +   Error occured. + +Security status +~~~~~~~~~~~~~~~~ + +.. data:: sec_status_unchecked + +   Means that object has yet to be validated. + +.. data:: sec_status_bogus + +   Means that the object *(RRset or message)* failed to validate +   *(according to local policy)*, but should have validated. +    +.. data:: sec_status_indeterminate + +   Means that the object is insecure, but not  +   authoritatively so. Generally this means that the RRset is not  +   below a configured trust anchor. +    +.. data:: sec_status_insecure + +   Means that the object is authoritatively known to be  +   insecure. Generally this means that this RRset is below a trust  +   anchor, but also below a verified, insecure delegation. + +.. data:: sec_status_secure + +   Means that the object (RRset or message) validated according to local policy. + +Resource records (RR sets) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The different RR classes. + +   .. data:: RR_CLASS_IN +    +      Internet. +       +   .. data:: RR_CLASS_CH +    +      Chaos. +       +   .. data:: RR_CLASS_HS +    +      Hesiod (Dyer 87) +       +   .. data:: RR_CLASS_NONE +    +      None class, dynamic update. +       +   .. data:: RR_CLASS_ANY +       +      Any class. +    + +The different RR types. + + +   .. data:: RR_TYPE_A  +    +      A host address. +       +   .. data:: RR_TYPE_NS +    +      An authoritative name server. +       +   .. data:: RR_TYPE_MD  +       +      A mail destination (Obsolete - use MX). +       +   .. data:: RR_TYPE_MF  +    +      A mail forwarder (Obsolete - use MX). +       +   .. data:: RR_TYPE_CNAME  +       +      The canonical name for an alias. +       +   .. data:: RR_TYPE_SOA  +       +      Marks the start of a zone of authority. +       +   .. data:: RR_TYPE_MB  +       +      A mailbox domain name (EXPERIMENTAL). +       +   .. data:: RR_TYPE_MG  +       +      A mail group member (EXPERIMENTAL). +       +   .. data:: RR_TYPE_MR  +       +      A mail rename domain name (EXPERIMENTAL). +       +   .. data:: RR_TYPE_NULL +       +      A null RR (EXPERIMENTAL). +       +   .. data:: RR_TYPE_WKS +       +      A well known service description. +       +   .. data:: RR_TYPE_PTR +    +      A domain name pointer. +       +   .. data:: RR_TYPE_HINFO +    +      Host information. +       +   .. data:: RR_TYPE_MINFO +    +      Mailbox or mail list information. +       +   .. data:: RR_TYPE_MX +    +      Mail exchange. +       +   .. data:: RR_TYPE_TXT +    +      Text strings. +    +   .. data:: RR_TYPE_RP +    +      RFC1183. +       +   .. data:: RR_TYPE_AFSDB +       +      RFC1183. +       +   .. data:: RR_TYPE_X25 +       +      RFC1183. +       +   .. data:: RR_TYPE_ISDN +    +      RFC1183. +       +   .. data:: RR_TYPE_RT +       +      RFC1183. +       +   .. data:: RR_TYPE_NSAP +       +      RFC1706. +       +   .. data:: RR_TYPE_NSAP_PTR +       +      RFC1348. +       +   .. data:: RR_TYPE_SIG +       +      2535typecode. +       +   .. data:: RR_TYPE_KEY +       +      2535typecode. +       +   .. data:: RR_TYPE_PX +       +      RFC2163. +       +   .. data:: RR_TYPE_GPOS +       +      RFC1712. +       +   .. data:: RR_TYPE_AAAA +       +      IPv6 address. +       +   .. data:: RR_TYPE_LOC +       +      LOC record  RFC1876. +       +   .. data:: RR_TYPE_NXT +       +      2535typecode. +       +   .. data:: RR_TYPE_EID +       +      draft-ietf-nimrod-dns-01.txt. +       +   .. data:: RR_TYPE_NIMLOC +       +      draft-ietf-nimrod-dns-01.txt. +       +   .. data:: RR_TYPE_SRV +       +      SRV record RFC2782. +       +   .. data:: RR_TYPE_ATMA +    +      http://www.jhsoft.com/rfc/af-saa-0069.000.rtf. +       +   .. data:: RR_TYPE_NAPTR +       +      RFC2915. +       +   .. data:: RR_TYPE_KX +       +      RFC2230. +       +   .. data:: RR_TYPE_CERT +       +      RFC2538. +       +   .. data:: RR_TYPE_A6 +       +      RFC2874. +       +   .. data:: RR_TYPE_DNAME +       +      RFC2672. +       +   .. data:: RR_TYPE_SINK +       +      dnsind-kitchen-sink-02.txt. +       +   .. data:: RR_TYPE_OPT +       +      Pseudo OPT record. +       +   .. data:: RR_TYPE_APL +       +      RFC3123. +       +   .. data:: RR_TYPE_DS +       +      draft-ietf-dnsext-delegation. +       +   .. data:: RR_TYPE_SSHFP +       +      SSH Key Fingerprint. +    +   .. data:: RR_TYPE_IPSECKEY +       +      draft-richardson-ipseckey-rr-11.txt. +       +   .. data:: RR_TYPE_RRSIG +       +      draft-ietf-dnsext-dnssec-25. +       +   .. data:: RR_TYPE_NSEC       +   .. data:: RR_TYPE_DNSKEY +   .. data:: RR_TYPE_DHCID +   .. data:: RR_TYPE_NSEC3 +   .. data:: RR_TYPE_NSEC3PARAMS +   .. data:: RR_TYPE_UINFO +   .. data:: RR_TYPE_UID +   .. data:: RR_TYPE_GID +   .. data:: RR_TYPE_UNSPEC +   .. data:: RR_TYPE_TSIG +   .. data:: RR_TYPE_IXFR +   .. data:: RR_TYPE_AXFR +   .. data:: RR_TYPE_MAILB +       +      A request for mailbox-related records (MB, MG or MR). +       +   .. data:: RR_TYPE_MAILA +       +      A request for mail agent RRs (Obsolete - see MX). +       +   .. data:: RR_TYPE_ANY +       +      Any type *(wildcard)*. +    +   .. data:: RR_TYPE_DLV +       +      RFC 4431, 5074, DNSSEC Lookaside Validation. +    +Return codes +~~~~~~~~~~~~ + +Return codes for packets. + +.. data:: RCODE_NOERROR +.. data:: RCODE_FORMERR +.. data:: RCODE_SERVFAIL +.. data:: RCODE_NXDOMAIN +.. data:: RCODE_NOTIMPL +.. data:: RCODE_REFUSED +.. data:: RCODE_YXDOMAIN +.. data:: RCODE_YXRRSET +.. data:: RCODE_NXRRSET +.. data:: RCODE_NOTAUTH +.. data:: RCODE_NOTZONE +    +Packet data +~~~~~~~~~~~~ + +.. data:: PKT_QR + +   Query - query flag. +    +.. data:: PKT_AA + +   Authoritative Answer - server flag. +    +.. data:: PKT_TC +    +   Truncated - server flag. +    +.. data:: PKT_RD +    +   Recursion desired - query flag. +    +.. data:: PKT_CD + +   Checking disabled - query flag. +    +.. data:: PKT_RA +    +   Recursion available - server flag. +    +.. data:: PKT_AD +    +   Authenticated data - server flag. + + +Verbosity value +~~~~~~~~~~~~~~~~ + +.. data:: NO_VERBOSE + +   No verbose messages. +    +.. data:: VERB_OPS + +   Operational information. +    +.. data:: VERB_DETAIL + +   Detailed information. +    +.. data:: VERB_QUERY + +   Query level information. +    +.. data:: VERB_ALGO + +   Algorithm level information. diff --git a/pythonmod/doc/modules/functions.rst b/pythonmod/doc/modules/functions.rst new file mode 100644 index 000000000000..45a469fec04e --- /dev/null +++ b/pythonmod/doc/modules/functions.rst @@ -0,0 +1,120 @@ +Scriptable functions +==================== + +Network +------- + +.. function:: ntohs(netshort) + +   This subroutine converts values between the host and network byte order.  +   Specifically, **ntohs()** converts 16-bit quantities from network byte order to host byte order. +    +   :param netshort: 16-bit short addr +   :rtype: converted addr +    +    +Cache +----- + +.. function:: storeQueryInCache(qstate, qinfo, msgrep, is_referral) + +   Store pending query in local cache. +    +   :param qstate: :class:`module_qstate` +   :param qinfo: :class:`query_info` +   :param msgrep: :class:`reply_info` +   :param is_referal: integer +   :rtype: boolean +    +.. function:: invalidateQueryInCache(qstate, qinfo) + +   Invalidate record in local cache. + +   :param qstate: :class:`module_qstate` +   :param qinfo: :class:`query_info` + + +Logging +------- + +.. function:: verbose(level, msg) + +   Log a verbose message, pass the level for this message. +   No trailing newline is needed. + +   :param level: verbosity level for this message, compared to global verbosity setting. +   :param msg: string message + +.. function:: log_info(msg) + +   Log informational message. No trailing newline is needed. + +   :param msg: string message + +.. function:: log_err(msg) + +   Log error message. No trailing newline is needed. + +   :param msg: string message + +.. function:: log_warn(msg) + +   Log warning message. No trailing newline is needed. + +   :param msg: string message + +.. function:: log_hex(msg, data, length) + +   Log a hex-string to the log. Can be any length. +   performs mallocs to do so, slow. But debug useful. + +   :param msg: string desc to accompany the hexdump. +   :param data: data to dump in hex format. +   :param length: length of data. +    +.. function:: log_dns_msg(str, qinfo, reply) + +   Log DNS message. +    +   :param str: string message +   :param qinfo: :class:`query_info` +   :param reply: :class:`reply_info` +    +.. function:: log_query_info(verbosity_value, str, qinf) + +   Log query information. +    +   :param verbosity_value: see constants +   :param str: string message +   :param qinf: :class:`query_info` +    +.. function:: regional_log_stats(r) + +   Log regional statistics. +    +   :param r: :class:`regional` + +Debugging +--------- + +.. function:: strextstate(module_ext_state) + +   Debug utility, module external qstate to string. +    +   :param module_ext_state: the state value. +   :rtype: descriptive string. + +.. function:: strmodulevent(module_event) + +   Debug utility, module event to string. +    +   :param module_event: the module event value. +   :rtype: descriptive string. +    +.. function:: ldns_rr_type2str(atype) + +   Convert RR type to string. +    +.. function:: ldns_rr_class2str(aclass) + +   Convert RR class to string. diff --git a/pythonmod/doc/modules/index.rst b/pythonmod/doc/modules/index.rst new file mode 100644 index 000000000000..ff0b956956ed --- /dev/null +++ b/pythonmod/doc/modules/index.rst @@ -0,0 +1,11 @@ +Unbound module documentation +======================================= + +.. toctree:: +   :maxdepth: 2 + +   env +   struct +   functions +   config + diff --git a/pythonmod/doc/modules/struct.rst b/pythonmod/doc/modules/struct.rst new file mode 100644 index 000000000000..c41e10b73df5 --- /dev/null +++ b/pythonmod/doc/modules/struct.rst @@ -0,0 +1,427 @@ +Scriptable structures +===================== + +module_qstate +----------------------- + +.. class:: module_qstate + +   Module state, per query. +    +   This class provides these data attributes: +    +   .. attribute:: qinfo +    +      (:class:`query_info`) Informations about query being answered. Name, RR type, RR class. +    +   .. attribute:: query_flags +    +      (uint16) Flags for query. See QF_BIT\_ predefined constants. +       +   .. attribute:: is_priming +    +      If this is a (stub or root) priming query (with hints). +    +   .. attribute:: reply +    +      comm_reply contains server replies. +       +   .. attribute:: return_msg +    +      (:class:`dns_msg`) The reply message, with message for client and calling module (read-only attribute). +		Note that if you want to create of modify return_msg you should use :class:`DNSMessage`. +       +   .. attribute:: return_rcode +    +      The rcode, in case of error, instead of a reply message. Determines whether the return_msg contains reply. +    +   .. attribute:: region +    +      Region for this query. Cleared when query process finishes. +    +   .. attribute:: curmod +    +      Which module is executing. +       +   .. attribute:: ext_state[] +    +      Module states. +       +   .. attribute:: env +    +      Environment for this query. +       +   .. attribute:: mesh_info +    +      Mesh related information for this query. + + +query_info +---------------- + +.. class:: query_info + +   This class provides these data attributes: + +   .. attribute:: qname +    +      The original question in the wireformat format (e.g. \\x03www\\x03nic\\x02cz\\x00 for www.nic.cz) +    +   .. attribute:: qname_len +    +      Lenght of question name (number of bytes). +	 +   .. attribute:: qname_list[] +    +      The question ``qname`` converted into list of labels (e.g. ['www','nic','cz',''] for www.nic.cz) +    +   .. attribute:: qname_str +    +      The question ``qname`` converted into string (e.g. www.nic.cz. for www.nic.cz) + +   .. attribute:: qtype +    +      The class type asked for. See RR_TYPE\_ predefined constants. +    +   .. attribute:: qtype_str +    +      The ``qtype`` in display presentation format (string) (e.g 'A' for RR_TYPE_A) + +   .. attribute:: qclass +    +      The question class. See RR_CLASS\_ predefined constants. +    +   .. attribute:: qclass_str +    +      The ``qclass`` in display presentation format (string). +    +reply_info +-------------------- + +.. class:: reply_info + +   This class provides these data attributes: + +   .. attribute:: flags +    +      The flags for the answer, host byte order. +    +   .. attribute:: qdcount +    +      Number of RRs in the query section. +      If qdcount is not 0, then it is 1, and the data that appears +      in the reply is the same as the query_info. +      Host byte order. +    +   .. attribute:: ttl +    +      TTL of the entire reply (for negative caching). +      only for use when there are 0 RRsets in this message. +      if there are RRsets, check those instead. +    +   .. attribute:: security +    +      The security status from DNSSEC validation of this message. See sec_status\_ predefined constants. +    +   .. attribute:: an_numrrsets +    +      Number of RRsets in each section. +      The answer section. Add up the RRs in every RRset to calculate +      the number of RRs, and the count for the dns packet.  +      The number of RRs in RRsets can change due to RRset updates. +    +   .. attribute:: ns_numrrsets +    +      Count of authority section RRsets +    +   .. attribute:: ar_numrrsets +    +      Count of additional section RRsets  +    +   .. attribute:: rrset_count +    +      Number of RRsets: an_numrrsets + ns_numrrsets + ar_numrrsets  +    +   .. attribute:: rrsets[] +    +         (:class:`ub_packed_rrset_key`) List of RR sets in the order in which they appear in the reply message.   +         Number of elements is ancount + nscount + arcount RRsets. +    +   .. attribute:: ref[] +    +         (:class:`rrset_ref`) Packed array of ids (see counts) and pointers to packed_rrset_key. +         The number equals ancount + nscount + arcount RRsets.  +         These are sorted in ascending pointer, the locking order. So +         this list can be locked (and id, ttl checked), to see if  +         all the data is available and recent enough. +    + +dns_msg +-------------- + +.. class:: dns_msg + +   Region allocated message reply + +   This class provides these data attributes: + +   .. attribute:: qinfo +    +      (:class:`query_info`) Informations about query. +    +   .. attribute:: rep +    +      (:class:`reply_info`) This attribute points to the packed reply structure. + + +packed_rrset_key +---------------------- +    +.. class:: packed_rrset_key + +   The identifying information for an RRset. + +   This class provides these data attributes: + +   .. attribute:: dname +    +      The domain name. If not empty (for ``id = None``) it is allocated, and +      contains the wireformat domain name. This dname is not canonicalized. +      E.g., the dname contains \\x03www\\x03nic\\x02cz\\x00 for www.nic.cz. +    +   .. attribute:: dname_len +    +      Length of the domain name, including last 0 root octet.  +       +   .. attribute:: dname_list[] +    +      The domain name ``dname`` converted into list of labels (see :attr:`query_info.qname_list`). +    +   .. attribute:: dname_str +    +      The domain name ``dname`` converted into string (see :attr:`query_info.qname_str`). + +   .. attribute:: flags +    +      Flags. +       +   .. attribute:: type +    +      The rrset type in network format. + +   .. attribute:: type_str +    +      The rrset type in display presentation format. +       +   .. attribute:: rrset_class +    +      The rrset class in network format. + +   .. attribute:: rrset_class_str +    +      The rrset class in display presentation format. + +ub_packed_rrset_key +------------------------- + +.. class:: ub_packed_rrset_key + +   This structure contains an RRset. A set of resource records that +   share the same domain name, type and class. +   Due to memory management and threading, the key structure cannot be +   deleted, although the data can be. The id can be set to 0 to store and the +   structure can be recycled with a new id. +    +   The :class:`ub_packed_rrset_key` provides these data attributes: +    +   .. attribute:: entry +       +      (:class:`lruhash_entry`) Entry into hashtable. Note the lock is never destroyed, +      even when this key is retired to the cache.  +      the data pointer (if not None) points to a :class:`packed_rrset`. +     +   .. attribute:: id +       +      The ID of this rrset. unique, based on threadid + sequenceno.  +      ids are not reused, except after flushing the cache. +      zero is an unused entry, and never a valid id. +      Check this value after getting entry.lock. +      The other values in this struct may only be altered after changing +      the id (which needs a writelock on entry.lock). +       +   .. attribute:: rk +    +      (:class:`packed_rrset_key`) RR set data. + + +lruhash_entry +------------------------- + +.. class:: lruhash_entry + +   The :class:`ub_packed_rrset_key` provides these data attributes: + +   .. attribute:: lock + +      rwlock for access to the contents of the entry. Note that you cannot change hash and key, if so, you have to delete it to change hash or key. + +   .. attribute:: data + +      (:class:`packed_rrset_data`) entry data stored in wireformat (RRs and RRsigs). + +packed_rrset_data +----------------------- +    +.. class:: packed_rrset_data + +   Rdata is stored in wireformat. The dname is stored in wireformat. +    +   TTLs are stored as absolute values (and could be expired). +    +   RRSIGs are stored in the arrays after the regular rrs. +    +   You need the packed_rrset_key to know dname, type, class of the +   resource records in this RRset. (if signed the rrsig gives the type too). + +   The :class:`packed_rrset_data` provides these data attributes: + +   .. attribute:: ttl +    +      TTL (in seconds like time()) of the RRset. +      Same for all RRs see rfc2181(5.2). +    +   .. attribute:: count +       +      Number of RRs. +    +   .. attribute:: rrsig_count +       +      Number of rrsigs, if 0 no rrsigs. +       +   .. attribute:: trust +    +      The trustworthiness of the RRset data. +       +   .. attribute:: security +    +      Security status of the RRset data. See sec_status\_ predefined constants. +       +   .. attribute:: rr_len[] +    +      Length of every RR's rdata, rr_len[i] is size of rr_data[i]. +       +   .. attribute:: rr_ttl[] +    +      TTL of every rr. rr_ttl[i] ttl of rr i. +       +   .. attribute:: rr_data[] +    +      Array of RR's rdata (list of strings). The rdata is stored in uncompressed wireformat.  +      The first 16B of rr_data[i] is rdlength in network format. +    + +DNSMessage +---------------- +    +.. class:: DNSMessage + +   Abstract representation of DNS message. +    +   **Usage** + +      This example shows how to create an authoritative answer response +		 +      :: + +         msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_A, RR_CLASS_IN, PKT_AA) + +         #append RR +         if (qstate.qinfo.qtype == RR_TYPE_A) or (qstate.qinfo.qtype == RR_TYPE_ANY): +             msg.answer.append("%s 10 IN A 127.0.0.1" % qstate.qinfo.qname_str) +          +         #set qstate.return_msg  +         if not msg.set_return_msg(qstate): +             raise Exception("Can't create response") + +   The :class:`DNSMessage` provides these methods and data attributes: +    +   .. method:: __init__(self, rr_name, rr_type, rr_class = RR_CLASS_IN, query_flags = 0, default_ttl = 0) +    +      Prepares an answer (DNS packet) from qiven information. Query flags are combination of PKT_xx contants. +       +   .. method:: set_return_msg(self, qstate) +    +      This method fills qstate return message according to the given informations.  +		It takes lists of RRs in each section of answer, created necessray RRsets in wire format and store the result in :attr:`qstate.return_msg`. +		Returns 1 if OK. +    +   .. attribute:: rr_name +    +      RR name of question. +       +   .. attribute:: rr_type +    +      RR type of question. +       +   .. attribute:: rr_class +    +      RR class of question. +       +   .. attribute:: default_ttl +    +      Default time-to-live. +       +   .. attribute:: query_flags +    +      Query flags. See PKT\_ predefined constants. +       +   .. attribute:: question[] +    +      List of resource records that should appear (in the same order) in question section of answer. +       +   .. attribute:: answer[] +    +      List of resource records that should appear (in the same order) in answer section of answer. +      +   .. attribute:: authority[] +    +      List of resource records that should appear (in the same order) in authority section of answer. +       +   .. attribute:: additional[] +    +      List of resource records that should appear (in the same order) in additional section of answer. + +pythonmod_env +----------------------- + +.. class:: pythonmod_env + +   Global state for the module.  + +   This class provides these data attributes: + +   .. attribute:: data +    +      Here you can keep your own data shared across each thread. + +   .. attribute:: fname +    +   	Python script filename. +    +   .. attribute:: qstate +    +      Module query state. + +pythonmod_qstate +----------------------- + +.. class:: pythonmod_qstate + +   Per query state for the iterator module. +	 +   This class provides these data attributes: +	 +   .. attribute:: data +	 +	   Here you can keep your own private data (each thread has own data object). + diff --git a/pythonmod/doc/usecase.rst b/pythonmod/doc/usecase.rst new file mode 100644 index 000000000000..7a77349f1e49 --- /dev/null +++ b/pythonmod/doc/usecase.rst @@ -0,0 +1,38 @@ +Use cases (examples) +==================== + +Dynamic DNS Service discovery (DNS-SD_) +------------------------------------------- +Synchronized with database engine, for example *MySQL*.  + +.. _DNS-SD: http://www.dns-sd.org/ + +Firewall control +---------------- +Control firewall (e.g. enable incomming SSH connection) with DNS query signed with private key.  +So firewall can blocks every service during normal operation. + +Scriptable DNS-based blacklist (DNS-BL_) +------------------------------------------- +Scripted in Python with already provided features, takes advantage of DNS reply, because +almost every mail server supports DNS based blacklisting. + +.. _DNS-BL: http://www.dnsbl.org + +DNS based Wake-On-Lan +--------------------- +Controled by secured queries secured with private key. + +Dynamic translation service +--------------------------- +DNS request can be translated to virtualy any answer, that's easy to implement in client side +because of many DNS libraries available. + +Examples : + * **Dictionary** - using *IDN* for non-ascii strings transfer, ``dig TXT slovo.en._dict_.nic.cz`` returns translation of "slovo" to EN. + * **Translation** - Extends *DNS-SD*, for example DNS to Jabber to find out people logged in. + * **Exchange rate calculator** - ``dig TXT 1000.99.czk.eur._rates_.nic.cz`` returns the given sum (1000.99 CZK) in EURs. + +Dynamic ENUM service  +-------------------- +Support for redirection, synchronization, etc. diff --git a/pythonmod/examples/calc.py b/pythonmod/examples/calc.py new file mode 100644 index 000000000000..3230e37e3eea --- /dev/null +++ b/pythonmod/examples/calc.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +''' + calc.py: DNS-based calculator  + + Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +  +    * Redistributions in binary form must reproduce the above copyright notice, +      this list of conditions and the following disclaimer in the documentation +      and/or other materials provided with the distribution. +  +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' + +#Try: dig @localhost 1*25._calc_.cz. + +def init(id, cfg): return True +def deinit(id): return True +def inform_super(id, qstate, superqstate, qdata): return True + +def operate(id, event, qstate, qdata): + +    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): + +        if qstate.qinfo.qname_str.endswith("._calc_.cz."): +            try: +                res = eval(''.join(qstate.qinfo.qname_list[0:-3])) +            except: +                res = "exception" + +            msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA) #, 300) +            msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str,res)) +            if not msg.set_return_msg(qstate): +               qstate.ext_state[id] = MODULE_ERROR  +               return True + +            qstate.return_rcode = RCODE_NOERROR +            qstate.ext_state[id] = MODULE_FINISHED  +            return True + +        else:  +            #Pass on the unknown query to the iterator +            qstate.ext_state[id] = MODULE_WAIT_MODULE  +            return True + +    elif event == MODULE_EVENT_MODDONE:  +        #the iterator has finished +        qstate.ext_state[id] = MODULE_FINISHED +        return True + +    log_err("pythonmod: Unknown event") +    qstate.ext_state[id] = MODULE_ERROR +    return True + diff --git a/pythonmod/examples/dict.py b/pythonmod/examples/dict.py new file mode 100644 index 000000000000..c8088a89c2f1 --- /dev/null +++ b/pythonmod/examples/dict.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +''' + calc.py: DNS-based czech-english dictionary + + Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +  +    * Redistributions in binary form must reproduce the above copyright notice, +      this list of conditions and the following disclaimer in the documentation +      and/or other materials provided with the distribution. +  +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' +import os +cz_dict = {} +en_dict = {} + +def init(id, cfg): +   log_info("pythonmod: dict init") +   f = open("examples/dict_data.txt", "r") +   try: +      for line in f: +         if line.startswith('#'): +            continue +         itm = line.split("\t", 3) +         if len(itm) < 2:  +            continue +         en,cs = itm[0:2] + +         if not (cs in cz_dict): +            cz_dict[cs] = [en]     # [cs] = en +         else: +            cz_dict[cs].append(en) # [cs] = en + +         if not (en in en_dict): +            en_dict[en] = [cs]     # [en] = cs +         else: +            en_dict[en].append(cs) # [en] = cs + +   finally: +      f.close() +   return True + +def deinit(id): +   log_info("pythonmod: dict deinit") +   return True + +def operate(id, event, qstate, qdata): +    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): + +       if qstate.qinfo.qname_str.endswith("._dict_.cz."): +         +         aword = ' '.join(qstate.qinfo.qname_list[0:-4]) +         adict = qstate.qinfo.qname_list[-4] + +         log_info("pythonmod: dictionary look up; word:%s dict:%s" % (aword,adict)) + +         words = [] +         if (adict == "en") and (aword in en_dict): +            words = en_dict[aword] # EN -> CS +         if (adict == "cs") and (aword in cz_dict): +            words = cz_dict[aword] # CS -> EN + +         if len(words) and ((qstate.qinfo.qtype == RR_TYPE_TXT) or (qstate.qinfo.qtype == RR_TYPE_ANY)): + +            msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_RD | PKT_RA | PKT_AA) +            for w in words: +                msg.answer.append("%s 300 IN TXT \"%s\"" % (qstate.qinfo.qname_str,w.replace("\"","\\\""))) + +            if not msg.set_return_msg(qstate): +               qstate.ext_state[id] = MODULE_ERROR  +               return True + +            qstate.return_rcode = RCODE_NOERROR +            qstate.ext_state[id] = MODULE_FINISHED  +            return True + +         else: +            qstate.return_rcode = RCODE_SERVFAIL +            qstate.ext_state[id] = MODULE_FINISHED  +            return True + +       else: #Pass on the unknown query to the iterator +         qstate.ext_state[id] = MODULE_WAIT_MODULE  +         return True + +    elif event == MODULE_EVENT_MODDONE: #the iterator has finished +         #we don't need modify result +         qstate.ext_state[id] = MODULE_FINISHED +         return True + +    log_err("pythonmod: Unknown event") +    qstate.ext_state[id] = MODULE_ERROR +    return True + +def inform_super(id, qstate, superqstate, qdata): +   return True + diff --git a/pythonmod/examples/dict_data.txt b/pythonmod/examples/dict_data.txt new file mode 100644 index 000000000000..04cd3badffa9 --- /dev/null +++ b/pythonmod/examples/dict_data.txt @@ -0,0 +1,6 @@ +*	*			web +computer	poèítaèový	adj:		Zdenìk Bro¾ +computer	poèítaè	n:		 +domain	doména	n:		Zdenìk Bro¾ +query	otazník	n:		Zdenìk Bro¾ +network	sí»	n: [it.]	poèítaèová	 diff --git a/pythonmod/examples/log.py b/pythonmod/examples/log.py new file mode 100644 index 000000000000..c17106b0f268 --- /dev/null +++ b/pythonmod/examples/log.py @@ -0,0 +1,119 @@ +import os +''' + calc.py: Response packet logger + + Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +  +    * Redistributions in binary form must reproduce the above copyright notice, +      this list of conditions and the following disclaimer in the documentation +      and/or other materials provided with the distribution. +  +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' + +def dataHex(data, prefix=""): +    """Converts binary string data to display representation form""" +    res = "" +    for i in range(0, (len(data)+15)/16): +        res += "%s0x%02X | " % (prefix, i*16) +        d = map(lambda x:ord(x), data[i*16:i*16+17]) +        for ch in d: +            res += "%02X " % ch +        for i in range(0,17-len(d)): +            res += "   " +        res += "| " +        for ch in d: +            if (ch < 32) or (ch > 127): +                res += ". " +            else: +                res += "%c " % ch +        res += "\n" +    return res + +def logDnsMsg(qstate): +    """Logs response""" + +    r  = qstate.return_msg.rep +    q  = qstate.return_msg.qinfo + +    print "-"*100 +    print("Query: %s, type: %s (%d), class: %s (%d) " % ( +            qstate.qinfo.qname_str, qstate.qinfo.qtype_str, qstate.qinfo.qtype, +            qstate.qinfo.qclass_str, qstate.qinfo.qclass)) +    print "-"*100 +    print "Return    reply :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (r.flags, r.qdcount, r.security, r.ttl) +    print "          qinfo :: qname: %s %s, qtype: %s, qclass: %s" % (str(q.qname_list), q.qname_str, q.qtype_str, q.qclass_str) + +    if (r): +        print "Reply:" +        for i in range(0, r.rrset_count): +            rr = r.rrsets[i] + +            rk = rr.rk +            print i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags, +            print "type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class) + +            d = rr.entry.data +            for j in range(0,d.count+d.rrsig_count): +                print "  ",j,":","TTL=",d.rr_ttl[j], +                if (j >= d.count): print "rrsig", +                print  +                print dataHex(d.rr_data[j],"       ") + +    print "-"*100 + +def init(id, cfg): +   log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script)) +   return True + +def deinit(id): +   log_info("pythonmod: deinit called, module id is %d" % id) +   return True + +def inform_super(id, qstate, superqstate, qdata): +   return True + +def operate(id, event, qstate, qdata): +   log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) +   +   if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): +      #Pass on the new event to the iterator +      qstate.ext_state[id] = MODULE_WAIT_MODULE  +      return True + +   if event == MODULE_EVENT_MODDONE: +      #Iterator finished, show response (if any) + +      if (qstate.return_msg): +          logDnsMsg(qstate) + +      qstate.ext_state[id] = MODULE_FINISHED  +      return True + +   qstate.ext_state[id] = MODULE_ERROR +   return True + diff --git a/pythonmod/examples/resgen.py b/pythonmod/examples/resgen.py new file mode 100644 index 000000000000..804c0bd1d354 --- /dev/null +++ b/pythonmod/examples/resgen.py @@ -0,0 +1,73 @@ +''' + resgen.py: This example shows how to generate authoritative response + + Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +  +    * Redistributions in binary form must reproduce the above copyright notice, +      this list of conditions and the following disclaimer in the documentation +      and/or other materials provided with the distribution. +  +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' +def init(id, cfg): return True + +def deinit(id): return True + +def inform_super(id, qstate, superqstate, qdata): return True + +def operate(id, event, qstate, qdata): +    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): +        if (qstate.qinfo.qname_str.endswith(".localdomain.")): #query name ends with localdomain +            #create instance of DNS message (packet) with given parameters +            msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_A, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA) +            #append RR +            if (qstate.qinfo.qtype == RR_TYPE_A) or (qstate.qinfo.qtype == RR_TYPE_ANY): +                msg.answer.append("%s 10 IN A 127.0.0.1" % qstate.qinfo.qname_str) +            #set qstate.return_msg  +            if not msg.set_return_msg(qstate): +                qstate.ext_state[id] = MODULE_ERROR  +                return True + +            #we don't need validation, result is valid +            qstate.return_msg.rep.security = 2 + +            qstate.return_rcode = RCODE_NOERROR +            qstate.ext_state[id] = MODULE_FINISHED  +            return True +        else: +            #pass the query to validator +            qstate.ext_state[id] = MODULE_WAIT_MODULE  +            return True + +    if event == MODULE_EVENT_MODDONE: +        log_info("pythonmod: iterator module done") +        qstate.ext_state[id] = MODULE_FINISHED  +        return True +       +    log_err("pythonmod: bad event") +    qstate.ext_state[id] = MODULE_ERROR +    return True diff --git a/pythonmod/examples/resip.py b/pythonmod/examples/resip.py new file mode 100644 index 000000000000..6bcac7252cd2 --- /dev/null +++ b/pythonmod/examples/resip.py @@ -0,0 +1,96 @@ +''' + resip.py: This example shows how to generate authoritative response +           and how to find out the IP address of a client + + Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +  +    * Redistributions in binary form must reproduce the above copyright notice, +      this list of conditions and the following disclaimer in the documentation +      and/or other materials provided with the distribution. +  +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + + Usage: + +   dig @127.0.0.1 -t TXT what.is.my.ip. +''' + +def init(id, cfg): return True + +def deinit(id): return True + +def inform_super(id, qstate, superqstate, qdata): return True + +def operate(id, event, qstate, qdata): +    print "Operate", event,"state:",qstate + +    # Please note that if this module blocks, by moving to the validator +    # to validate or iterator to lookup or spawn a subquery to look up, +    # then, other incoming queries are queued up onto this module and +    # all of them receive the same reply. +    # You can inspect the cache. + +    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): +        if (qstate.qinfo.qname_str.endswith("what.is.my.ip.")): #query name ends with localdomain +            #create instance of DNS message (packet) with given parameters +            msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_TXT, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA) +            #append RR +            if (qstate.qinfo.qtype == RR_TYPE_TXT) or (qstate.qinfo.qtype == RR_TYPE_ANY): +               rl = qstate.mesh_info.reply_list +               while (rl): +                   if rl.query_reply: +                      q = rl.query_reply +		      # The TTL of 0 is mandatory, otherwise it ends up in +		      # the cache, and is returned to other IP addresses. +                      msg.answer.append("%s 0 IN TXT \"%s %d (%s)\"" % (qstate.qinfo.qname_str, q.addr,q.port,q.family)) +                   rl = rl.next + +            #set qstate.return_msg  +            if not msg.set_return_msg(qstate): +                qstate.ext_state[id] = MODULE_ERROR  +                return True + +            #we don't need validation, result is valid +            qstate.return_msg.rep.security = 2 + +            qstate.return_rcode = RCODE_NOERROR +            qstate.ext_state[id] = MODULE_FINISHED  +            return True +        else: +            #pass the query to validator +            qstate.ext_state[id] = MODULE_WAIT_MODULE  +            return True + +    if event == MODULE_EVENT_MODDONE: +        log_info("pythonmod: iterator module done") +        qstate.ext_state[id] = MODULE_FINISHED  +        return True +       +    log_err("pythonmod: bad event") +    qstate.ext_state[id] = MODULE_ERROR +    return True diff --git a/pythonmod/examples/resmod.py b/pythonmod/examples/resmod.py new file mode 100644 index 000000000000..cf392e4da274 --- /dev/null +++ b/pythonmod/examples/resmod.py @@ -0,0 +1,88 @@ +''' + resmod.py: This example shows how to modify the response from iterator  + + Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) +                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  +    * Redistributions of source code must retain the above copyright notice, +      this list of conditions and the following disclaimer. +  +    * Redistributions in binary form must reproduce the above copyright notice, +      this list of conditions and the following disclaimer in the documentation +      and/or other materials provided with the distribution. +  +    * Neither the name of the organization nor the names of its +      contributors may be used to endorse or promote products derived from this +      software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' + +def init(id, cfg): return True + +def deinit(id): return True + +def inform_super(id, qstate, superqstate, qdata): return True + +def setTTL(qstate, ttl): +    """Updates return_msg TTL and the TTL of all the RRs""" +    if qstate.return_msg: +        qstate.return_msg.rep.ttl = ttl +        if (qstate.return_msg.rep): +            for i in range(0,qstate.return_msg.rep.rrset_count): +                d = qstate.return_msg.rep.rrsets[i].entry.data +                for j in range(0,d.count+d.rrsig_count): +                    d.rr_ttl[j] = ttl + +def operate(id, event, qstate, qdata): +    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): +        #pass the query to validator +        qstate.ext_state[id] = MODULE_WAIT_MODULE  +        return True + +    if event == MODULE_EVENT_MODDONE: +        log_info("pythonmod: iterator module done") + +        if not qstate.return_msg: +            qstate.ext_state[id] = MODULE_FINISHED  +            return True + +        #modify the response + +        qdn = qstate.qinfo.qname_str +        if qdn.endswith(".nic.cz."): +            #invalidate response in cache added by iterator +            #invalidateQueryInCache(qstate, qstate.return_msg.qinfo) + +            #modify TTL to 10 secs and store response in cache +            #setTTL(qstate, 5) +            #if not storeQueryInCache(qstate, qstate.return_msg.qinfo, qstate.return_msg.rep, 0): +            #    qstate.ext_state[id] = MODULE_ERROR +            #    return False + +            #modify TTL of response, which will be send to a) validator and then b) client +            setTTL(qstate, 10) +            qstate.return_rcode = RCODE_NOERROR + +        qstate.ext_state[id] = MODULE_FINISHED  +        return True +       +    log_err("pythonmod: bad event") +    qstate.ext_state[id] = MODULE_ERROR +    return True diff --git a/pythonmod/interface.i b/pythonmod/interface.i new file mode 100644 index 000000000000..903563658b0e --- /dev/null +++ b/pythonmod/interface.i @@ -0,0 +1,889 @@ +/* + * interface.i: unbound python module + */ + +%module unboundmodule +%{ +/** + * \file  + * This is the interface between the unbound server and a python module + * called to perform operations on queries. + */ +   #include <sys/types.h> +   #include <sys/socket.h> +   #include <netinet/in.h> +   #include <arpa/inet.h> +   #include <stdarg.h> +   #include "config.h" +   #include "util/log.h" +   #include "util/module.h" +   #include "util/netevent.h" +   #include "util/regional.h" +   #include "util/config_file.h" +   #include "util/data/msgreply.h" +   #include "util/data/packed_rrset.h" +   #include "util/data/dname.h" +   #include "util/storage/lruhash.h" +   #include "services/cache/dns.h" +   #include "services/mesh.h" +%} + +%include "stdint.i" // uint_16_t can be known type now + +%inline %{ +   //converts [len][data][len][data][0] string to a List of labels (PyStrings) +   PyObject* GetNameAsLabelList(const char* name, int len) { +     PyObject* list; +     int cnt=0, i; + +     i = 0; +     while (i < len) { +        i += name[i] + 1; +        cnt++; +     } + +     list = PyList_New(cnt); +     i = 0; cnt = 0; +     while (i < len) { +        PyList_SetItem(list, cnt, PyString_FromStringAndSize(name + i + 1, name[i])); +        i += name[i] + 1; +        cnt++; +     } +     return list; +   } +%} + +/* ************************************************************************************ *  +   Structure query_info + * ************************************************************************************ */ +/* Query info */ +%ignore query_info::qname; +%ignore query_info::qname_len; + + +struct query_info { +   %immutable; +   char* qname; +   size_t qname_len; +   uint16_t qtype; +   uint16_t qclass; +   %mutable; +}; + +%inline %{ +   enum enum_rr_class  {  +      RR_CLASS_IN = 1, +      RR_CLASS_CH	= 3, +      RR_CLASS_HS	= 4, +      RR_CLASS_NONE = 254, +      RR_CLASS_ANY = 255, +   }; +    +   enum enum_rr_type { +      RR_TYPE_A = 1,  +      RR_TYPE_NS = 2,  +      RR_TYPE_MD = 3,  +      RR_TYPE_MF = 4,  +      RR_TYPE_CNAME = 5,  +      RR_TYPE_SOA = 6,  +      RR_TYPE_MB = 7,  +      RR_TYPE_MG = 8,  +      RR_TYPE_MR = 9,  +      RR_TYPE_NULL = 10, +      RR_TYPE_WKS = 11, +      RR_TYPE_PTR = 12, +      RR_TYPE_HINFO = 13, +      RR_TYPE_MINFO = 14, +      RR_TYPE_MX = 15, +      RR_TYPE_TXT = 16, +      RR_TYPE_RP = 17, +      RR_TYPE_AFSDB = 18, +      RR_TYPE_X25 = 19, +      RR_TYPE_ISDN = 20, +      RR_TYPE_RT = 21, +      RR_TYPE_NSAP = 22, +      RR_TYPE_NSAP_PTR = 23, +      RR_TYPE_SIG = 24, +      RR_TYPE_KEY = 25, +      RR_TYPE_PX = 26, +      RR_TYPE_GPOS = 27, +      RR_TYPE_AAAA = 28, +      RR_TYPE_LOC = 29, +      RR_TYPE_NXT = 30, +      RR_TYPE_EID = 31, +      RR_TYPE_NIMLOC = 32, +      RR_TYPE_SRV = 33, +      RR_TYPE_ATMA = 34, +      RR_TYPE_NAPTR = 35, +      RR_TYPE_KX = 36, +      RR_TYPE_CERT = 37, +      RR_TYPE_A6 = 38, +      RR_TYPE_DNAME = 39, +      RR_TYPE_SINK = 40, +      RR_TYPE_OPT = 41, +      RR_TYPE_APL = 42, +      RR_TYPE_DS = 43, +      RR_TYPE_SSHFP = 44, +      RR_TYPE_IPSECKEY = 45, +      RR_TYPE_RRSIG = 46, +      RR_TYPE_NSEC = 47,       +      RR_TYPE_DNSKEY = 48, +      RR_TYPE_DHCID = 49, +      RR_TYPE_NSEC3 = 50, +      RR_TYPE_NSEC3PARAMS = 51, +      RR_TYPE_UINFO = 100, +      RR_TYPE_UID = 101, +      RR_TYPE_GID = 102, +      RR_TYPE_UNSPEC = 103, +      RR_TYPE_TSIG = 250, +      RR_TYPE_IXFR = 251, +      RR_TYPE_AXFR = 252, +      RR_TYPE_MAILB = 253, +      RR_TYPE_MAILA = 254, +      RR_TYPE_ANY = 255, +      RR_TYPE_DLV = 32769, +   }; + +   PyObject* _get_qname(struct query_info* q) { +      return PyString_FromStringAndSize((char*)q->qname, q->qname_len); +   }  + +   PyObject* _get_qname_components(struct query_info* q) { +      return GetNameAsLabelList((const char*)q->qname, q->qname_len); +   } +%} + +%inline %{ +   PyObject* dnameAsStr(const char* dname) { +       char buf[LDNS_MAX_DOMAINLEN+1]; +       buf[0] = '\0'; +       dname_str((uint8_t*)dname, buf); +       return PyString_FromString(buf); +   } +%} + +%extend query_info { +   %pythoncode %{ +        def _get_qtype_str(self): return ldns_rr_type2str(self.qtype) +        __swig_getmethods__["qtype_str"] = _get_qtype_str +        if _newclass:qtype_str = _swig_property(_get_qtype_str) + +        def _get_qclass_str(self): return ldns_rr_class2str(self.qclass) +        __swig_getmethods__["qclass_str"] = _get_qclass_str +        if _newclass:qclass_str = _swig_property(_get_qclass_str) + +        __swig_getmethods__["qname"] = _unboundmodule._get_qname +        if _newclass:qname = _swig_property(_unboundmodule._get_qname) +         +        __swig_getmethods__["qname_list"] = _unboundmodule._get_qname_components +        if _newclass:qname_list = _swig_property(_unboundmodule._get_qname_components) + +        def _get_qname_str(self): return dnameAsStr(self.qname) +        __swig_getmethods__["qname_str"] = _get_qname_str +        if _newclass:qname_str = _swig_property(_get_qname_str) +   %} +} + +/* ************************************************************************************ *  +   Structure packed_rrset_key + * ************************************************************************************ */ +%ignore packed_rrset_key::dname; +%ignore packed_rrset_key::dname_len; + +/* RRsets */ +struct packed_rrset_key { +   %immutable; +   char*    dname; +   size_t   dname_len; +   uint32_t flags;  +   uint16_t type;  //rrset type in network format +   uint16_t rrset_class; //rrset class in network format +   %mutable; +}; + +//This subroutine converts values between the host and network byte order.  +//Specifically, ntohs() converts 16-bit quantities from network byte order to host byte order. +uint16_t ntohs(uint16_t netshort); + +%inline %{ +   PyObject* _get_dname(struct packed_rrset_key* k) { +      return PyString_FromStringAndSize((char*)k->dname, k->dname_len); +   }  +   PyObject* _get_dname_components(struct packed_rrset_key* k) { +      return GetNameAsLabelList((char*)k->dname, k->dname_len); +   } +%} + +%extend packed_rrset_key { +   %pythoncode %{ +        def _get_type_str(self): return ldns_rr_type2str(_unboundmodule.ntohs(self.type)) +        __swig_getmethods__["type_str"] = _get_type_str +        if _newclass:type_str = _swig_property(_get_type_str) + +        def _get_class_str(self): return ldns_rr_class2str(_unboundmodule.ntohs(self.rrset_class)) +        __swig_getmethods__["rrset_class_str"] = _get_class_str +        if _newclass:rrset_class_str = _swig_property(_get_class_str) + +        __swig_getmethods__["dname"] = _unboundmodule._get_dname +        if _newclass:dname = _swig_property(_unboundmodule._get_dname) + +        __swig_getmethods__["dname_list"] = _unboundmodule._get_dname_components +        if _newclass:dname_list = _swig_property(_unboundmodule._get_dname_components) + +        def _get_dname_str(self): return dnameAsStr(self.dname) +        __swig_getmethods__["dname_str"] = _get_dname_str +        if _newclass:dname_str = _swig_property(_get_dname_str) +   %} +} + +#if defined(SWIGWORDSIZE64)  +typedef long int                rrset_id_t; +#else  +typedef long long int           rrset_id_t; +#endif  + +struct ub_packed_rrset_key { +   struct lruhash_entry entry; +   rrset_id_t id; +   struct packed_rrset_key rk; +}; + +struct lruhash_entry { +  lock_rw_t lock; +  struct lruhash_entry* overflow_next; +  struct lruhash_entry* lru_next; +  struct lruhash_entry* lru_prev; +  hashvalue_t hash; +  void* key; +  struct packed_rrset_data* data; +}; + +%ignore packed_rrset_data::rr_len; +%ignore packed_rrset_data::rr_ttl; +%ignore packed_rrset_data::rr_data; + +struct packed_rrset_data { +  uint32_t ttl; //TTL (in seconds like time()) + +  size_t count; //number of rrs +  size_t rrsig_count; //number of rrsigs + +  enum rrset_trust trust;  +  enum sec_status security; + +  size_t* rr_len;   //length of every rr's rdata +  uint32_t *rr_ttl; //ttl of every rr +  uint8_t** rr_data; //array of pointers to every rr's rdata; The rr_data[i] rdata is stored in uncompressed wireformat.  +}; + +%pythoncode %{ +    class RRSetData_RRLen: +        def __init__(self, obj): self.obj = obj +        def __getitem__(self, index): return _unboundmodule._get_data_rr_len(self.obj, index) +        def __len__(self): return obj.count + obj.rrsig_count +    class RRSetData_RRTTL: +        def __init__(self, obj): self.obj = obj +        def __getitem__(self, index): return _unboundmodule._get_data_rr_ttl(self.obj, index) +        def __setitem__(self, index, value): _unboundmodule._set_data_rr_ttl(self.obj, index, value) +        def __len__(self): return obj.count + obj.rrsig_count +    class RRSetData_RRData: +        def __init__(self, obj): self.obj = obj +        def __getitem__(self, index): return _unboundmodule._get_data_rr_data(self.obj, index) +        def __len__(self): return obj.count + obj.rrsig_count +%} + +%inline %{ +   PyObject* _get_data_rr_len(struct packed_rrset_data* d, int idx) { +     if ((d != NULL) && (idx >= 0) &&  +             ((size_t)idx < (d->count+d->rrsig_count)))  +        return PyInt_FromLong(d->rr_len[idx]); +     return Py_None; +   } +   void _set_data_rr_ttl(struct packed_rrset_data* d, int idx, uint32_t ttl) +   { +     if ((d != NULL) && (idx >= 0) &&  +             ((size_t)idx < (d->count+d->rrsig_count)))  +        d->rr_ttl[idx] = ttl; +   } +   PyObject* _get_data_rr_ttl(struct packed_rrset_data* d, int idx) { +     if ((d != NULL) && (idx >= 0) &&  +             ((size_t)idx < (d->count+d->rrsig_count)))  +        return PyInt_FromLong(d->rr_ttl[idx]); +     return Py_None; +   } +   PyObject* _get_data_rr_data(struct packed_rrset_data* d, int idx) { +     if ((d != NULL) && (idx >= 0) &&  +             ((size_t)idx < (d->count+d->rrsig_count)))  +        return PyString_FromStringAndSize((char*)d->rr_data[idx], +                d->rr_len[idx]); +     return Py_None; +   } +%} + +%extend packed_rrset_data { +   %pythoncode %{ +        def _get_data_rr_len(self): return RRSetData_RRLen(self) +        __swig_getmethods__["rr_len"] = _get_data_rr_len +        if _newclass:rr_len = _swig_property(_get_data_rr_len) +        def _get_data_rr_ttl(self): return RRSetData_RRTTL(self) +        __swig_getmethods__["rr_ttl"] =_get_data_rr_ttl +        if _newclass:rr_len = _swig_property(_get_data_rr_ttl) +        def _get_data_rr_data(self): return RRSetData_RRData(self) +        __swig_getmethods__["rr_data"] = _get_data_rr_data +        if _newclass:rr_len = _swig_property(_get_data_rr_data) +   %} +} + +/* ************************************************************************************ *  +   Structure reply_info + * ************************************************************************************ */ +/* Messages */ +%ignore reply_info::rrsets; +%ignore reply_info::ref; + +struct reply_info { +   uint16_t flags; +   uint16_t qdcount; +   uint32_t ttl; +   uint32_t prefetch_ttl; + +   uint16_t authoritative; +   enum sec_status security; + +   size_t an_numrrsets; +   size_t ns_numrrsets; +   size_t ar_numrrsets; +   size_t rrset_count; // an_numrrsets + ns_numrrsets + ar_numrrsets + +   struct ub_packed_rrset_key** rrsets; +   struct rrset_ref ref[1]; //? +}; + +struct rrset_ref { +   struct ub_packed_rrset_key* key; +   rrset_id_t id; +}; + +struct dns_msg { +   struct query_info qinfo; +   struct reply_info *rep; +}; + +%pythoncode %{ +    class ReplyInfo_RRSet: +        def __init__(self, obj): self.obj = obj +        def __getitem__(self, index): return _unboundmodule._rrset_rrsets_get(self.obj, index) +        def __len__(self): return obj.rrset_count + +    class ReplyInfo_Ref: +        def __init__(self, obj): self.obj = obj +        def __getitem__(self, index): return _unboundmodule._rrset_ref_get(self.obj, index) +        def __len__(self): return obj.rrset_count +%} + +%inline %{ +   struct ub_packed_rrset_key* _rrset_rrsets_get(struct reply_info* r, int idx) { +     if ((r != NULL) && (idx >= 0) && ((size_t)idx < r->rrset_count)) +        return r->rrsets[idx]; +     return NULL; +   } + +   struct rrset_ref* _rrset_ref_get(struct reply_info* r, int idx) { +     if ((r != NULL) && (idx >= 0) && ((size_t)idx < r->rrset_count)) { +//printf("_rrset_ref_get: %lX key:%lX\n", r->ref + idx, r->ref[idx].key); +             return &(r->ref[idx]); +//        return &(r->ref[idx]); +     } +//printf("_rrset_ref_get: NULL\n"); +     return NULL; +   } +%} + +%extend reply_info { +   %pythoncode %{ +        def _rrset_ref_get(self): return ReplyInfo_Ref(self) +        __swig_getmethods__["ref"] = _rrset_ref_get +        if _newclass:ref = _swig_property(_rrset_ref_get) + +        def _rrset_rrsets_get(self): return ReplyInfo_RRSet(self) +        __swig_getmethods__["rrsets"] = _rrset_rrsets_get +        if _newclass:rrsets = _swig_property(_rrset_rrsets_get) +   %} +} + +/* ************************************************************************************ *  +   Structure mesh_state + * ************************************************************************************ */ +struct mesh_state { +   struct mesh_reply* reply_list; +}; + +struct mesh_reply { +   struct mesh_reply* next; +   struct comm_reply query_reply; +}; + +struct comm_reply { +    +}; + +%inline %{ + +  PyObject* _comm_reply_addr_get(struct comm_reply* reply) { +     char dest[64]; +     reply_addr2str(reply, dest, 64); +     if (dest[0] == 0) +        return Py_None; +     return PyString_FromString(dest); +  } + +  PyObject* _comm_reply_family_get(struct comm_reply* reply) { + +        int af = (int)((struct sockaddr_in*) &(reply->addr))->sin_family; + +        switch(af) { +           case AF_INET: return PyString_FromString("ip4"); +           case AF_INET6: return PyString_FromString("ip6");  +           case AF_UNIX: return PyString_FromString("unix"); +        } + +        return Py_None; +  } + +  PyObject* _comm_reply_port_get(struct comm_reply* reply) { +     uint16_t port; +     port = ntohs(((struct sockaddr_in*)&(reply->addr))->sin_port); +     return PyInt_FromLong(port); +  } + +%} + +%extend comm_reply { +   %pythoncode %{ +        def _addr_get(self): return _comm_reply_addr_get(self) +        __swig_getmethods__["addr"] = _addr_get +        if _newclass:addr = _swig_property(_addr_get) + +        def _port_get(self): return _comm_reply_port_get(self) +        __swig_getmethods__["port"] = _port_get +        if _newclass:port = _swig_property(_port_get) + +        def _family_get(self): return _comm_reply_family_get(self) +        __swig_getmethods__["family"] = _family_get +        if _newclass:family = _swig_property(_family_get) +   %} +} +/* ************************************************************************************ *  +   Structure module_qstate + * ************************************************************************************ */ +%ignore module_qstate::ext_state; +%ignore module_qstate::minfo; + +/* Query state */ +struct module_qstate { +   struct query_info qinfo; +   uint16_t query_flags; //See QF_BIT_xx constants +   int      is_priming; + +   struct comm_reply* reply; +   struct dns_msg* return_msg; +   int    return_rcode; +   struct regional* region; /* unwrapped */ + +   int    curmod; + +   enum   module_ext_state ext_state[MAX_MODULE]; +   void*  minfo[MAX_MODULE]; + +   struct module_env* env;         /* unwrapped */ +   struct mesh_state* mesh_info; +}; + +%constant int MODULE_COUNT = MAX_MODULE; + +%constant int QF_BIT_CD = 0x0010; +%constant int QF_BIT_AD = 0x0020; +%constant int QF_BIT_Z  = 0x0040; +%constant int QF_BIT_RA = 0x0080; +%constant int QF_BIT_RD = 0x0100; +%constant int QF_BIT_TC = 0x0200; +%constant int QF_BIT_AA = 0x0400; +%constant int QF_BIT_QR = 0x8000; + +%inline %{ + enum enum_return_rcode { +   RCODE_NOERROR = 0, +   RCODE_FORMERR = 1, +   RCODE_SERVFAIL = 2, +   RCODE_NXDOMAIN = 3, +   RCODE_NOTIMPL = 4, +   RCODE_REFUSED = 5, +   RCODE_YXDOMAIN = 6, +   RCODE_YXRRSET = 7, +   RCODE_NXRRSET = 8, +   RCODE_NOTAUTH = 9, +   RCODE_NOTZONE = 10 + }; +%} + +%pythoncode %{ +    class ExtState: +        def __init__(self, obj): self.obj = obj +        def __str__(self): +            return ", ".join([_unboundmodule.strextstate(_unboundmodule._ext_state_get(self.obj,a)) for a in range(0, _unboundmodule.MODULE_COUNT)]) +        def __getitem__(self, index): return _unboundmodule._ext_state_get(self.obj, index) +        def __setitem__(self, index, value): _unboundmodule._ext_state_set(self.obj, index, value) +        def __len__(self): return _unboundmodule.MODULE_COUNT +%} + +%inline %{ +   enum module_ext_state _ext_state_get(struct module_qstate* q, int idx) { +     if ((q != NULL) && (idx >= 0) && (idx < MAX_MODULE)) { +        return q->ext_state[idx]; +     }  +     return 0; +   } +   +   void _ext_state_set(struct module_qstate* q, int idx, enum module_ext_state state) { +     if ((q != NULL) && (idx >= 0) && (idx < MAX_MODULE)) { +        q->ext_state[idx] = state; +     }  +   } +%} + +%extend module_qstate { +   %pythoncode %{ +        def set_ext_state(self, id, state): +            """Sets the ext state""" +            _unboundmodule._ext_state_set(self, id, state) + +        def __ext_state_get(self): return ExtState(self) +        __swig_getmethods__["ext_state"] = __ext_state_get +        if _newclass:ext_state = _swig_property(__ext_state_get)#, __ext_state_set) +   %} +} + +/* ************************************************************************************ *  +   Structure config_strlist + * ************************************************************************************ */ +struct config_strlist { +   struct config_strlist* next; +   char* str; +}; + +/* ************************************************************************************ *  +   Structure config_str2list + * ************************************************************************************ */ +struct config_str2list { +   struct config_str2list* next; +   char* str; +   char* str2; +}; + +/* ************************************************************************************ *  +   Structure config_file + * ************************************************************************************ */ +struct config_file { +   int verbosity; +   int stat_interval; +   int stat_cumulative; +   int stat_extended; +   int num_threads; +   int port; +   int do_ip4; +   int do_ip6; +   int do_udp; +   int do_tcp; +   int outgoing_num_ports; +   size_t outgoing_num_tcp; +   size_t incoming_num_tcp; +   int* outgoing_avail_ports; +   size_t msg_buffer_size; +   size_t msg_cache_size; +   size_t msg_cache_slabs; +   size_t num_queries_per_thread; +   size_t jostle_time; +   size_t rrset_cache_size; +   size_t rrset_cache_slabs; +   int host_ttl; +   size_t infra_cache_slabs; +   size_t infra_cache_numhosts; +   char* target_fetch_policy; +   int if_automatic; +   int num_ifs; +   char **ifs; +   int num_out_ifs; +   char **out_ifs; +   struct config_strlist* root_hints; +   struct config_stub* stubs; +   struct config_stub* forwards; +   struct config_strlist* donotqueryaddrs; +   struct config_str2list* acls; +   int donotquery_localhost; +   int harden_short_bufsize; +   int harden_large_queries; +   int harden_glue; +   int harden_dnssec_stripped; +   int harden_referral_path; +   int use_caps_bits_for_id; +   struct config_strlist* private_address; +   struct config_strlist* private_domain; +   size_t unwanted_threshold; +   char* chrootdir; +   char* username; +   char* directory; +   char* logfile; +   char* pidfile; +   int use_syslog; +   int hide_identity; +   int hide_version; +   char* identity; +   char* version; +   char* module_conf; +   struct config_strlist* trust_anchor_file_list; +   struct config_strlist* trust_anchor_list; +   struct config_strlist* trusted_keys_file_list; +   char* dlv_anchor_file; +   struct config_strlist* dlv_anchor_list; +   int max_ttl; +   int32_t val_date_override; +   int bogus_ttl;  +   int val_clean_additional; +   int val_permissive_mode; +   char* val_nsec3_key_iterations; +   size_t key_cache_size; +   size_t key_cache_slabs; +   size_t neg_cache_size; +   struct config_str2list* local_zones; +   struct config_strlist* local_zones_nodefault; +   struct config_strlist* local_data; +   int remote_control_enable; +   struct config_strlist* control_ifs; +   int control_port; +   char* server_key_file; +   char* server_cert_file; +   char* control_key_file; +   char* control_cert_file; +   int do_daemonize; +   char* python_script; +}; + +/* ************************************************************************************ *  +   Enums + * ************************************************************************************ */ +%rename ("MODULE_STATE_INITIAL") "module_state_initial"; +%rename ("MODULE_WAIT_REPLY") "module_wait_reply"; +%rename ("MODULE_WAIT_MODULE") "module_wait_module"; +%rename ("MODULE_WAIT_SUBQUERY") "module_wait_subquery"; +%rename ("MODULE_ERROR") "module_error"; +%rename ("MODULE_FINISHED") "module_finished"; + +enum module_ext_state { +   module_state_initial = 0, +   module_wait_reply, +   module_wait_module, +   module_wait_subquery, +   module_error, +   module_finished +}; + +%rename ("MODULE_EVENT_NEW") "module_event_new"; +%rename ("MODULE_EVENT_PASS") "module_event_pass"; +%rename ("MODULE_EVENT_REPLY") "module_event_reply"; +%rename ("MODULE_EVENT_NOREPLY") "module_event_noreply"; +%rename ("MODULE_EVENT_CAPSFAIL") "module_event_capsfail"; +%rename ("MODULE_EVENT_MODDONE") "module_event_moddone"; +%rename ("MODULE_EVENT_ERROR") "module_event_error"; + +enum module_ev { +   module_event_new = 0, +   module_event_pass, +   module_event_reply, +   module_event_noreply, +   module_event_capsfail, +   module_event_moddone, +   module_event_error +}; + +enum sec_status { +   sec_status_unchecked = 0, +   sec_status_bogus, +   sec_status_indeterminate, +   sec_status_insecure, +   sec_status_secure +}; + +enum verbosity_value { +   NO_VERBOSE = 0, +   VERB_OPS, +   VERB_DETAIL, +   VERB_QUERY, +   VERB_ALGO +}; + +%{ +int checkList(PyObject *l)  +{ +    PyObject* item; +    int i; + +    if (l == Py_None)  +       return 1; + +    if (PyList_Check(l))  +    { +       for (i=0; i < PyList_Size(l); i++)  +       { +           item = PyList_GetItem(l, i); +           if (!PyString_Check(item)) +              return 0; +       } +       return 1; +    } + +    return 0; +} + +ldns_rr_list* createRRList(PyObject *l, uint32_t default_ttl)  +{ +    PyObject* item; +    ldns_status status; +    ldns_rr_list* rr_list; +    ldns_rr* rr; +    int i; + +    if (PyList_Size(l) == 0) +       return NULL; + +    rr_list = ldns_rr_list_new(); + +    for (i=0; i < PyList_Size(l); i++)  +    { +        item = PyList_GetItem(l, i); + +        status = ldns_rr_new_frm_str(&rr, PyString_AsString(item), default_ttl, 0, 0); +        if (status != LDNS_STATUS_OK)  +            continue; + +        if (!ldns_rr_list_push_rr(rr_list, rr))  +            continue; + +    } +    return rr_list; +} + +int set_return_msg(struct module_qstate* qstate,  +                   const char* rr_name, ldns_rr_type rr_type, ldns_rr_class rr_class , uint16_t flags, uint32_t default_ttl, +                   PyObject* question, PyObject* answer, PyObject* authority, PyObject* additional)  +{ +     ldns_pkt* pkt = 0; +     ldns_status status; +     ldns_rr_list* rr_list = 0; +     ldns_buffer *qb = 0; +     int res = 1; +      +     if ((!checkList(question)) || (!checkList(answer)) || (!checkList(authority)) || (!checkList(additional))) +        return 0; + +     status = ldns_pkt_query_new_frm_str(&pkt, rr_name, rr_type, rr_class, flags); +     if ((status != LDNS_STATUS_OK) || (pkt == 0)) +        return 0; + +     rr_list = createRRList(question, default_ttl); +     if ((rr_list) && (res)) res = ldns_pkt_push_rr_list(pkt, LDNS_SECTION_QUESTION, rr_list); +     ldns_rr_list_free(rr_list); +     rr_list = createRRList(answer, default_ttl); +     if ((rr_list) && (res)) res = ldns_pkt_push_rr_list(pkt, LDNS_SECTION_ANSWER, rr_list); +     ldns_rr_list_free(rr_list); +     rr_list = createRRList(authority, default_ttl); +     if ((rr_list) && (res)) res = ldns_pkt_push_rr_list(pkt, LDNS_SECTION_AUTHORITY, rr_list); +     ldns_rr_list_free(rr_list); +     rr_list = createRRList(additional, default_ttl); +     if ((rr_list) && (res)) res = ldns_pkt_push_rr_list(pkt, LDNS_SECTION_ADDITIONAL, rr_list); +     ldns_rr_list_free(rr_list); + +     if ((res) && ((qb = ldns_buffer_new(LDNS_MIN_BUFLEN)) == 0)) res = 0; +     if ((res) && (ldns_pkt2buffer_wire(qb, pkt) != LDNS_STATUS_OK)) res = 0; + +     if (res) res = createResponse(qstate, qb); + +     if (qb) ldns_buffer_free(qb); + +     ldns_pkt_free(pkt); //this function dealocates pkt as well as rrs +     return res; +} +%} + +%constant uint16_t PKT_QR = 1;      /* QueRy - query flag */ +%constant uint16_t PKT_AA = 2;      /* Authoritative Answer - server flag */ +%constant uint16_t PKT_TC = 4;      /* TrunCated - server flag */ +%constant uint16_t PKT_RD = 8;      /* Recursion Desired - query flag */ +%constant uint16_t PKT_CD = 16;     /* Checking Disabled - query flag */ +%constant uint16_t PKT_RA = 32;     /* Recursion Available - server flag */ +%constant uint16_t PKT_AD = 64;     /* Authenticated Data - server flag */ + +int set_return_msg(struct module_qstate* qstate,  +                   const char* rr_name, int rr_type, int rr_class , uint16_t flags, uint32_t default_ttl, +                   PyObject* question, PyObject* answer, PyObject* authority, PyObject* additional); + +%pythoncode %{ +    class DNSMessage: +        def __init__(self, rr_name, rr_type, rr_class = RR_CLASS_IN, query_flags = 0, default_ttl = 0): +            """Query flags is a combination of PKT_xx contants""" +            self.rr_name = rr_name +            self.rr_type = rr_type +            self.rr_class = rr_class +            self.default_ttl = default_ttl +            self.query_flags = query_flags +            self.question = [] +            self.answer = [] +            self.authority = [] +            self.additional = [] + +        def set_return_msg(self, qstate): +            """Returns 1 if OK""" +            status = _unboundmodule.set_return_msg(qstate, self.rr_name, self.rr_type, self.rr_class,  +                                           self.query_flags, self.default_ttl, +                                           self.question, self.answer, self.authority, self.additional) + +            if (status) and (PKT_AA & self.query_flags): +                qstate.return_msg.rep.authoritative = 1 + +            return status  + +%} +/* ************************************************************************************ *  +   Functions + * ************************************************************************************ */ + +// Various debuging functions +void verbose(enum verbosity_value level, const char* format, ...); +void log_info(const char* format, ...); +void log_err(const char* format, ...); +void log_warn(const char* format, ...); +void log_hex(const char* msg, void* data, size_t length); +void log_dns_msg(const char* str, struct query_info* qinfo, struct reply_info* rep); +void log_query_info(enum verbosity_value v, const char* str, struct query_info* qinf); +void regional_log_stats(struct regional *r); + +// Free allocated memory from marked sources returning corresponding types +%typemap(newfree, noblock = 1) char * { +  free($1); +} + +// Mark as source returning newly allocated memory +%newobject ldns_rr_type2str; +%newobject ldns_rr_class2str; + +// LDNS functions +char *ldns_rr_type2str(const uint16_t atype); +char *ldns_rr_class2str(const uint16_t aclass); + +// Functions from pythonmod_utils +int storeQueryInCache(struct module_qstate* qstate, struct query_info* qinfo, struct reply_info* msgrep, int is_referral); +void invalidateQueryInCache(struct module_qstate* qstate, struct query_info* qinfo); + +// Module conversion functions +const char* strextstate(enum module_ext_state s); +const char* strmodulevent(enum module_ev e); + diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c new file mode 100644 index 000000000000..9860d001d0e2 --- /dev/null +++ b/pythonmod/pythonmod.c @@ -0,0 +1,383 @@ +/* + * pythonmod.c: unbound module C wrapper + *  + * Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) + *                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + * + * This software is open source. + *  + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *  + *    * Redistributions of source code must retain the above copyright notice, + *      this list of conditions and the following disclaimer. + *  + *    * Redistributions in binary form must reproduce the above copyright notice, + *      this list of conditions and the following disclaimer in the documentation + *      and/or other materials provided with the distribution. + *  + *    * Neither the name of the organization nor the names of its + *      contributors may be used to endorse or promote products derived from this + *      software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file + * Python module for unbound.  Calls python script. + */ + +/* ignore the varargs unused warning from SWIGs internal vararg support */ +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include "config.h" +#include <ldns/ldns.h> + +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#include <Python.h> + +#include "pythonmod/pythonmod.h" +#include "util/module.h" +#include "util/config_file.h" +#include "pythonmod_utils.h" + +#ifdef S_SPLINT_S +typedef struct PyObject PyObject; +typedef struct PyThreadState PyThreadState; +typedef void* PyGILState_STATE; +#endif + +/** + * Global state for the module.  + */ +struct pythonmod_env { + +	/** Python script filename. */ +	const char* fname; + +	/** Python main thread */ +	PyThreadState* mainthr; +	/** Python module. */ +	PyObject* module; + +	/** Module init function */ +	PyObject* func_init; +	/** Module deinit function */ +	PyObject* func_deinit; +	/** Module operate function */ +	PyObject* func_operate; +	/** Module super_inform function */ +	PyObject* func_inform; + +	/** Python dictionary. */ +	PyObject* dict; + +	/** Module data. */ +	PyObject* data; + +	/** Module qstate. */ +	struct module_qstate* qstate; +}; + +/** + * Per query state for the iterator module. + */ +struct pythonmod_qstate { + +	/** Module per query data. */ +	PyObject* data; +}; + +/* Generated */ +#ifndef S_SPLINT_S +#include "pythonmod/interface.h" +#endif + +int pythonmod_init(struct module_env* env, int id) +{ +   /* Initialize module */ +   FILE* script_py = NULL; +   PyObject* py_cfg, *res; +   PyGILState_STATE gil; +   struct pythonmod_env* pe = (struct pythonmod_env*)calloc(1, sizeof(struct pythonmod_env)); +   if (!pe)  +   { +      log_err("pythonmod: malloc failure"); +      return 0; +   } + +   env->modinfo[id] = (void*) pe; + +   /* Initialize module */ +   pe->fname = env->cfg->python_script; +   if(pe->fname==NULL || pe->fname[0]==0) { +      log_err("pythonmod: no script given."); +      return 0; +   } + +   /* Initialize Python libraries */ +   if (!Py_IsInitialized())  +   { +      Py_SetProgramName("unbound"); +      Py_NoSiteFlag = 1; +      Py_Initialize(); +      PyEval_InitThreads(); +      SWIG_init(); +      pe->mainthr = PyEval_SaveThread(); +   } + +   gil = PyGILState_Ensure(); + +   /* Initialize Python */ +   PyRun_SimpleString("import sys \n"); +   PyRun_SimpleString("sys.path.append('.') \n"); +   if(env->cfg->directory && env->cfg->directory[0]) { +   	char wdir[1524]; +   	snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n", +		env->cfg->directory); +   	PyRun_SimpleString(wdir); +   } +   PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n"); +   PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n"); +   PyRun_SimpleString("import distutils.sysconfig \n"); +   PyRun_SimpleString("sys.path.append(distutils.sysconfig.get_python_lib(1,0)) \n"); +   if (PyRun_SimpleString("from unboundmodule import *\n") < 0) +   { +      log_err("pythonmod: cannot initialize core module: unboundmodule.py");  +      PyGILState_Release(gil); +      return 0; +   } + +   /* Check Python file load */ +   if ((script_py = fopen(pe->fname, "r")) == NULL)  +   { +      log_err("pythonmod: can't open file %s for reading", pe->fname); +      PyGILState_Release(gil); +      return 0; +   } + +   /* Load file */ +   pe->module = PyImport_AddModule("__main__"); +   pe->dict = PyModule_GetDict(pe->module); +   pe->data = Py_None; +   Py_INCREF(pe->data); +   PyModule_AddObject(pe->module, "mod_env", pe->data); + +   /* TODO: deallocation of pe->... if an error occurs */ +   +   if (PyRun_SimpleFile(script_py, pe->fname) < 0)  +   { +      log_err("pythonmod: can't parse Python script %s", pe->fname); +      PyGILState_Release(gil); +      return 0; +   } + +   fclose(script_py); + +   if ((pe->func_init = PyDict_GetItemString(pe->dict, "init")) == NULL)  +   { +      log_err("pythonmod: function init is missing in %s", pe->fname); +      PyGILState_Release(gil); +      return 0; +   } +   if ((pe->func_deinit = PyDict_GetItemString(pe->dict, "deinit")) == NULL)  +   { +      log_err("pythonmod: function deinit is missing in %s", pe->fname); +      PyGILState_Release(gil); +      return 0; +   } +   if ((pe->func_operate = PyDict_GetItemString(pe->dict, "operate")) == NULL)  +   { +      log_err("pythonmod: function operate is missing in %s", pe->fname); +      PyGILState_Release(gil); +      return 0; +   } +   if ((pe->func_inform = PyDict_GetItemString(pe->dict, "inform_super")) == NULL)  +   { +      log_err("pythonmod: function inform_super is missing in %s", pe->fname); +      PyGILState_Release(gil); +      return 0; +   } + +   py_cfg = SWIG_NewPointerObj((void*) env->cfg, SWIGTYPE_p_config_file, 0); +   res = PyObject_CallFunction(pe->func_init, "iO", id, py_cfg); +   if (PyErr_Occurred())  +   { +      log_err("pythonmod: Exception occurred in function init"); +      PyErr_Print(); +   } + +   Py_XDECREF(res); +   Py_XDECREF(py_cfg); +   PyGILState_Release(gil); + +   return 1; +} + +void pythonmod_deinit(struct module_env* env, int id) +{ +   struct pythonmod_env* pe = env->modinfo[id]; +   if(pe == NULL) +      return; + +   /* Free Python resources */ +   if(pe->module != NULL) +   { +      PyObject* res; +      PyGILState_STATE gil = PyGILState_Ensure(); + +      /* Deinit module */ +      res = PyObject_CallFunction(pe->func_deinit, "i", id); +      if (PyErr_Occurred()) { +         log_err("pythonmod: Exception occurred in function deinit"); +         PyErr_Print(); +      } +      /* Free result if any */ +      Py_XDECREF(res); +      /* Free shared data if any */ +      Py_XDECREF(pe->data); +      PyGILState_Release(gil); + +      PyEval_RestoreThread(pe->mainthr); +      Py_Finalize(); +      pe->mainthr = NULL; +   } +   pe->fname = NULL; +   free(pe); + +   /* Module is deallocated in Python */ +   env->modinfo[id] = NULL; +} + +void pythonmod_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super) +{ +   struct pythonmod_env* pe = (struct pythonmod_env*)qstate->env->modinfo[id]; +   struct pythonmod_qstate* pq = (struct pythonmod_qstate*)qstate->minfo[id]; +   PyObject* py_qstate, *py_sqstate, *res; +   PyGILState_STATE gil = PyGILState_Ensure(); + +   log_query_info(VERB_ALGO, "pythonmod: inform_super, sub is", &qstate->qinfo); +   log_query_info(VERB_ALGO, "super is", &super->qinfo); + +   py_qstate = SWIG_NewPointerObj((void*) qstate, SWIGTYPE_p_module_qstate, 0); +   py_sqstate = SWIG_NewPointerObj((void*) super, SWIGTYPE_p_module_qstate, 0); + +   res = PyObject_CallFunction(pe->func_inform, "iOOO", id, py_qstate,  +	py_sqstate, pq->data); + +   if (PyErr_Occurred())  +   { +      log_err("pythonmod: Exception occurred in function inform_super"); +      PyErr_Print(); +      qstate->ext_state[id] = module_error; +   }  +   else if ((res == NULL)  || (!PyObject_IsTrue(res)))  +   { +      log_err("pythonmod: python returned bad code in inform_super"); +      qstate->ext_state[id] = module_error; +   }  + +   Py_XDECREF(res); +   Py_XDECREF(py_sqstate); +   Py_XDECREF(py_qstate); + +   PyGILState_Release(gil); +} + +void pythonmod_operate(struct module_qstate* qstate, enum module_ev event,  +	int id, struct outbound_entry* ATTR_UNUSED(outbound)) +{ +   struct pythonmod_env* pe = (struct pythonmod_env*)qstate->env->modinfo[id]; +   struct pythonmod_qstate* pq = (struct pythonmod_qstate*)qstate->minfo[id]; +   PyObject* py_qstate, *res; +   PyGILState_STATE gil = PyGILState_Ensure(); + +   if ( pq == NULL) +   {  +      /* create qstate */ +      pq = qstate->minfo[id] = malloc(sizeof(struct pythonmod_qstate)); +       +      /* Initialize per query data */ +      pq->data = Py_None; +      Py_INCREF(pq->data); +   } + +   /* Call operate */ +   py_qstate = SWIG_NewPointerObj((void*) qstate, SWIGTYPE_p_module_qstate, 0); +   res = PyObject_CallFunction(pe->func_operate, "iiOO", id, (int) event,  +	py_qstate, pq->data); +   if (PyErr_Occurred())  +   { +      log_err("pythonmod: Exception occurred in function operate, event: %s", strmodulevent(event)); +      PyErr_Print(); +      qstate->ext_state[id] = module_error; +   }  +   else if ((res == NULL)  || (!PyObject_IsTrue(res)))  +   { +      log_err("pythonmod: python returned bad code, event: %s", strmodulevent(event)); +      qstate->ext_state[id] = module_error; +   }  +   Py_XDECREF(res); +   Py_XDECREF(py_qstate); + +   PyGILState_Release(gil); +} + +void pythonmod_clear(struct module_qstate* qstate, int id) +{ +   struct pythonmod_qstate* pq; +   if (qstate == NULL) +      return; + +   pq = (struct pythonmod_qstate*)qstate->minfo[id]; +   verbose(VERB_ALGO, "pythonmod: clear, id: %d, pq:%lX", id,  +   	(unsigned long int)pq); +   if(pq != NULL) +   { +      PyGILState_STATE gil = PyGILState_Ensure(); +      Py_DECREF(pq->data); +      PyGILState_Release(gil); +      /* Free qstate */ +      free(pq); +   } + +   qstate->minfo[id] = NULL; +} + +size_t pythonmod_get_mem(struct module_env* env, int id) +{ +   struct pythonmod_env* pe = (struct pythonmod_env*)env->modinfo[id]; +   verbose(VERB_ALGO, "pythonmod: get_mem, id: %d, pe:%lX", id,  +   	(unsigned long int)pe); +   if(!pe) +      return 0; +   return sizeof(*pe); +} + +/** + * The module function block  + */ +static struct module_func_block pythonmod_block = { +   "python", +   &pythonmod_init, &pythonmod_deinit, &pythonmod_operate, &pythonmod_inform_super,  +   &pythonmod_clear, &pythonmod_get_mem +}; + +struct module_func_block* pythonmod_get_funcblock(void) +{ +   return &pythonmod_block; +} diff --git a/pythonmod/pythonmod.h b/pythonmod/pythonmod.h new file mode 100644 index 000000000000..b108cf9234c1 --- /dev/null +++ b/pythonmod/pythonmod.h @@ -0,0 +1,68 @@ +/* + * pythonmod.h: module header file + *  + * Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) + *                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + * + * This software is open source. + *  + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *  + *    * Redistributions of source code must retain the above copyright notice, + *      this list of conditions and the following disclaimer. + *  + *    * Redistributions in binary form must reproduce the above copyright notice, + *      this list of conditions and the following disclaimer in the documentation + *      and/or other materials provided with the distribution. + *  + *    * Neither the name of the organization nor the names of its + *      contributors may be used to endorse or promote products derived from this + *      software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file + * Python module for unbound.  Calls python script. + */ +#ifndef PYTHONMOD_H +#define PYTHONMOD_H +#include "util/module.h" +#include "services/outbound_list.h" + +/** + * Get the module function block. + * @return: function block with function pointers to module methods. + */ +struct module_func_block* pythonmod_get_funcblock(void); + +/** python module init */ +int pythonmod_init(struct module_env* env, int id); + +/** python module deinit */ +void pythonmod_deinit(struct module_env* env, int id); + +/** python module operate on a query */ +void pythonmod_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound); + +/** python module  */ +void pythonmod_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super); + +/** python module cleanup query state */ +void pythonmod_clear(struct module_qstate* qstate, int id); + +/** python module alloc size routine */ +size_t pythonmod_get_mem(struct module_env* env, int id); +#endif /* PYTHONMOD_H */ diff --git a/pythonmod/pythonmod_utils.c b/pythonmod/pythonmod_utils.c new file mode 100644 index 000000000000..b25acd3a8904 --- /dev/null +++ b/pythonmod/pythonmod_utils.c @@ -0,0 +1,177 @@ +/* + * pythonmod_utils.c: utilities used by wrapper + * + * Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) + *                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + * + * This software is open source. + *  + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *  + *    * Redistributions of source code must retain the above copyright notice, + *      this list of conditions and the following disclaimer. + *  + *    * Redistributions in binary form must reproduce the above copyright notice, + *      this list of conditions and the following disclaimer in the documentation + *      and/or other materials provided with the distribution. + *  + *    * Neither the name of the organization nor the names of its + *      contributors may be used to endorse or promote products derived from this + *      software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file + * Utility functions for the python module that perform stores and loads and + * conversions. + */ +#include "config.h" +#include "util/module.h" +#include "util/netevent.h" +#include "util/net_help.h" +#include "services/cache/dns.h" +#include "services/cache/rrset.h" +#include "util/data/msgparse.h" +#include "util/data/msgreply.h" +#include "util/storage/slabhash.h" +#include "util/regional.h" + +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#include <Python.h> + +/* Store the reply_info and query_info pair in message cache (qstate->msg_cache) */ +int storeQueryInCache(struct module_qstate* qstate, struct query_info* qinfo, struct reply_info* msgrep, int is_referral) +{ +    if (!msgrep)  +       return 0; + +    if (msgrep->authoritative) /*authoritative answer can't be stored in cache*/ +    { +       PyErr_SetString(PyExc_ValueError, "Authoritative answer can't be stored"); +       return 0; +    } + +    return dns_cache_store(qstate->env, qinfo, msgrep, is_referral,  +	qstate->prefetch_leeway, 0, NULL); +} + +/*  Invalidate the message associated with query_info stored in message cache */ +void invalidateQueryInCache(struct module_qstate* qstate, struct query_info* qinfo) +{  +    hashvalue_t h; +    struct lruhash_entry* e; +    struct reply_info *r; +    size_t i, j; + +    h = query_info_hash(qinfo); +    if ((e=slabhash_lookup(qstate->env->msg_cache, h, qinfo, 0)))  +    { +	r = (struct reply_info*)(e->data); +	if (r)  +	{ +	   r->ttl = 0; +	   if(rrset_array_lock(r->ref, r->rrset_count, *qstate->env->now)) { +		   for(i=0; i< r->rrset_count; i++)  +		   { +		       struct packed_rrset_data* data =  +		       	(struct packed_rrset_data*) r->ref[i].key->entry.data; +		       if(i>0 && r->ref[i].key == r->ref[i-1].key) +			   continue; +	       +		       data->ttl = r->ttl; +		       for(j=0; j<data->count + data->rrsig_count; j++) +			   data->rr_ttl[j] = r->ttl; +		   } +		   rrset_array_unlock(r->ref, r->rrset_count); +	   } +	} +	lock_rw_unlock(&e->lock); +    } else { +	log_info("invalidateQueryInCache: qinfo is not in cache"); +    } +} + +/* Create response according to the ldns packet content */ +int createResponse(struct module_qstate* qstate, ldns_buffer* pkt) +{ +    struct msg_parse* prs; +    struct edns_data edns; +     +    /* parse message */ +    prs = (struct msg_parse*) regional_alloc(qstate->env->scratch, sizeof(struct msg_parse)); +    if (!prs) { +	log_err("storeResponse: out of memory on incoming message"); +	return 0; +    } + +    memset(prs, 0, sizeof(*prs)); +    memset(&edns, 0, sizeof(edns)); + +    ldns_buffer_set_position(pkt, 0); +    if (parse_packet(pkt, prs, qstate->env->scratch) != LDNS_RCODE_NOERROR) { +	verbose(VERB_ALGO, "storeResponse: parse error on reply packet"); +	return 0; +    } +    /* edns is not examined, but removed from message to help cache */ +    if(parse_extract_edns(prs, &edns) != LDNS_RCODE_NOERROR) +	return 0; + +    /* remove CD-bit, we asked for in case we handle validation ourself */ +    prs->flags &= ~BIT_CD; + +    /* allocate response dns_msg in region */ +    qstate->return_msg = (struct dns_msg*)regional_alloc(qstate->region, sizeof(struct dns_msg)); +    if (!qstate->return_msg) +       return 0; + +    memset(qstate->return_msg, 0, sizeof(*qstate->return_msg)); +    if(!parse_create_msg(pkt, prs, NULL, &(qstate->return_msg)->qinfo, &(qstate->return_msg)->rep, qstate->region)) { +	log_err("storeResponse: malloc failure: allocating incoming dns_msg"); +	return 0; +    } +     +    /* Make sure that the RA flag is set (since the presence of  +     * this module means that recursion is available) */ +    /* qstate->return_msg->rep->flags |= BIT_RA; */ + +    /* Clear the AA flag */ +    /* FIXME: does this action go here or in some other module? */ +    /*qstate->return_msg->rep->flags &= ~BIT_AA; */ + +    /* make sure QR flag is on */ +    /*qstate->return_msg->rep->flags |= BIT_QR; */ + +    if(verbosity >= VERB_ALGO) +	log_dns_msg("storeResponse: packet:", &qstate->return_msg->qinfo, qstate->return_msg->rep); + +    return 1; +} + + +/* Convert reply->addr to string */ +void reply_addr2str(struct comm_reply* reply, char* dest, int maxlen) +{ +	int af = (int)((struct sockaddr_in*) &(reply->addr))->sin_family; +	void* sinaddr = &((struct sockaddr_in*) &(reply->addr))->sin_addr; + +	if(af == AF_INET6) +		sinaddr = &((struct sockaddr_in6*)&(reply->addr))->sin6_addr; +	dest[0] = 0; +	if (inet_ntop(af, sinaddr, dest, (socklen_t)maxlen) == 0) +	   return; +	dest[maxlen-1] = 0; +} diff --git a/pythonmod/pythonmod_utils.h b/pythonmod/pythonmod_utils.h new file mode 100644 index 000000000000..a1641d30858e --- /dev/null +++ b/pythonmod/pythonmod_utils.h @@ -0,0 +1,89 @@ +/* + * pythonmod_utils.h: utils header file + *  + * Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) + *                     Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + * + * This software is open source. + *  + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *  + *    * Redistributions of source code must retain the above copyright notice, + *      this list of conditions and the following disclaimer. + *  + *    * Redistributions in binary form must reproduce the above copyright notice, + *      this list of conditions and the following disclaimer in the documentation + *      and/or other materials provided with the distribution. + *  + *    * Neither the name of the organization nor the names of its + *      contributors may be used to endorse or promote products derived from this + *      software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file + * Utility functions for the python module that perform stores and loads and + * conversions. + */ +#ifndef PYTHONMOD_UTILS_H +#define PYTHONMOD_UTILS_H + +#include "util/module.h" + +/** + *  Store the reply_info and query_info pair in message cache (qstate->msg_cache) + * + * @param qstate: module environment + * @param qinfo: query info, the query for which answer is stored. + * @param msgrep: reply in dns_msg + * @param is_referral: If true, then the given message to be stored is a + *      referral. The cache implementation may use this as a hint. + *      It will store only the RRsets, not the message. + * @return 0 on alloc error (out of memory). + */ +int storeQueryInCache(struct module_qstate* qstate, struct query_info* qinfo, struct reply_info* msgrep, int is_referral); + + +/** + *  Invalidate the message associated with query_info stored in message cache. + * + *  This function invalidates the record in message cache associated with the given query only if such a record exists. + * + * @param qstate: module environment + * @param qinfo: query info, the query for which answer is stored. + */ +void invalidateQueryInCache(struct module_qstate* qstate, struct query_info* qinfo); + +/** + *  Create response according to the ldns packet content + * + *  This function fills qstate.return_msg up with data of a given packet + *  + * @param qstate: module environment + * @param pkt: a ldns_buffer which contains ldns_packet data + * @return 0 on failure, out of memory or parse error. + */ +int createResponse(struct module_qstate* qstate, ldns_buffer* pkt); + +/** + *  Convert reply->addr to string + *  @param reply: comm reply with address in it. + *  @param dest: destination string. + *  @param maxlen: length of string buffer. + */ +void reply_addr2str(struct comm_reply* reply, char* dest, int maxlen); + +#endif /* PYTHONMOD_UTILS_H */ diff --git a/pythonmod/test-calc.conf b/pythonmod/test-calc.conf new file mode 100644 index 000000000000..ef854ce1bf6e --- /dev/null +++ b/pythonmod/test-calc.conf @@ -0,0 +1,18 @@ +# Example configuration file for calc.py +server: +	verbosity: 1 +	interface: 0.0.0.0 +	do-daemonize: no +	access-control: 0.0.0.0/0 allow +	chroot: "" +	username: "" +	directory: "" +	logfile: "" +	pidfile: "unbound.pid" +	module-config: "validator python iterator" + +# Python config section +python: +	# Script file to load +	python-script: "./examples/calc.py" + diff --git a/pythonmod/test-dict.conf b/pythonmod/test-dict.conf new file mode 100644 index 000000000000..daab7250e5db --- /dev/null +++ b/pythonmod/test-dict.conf @@ -0,0 +1,18 @@ +# Example configuration file for dict.py +server: +	verbosity: 1 +	interface: 0.0.0.0 +	do-daemonize: no +	access-control: 0.0.0.0/0 allow +	chroot: "" +	username: "" +	directory: "" +	logfile: "" +	pidfile: "unbound.pid" +	module-config: "validator python iterator" + +# Python config section +python: +	# Script file to load +	python-script: "./examples/dict.py" + diff --git a/pythonmod/test-log.conf b/pythonmod/test-log.conf new file mode 100644 index 000000000000..02214ba1d63a --- /dev/null +++ b/pythonmod/test-log.conf @@ -0,0 +1,17 @@ +# Example configuration file for log.py +server: +	verbosity: 1 +	interface: 0.0.0.0 +	do-daemonize: no +	access-control: 0.0.0.0/0 allow +	chroot: "" +	username: "" +	directory: "" +	logfile: "" +	pidfile: "unbound.pid" +	module-config: "validator python iterator" + +# Python config section +python: +	# Script file to load +	python-script: "./examples/log.py" diff --git a/pythonmod/test-resgen.conf b/pythonmod/test-resgen.conf new file mode 100644 index 000000000000..a0a79e42dfe1 --- /dev/null +++ b/pythonmod/test-resgen.conf @@ -0,0 +1,18 @@ +# Example configuration file for resgen.py +server: +	verbosity: 1 +	interface: 0.0.0.0 +	do-daemonize: no +	access-control: 0.0.0.0/0 allow +	chroot: "" +	username: "" +	directory: "" +	logfile: "" +	pidfile: "unbound.pid" +	module-config: "validator python iterator" + +# Python config section +python: +	# Script file to load +	python-script: "./examples/resgen.py" + diff --git a/pythonmod/test-resip.conf b/pythonmod/test-resip.conf new file mode 100644 index 000000000000..7620f736fe3f --- /dev/null +++ b/pythonmod/test-resip.conf @@ -0,0 +1,18 @@ +# Example configuration file for resip.py +server: +	verbosity: 1 +	#interface: 0.0.0.0 +	do-daemonize: no +	#access-control: 0.0.0.0/0 allow +	chroot: "" +	username: "" +	directory: "" +	logfile: "" +	pidfile: "unbound.pid" +	module-config: "validator python iterator" + +# Python config section +python: +	# Script file to load +	python-script: "./examples/resip.py" + diff --git a/pythonmod/test-resmod.conf b/pythonmod/test-resmod.conf new file mode 100644 index 000000000000..8de5fd257fac --- /dev/null +++ b/pythonmod/test-resmod.conf @@ -0,0 +1,19 @@ +# Example configuration file for resmod.py +server: +	verbosity: 1 +	interface: 0.0.0.0 +	do-daemonize: no +	access-control: 0.0.0.0/0 allow +	chroot: "" +	username: "" +	directory: "" +	logfile: "" +	pidfile: "unbound.pid" +	#module-config: "python iterator" +	module-config: "validator python iterator" + +# Python config section +python: +	# Script file to load +	python-script: "./examples/resmod.py" + diff --git a/pythonmod/ubmodule-msg.py b/pythonmod/ubmodule-msg.py new file mode 100644 index 000000000000..648368080c07 --- /dev/null +++ b/pythonmod/ubmodule-msg.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +''' + ubmodule-msg.py: simple response packet logger + + Authors: Zdenek Vasicek (vasicek AT fit.vutbr.cz) +          Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + Copyright (c) 2008. All rights reserved. + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +  + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +  + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' +import os + +def init(id, cfg): +    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script)) +    return True + +def deinit(id): +    log_info("pythonmod: deinit called, module id is %d" % id) +    return True + +def inform_super(id, qstate, superqstate, qdata): +    return True + +def setTTL(qstate, ttl): +    """Sets return_msg TTL and all the RRs TTL""" +    if qstate.return_msg: +        qstate.return_msg.rep.ttl = ttl +        if (qstate.return_msg.rep): +            for i in range(0,qstate.return_msg.rep.rrset_count): +                d = qstate.return_msg.rep.rrsets[i].entry.data +                for j in range(0,d.count+d.rrsig_count): +                    d.rr_ttl[j] = ttl + +def dataHex(data, prefix=""): +    res = "" +    for i in range(0, (len(data)+15)/16): +        res += "%s0x%02X | " % (prefix, i*16) +        d = map(lambda x:ord(x), data[i*16:i*16+17]) +        for ch in d: +            res += "%02X " % ch +        for i in range(0,17-len(d)): +            res += "   " +        res += "| " +        for ch in d: +            if (ch < 32) or (ch > 127): +                res += ". " +            else: +                res += "%c " % ch +        res += "\n" +    return res + +def printReturnMsg(qstate): +    print "Return MSG rep   :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount,qstate.return_msg.rep.security, qstate.return_msg.rep.ttl) +    print "           qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str +    if (qstate.return_msg.rep): +        print "RRSets:",qstate.return_msg.rep.rrset_count +        prevkey = None +        for i in range(0,qstate.return_msg.rep.rrset_count): +            r = qstate.return_msg.rep.rrsets[i] +            rk = r.rk +            print i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags, +            print "type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class) + +            d = r.entry.data +            print "    RRDatas:",d.count+d.rrsig_count +            for j in range(0,d.count+d.rrsig_count): +                print "    ",j,":","TTL=",d.rr_ttl[j],"RR data:" +                print dataHex(d.rr_data[j],"         ") + + +def operate(id, event, qstate, qdata): +    log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) +    #print "pythonmod: per query data", qdata + +    print "Query:", ''.join(map(lambda x:chr(max(32,ord(x))),qstate.qinfo.qname)), qstate.qinfo.qname_list, qstate.qinfo.qname_str, +    print "Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype, +    print "Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass +    print + +    #if event == MODULE_EVENT_PASS: #pokud mame "validator python iterator" +    if (event == MODULE_EVENT_NEW) and (qstate.qinfo.qname_str.endswith(".seznam.cz.")): #pokud mame "python validator iterator" +        print qstate.qinfo.qname_str + +        qstate.ext_state[id] = MODULE_FINISHED  + +        msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_A, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA) #, 300) +        #msg.authority.append("xxx.seznam.cz. 10 IN A 192.168.1.1") +        #msg.additional.append("yyy.seznam.cz. 10 IN A 1.1.1.2.") + +        if qstate.qinfo.qtype == RR_TYPE_A: +            msg.answer.append("%s 10 IN A 192.168.1.1" % qstate.qinfo.qname_str) +        if (qstate.qinfo.qtype == RR_TYPE_SRV) or (qstate.qinfo.qtype == RR_TYPE_ANY): +            msg.answer.append("%s 10 IN SRV 0 0 80 neinfo.example.com." % qstate.qinfo.qname_str) +        if (qstate.qinfo.qtype == RR_TYPE_TXT) or (qstate.qinfo.qtype == RR_TYPE_ANY): +            msg.answer.append("%s 10 IN TXT path=/" % qstate.qinfo.qname_str) + +        if not msg.set_return_msg(qstate): +            qstate.ext_state[id] = MODULE_ERROR  +            return True + +        #qstate.return_msg.rep.security = 2 #pokud nebude nasledovat validator, je zapotrebi nastavit security, aby nebyl paket zahozen v mesh_send_reply +        printReturnMsg(qstate) + +        #Authoritative result can't be stored in cache +        #if (not storeQueryInCache(qstate, qstate.return_msg.qinfo, qstate.return_msg.rep, 0)): +        #    print "Can't store in cache" +        #    qstate.ext_state[id] = MODULE_ERROR +        #    return False +        #print "Store OK" + +        qstate.return_rcode = RCODE_NOERROR +        return True + +    if event == MODULE_EVENT_NEW: +        qstate.ext_state[id] = MODULE_WAIT_MODULE  +        return True + +    if event == MODULE_EVENT_MODDONE: +        log_info("pythonmod: previous module done") +        qstate.ext_state[id] = MODULE_FINISHED  +        return True +       +    if event == MODULE_EVENT_PASS: +        log_info("pythonmod: event_pass") +        qstate.ext_state[id] = MODULE_WAIT_MODULE  +        return True + +    log_err("pythonmod: BAD event") +    qstate.ext_state[id] = MODULE_ERROR +    return True + +log_info("pythonmod: script loaded.") diff --git a/pythonmod/ubmodule-tst.py b/pythonmod/ubmodule-tst.py new file mode 100644 index 000000000000..0b9b5a9d2cfa --- /dev/null +++ b/pythonmod/ubmodule-tst.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +''' + ubmodule-tst.py:   + + Authors: Zdenek Vasicek (vasicek AT fit.vutbr.cz) +          Marek Vavrusa  (xvavru00 AT stud.fit.vutbr.cz) + + Copyright (c) 2008. All rights reserved. + + This software is open source. +  + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: +  + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +  + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +  + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +''' +def init(id, cfg): +    log_info("pythonmod: init called, module id is %d port: %d script: %s" % (id, cfg.port, cfg.python_script)) +    return True + +def deinit(id): +    log_info("pythonmod: deinit called, module id is %d" % id) +    return True + +def inform_super(id, qstate, superqstate, qdata): +    return True + +def setTTL(qstate, ttl): +    """Sets return_msg TTL and all the RRs TTL""" +    if qstate.return_msg: +        qstate.return_msg.rep.ttl = ttl +        if (qstate.return_msg.rep): +            for i in range(0,qstate.return_msg.rep.rrset_count): +                d = qstate.return_msg.rep.rrsets[i].entry.data +                for j in range(0,d.count+d.rrsig_count): +                    d.rr_ttl[j] = ttl + +def dataHex(data, prefix=""): +    res = "" +    for i in range(0, (len(data)+15)/16): +        res += "%s0x%02X | " % (prefix, i*16) +        d = map(lambda x:ord(x), data[i*16:i*16+17]) +        for ch in d: +            res += "%02X " % ch +        for i in range(0,17-len(d)): +            res += "   " +        res += "| " +        for ch in d: +            if (ch < 32) or (ch > 127): +                res += ". " +            else: +                res += "%c " % ch +        res += "\n" +    return res + +def printReturnMsg(qstate): +    print "Return MSG rep   :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount,qstate.return_msg.rep.security, qstate.return_msg.rep.ttl) +    print "           qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str +    if (qstate.return_msg.rep): +        print "RRSets:",qstate.return_msg.rep.rrset_count +        prevkey = None +        for i in range(0,qstate.return_msg.rep.rrset_count): +            r = qstate.return_msg.rep.rrsets[i] +            rk = r.rk +            print i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags, +            print "type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class) + +            d = r.entry.data +            print "    RRDatas:",d.count+d.rrsig_count +            for j in range(0,d.count+d.rrsig_count): +                print "    ",j,":","TTL=",d.rr_ttl[j],"RR data:" +                print dataHex(d.rr_data[j],"         ") + +def operate(id, event, qstate, qdata): +    log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) +    #print "pythonmod: per query data", qdata + +    print "Query:", ''.join(map(lambda x:chr(max(32,ord(x))),qstate.qinfo.qname)), qstate.qinfo.qname_list,  +    print "Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype, +    print "Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass +    print + +    # TEST: +    #   > dig @127.0.0.1 www.seznam.cz A +    #   > dig @127.0.0.1 3.76.75.77.in-addr.arpa. PTR +    #   prvni dva dotazy vrati TTL 100 +    #   > dig @127.0.0.1 www.seznam.cz A +    #   > dig @127.0.0.1 3.76.75.77.in-addr.arpa. PTR +    #   dalsi dva dotazy vrati TTL 10, ktere se bude s dalsimi dotazy snizovat, nez vyprsi a znovu se zaktivuje mesh +  +    if qstate.return_msg: +        printReturnMsg(qstate) + +        #qdn = '.'.join(qstate.qinfo.qname_list) +        qdn = qstate.qinfo.qname_str + +        #Pokud dotaz konci na nasledujici, pozmenime TTL zpravy, ktera se posle klientovi (return_msg) i zpravy v CACHE  +        if qdn.endswith(".seznam.cz.") or qdn.endswith('.in-addr.arpa.'): +            #pokud je v cache odpoved z iteratoru, pak ji zneplatnime, jinak se moduly nazavolaji do te doby, nez vyprsi TTL +            invalidateQueryInCache(qstate, qstate.return_msg.qinfo) + +            if (qstate.return_msg.rep.authoritative): +                print "X"*300 + +            setTTL(qstate, 10) #do cache nastavime TTL na 10 +            if not storeQueryInCache(qstate, qstate.return_msg.qinfo, qstate.return_msg.rep, 0): +                qstate.ext_state[id] = MODULE_ERROR +                return False + +            setTTL(qstate, 100) #odpoved klientovi prijde s TTL 100 +            qstate.return_rcode = RCODE_NOERROR + +    if event == MODULE_EVENT_NEW: +        qstate.ext_state[id] = MODULE_WAIT_MODULE  +        return True + +    if event == MODULE_EVENT_MODDONE: +        log_info("pythonmod: previous module done") +        qstate.ext_state[id] = MODULE_FINISHED  +        return True +       +    if event == MODULE_EVENT_PASS: +        log_info("pythonmod: event_pass") +        qstate.ext_state[id] = MODULE_WAIT_MODULE  +        return True + +    log_err("pythonmod: BAD event") +    qstate.ext_state[id] = MODULE_ERROR +    return True + +log_info("pythonmod: script loaded.") | 
