RPKI Engine 1.0

publication.py (3793)

Go to the documentation of this file.
00001 """
00002 RPKI "publication" protocol.
00003 
00004 $Id: publication.py 3793 2011-04-27 04:34:52Z sra $
00005 
00006 Copyright (C) 2009--2011  Internet Systems Consortium ("ISC")
00007 
00008 Permission to use, copy, modify, and distribute this software for any
00009 purpose with or without fee is hereby granted, provided that the above
00010 copyright notice and this permission notice appear in all copies.
00011 
00012 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00013 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00014 AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00015 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00016 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00017 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00018 PERFORMANCE OF THIS SOFTWARE.
00019 
00020 Portions copyright (C) 2007--2008  American Registry for Internet Numbers ("ARIN")
00021 
00022 Permission to use, copy, modify, and distribute this software for any
00023 purpose with or without fee is hereby granted, provided that the above
00024 copyright notice and this permission notice appear in all copies.
00025 
00026 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00027 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00028 AND FITNESS.  IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00029 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00030 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00031 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00032 PERFORMANCE OF THIS SOFTWARE.
00033 """
00034 
00035 import os, errno
00036 import rpki.resource_set, rpki.x509, rpki.sql, rpki.exceptions, rpki.xml_utils
00037 import rpki.http, rpki.up_down, rpki.relaxng, rpki.sundial, rpki.log, rpki.roa
00038 
00039 class publication_namespace(object):
00040   """
00041   XML namespace parameters for publication protocol.
00042   """
00043 
00044   xmlns = "http://www.hactrn.net/uris/rpki/publication-spec/"
00045   nsmap = { None : xmlns }
00046 
00047 class control_elt(rpki.xml_utils.data_elt, rpki.sql.sql_persistent, publication_namespace):
00048   """
00049   Virtual class for control channel objects.
00050   """
00051 
00052   def serve_dispatch(self, r_msg, cb, eb):
00053     """
00054     Action dispatch handler.  This needs special handling because we
00055     need to make sure that this PDU arrived via the control channel.
00056     """
00057     if self.client is not None:
00058       raise rpki.exceptions.BadQuery, "Control query received on client channel"
00059     rpki.xml_utils.data_elt.serve_dispatch(self, r_msg, cb, eb)
00060 
00061 class config_elt(control_elt):
00062   """
00063   <config/> element.  This is a little weird because there should
00064   never be more than one row in the SQL config table, but we have to
00065   put the BPKI CRL somewhere and SQL is the least bad place available.
00066 
00067   So we reuse a lot of the SQL machinery, but we nail config_id at 1,
00068   we don't expose it in the XML protocol, and we only support the get
00069   and set actions.
00070   """
00071 
00072   attributes = ("action", "tag")
00073   element_name = "config"
00074   elements = ("bpki_crl",)
00075 
00076   sql_template = rpki.sql.template("config", "config_id", ("bpki_crl", rpki.x509.CRL))
00077 
00078   wired_in_config_id = 1
00079 
00080   def startElement(self, stack, name, attrs):
00081     """
00082     StartElement() handler for config object.  This requires special
00083     handling because of the weird way we treat config_id.
00084     """
00085     control_elt.startElement(self, stack, name, attrs)
00086     self.config_id = self.wired_in_config_id
00087 
00088   @classmethod
00089   def fetch(cls, gctx):
00090     """
00091     Fetch the config object from SQL.  This requires special handling
00092     because of the weird way we treat config_id.
00093     """
00094     return cls.sql_fetch(gctx, cls.wired_in_config_id)
00095 
00096   def serve_set(self, r_msg, cb, eb):
00097     """
00098     Handle a set action.  This requires special handling because
00099     config doesn't support the create method.
00100     """
00101     if self.sql_fetch(self.gctx, self.config_id) is None:
00102       control_elt.serve_create(self, r_msg, cb, eb)
00103     else:
00104       control_elt.serve_set(self, r_msg, cb, eb)
00105 
00106   def serve_fetch_one_maybe(self):
00107     """
00108     Find the config object on which a get or set method should
00109     operate.
00110     """
00111     return self.sql_fetch(self.gctx, self.config_id)
00112 
00113 class client_elt(control_elt):
00114   """
00115   <client/> element.
00116   """
00117 
00118   element_name = "client"
00119   attributes = ("action", "tag", "client_handle", "base_uri")
00120   elements = ("bpki_cert", "bpki_glue")
00121 
00122   sql_template = rpki.sql.template("client", "client_id", "client_handle", "base_uri",
00123                                    ("bpki_cert", rpki.x509.X509),
00124                                    ("bpki_glue", rpki.x509.X509),
00125                                    ("last_cms_timestamp", rpki.sundial.datetime))
00126 
00127   base_uri  = None
00128   bpki_cert = None
00129   bpki_glue = None
00130 
00131   def serve_fetch_one_maybe(self):
00132     """
00133     Find the client object on which a get, set, or destroy method
00134     should operate, or which would conflict with a create method.
00135     """
00136     return self.sql_fetch_where1(self.gctx, "client_handle = %s", self.client_handle)
00137 
00138   def serve_fetch_all(self):
00139     """
00140     Find client objects on which a list method should operate.
00141     """
00142     return self.sql_fetch_all(self.gctx)
00143 
00144   def check_allowed_uri(self, uri):
00145     if not uri.startswith(self.base_uri):
00146       raise rpki.exceptions.ForbiddenURI
00147 
00148 class publication_object_elt(rpki.xml_utils.base_elt, publication_namespace):
00149   """
00150   Virtual class for publishable objects.  These have very similar
00151   syntax, differences lie in underlying datatype and methods.  XML
00152   methods are a little different from the pattern used for objects
00153   that support the create/set/get/list/destroy actions, but
00154   publishable objects don't go in SQL either so these classes would be
00155   different in any case.
00156   """
00157 
00158   attributes = ("action", "tag", "client_handle", "uri")
00159   payload_type = None
00160   payload = None
00161 
00162   def endElement(self, stack, name, text):
00163     """
00164     Handle a publishable element element.
00165     """
00166     assert name == self.element_name, "Unexpected name %s, stack %s" % (name, stack)
00167     if text:
00168       self.payload = self.payload_type(Base64 = text)
00169     stack.pop()
00170 
00171   def toXML(self):
00172     """
00173     Generate XML element for publishable object.
00174     """
00175     elt = self.make_elt()
00176     if self.payload:
00177       elt.text = self.payload.get_Base64()
00178     return elt
00179 
00180   def serve_dispatch(self, r_msg, cb, eb):
00181     """
00182     Action dispatch handler.
00183     """
00184     try:
00185       if self.client is None:
00186         raise rpki.exceptions.BadQuery, "Client query received on control channel"
00187       dispatch = { "publish"  : self.serve_publish,
00188                    "withdraw" : self.serve_withdraw }
00189       if self.action not in dispatch:
00190         raise rpki.exceptions.BadQuery, "Unexpected query: action %s" % self.action
00191       self.client.check_allowed_uri(self.uri)
00192       dispatch[self.action]()
00193       r_pdu = self.__class__()
00194       r_pdu.action = self.action
00195       r_pdu.tag = self.tag
00196       r_pdu.uri = self.uri
00197       r_msg.append(r_pdu)
00198       cb()
00199     except rpki.exceptions.NoObjectAtURI, e:
00200       # This can happen when we're cleaning up from a prior mess, so
00201       # we generate a <report_error/> PDU then carry on.
00202       r_msg.append(report_error_elt.from_exception(e, self.tag))
00203       cb()
00204 
00205   def serve_publish(self):
00206     """
00207     Publish an object.
00208     """
00209     rpki.log.info("Publishing %s" % self.uri)
00210     filename = self.uri_to_filename()
00211     filename_tmp = filename + ".tmp"
00212     dirname = os.path.dirname(filename)
00213     if not os.path.isdir(dirname):
00214       os.makedirs(dirname)
00215     f = open(filename_tmp, "wb")
00216     f.write(self.payload.get_DER())
00217     f.close()
00218     os.rename(filename_tmp, filename)
00219 
00220   def serve_withdraw(self):
00221     """
00222     Withdraw an object.
00223     """
00224     rpki.log.info("Withdrawing %s" % self.uri)
00225     filename = self.uri_to_filename()
00226     try:
00227       os.remove(filename)
00228     except OSError, e:
00229       if e.errno == errno.ENOENT:
00230         raise rpki.exceptions.NoObjectAtURI, "No object published at %s" % self.uri
00231       else:
00232         raise
00233 
00234   def uri_to_filename(self):
00235     """
00236     Convert a URI to a local filename.
00237     """
00238     if not self.uri.startswith("rsync://"):
00239       raise rpki.exceptions.BadURISyntax, self.uri
00240     path = self.uri.split("/")[3:]
00241     if not self.gctx.publication_multimodule:
00242       del path[0]
00243     path.insert(0, self.gctx.publication_base.rstrip("/"))
00244     filename = "/".join(path)
00245     if "/../" in filename or filename.endswith("/.."):
00246       raise rpki.exceptions.BadURISyntax, filename
00247     return filename
00248 
00249   @classmethod
00250   def make_publish(cls, uri, obj, tag = None):
00251     """
00252     Construct a publication PDU.
00253     """
00254     assert cls.payload_type is not None and type(obj) is cls.payload_type
00255     return cls.make_pdu(action = "publish", uri = uri, payload = obj, tag = tag)      
00256 
00257   @classmethod
00258   def make_withdraw(cls, uri, obj, tag = None):
00259     """
00260     Construct a withdrawal PDU.
00261     """
00262     assert cls.payload_type is not None and type(obj) is cls.payload_type
00263     return cls.make_pdu(action = "withdraw", uri = uri, tag = tag)
00264 
00265   def raise_if_error(self):
00266     """
00267     No-op, since this is not a <report_error/> PDU.
00268     """
00269     pass
00270 
00271 class certificate_elt(publication_object_elt):
00272   """
00273   <certificate/> element.
00274   """
00275 
00276   element_name = "certificate"
00277   payload_type = rpki.x509.X509
00278 
00279 class crl_elt(publication_object_elt):
00280   """
00281   <crl/> element.
00282   """
00283 
00284   element_name = "crl"
00285   payload_type = rpki.x509.CRL
00286   
00287 class manifest_elt(publication_object_elt):
00288   """
00289   <manifest/> element.
00290   """
00291 
00292   element_name = "manifest"
00293   payload_type = rpki.x509.SignedManifest
00294 
00295 class roa_elt(publication_object_elt):
00296   """
00297   <roa/> element.
00298   """
00299 
00300   element_name = "roa"
00301   payload_type = rpki.x509.ROA
00302 
00303 class ghostbuster_elt(publication_object_elt):
00304   """
00305   <ghostbuster/> element.
00306   """
00307 
00308   element_name = "ghostbuster"
00309   payload_type = rpki.x509.Ghostbuster
00310 
00311 publication_object_elt.obj2elt = dict(
00312   (e.payload_type, e) for e in
00313   (certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt))
00314 
00315 class report_error_elt(rpki.xml_utils.text_elt, publication_namespace):
00316   """
00317   <report_error/> element.
00318   """
00319 
00320   element_name = "report_error"
00321   attributes = ("tag", "error_code")
00322   text_attribute = "error_text"
00323 
00324   error_text = None
00325 
00326   @classmethod
00327   def from_exception(cls, e, tag = None):
00328     """
00329     Generate a <report_error/> element from an exception.
00330     """
00331     self = cls()
00332     self.tag = tag
00333     self.error_code = e.__class__.__name__
00334     self.error_text = str(e)
00335     return self
00336 
00337   def __str__(self):
00338     s = ""
00339     if getattr(self, "tag", None) is not None:
00340       s += "[%s] " % self.tag
00341     s += self.error_code
00342     if getattr(self, "error_text", None) is not None:
00343       s += ": " + self.error_text
00344     return s
00345 
00346   def raise_if_error(self):
00347     """
00348     Raise exception associated with this <report_error/> PDU.
00349     """
00350     t = rpki.exceptions.__dict__.get(self.error_code)
00351     if isinstance(t, type) and issubclass(t, rpki.exceptions.RPKI_Exception):
00352       raise t, getattr(self, "text", None)
00353     else:
00354       raise rpki.exceptions.BadPublicationReply, "Unexpected response from pubd: %s" % self
00355 
00356 class msg(rpki.xml_utils.msg, publication_namespace):
00357   """
00358   Publication PDU.
00359   """
00360 
00361   ## @var version
00362   # Protocol version
00363   version = 1
00364 
00365   ## @var pdus
00366   # Dispatch table of PDUs for this protocol.
00367   pdus = dict((x.element_name, x) for x in
00368               (config_elt, client_elt, certificate_elt, crl_elt, manifest_elt, roa_elt, ghostbuster_elt, report_error_elt))
00369 
00370   def serve_top_level(self, gctx, client, cb):
00371     """
00372     Serve one msg PDU.
00373     """
00374     if not self.is_query():
00375       raise rpki.exceptions.BadQuery, "Message type is not query"
00376     r_msg = self.__class__.reply()
00377 
00378     def loop(iterator, q_pdu):
00379 
00380       def fail(e):
00381         if not isinstance(e, rpki.exceptions.NotFound):
00382           rpki.log.traceback()
00383         r_msg.append(report_error_elt.from_exception(e, q_pdu.tag))
00384         cb(r_msg)
00385 
00386       try:
00387         q_pdu.gctx = gctx
00388         q_pdu.client = client
00389         q_pdu.serve_dispatch(r_msg, iterator, fail)
00390       except (rpki.async.ExitNow, SystemExit):
00391         raise
00392       except Exception, e:
00393         fail(e)
00394 
00395     def done():
00396       cb(r_msg)
00397 
00398     rpki.async.iterator(self, loop, done)
00399 
00400 class sax_handler(rpki.xml_utils.sax_handler):
00401   """
00402   SAX handler for publication protocol.
00403   """
00404 
00405   pdu = msg
00406   name = "msg"
00407   version = "1"
00408 
00409 class cms_msg(rpki.x509.XML_CMS_object):
00410   """
00411   Class to hold a CMS-signed publication PDU.
00412   """
00413 
00414   encoding = "us-ascii"
00415   schema = rpki.relaxng.publication
00416   saxify = sax_handler.saxify
 All Classes Namespaces Files Functions Variables