diff options
Diffstat (limited to 'rpkid')
-rw-r--r-- | rpkid/rpki/left_right.py | 215 | ||||
-rw-r--r-- | rpkid/rpki/publication.py | 90 | ||||
-rw-r--r-- | rpkid/rpki/sax_utils.py | 98 | ||||
-rw-r--r-- | rpkid/rpki/up_down.py | 4 | ||||
-rw-r--r-- | rpkid/rpki/xml_utils.py | 278 | ||||
-rw-r--r-- | rpkid/testbed.py | 2 |
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(): |