diff options
-rw-r--r-- | rpki/left_right.py | 414 | ||||
-rw-r--r-- | rpki/publication.py | 7 | ||||
-rw-r--r-- | rpki/publication_control.py | 7 | ||||
-rw-r--r-- | rpki/rootd.py | 4 | ||||
-rw-r--r-- | rpki/rpkic.py | 1 | ||||
-rw-r--r-- | rpki/rpkid.py | 3 | ||||
-rw-r--r-- | rpki/xml_utils.py | 357 |
7 files changed, 201 insertions, 592 deletions
diff --git a/rpki/left_right.py b/rpki/left_right.py index fff3404d..1b3eb581 100644 --- a/rpki/left_right.py +++ b/rpki/left_right.py @@ -28,7 +28,6 @@ import rpki.resource_set import rpki.x509 import rpki.sql import rpki.exceptions -import rpki.xml_utils import rpki.http import rpki.up_down import rpki.relaxng @@ -38,7 +37,7 @@ import rpki.publication import rpki.async import rpki.rpkid_tasks -from lxml.etree import Element, SubElement +from lxml.etree import Element, SubElement, tostring as ElementToString logger = logging.getLogger(__name__) @@ -76,40 +75,208 @@ tag_signing_cert_crl = xmlns + "signing_cert_crl" enforce_strict_up_down_xml_sender = False -class left_right_namespace(object): +class base_elt(rpki.sql.sql_persistent): """ - XML namespace parameters for left-right protocol. + Virtual class for persistent left-right protocol elements. + These classes are being phased out in favor of Django ORM models. """ xmlns = rpki.relaxng.left_right.xmlns nsmap = rpki.relaxng.left_right.nsmap -class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_namespace): - """ - Virtual class for top-level left-right protocol data elements. - """ - handles = () + attributes = () + elements = () + booleans = () + text_attribute = None self_id = None self_handle = None - @property - @rpki.sql.cache_reference - def self(self): + def __str__(self): + return ElementToString(self.toXML(), pretty_print = True, encoding = "us-ascii") + + @classmethod + def fromXML(cls, elt): + + self = cls() + + for key in self.attributes: + val = elt.get(key, None) + if val is not None: + val = val.encode("ascii") + if isinstance(self.attributes, dict) and self.attributes[key] is not None: + val = self.attributes[key](val) + elif val.isdigit() and not key.endswith("_handle"): + val = long(val) + setattr(self, key, val) + for key in self.booleans: + setattr(self, key, elt.get(key, False)) + + if self.text_attribute is not None: + setattr(self, self.text_attribute, elt.text) + + # In the long run, we probably want the key for that to include + # the namespace, but that would break the current .toXML() code, + # so kludge it for now. + + for b64 in elt: + assert b64.tag.startswith(self.xmlns) + ename = b64.tag[len(self.xmlns):] + etype = self.elements[ename] + setattr(self, ename, etype(Base64 = b64.text)) + + return self + + def toXML(self): + """ + Default element generator for SQL-based objects. This assumes + that sub-elements are Base64-encoded DER objects. + """ + + elt = Element(self.xmlns + self.element_name, nsmap = self.nsmap) + for key in self.attributes: + val = getattr(self, key, None) + if val is not None: + elt.set(key, str(val)) + for key in self.booleans: + if getattr(self, key, False): + elt.set(key, "yes") + for name in self.elements: + value = getattr(self, name, None) + if value is not None and not value.empty(): + SubElement(elt, self.xmlns + name, nsmap = self.nsmap).text = value.get_Base64() + return elt + + def make_reply(self, r_pdu = None): + """ + Construct a reply PDU. + """ + + if r_pdu is None: + r_pdu = self.__class__() + self.make_reply_clone_hook(r_pdu) + handle_name = self.element_name + "_handle" + setattr(r_pdu, handle_name, getattr(self, handle_name, None)) + else: + self.make_reply_clone_hook(r_pdu) + for b in r_pdu.booleans: + setattr(r_pdu, b, False) + r_pdu.action = self.action + r_pdu.tag = self.tag + return r_pdu + + def serve_fetch_one(self): """ - Fetch self object to which this object links. + Find the object on which a get, set, or destroy method should + operate. """ + r = self.serve_fetch_one_maybe() + if r is None: + raise rpki.exceptions.NotFound + return r + + def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): + cb() + + def serve_create(self, r_msg, cb, eb): + r_pdu = self.make_reply() + + def one(): + self.sql_store() + setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index)) + self.serve_post_save_hook(self, r_pdu, two, eb) + + def two(): + r_msg.append(r_pdu) + cb() + + oops = self.serve_fetch_one_maybe() + if oops is not None: + raise rpki.exceptions.DuplicateObject("Object already exists: %r[%r] %r[%r]" % (self, getattr(self, self.element_name + "_handle"), + oops, getattr(oops, oops.element_name + "_handle"))) + + self.serve_pre_save_hook(self, r_pdu, one, eb) + + def serve_set(self, r_msg, cb, eb): + + db_pdu = self.serve_fetch_one() + r_pdu = self.make_reply() + for a in db_pdu.sql_template.columns[1:]: + v = getattr(self, a, None) + if v is not None: + setattr(db_pdu, a, v) + db_pdu.sql_mark_dirty() + + def one(): + db_pdu.sql_store() + db_pdu.serve_post_save_hook(self, r_pdu, two, eb) + + def two(): + r_msg.append(r_pdu) + cb() + + db_pdu.serve_pre_save_hook(self, r_pdu, one, eb) + + def serve_get(self, r_msg, cb, eb): + r_pdu = self.serve_fetch_one() + self.make_reply(r_pdu) + r_msg.append(r_pdu) + cb() + + def serve_list(self, r_msg, cb, eb): + for r_pdu in self.serve_fetch_all(): + self.make_reply(r_pdu) + r_msg.append(r_pdu) + cb() + + def serve_destroy_hook(self, cb, eb): + cb() + + def serve_destroy(self, r_msg, cb, eb): + def done(): + db_pdu.sql_delete() + r_msg.append(self.make_reply()) + cb() + db_pdu = self.serve_fetch_one() + db_pdu.serve_destroy_hook(done, eb) + + def serve_dispatch(self, r_msg, cb, eb): + # Transition hack: handle the .toXML() call for old handlers. + + fake_r_msg = [] + + def fake_convert(): + r_msg.extend(r_pdu.toXML() if isinstance(r_pdu, base_elt) else r_pdu + for r_pdu in fake_r_msg) + + def fake_cb(): + fake_convert() + cb() + + def fake_eb(e): + fake_convert() + eb(e) + + method = getattr(self, "serve_" + self.action, None) + if method is None: + raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action) + method(fake_r_msg, fake_cb, fake_eb) + + def unimplemented_control(self, *controls): + unimplemented = [x for x in controls if getattr(self, x, False)] + if unimplemented: + raise rpki.exceptions.NotImplementedYet("Unimplemented control %s" % ", ".join(unimplemented)) + + @property + @rpki.sql.cache_reference + def self(self): return self_elt.sql_fetch(self.gctx, self.self_id) @property @rpki.sql.cache_reference def bsc(self): - """ - Return BSC object to which this object links. - """ - return bsc_elt.sql_fetch(self.gctx, self.bsc_id) def make_reply_clone_hook(self, r_pdu): @@ -130,10 +297,6 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name @classmethod def serve_fetch_handle(cls, gctx, self_id, handle): - """ - Find an object based on its handle. - """ - return cls.sql_fetch_where1(gctx, cls.element_name + "_handle = %s AND self_id = %s", (handle, self_id)) def serve_fetch_one_maybe(self): @@ -173,7 +336,8 @@ class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, left_right_name setattr(self, id_name, getattr(x, id_name)) cb() -class self_elt(data_elt): + +class self_elt(base_elt): """ <self/> element. """ @@ -211,66 +375,33 @@ class self_elt(data_elt): @property def bscs(self): - """ - Fetch all BSC objects that link to this self object. - """ - return bsc_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property def repositories(self): - """ - Fetch all repository objects that link to this self object. - """ - return repository_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property def parents(self): - """ - Fetch all parent objects that link to this self object. - """ - return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property def children(self): - """ - Fetch all child objects that link to this self object. - """ - return child_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property def roas(self): - """ - Fetch all ROA objects that link to this self object. - """ - return rpki.rpkid.roa_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property def ghostbusters(self): - """ - Fetch all Ghostbuster record objects that link to this self object. - """ - return rpki.rpkid.ghostbuster_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) @property def ee_certificates(self): - """ - Fetch all EE certificate objects that link to this self object. - """ - return rpki.rpkid.ee_cert_obj.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for self_elt. - """ - actions = [] if q_pdu.rekey: actions.append(self.serve_rekey) @@ -291,65 +422,37 @@ class self_elt(data_elt): rpki.async.iterator(actions, loop, cb) def serve_rekey(self, cb, eb): - """ - Handle a left-right rekey action for this self. - """ - def loop(iterator, parent): parent.serve_rekey(iterator, eb) rpki.async.iterator(self.parents, loop, cb) def serve_revoke(self, cb, eb): - """ - Handle a left-right revoke action for this self. - """ - def loop(iterator, parent): parent.serve_revoke(iterator, eb) rpki.async.iterator(self.parents, loop, cb) def serve_reissue(self, cb, eb): - """ - Handle a left-right reissue action for this self. - """ - def loop(iterator, parent): parent.serve_reissue(iterator, eb) rpki.async.iterator(self.parents, loop, cb) def serve_revoke_forgotten(self, cb, eb): - """ - Handle a left-right revoke_forgotten action for this self. - """ - def loop(iterator, parent): parent.serve_revoke_forgotten(iterator, eb) rpki.async.iterator(self.parents, loop, cb) def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this self. - """ - def loop(iterator, obj): obj.serve_clear_replay_protection(iterator, eb) rpki.async.iterator(self.parents + self.children + self.repositories, loop, cb) def serve_destroy_hook(self, cb, eb): - """ - Extra cleanup actions when destroying a self_elt. - """ - def loop(iterator, parent): parent.delete(iterator) rpki.async.iterator(self.parents, loop, cb) def serve_publish_world_now(self, cb, eb): - """ - Handle a left-right publish_world_now action for this self. - """ - publisher = rpki.rpkid.publication_queue() repositories = set() objects = dict() @@ -417,10 +520,6 @@ class self_elt(data_elt): rpki.async.iterator(self.parents, loop, done) def serve_run_now(self, cb, eb): - """ - Handle a left-right run_now action for this self. - """ - logger.debug("Forced immediate run of periodic actions for self %s[%d]", self.self_handle, self.self_id) completion = rpki.rpkid_tasks.CompletionHandler(cb) @@ -438,10 +537,6 @@ class self_elt(data_elt): @classmethod def serve_fetch_handle(cls, gctx, self_id, self_handle): - """ - Find a self object based on its self_handle. - """ - return cls.sql_fetch_where1(gctx, "self_handle = %s", (self_handle,)) def serve_fetch_all(self): @@ -454,13 +549,8 @@ class self_elt(data_elt): return self.sql_fetch_all(self.gctx) def schedule_cron_tasks(self, completion): - """ - Schedule periodic tasks. - """ - if self.cron_tasks is None: self.cron_tasks = tuple(task(self) for task in rpki.rpkid_tasks.task_classes) - for task in self.cron_tasks: self.gctx.task_add(task) completion.register(task) @@ -486,7 +576,7 @@ class self_elt(data_elt): return results -class bsc_elt(data_elt): +class bsc_elt(base_elt): """ <bsc/> (Business Signing Context) element. """ @@ -523,32 +613,19 @@ class bsc_elt(data_elt): @property def repositories(self): - """ - Fetch all repository objects that link to this BSC object. - """ - return repository_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) @property def parents(self): - """ - Fetch all parent objects that link to this BSC object. - """ - return parent_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) @property def children(self): - """ - Fetch all child objects that link to this BSC object. - """ - return child_elt.sql_fetch_where(self.gctx, "bsc_id = %s", (self.bsc_id,)) def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): """ - Extra server actions for bsc_elt -- handle key generation. For - now this only allows RSA with SHA-256. + Extra server actions -- handle key generation, only RSA with SHA-256 for now. """ if q_pdu.generate_keypair: @@ -556,9 +633,10 @@ class bsc_elt(data_elt): self.private_key_id = rpki.x509.RSA.generate(keylength = q_pdu.key_length or 2048) self.pkcs10_request = rpki.x509.PKCS10.create(keypair = self.private_key_id) r_pdu.pkcs10_request = self.pkcs10_request - data_elt.serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb) + super(bsc_elt, self).serve_pre_save_hook(q_pdu, r_pdu, cb, eb) -class repository_elt(data_elt): + +class repository_elt(base_elt): """ <repository/> element. """ @@ -594,17 +672,9 @@ class repository_elt(data_elt): @property def parents(self): - """ - Fetch all parent objects that link to this repository object. - """ - return parent_elt.sql_fetch_where(self.gctx, "repository_id = %s", (self.repository_id,)) def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for repository_elt. - """ - actions = [] if q_pdu.clear_replay_protection: actions.append(self.serve_clear_replay_protection) @@ -613,10 +683,6 @@ class repository_elt(data_elt): rpki.async.iterator(actions, loop, cb) def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this repository. - """ - self.last_cms_timestamp = None self.sql_mark_dirty() cb() @@ -680,7 +746,8 @@ class repository_elt(data_elt): except Exception, e: errback(e) -class parent_elt(data_elt): + +class parent_elt(base_elt): """ <parent/> element. """ @@ -723,25 +790,13 @@ class parent_elt(data_elt): @property @rpki.sql.cache_reference def repository(self): - """ - Fetch repository object to which this parent object links. - """ - return repository_elt.sql_fetch(self.gctx, self.repository_id) @property def cas(self): - """ - Fetch all CA objects that link to this parent object. - """ - return rpki.rpkid.ca_obj.sql_fetch_where(self.gctx, "parent_id = %s", (self.parent_id,)) def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for parent_elt. - """ - actions = [] if q_pdu.rekey: actions.append(self.serve_rekey) @@ -758,37 +813,21 @@ class parent_elt(data_elt): rpki.async.iterator(actions, loop, cb) def serve_rekey(self, cb, eb): - """ - Handle a left-right rekey action for this parent. - """ - def loop(iterator, ca): ca.rekey(iterator, eb) rpki.async.iterator(self.cas, loop, cb) def serve_revoke(self, cb, eb): - """ - Handle a left-right revoke action for this parent. - """ - def loop(iterator, ca): ca.revoke(cb = iterator, eb = eb) rpki.async.iterator(self.cas, loop, cb) def serve_reissue(self, cb, eb): - """ - Handle a left-right reissue action for this parent. - """ - def loop(iterator, ca): ca.reissue(cb = iterator, eb = eb) rpki.async.iterator(self.cas, loop, cb) def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this parent. - """ - self.last_cms_timestamp = None self.sql_mark_dirty() cb() @@ -883,36 +922,20 @@ class parent_elt(data_elt): def serve_destroy_hook(self, cb, eb): - """ - Extra server actions when destroying a parent_elt. - """ - self.delete(cb, delete_parent = False) def _compose_up_down_query(self, query_type): - """ - Compose top level element of an up-down query to this parent. - """ - return Element(rpki.up_down.tag_message, nsmap = rpki.up_down.nsmap, version = rpki.up_down.version, sender = self.sender_name, recipient = self.recipient_name, type = query_type) def up_down_list_query(self, cb, eb): - """ - Send an up-down list query to this parent. - """ - q_msg = self._compose_up_down_query("list") self.query_up_down(q_msg, cb, eb) def up_down_issue_query(self, ca, ca_detail, cb, eb): - """ - Send an up-down issue query to this parent. - """ - pkcs10 = rpki.x509.PKCS10.create( keypair = ca_detail.private_key_id, is_ca = True, @@ -926,19 +949,12 @@ class parent_elt(data_elt): def up_down_revoke_query(self, class_name, ski, cb, eb): - """ - Send an up-down revoke query to this parent. - """ - q_msg = self._compose_up_down_query("revoke") SubElement(q_msg, rpki.up_down.tag_key, class_name = class_name, ski = ski) self.query_up_down(q_msg, cb, eb) def query_up_down(self, q_msg, cb, eb): - """ - Client code for sending one up-down query PDU to this parent. - """ bsc = self.bsc if bsc is None: @@ -976,7 +992,8 @@ class parent_elt(data_elt): errback = eb, content_type = rpki.up_down.content_type) -class child_elt(data_elt): + +class child_elt(base_elt): """ <child/> element. """ @@ -1010,33 +1027,17 @@ class child_elt(data_elt): return rpki.log.log_repr(self, self.child_handle) def fetch_child_certs(self, ca_detail = None, ski = None, unique = False): - """ - Fetch all child_cert objects that link to this child object. - """ - return rpki.rpkid.child_cert_obj.fetch(self.gctx, self, ca_detail, ski, unique) @property def child_certs(self): - """ - Fetch all child_cert objects that link to this child object. - """ - return self.fetch_child_certs() @property def parents(self): - """ - Fetch all parent objects that link to self object to which this child object links. - """ - return parent_elt.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)) def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Extra server actions for child_elt. - """ - actions = [] if q_pdu.reissue: actions.append(self.serve_reissue) @@ -1047,29 +1048,17 @@ class child_elt(data_elt): rpki.async.iterator(actions, loop, cb) def serve_reissue(self, cb, eb): - """ - Handle a left-right reissue action for this child. - """ - publisher = rpki.rpkid.publication_queue() for child_cert in self.child_certs: child_cert.reissue(child_cert.ca_detail, publisher, force = True) publisher.call_pubd(cb, eb) def serve_clear_replay_protection(self, cb, eb): - """ - Handle a left-right clear_replay_protection action for this child. - """ - self.last_cms_timestamp = None self.sql_mark_dirty() cb() def ca_from_class_name(self, class_name): - """ - Fetch the CA corresponding to an up-down class_name. - """ - if not class_name.isdigit(): raise rpki.exceptions.BadClassNameSyntax("Bad class name %s" % class_name) ca = rpki.rpkid.ca_obj.sql_fetch(self.gctx, long(class_name)) @@ -1083,10 +1072,6 @@ class child_elt(data_elt): return ca def serve_destroy_hook(self, cb, eb): - """ - Extra server actions when destroying a child_elt. - """ - publisher = rpki.rpkid.publication_queue() for child_cert in self.child_certs: child_cert.revoke(publisher = publisher, @@ -1095,9 +1080,6 @@ class child_elt(data_elt): def up_down_handle_list(self, q_msg, r_msg, callback, errback): - """ - Serve one up-down "list" PDU. - """ def got_resources(irdb_resources): @@ -1132,9 +1114,6 @@ class child_elt(data_elt): def up_down_handle_issue(self, q_msg, r_msg, callback, errback): - """ - Serve one issue request PDU. - """ def got_resources(irdb_resources): @@ -1205,9 +1184,6 @@ class child_elt(data_elt): def up_down_handle_revoke(self, q_msg, r_msg, callback, errback): - """ - Serve one revoke request PDU. - """ def done(): SubElement(r_msg, key.tag, class_name = class_name, ski = key.get("ski")) diff --git a/rpki/publication.py b/rpki/publication.py index e64b729c..58c52d34 100644 --- a/rpki/publication.py +++ b/rpki/publication.py @@ -24,15 +24,10 @@ RPKI publication protocol. import os import errno import logging -import rpki.resource_set + import rpki.x509 -import rpki.sql import rpki.exceptions -import rpki.http -import rpki.up_down import rpki.relaxng -import rpki.sundial -import rpki.log from lxml.etree import Element, SubElement diff --git a/rpki/publication_control.py b/rpki/publication_control.py index 06e7aa2d..b5e08f3e 100644 --- a/rpki/publication_control.py +++ b/rpki/publication_control.py @@ -26,15 +26,10 @@ protocol itself. import logging import collections -import rpki.resource_set + import rpki.x509 -import rpki.sql import rpki.exceptions -import rpki.http -import rpki.up_down import rpki.relaxng -import rpki.sundial -import rpki.log logger = logging.getLogger(__name__) diff --git a/rpki/rootd.py b/rpki/rootd.py index 32b6038b..5a84b5df 100644 --- a/rpki/rootd.py +++ b/rpki/rootd.py @@ -28,11 +28,10 @@ import logging import httplib import argparse import urlparse + import rpki.resource_set import rpki.up_down -import rpki.left_right import rpki.x509 -import rpki.http import rpki.http_simple import rpki.config import rpki.exceptions @@ -40,6 +39,7 @@ import rpki.relaxng import rpki.sundial import rpki.log import rpki.daemonize +import rpki.publication from lxml.etree import Element, SubElement diff --git a/rpki/rpkic.py b/rpki/rpkic.py index 3f899f45..eefa0116 100644 --- a/rpki/rpkic.py +++ b/rpki/rpkic.py @@ -38,7 +38,6 @@ import time import rpki.config import rpki.sundial import rpki.log -import rpki.http import rpki.resource_set import rpki.relaxng import rpki.exceptions diff --git a/rpki/rpkid.py b/rpki/rpkid.py index 6cbbc7dd..ddc04bd0 100644 --- a/rpki/rpkid.py +++ b/rpki/rpkid.py @@ -28,6 +28,7 @@ import random import base64 import logging import argparse + import rpki.resource_set import rpki.up_down import rpki.left_right @@ -405,7 +406,7 @@ class main(object): logger.exception("Unhandled exception serving left-right PDU %r", q_pdu) # Compatability kludge - if isinstance(q_pdu, rpki.left_right.data_elt): + if isinstance(q_pdu, rpki.left_right.base_elt): error_self_handle = q_pdu.self_handle error_tag = q_pdu.tag else: diff --git a/rpki/xml_utils.py b/rpki/xml_utils.py deleted file mode 100644 index da907a0d..00000000 --- a/rpki/xml_utils.py +++ /dev/null @@ -1,357 +0,0 @@ -# $Id$ -# -# Copyright (C) 2009-2012 Internet Systems Consortium ("ISC") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. -# -# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -""" -XML utilities. -""" - -import logging -import lxml.etree -import rpki.exceptions - -logger = logging.getLogger(__name__) - - -class base_elt(object): - """ - Virtual base class for XML message elements. The left-right and - publication_control protocols use this. - """ - - ## @var attributes - # XML attributes for this element. - attributes = () - - ## @var elements - # XML elements contained by this element. - elements = () - - ## @var booleans - # Boolean attributes (value "yes" or "no") for this element. - booleans = () - - ## @var text_attribute - # Name of class attribute that tells us where to put text values, if any. - text_attribute = None - - @classmethod - def fromXML(cls, elt): - """ - First cut at non-SAX message unpacker. This will probably change. - """ - - self = cls() - - for key in self.attributes: - val = elt.get(key, None) - if val is not None: - val = val.encode("ascii") - if isinstance(self.attributes, dict) and self.attributes[key] is not None: - val = self.attributes[key](val) - elif val.isdigit() and not key.endswith("_handle"): - val = long(val) - setattr(self, key, val) - for key in self.booleans: - setattr(self, key, elt.get(key, False)) - - # This test could go in an extended method in text_elt. Then - # again, perhaps spreading the logic in as many places as we - # possibly can is not really helping matters.... - - if self.text_attribute is not None: - setattr(self, self.text_attribute, elt.text) - - # In the long run, we probably want the key for that to include - # the namespace, but that would break the current .toXML() code, - # so kludge it for now. - - for b64 in elt: - assert b64.tag.startswith(self.xmlns) - ename = b64.tag[len(self.xmlns):] - etype = self.elements[ename] - setattr(self, ename, etype(Base64 = b64.text)) - - return self - - def toXML(self): - """ - Default toXML() element generator. - """ - - return self.make_elt() - - def read_attrs(self, attrs): - """ - Template-driven attribute reader. - """ - - def make_elt(self): - """ - XML element constructor. - """ - - elt = lxml.etree.Element(self.xmlns + self.element_name, nsmap = self.nsmap) - for key in self.attributes: - val = getattr(self, key, None) - if val is not None: - elt.set(key, str(val)) - for key in self.booleans: - if getattr(self, key, False): - elt.set(key, "yes") - return elt - - def make_b64elt(self, elt, name, value): - """ - Constructor for Base64-encoded subelement. - """ - - if value is not None and not value.empty(): - lxml.etree.SubElement(elt, self.xmlns + name, nsmap = self.nsmap).text = value.get_Base64() - - def __str__(self): - """ - Convert a base_elt object to string format. - """ - - return lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii") - - @classmethod - def make_pdu(cls, **kargs): - """ - Generic PDU constructor. - """ - - self = cls() - for k, v in kargs.items(): - if isinstance(v, bool): - v = 1 if v else 0 - setattr(self, k, v) - return self - -class text_elt(base_elt): - """ - Virtual base class for XML message elements that contain text. - """ - - def toXML(self): - """ - Insert text into generated XML. - """ - - elt = self.make_elt() - elt.text = getattr(self, self.text_attribute) or None - return elt - -class data_elt(base_elt): - """ - Virtual base class for PDUs that map to SQL objects. These objects - all implement the create/set/get/list/destroy action attribute. - """ - - def toXML(self): - """ - Default element generator for SQL-based objects. This assumes - that sub-elements are Base64-encoded DER objects. - """ - - elt = self.make_elt() - for i in self.elements: - self.make_b64elt(elt, i, getattr(self, i, None)) - return elt - - def make_reply(self, r_pdu = None): - """ - Construct a reply PDU. - """ - - if r_pdu is None: - r_pdu = self.__class__() - self.make_reply_clone_hook(r_pdu) - handle_name = self.element_name + "_handle" - setattr(r_pdu, handle_name, getattr(self, handle_name, None)) - else: - self.make_reply_clone_hook(r_pdu) - for b in r_pdu.booleans: - setattr(r_pdu, b, False) - r_pdu.action = self.action - r_pdu.tag = self.tag - return r_pdu - - def make_reply_clone_hook(self, r_pdu): - """ - Overridable hook. - """ - - pass - - def serve_fetch_one(self): - """ - Find the object on which a get, set, or destroy method should - operate. - """ - - r = self.serve_fetch_one_maybe() - if r is None: - raise rpki.exceptions.NotFound - return r - - def serve_pre_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Overridable hook. - """ - - cb() - - def serve_post_save_hook(self, q_pdu, r_pdu, cb, eb): - """ - Overridable hook. - """ - - cb() - - def serve_create(self, r_msg, cb, eb): - """ - Handle a create action. - """ - - r_pdu = self.make_reply() - - def one(): - self.sql_store() - setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index)) - self.serve_post_save_hook(self, r_pdu, two, eb) - - def two(): - r_msg.append(r_pdu) - cb() - - oops = self.serve_fetch_one_maybe() - if oops is not None: - raise rpki.exceptions.DuplicateObject("Object already exists: %r[%r] %r[%r]" % (self, getattr(self, self.element_name + "_handle"), - oops, getattr(oops, oops.element_name + "_handle"))) - - self.serve_pre_save_hook(self, r_pdu, one, eb) - - def serve_set(self, r_msg, cb, eb): - """ - Handle a set action. - """ - - db_pdu = self.serve_fetch_one() - r_pdu = self.make_reply() - for a in db_pdu.sql_template.columns[1:]: - v = getattr(self, a, None) - if v is not None: - setattr(db_pdu, a, v) - db_pdu.sql_mark_dirty() - - def one(): - db_pdu.sql_store() - db_pdu.serve_post_save_hook(self, r_pdu, two, eb) - - def two(): - r_msg.append(r_pdu) - cb() - - db_pdu.serve_pre_save_hook(self, r_pdu, one, eb) - - def serve_get(self, r_msg, cb, eb): - """ - Handle a get action. - """ - - r_pdu = self.serve_fetch_one() - self.make_reply(r_pdu) - r_msg.append(r_pdu) - cb() - - def serve_list(self, r_msg, cb, eb): - """ - Handle a list action for non-self objects. - """ - - for r_pdu in self.serve_fetch_all(): - self.make_reply(r_pdu) - r_msg.append(r_pdu) - cb() - - def serve_destroy_hook(self, cb, eb): - """ - Overridable hook. - """ - - cb() - - def serve_destroy(self, r_msg, cb, eb): - """ - Handle a destroy action. - """ - - def done(): - db_pdu.sql_delete() - r_msg.append(self.make_reply()) - cb() - db_pdu = self.serve_fetch_one() - db_pdu.serve_destroy_hook(done, eb) - - def serve_dispatch(self, r_msg, cb, eb): - """ - Action dispatch handler. - """ - - # Transition hack: handle the .toXML() call for old handlers. - - fake_r_msg = [] - - def fake_convert(): - r_msg.extend(r_pdu.toXML() if isinstance(r_pdu, base_elt) else r_pdu - for r_pdu in fake_r_msg) - - def fake_cb(): - fake_convert() - cb() - - def fake_eb(e): - fake_convert() - eb(e) - - method = getattr(self, "serve_" + self.action, None) - if method is None: - raise rpki.exceptions.BadQuery("Unexpected query: action %s" % self.action) - method(fake_r_msg, fake_cb, fake_eb) - - def unimplemented_control(self, *controls): - """ - Uniform handling for unimplemented control operations. - """ - - unimplemented = [x for x in controls if getattr(self, x, False)] - if unimplemented: - raise rpki.exceptions.NotImplementedYet("Unimplemented control %s" % ", ".join(unimplemented)) |