RPKI Engine 1.0
|
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