aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rpkid/rpki/left_right.py215
-rw-r--r--rpkid/rpki/publication.py90
-rw-r--r--rpkid/rpki/sax_utils.py98
-rw-r--r--rpkid/rpki/up_down.py4
-rw-r--r--rpkid/rpki/xml_utils.py278
-rw-r--r--rpkid/testbed.py2
6 files changed, 370 insertions, 317 deletions
diff --git a/rpkid/rpki/left_right.py b/rpkid/rpki/left_right.py
index 7fb12998..b44fa547 100644
--- a/rpkid/rpki/left_right.py
+++ b/rpkid/rpki/left_right.py
@@ -17,8 +17,9 @@
"""RPKI "left-right" protocol."""
import base64, lxml.etree, time, traceback, os
-import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.sax_utils
+import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
+import rpki.publication
left_right_xmlns = "http://www.hactrn.net/uris/rpki/left-right-spec/"
left_right_nsmap = { None : left_right_xmlns }
@@ -26,70 +27,18 @@ left_right_nsmap = { None : left_right_xmlns }
# Enforce strict checking of XML "sender" field in up-down protocol
enforce_strict_up_down_xml_sender = False
-class base_elt(object):
- """Virtual base type for left-right message elements."""
-
- attributes = ()
- elements = ()
- booleans = ()
+class base_elt(rpki.xml_utils.base_elt):
+ """Virtual class for left-right protocol PDUs."""
xmlns = left_right_xmlns
nsmap = left_right_nsmap
- def startElement(self, stack, name, attrs):
- """Default startElement() handler: just process attributes."""
- self.read_attrs(attrs)
-
- def endElement(self, stack, name, text):
- """Default endElement() handler: just pop the stack."""
- stack.pop()
-
- def read_attrs(self, attrs):
- """Template-driven attribute reader."""
- for key in self.attributes:
- val = attrs.get(key, None)
- if isinstance(val, str) and val.isdigit():
- val = long(val)
- setattr(self, key, val)
- for key in self.booleans:
- setattr(self, key, attrs.get(key, False))
-
- def make_elt(self):
- """XML element constructor."""
- elt = lxml.etree.Element("{%s}%s" % (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 = None):
- """Constructor for Base64-encoded subelement."""
- if value is None:
- value = getattr(self, name, None)
- if value is not None:
- lxml.etree.SubElement(elt, "{%s}%s" % (self.xmlns, name), nsmap = self.nsmap).text = base64.b64encode(value)
-
- def __str__(self):
- """Convert a base_elt object to string format."""
- lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
-
- @classmethod
- def make_pdu(cls, **kargs):
- """Generic left-right 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 data_elt(base_elt, rpki.sql.sql_persistant):
+class data_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant):
"""Virtual class for top-level left-right protocol data elements."""
+ xmlns = left_right_xmlns
+ nsmap = left_right_nsmap
+
def self(this):
"""Fetch self object to which this object links."""
return self_elt.sql_fetch(this.gctx, this.self_id)
@@ -98,40 +47,13 @@ class data_elt(base_elt, rpki.sql.sql_persistant):
"""Return BSC object to which this object links."""
return bsc_elt.sql_fetch(self.gctx, self.bsc_id)
- def make_reply(self, r_pdu = None):
- """Construct a reply PDU."""
- if r_pdu is None:
- r_pdu = self.__class__()
- r_pdu.self_id = self.self_id
- setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
- else:
- 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_pre_save_hook(self, q_pdu, r_pdu):
- """Overridable hook."""
- pass
-
- def serve_post_save_hook(self, q_pdu, r_pdu):
- """Overridable hook."""
- pass
-
- def serve_create(self, r_msg):
- """Handle a create action."""
- r_pdu = self.make_reply()
- self.serve_pre_save_hook(self, r_pdu)
- self.sql_store()
- setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
- self.serve_post_save_hook(self, r_pdu)
- r_msg.append(r_pdu)
+ def make_reply_clone_hook(self, r_pdu):
+ """Set self_id when cloning."""
+ r_pdu.self_id = self.self_id
def serve_fetch_one(self):
"""Find the object on which a get, set, or destroy method should
- operate. This is a separate method because the self_elt object
- needs to override it.
+ operate.
"""
where = self.sql_template.index + " = %s AND self_id = %s"
args = (getattr(self, self.sql_template.index), self.self_id)
@@ -140,48 +62,9 @@ class data_elt(base_elt, rpki.sql.sql_persistant):
raise rpki.exceptions.NotFound, "Lookup failed where %s" + (where % args)
return r
- def serve_set(self, r_msg):
- """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)
- if v is not None:
- setattr(db_pdu, a, v)
- db_pdu.sql_mark_dirty()
- db_pdu.serve_pre_save_hook(self, r_pdu)
- db_pdu.sql_store()
- db_pdu.serve_post_save_hook(self, r_pdu)
- r_msg.append(r_pdu)
-
- def serve_get(self, r_msg):
- """Handle a get action."""
- r_pdu = self.serve_fetch_one()
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
-
- def serve_list(self, r_msg):
- """Handle a list action for non-self objects."""
- for r_pdu in self.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,)):
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
-
- def serve_destroy(self, r_msg):
- """Handle a destroy action."""
- db_pdu = self.serve_fetch_one()
- db_pdu.sql_delete()
- r_msg.append(self.make_reply())
-
- def serve_dispatch(self, r_msg):
- """Action dispatch handler."""
- dispatch = { "create" : self.serve_create,
- "set" : self.serve_set,
- "get" : self.serve_get,
- "list" : self.serve_list,
- "destroy" : self.serve_destroy }
- if self.action not in dispatch:
- raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
- dispatch[self.action](r_msg)
+ def serve_fetch_all(self):
+ """Find the objects on which a list method should operate."""
+ return self.sql_fetch_where(self.gctx, "self_id = %s", (self.self_id,))
def unimplemented_control(self, *controls):
"""Uniform handling for unimplemented control operations."""
@@ -249,7 +132,7 @@ class self_elt(data_elt):
parent.serve_revoke()
def serve_fetch_one(self):
- """Find the self object on which a get, set, or destroy method
+ """Find the self object upon which a get, set, or destroy action
should operate.
"""
r = self.sql_fetch(self.gctx, self.self_id)
@@ -257,14 +140,12 @@ class self_elt(data_elt):
raise rpki.exceptions.NotFound
return r
- def serve_list(self, r_msg):
- """Handle a list action for self objects. This is different from
- the list action for all other objects, where list only works
- within a given self_id context.
+ def serve_fetch_all(self):
+ """Find the self objects upon which a list action should operate.
+ This is different from the list action for all other objects,
+ where list only works within a given self_id context.
"""
- for r_pdu in self.sql_fetch_all(self.gctx):
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
+ return self.sql_fetch_all(self.gctx)
def startElement(self, stack, name, attrs):
"""Handle <self/> element."""
@@ -755,12 +636,31 @@ class repository_elt(data_elt):
rpki.log.trace()
os.remove(cls.uri_to_filename(base, uri))
+ def call_pubd(self, *pdus):
+ """Send a message to publication daemon and return the response."""
+ rpki.log.trace()
+ bsc = self.bsc()
+ q_msg = rpki.publication.msg(pdus)
+ q_msg.type = "query"
+ q_cms = rpki.publication.cms_msg.wrap(q_msg, bsc.private_key_id, bsc.signing_cert, bsc.signing_cert_crl)
+ bpki_ta_path = (self.gctx.bpki_ta, self.self().bpki_cert, self.self().bpki_glue, self.bpki_https_cert, self.bpki_https_glue)
+ r_cms = rpki.https.client(
+ client_key = bsc.private_key_id,
+ client_cert = bsc.signing_cert,
+ server_ta = bpki_ta_path,
+ url = self.peer_contact_uri,
+ msg = q_cms)
+ r_msg = rpki.publication.cms_msg.unwrap(r_cms, bpki_ta_path)
+ r_msg.payload_check_response()
+ assert len(r_msg) == 1
+ return r_msg[0]
+
def publish(self, obj, uri):
"""Placeholder for publication operation. [TEMPORARY]"""
rpki.log.trace()
rpki.log.info("Publishing %s as %s" % (repr(obj), repr(uri)))
if self.use_pubd:
- raise rpki.exceptions.NotImplementedYet
+ self.call_pubd(rpki.publication.obj2elt[type(obj)].make_pdu(action = "publish", uri = uri, payload = obj))
else:
self.object_write(self.gctx.publication_kludge_base, uri, obj)
@@ -769,7 +669,7 @@ class repository_elt(data_elt):
rpki.log.trace()
rpki.log.info("Withdrawing %s from at %s" % (repr(obj), repr(uri)))
if self.use_pubd:
- raise rpki.exceptions.NotImplementedYet
+ self.call_pubd(rpki.publication.obj2elt[obj].make_pdu(action = "withdraw", uri = uri))
else:
self.object_delete(self.gctx.publication_kludge_base, uri)
@@ -1033,7 +933,7 @@ class report_error_elt(base_elt):
self.error_code = exc.__class__.__name__
return self
-class msg(list):
+class msg(rpki.xml_utils.msg):
"""Left-right PDU."""
xmlns = left_right_xmlns
@@ -1049,33 +949,6 @@ class msg(list):
for x in (self_elt, child_elt, parent_elt, bsc_elt, repository_elt,
route_origin_elt, list_resources_elt, report_error_elt))
- def startElement(self, stack, name, attrs):
- """Handle left-right PDU."""
- if name == "msg":
- assert self.version == int(attrs["version"])
- self.type = attrs["type"]
- else:
- elt = self.pdus[name]()
- self.append(elt)
- stack.append(elt)
- elt.startElement(stack, name, attrs)
-
- def endElement(self, stack, name, text):
- """Handle left-right PDU."""
- assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
- assert len(stack) == 1
- stack.pop()
-
- def __str__(self):
- """Convert msg object to string."""
- lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
-
- def toXML(self):
- """Generate left-right PDU."""
- elt = lxml.etree.Element("{%s}msg" % (self.xmlns), nsmap = self.nsmap, version = str(self.version), type = self.type)
- elt.extend([i.toXML() for i in self])
- return elt
-
def serve_top_level(self, gctx):
"""Serve one msg PDU."""
r_msg = self.__class__()
@@ -1085,7 +958,7 @@ class msg(list):
q_pdu.serve_dispatch(r_msg)
return r_msg
-class sax_handler(rpki.sax_utils.handler):
+class sax_handler(rpki.xml_utils.sax_handler):
"""SAX handler for Left-Right protocol."""
pdu = msg
diff --git a/rpkid/rpki/publication.py b/rpkid/rpki/publication.py
index 88484545..09ed0aac 100644
--- a/rpkid/rpki/publication.py
+++ b/rpkid/rpki/publication.py
@@ -14,40 +14,23 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
-"""RPKI "publication" protocol.
-
-At the moment this module imports and tweaks classes from
-rpki.left_right. The code in question should be refactored at some
-point to make the imports cleaner, but it's faster to write it this
-way and see which things I end up using before spending time on
-refactoring stuff I don't really need....
-"""
+"""RPKI "publication" protocol."""
import base64, lxml.etree, time, traceback, os
-import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.sax_utils
+import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
import rpki.https, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
-import rpki.left_right
publication_xmlns = "http://www.hactrn.net/uris/rpki/publication-spec/"
publication_nsmap = { None : publication_xmlns }
-class data_elt(rpki.left_right.base_elt):
- """Virtual class for top-level publication protocol data elements.
-
- This is a placeholder. It may end up being a mixin that uses
- rpki.sql.sql_persistant, just like its counterpart in
- rpki.left_right, but wait and see.
- """
+class data_elt(rpki.xml_utils.base_elt):
+ """Virtual class for publication protocol PDUs."""
xmlns = publication_xmlns
nsmap = publication_nsmap
-class client_elt(rpki.left_right.data_elt):
- """<client/> element.
-
- This reuses the rpki.left-right.data_elt class because its structure
- is identical to that used in the left-right protocol.
- """
+class client_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistant):
+ """<client/> element."""
xmlns = publication_xmlns
nsmap = publication_nsmap
@@ -62,6 +45,8 @@ class client_elt(rpki.left_right.data_elt):
bpki_cert = None
bpki_glue = None
+ clear_https_ta_cache = False
+
def startElement(self, stack, name, attrs):
"""Handle <client/> element."""
if name not in ("bpki_cert", "bpki_glue"):
@@ -89,6 +74,12 @@ class client_elt(rpki.left_right.data_elt):
self.make_b64elt(elt, "bpki_glue", self.bpki_glue.get_DER())
return elt
+ def serve_post_save_hook(self, q_pdu, r_pdu):
+ """Extra server actions for client_elt."""
+ if self.clear_https_ta_cache:
+ self.gctx.clear_https_ta_cache()
+ self.clear_https_ta_cache = False
+
def serve_fetch_one(self):
"""Find the client object on which a get, set, or destroy method
should operate.
@@ -98,26 +89,15 @@ class client_elt(rpki.left_right.data_elt):
raise rpki.exceptions.NotFound
return r
- def serve_list(self, r_msg):
- """Handle a list action for client objects."""
- for r_pdu in self.sql_fetch_all(self.gctx):
- self.make_reply(r_pdu)
- r_msg.append(r_pdu)
-
- def make_reply(self, r_pdu = None):
- """Construct a reply PDU."""
- if r_pdu is None:
- r_pdu = client_elt()
- r_pdu.client_id = self.client_id
- r_pdu.action = self.action
- r_pdu.tag = self.tag
- return r_pdu
+ def serve_fetch_all(self):
+ """Find client objects on which a list method should operate."""
+ return self.sql_fetch_all(self.gctx)
def serve_dispatch(self, r_msg, client):
"""Action dispatch handler."""
if client is not None:
raise rpki.exceptions.BadQuery, "Client query received on control channel"
- rpki.left_right.data_elt.serve_dispatch(self, r_msg)
+ rpki.xml_utils.data_elt.serve_dispatch(self, r_msg)
def check_allowed_uri(self, uri):
if not uri.startswith(self.base_uri):
@@ -191,7 +171,6 @@ class publication_object_elt(data_elt):
raise rpki.exceptions.BadURISyntax
return filename
-
class certificate_elt(publication_object_elt):
"""<certificate/> element."""
@@ -216,16 +195,37 @@ class roa_elt(publication_object_elt):
element_name = "roa"
payload_type = rpki.x509.ROA
-class report_error_elt(rpki.left_right.report_error_elt):
- """<report_error/> element.
+## @var obj2elt
+# Map of data types to publication element wrapper types
- For now this is identical to its left_right equivilent.
- """
+obj2elt = dict((e.payload_type, e) for e in (certificate_elt, crl_elt, manifest_elt, roa_elt))
+
+class report_error_elt(rpki.xml_utils.base_elt):
+ """<report_error/> element."""
xmlns = publication_xmlns
nsmap = publication_nsmap
-class msg(rpki.left_right.msg):
+ element_name = "report_error"
+ attributes = ("tag", "error_code")
+
+ def startElement(self, stack, name, attrs):
+ """Handle <report_error/> element."""
+ assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
+ self.read_attrs(attrs)
+
+ def toXML(self):
+ """Generate <report_error/> element."""
+ return self.make_elt()
+
+ @classmethod
+ def from_exception(cls, exc):
+ """Generate a <report_error/> element from an exception."""
+ self = cls()
+ self.error_code = exc.__class__.__name__
+ return self
+
+class msg(rpki.xml_utils.msg):
"""Publication PDU."""
xmlns = publication_xmlns
@@ -251,7 +251,7 @@ class msg(rpki.left_right.msg):
q_pdu.serve_dispatch(r_msg, client)
return r_msg
-class sax_handler(rpki.sax_utils.handler):
+class sax_handler(rpki.xml_utils.sax_handler):
"""SAX handler for publication protocol."""
pdu = msg
diff --git a/rpkid/rpki/sax_utils.py b/rpkid/rpki/sax_utils.py
deleted file mode 100644
index 75443251..00000000
--- a/rpkid/rpki/sax_utils.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# $Id$
-
-# 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.
-
-"""SAX utilities."""
-
-import xml.sax, lxml.sax
-
-class handler(xml.sax.handler.ContentHandler):
- """SAX handler for RPKI protocols.
-
- This class provides some basic amenities for parsing protocol XML of
- the kind we use in the RPKI protocols, including whacking all the
- protocol element text into US-ASCII, simplifying accumulation of
- text fields, and hiding some of the fun relating to XML namespaces.
-
- General assumption: by the time this parsing code gets invoked, the
- XML has already passed RelaxNG validation, so we only have to check
- for errors that the schema can't catch, and we don't have to play as
- many XML namespace games.
- """
-
- def __init__(self):
- """Initialize SAX handler."""
- self.text = ""
- self.stack = []
-
- def startElementNS(self, name, qname, attrs):
- """Redirect startElementNS() events to startElement()."""
- return self.startElement(name[1], attrs)
-
- def endElementNS(self, name, qname):
- """Redirect endElementNS() events to endElement()."""
- return self.endElement(name[1])
-
- def characters(self, content):
- """Accumulate a chuck of element content (text)."""
- self.text += content
-
- def startElement(self, name, attrs):
- """Handle startElement() events.
-
- We maintain a stack of nested elements under construction so that
- we can feed events directly to the current element rather than
- having to pass them through all the nesting elements.
-
- If the stack is empty, this event is for the outermost element, so
- we call a virtual method to create the corresponding object and
- that's the object we'll be returning as our final result.
- """
- a = dict()
- for k,v in attrs.items():
- if isinstance(k, tuple):
- if k == ("http://www.w3.org/XML/1998/namespace", "lang"):
- k = "xml:lang"
- else:
- assert k[0] is None
- k = k[1]
- a[k.encode("ascii")] = v.encode("ascii")
- if len(self.stack) == 0:
- assert not hasattr(self, "result")
- self.result = self.create_top_level(name, a)
- self.stack.append(self.result)
- self.stack[-1].startElement(self.stack, name, a)
-
- def endElement(self, name):
- """Handle endElement() events.
-
- Mostly this means handling any accumulated element text.
- """
- text = self.text.encode("ascii").strip()
- self.text = ""
- self.stack[-1].endElement(self.stack, name, text)
-
- @classmethod
- def saxify(cls, elt):
- """Create a one-off SAX parser, parse an ETree, return the result.
- """
- self = cls()
- lxml.sax.saxify(elt, self)
- return self.result
-
- def create_top_level(self, name, attrs):
- """Handle top-level PDU for this protocol."""
- assert name == self.name and attrs["version"] == self.version
- return self.pdu()
diff --git a/rpkid/rpki/up_down.py b/rpkid/rpki/up_down.py
index fb7def4d..df5445e4 100644
--- a/rpkid/rpki/up_down.py
+++ b/rpkid/rpki/up_down.py
@@ -18,7 +18,7 @@
import base64, lxml.etree, time
import rpki.resource_set, rpki.x509, rpki.exceptions
-import rpki.sax_utils, rpki.relaxng
+import rpki.xml_utils, rpki.relaxng
xmlns="http://www.apnic.net/specs/rescerts/up-down/"
@@ -517,7 +517,7 @@ class message_pdu(base_elt):
self.type = self.type2name[type(payload)]
return self
-class sax_handler(rpki.sax_utils.handler):
+class sax_handler(rpki.xml_utils.sax_handler):
"""SAX handler for Up-Down protocol."""
pdu = message_pdu
diff --git a/rpkid/rpki/xml_utils.py b/rpkid/rpki/xml_utils.py
new file mode 100644
index 00000000..9a2f6f61
--- /dev/null
+++ b/rpkid/rpki/xml_utils.py
@@ -0,0 +1,278 @@
+# $Id$
+
+# 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 xml.sax, lxml.sax, lxml.etree, base64
+
+class sax_handler(xml.sax.handler.ContentHandler):
+ """SAX handler for RPKI protocols.
+
+ This class provides some basic amenities for parsing protocol XML of
+ the kind we use in the RPKI protocols, including whacking all the
+ protocol element text into US-ASCII, simplifying accumulation of
+ text fields, and hiding some of the fun relating to XML namespaces.
+
+ General assumption: by the time this parsing code gets invoked, the
+ XML has already passed RelaxNG validation, so we only have to check
+ for errors that the schema can't catch, and we don't have to play as
+ many XML namespace games.
+ """
+
+ def __init__(self):
+ """Initialize SAX handler."""
+ self.text = ""
+ self.stack = []
+
+ def startElementNS(self, name, qname, attrs):
+ """Redirect startElementNS() events to startElement()."""
+ return self.startElement(name[1], attrs)
+
+ def endElementNS(self, name, qname):
+ """Redirect endElementNS() events to endElement()."""
+ return self.endElement(name[1])
+
+ def characters(self, content):
+ """Accumulate a chuck of element content (text)."""
+ self.text += content
+
+ def startElement(self, name, attrs):
+ """Handle startElement() events.
+
+ We maintain a stack of nested elements under construction so that
+ we can feed events directly to the current element rather than
+ having to pass them through all the nesting elements.
+
+ If the stack is empty, this event is for the outermost element, so
+ we call a virtual method to create the corresponding object and
+ that's the object we'll be returning as our final result.
+ """
+ a = dict()
+ for k,v in attrs.items():
+ if isinstance(k, tuple):
+ if k == ("http://www.w3.org/XML/1998/namespace", "lang"):
+ k = "xml:lang"
+ else:
+ assert k[0] is None
+ k = k[1]
+ a[k.encode("ascii")] = v.encode("ascii")
+ if len(self.stack) == 0:
+ assert not hasattr(self, "result")
+ self.result = self.create_top_level(name, a)
+ self.stack.append(self.result)
+ self.stack[-1].startElement(self.stack, name, a)
+
+ def endElement(self, name):
+ """Handle endElement() events.
+
+ Mostly this means handling any accumulated element text.
+ """
+ text = self.text.encode("ascii").strip()
+ self.text = ""
+ self.stack[-1].endElement(self.stack, name, text)
+
+ @classmethod
+ def saxify(cls, elt):
+ """Create a one-off SAX parser, parse an ETree, return the result.
+ """
+ self = cls()
+ lxml.sax.saxify(elt, self)
+ return self.result
+
+ def create_top_level(self, name, attrs):
+ """Handle top-level PDU for this protocol."""
+ assert name == self.name and attrs["version"] == self.version
+ return self.pdu()
+
+class base_elt(object):
+ """Virtual base class for XML message elements. The left-right and
+ publication protocols use this. At least for now, the up-down
+ protocol does not, due to different design assumptions.
+ """
+
+ attributes = ()
+ elements = ()
+ booleans = ()
+
+ def startElement(self, stack, name, attrs):
+ """Default startElement() handler: just process attributes."""
+ self.read_attrs(attrs)
+
+ def endElement(self, stack, name, text):
+ """Default endElement() handler: just pop the stack."""
+ stack.pop()
+
+ def read_attrs(self, attrs):
+ """Template-driven attribute reader."""
+ for key in self.attributes:
+ val = attrs.get(key, None)
+ if isinstance(val, str) and val.isdigit():
+ val = long(val)
+ setattr(self, key, val)
+ for key in self.booleans:
+ setattr(self, key, attrs.get(key, False))
+
+ def make_elt(self):
+ """XML element constructor."""
+ elt = lxml.etree.Element("{%s}%s" % (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 = None):
+ """Constructor for Base64-encoded subelement."""
+ if value is None:
+ value = getattr(self, name, None)
+ if value is not None:
+ lxml.etree.SubElement(elt, "{%s}%s" % (self.xmlns, name), nsmap = self.nsmap).text = base64.b64encode(value)
+
+ def __str__(self):
+ """Convert a base_elt object to string format."""
+ 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 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 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)
+ setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
+ else:
+ 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_pre_save_hook(self, q_pdu, r_pdu):
+ """Overridable hook."""
+ pass
+
+ def serve_post_save_hook(self, q_pdu, r_pdu):
+ """Overridable hook."""
+ pass
+
+ def serve_create(self, r_msg):
+ """Handle a create action."""
+ r_pdu = self.make_reply()
+ self.serve_pre_save_hook(self, r_pdu)
+ self.sql_store()
+ setattr(r_pdu, self.sql_template.index, getattr(self, self.sql_template.index))
+ self.serve_post_save_hook(self, r_pdu)
+ r_msg.append(r_pdu)
+
+ def serve_set(self, r_msg):
+ """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)
+ if v is not None:
+ setattr(db_pdu, a, v)
+ db_pdu.sql_mark_dirty()
+ db_pdu.serve_pre_save_hook(self, r_pdu)
+ db_pdu.sql_store()
+ db_pdu.serve_post_save_hook(self, r_pdu)
+ r_msg.append(r_pdu)
+
+ def serve_get(self, r_msg):
+ """Handle a get action."""
+ r_pdu = self.serve_fetch_one()
+ self.make_reply(r_pdu)
+ r_msg.append(r_pdu)
+
+ def serve_list(self, r_msg):
+ """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)
+
+ def serve_destroy(self, r_msg):
+ """Handle a destroy action."""
+ db_pdu = self.serve_fetch_one()
+ db_pdu.sql_delete()
+ r_msg.append(self.make_reply())
+
+ def serve_dispatch(self, r_msg):
+ """Action dispatch handler."""
+ dispatch = { "create" : self.serve_create,
+ "set" : self.serve_set,
+ "get" : self.serve_get,
+ "list" : self.serve_list,
+ "destroy" : self.serve_destroy }
+ if self.action not in dispatch:
+ raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
+ dispatch[self.action](r_msg)
+
+ 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)
+
+class msg(list):
+ """Generic top-level PDU."""
+
+ def startElement(self, stack, name, attrs):
+ """Handle top-level PDU."""
+ if name == "msg":
+ assert self.version == int(attrs["version"])
+ self.type = attrs["type"]
+ else:
+ elt = self.pdus[name]()
+ self.append(elt)
+ stack.append(elt)
+ elt.startElement(stack, name, attrs)
+
+ def endElement(self, stack, name, text):
+ """Handle top-level PDU."""
+ assert name == "msg", "Unexpected name %s, stack %s" % (name, stack)
+ assert len(stack) == 1
+ stack.pop()
+
+ def __str__(self):
+ """Convert msg object to string."""
+ lxml.etree.tostring(self.toXML(), pretty_print = True, encoding = "us-ascii")
+
+ def toXML(self):
+ """Generate top-level PDU."""
+ elt = lxml.etree.Element("{%s}msg" % (self.xmlns), nsmap = self.nsmap, version = str(self.version), type = self.type)
+ elt.extend([i.toXML() for i in self])
+ return elt
diff --git a/rpkid/testbed.py b/rpkid/testbed.py
index 26621ba8..2c6cf53f 100644
--- a/rpkid/testbed.py
+++ b/rpkid/testbed.py
@@ -637,7 +637,7 @@ class allocation(object):
self.repository_id = self.call_rpkid(rpki.left_right.repository_elt.make_pdu(
action = "create", self_id = self.self_id, bsc_id = self.bsc_id,
bpki_cms_cert = repository_cert, bpki_https_cert = repository_cert,
- peer_contact_uri = "https://localhost:%d/client/%d/" % (pubd_port, client_id))).repository_id
+ peer_contact_uri = "https://localhost:%d/client/%d" % (pubd_port, client_id))).repository_id
rpki.log.info("Creating rpkid parent object for %s" % self.name)
if self.is_root():