00001 """
00002 HTTPS utilities, both client and server.
00003
00004 At the moment this only knows how to use the PEM certs in my
00005 subversion repository; generalizing it would not be hard, but the more
00006 general version should use SQL anyway.
00007
00008 $Id: https.py 2574 2009-07-04 22:34:50Z sra $
00009
00010 Copyright (C) 2009 Internet Systems Consortium ("ISC")
00011
00012 Permission to use, copy, modify, and distribute this software for any
00013 purpose with or without fee is hereby granted, provided that the above
00014 copyright notice and this permission notice appear in all copies.
00015
00016 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
00017 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00018 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
00019 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00020 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00021 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00022 PERFORMANCE OF THIS SOFTWARE.
00023
00024 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00025
00026 Permission to use, copy, modify, and distribute this software for any
00027 purpose with or without fee is hereby granted, provided that the above
00028 copyright notice and this permission notice appear in all copies.
00029
00030 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00031 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00032 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00033 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00034 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00035 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00036 PERFORMANCE OF THIS SOFTWARE.
00037 """
00038
00039 import time, socket, asyncore, asynchat, urlparse, sys
00040 import rpki.async, rpki.sundial, rpki.x509, rpki.exceptions, rpki.log
00041 import POW
00042
00043 rpki_content_type = "application/x-rpki"
00044
00045
00046
00047
00048
00049 debug_tls_certs = True
00050
00051
00052 debug = True
00053
00054
00055 want_persistent_client = True
00056 want_persistent_server = True
00057
00058
00059 default_timeout = rpki.sundial.timedelta(seconds = 90)
00060
00061 default_http_version = (1, 0)
00062
00063 class http_message(object):
00064
00065 software_name = "ISC RPKI library"
00066
00067 def __init__(self, version = None, body = None, headers = None):
00068 self.version = version
00069 self.body = body
00070 self.headers = headers
00071 self.normalize_headers()
00072
00073 def normalize_headers(self, headers = None):
00074 if headers is None:
00075 headers = () if self.headers is None else self.headers.items()
00076 translate_underscore = True
00077 else:
00078 translate_underscore = False
00079 result = {}
00080 for k, v in headers:
00081 if translate_underscore:
00082 k = k.replace("_", "-")
00083 k = "-".join(s.capitalize() for s in k.split("-"))
00084 v = v.strip()
00085 if k in result:
00086 result[k] += ", " + v
00087 else:
00088 result[k] = v
00089 self.headers = result
00090
00091 @classmethod
00092 def parse_from_wire(cls, headers):
00093 self = cls()
00094 headers = headers.split("\r\n")
00095 self.parse_first_line(*headers.pop(0).split(None, 2))
00096 for i in xrange(len(headers) - 2, -1, -1):
00097 if headers[i + 1][0].isspace():
00098 headers[i] += headers[i + 1]
00099 del headers[i + 1]
00100 self.normalize_headers([h.split(":", 1) for h in headers])
00101 return self
00102
00103 def format(self):
00104 s = self.format_first_line()
00105 if self.body is not None:
00106 assert isinstance(self.body, str)
00107 self.headers["Content-Length"] = len(self.body)
00108 for kv in self.headers.iteritems():
00109 s += "%s: %s\r\n" % kv
00110 s += "\r\n"
00111 if self.body is not None:
00112 s += self.body
00113 return s
00114
00115 def __str__(self):
00116 return self.format()
00117
00118 def parse_version(self, version):
00119 if version[:5] != "HTTP/":
00120 raise RuntimeError, "Couldn't parse version %s" % version
00121 self.version = tuple(int(i) for i in version[5:].split("."))
00122
00123 def persistent(self):
00124 c = self.headers.get("Connection")
00125 if self.version == (1, 1):
00126 return c is None or "close" not in c.lower()
00127 elif self.version == (1, 0):
00128 return c is not None and "keep-alive" in c.lower()
00129 else:
00130 return False
00131
00132 class http_request(http_message):
00133
00134 def __init__(self, cmd = None, path = None, version = default_http_version, body = None, callback = None, errback = None, **headers):
00135 if cmd is not None and cmd != "POST" and body is not None:
00136 raise RuntimeError
00137 http_message.__init__(self, version = version, body = body, headers = headers)
00138 self.cmd = cmd
00139 self.path = path
00140 self.callback = callback
00141 self.errback = errback
00142 self.retried = False
00143
00144 def parse_first_line(self, cmd, path, version):
00145 self.parse_version(version)
00146 self.cmd = cmd
00147 self.path = path
00148
00149 def format_first_line(self):
00150 self.headers.setdefault("User-Agent", self.software_name)
00151 return "%s %s HTTP/%d.%d\r\n" % (self.cmd, self.path, self.version[0], self.version[1])
00152
00153 class http_response(http_message):
00154
00155 def __init__(self, code = None, reason = None, version = default_http_version, body = None, **headers):
00156 http_message.__init__(self, version = version, body = body, headers = headers)
00157 self.code = code
00158 self.reason = reason
00159
00160 def parse_first_line(self, version, code, reason):
00161 self.parse_version(version)
00162 self.code = int(code)
00163 self.reason = reason
00164
00165 def format_first_line(self):
00166 self.headers.setdefault("Date", time.strftime("%a, %d %b %Y %T GMT"))
00167 self.headers.setdefault("Server", self.software_name)
00168 return "HTTP/%d.%d %s %s\r\n" % (self.version[0], self.version[1], self.code, self.reason)
00169
00170 def logger(self, msg):
00171 if debug:
00172 rpki.log.debug("%r: %s" % (self, msg))
00173
00174 class http_stream(asynchat.async_chat):
00175
00176 log = logger
00177 tls = None
00178 retry_read = None
00179 retry_write = None
00180
00181 timeout = default_timeout
00182
00183 def __init__(self, conn = None):
00184 asynchat.async_chat.__init__(self, conn = conn)
00185 self.buffer = []
00186 self.timer = rpki.async.timer(self.handle_timeout)
00187 self.restart()
00188
00189 def restart(self):
00190 assert not self.buffer
00191 self.chunk_handler = None
00192 self.set_terminator("\r\n\r\n")
00193 if self.timeout is not None:
00194 self.timer.set(self.timeout)
00195 else:
00196 self.timer.cancel()
00197
00198 def update_timeout(self):
00199 if self.timeout is not None:
00200 self.timer.set(self.timeout)
00201 else:
00202 self.timer.cancel()
00203
00204 def collect_incoming_data(self, data):
00205 """
00206 Buffer the data
00207 """
00208 self.buffer.append(data)
00209 self.update_timeout()
00210
00211 def get_buffer(self):
00212 val = "".join(self.buffer)
00213 self.buffer = []
00214 return val
00215
00216 def found_terminator(self):
00217 self.update_timeout()
00218 if self.chunk_handler:
00219 self.chunk_handler()
00220 elif not isinstance(self.get_terminator(), str):
00221 self.handle_body()
00222 else:
00223 self.msg = self.parse_type.parse_from_wire(self.get_buffer())
00224 if self.msg.version == (1, 1) and "chunked" in self.msg.headers.get("Transfer-Encoding", "").lower():
00225 self.msg.body = []
00226 self.chunk_handler = self.chunk_header
00227 self.set_terminator("\r\n")
00228 elif "Content-Length" in self.msg.headers:
00229 self.set_terminator(int(self.msg.headers["Content-Length"]))
00230 else:
00231 self.handle_no_content_length()
00232
00233 def chunk_header(self):
00234 n = int(self.get_buffer().partition(";")[0], 16)
00235 self.log("Chunk length %s" % n)
00236 if n:
00237 self.chunk_handler = self.chunk_body
00238 self.set_terminator(n)
00239 else:
00240 self.msg.body = "".join(self.msg.body)
00241 self.chunk_handler = self.chunk_discard_trailer
00242
00243 def chunk_body(self):
00244 self.log("Chunk body")
00245 self.msg.body += self.buffer
00246 self.buffer = []
00247 self.chunk_handler = self.chunk_discard_crlf
00248 self.set_terminator("\r\n")
00249
00250 def chunk_discard_crlf(self):
00251 self.log("Chunk CRLF")
00252 s = self.get_buffer()
00253 assert s == "", "Expected chunk CRLF, got '%s'" % s
00254 self.chunk_handler = self.chunk_header
00255
00256 def chunk_discard_trailer(self):
00257 self.log("Chunk trailer")
00258 s = self.get_buffer()
00259 assert s == "", "Expected end of chunk trailers, got '%s'" % s
00260 self.chunk_handler = None
00261 self.handle_message()
00262
00263 def handle_body(self):
00264 self.msg.body = self.get_buffer()
00265 self.handle_message()
00266
00267 def handle_error(self):
00268 if sys.exc_info()[0] is SystemExit:
00269 self.log("Caught SystemExit, propagating")
00270 raise
00271 else:
00272 self.log("Error in HTTP stream handler")
00273 rpki.log.traceback()
00274 self.log("Closing due to error")
00275 self.close()
00276
00277 def handle_timeout(self):
00278 self.log("Timeout, closing")
00279 self.close()
00280
00281 def handle_close(self):
00282 self.log("Close event in HTTP stream handler")
00283 self.timer.cancel()
00284
00285 def send(self, data):
00286 assert self.retry_read is None and self.retry_write is None
00287 return self.tls.write(data)
00288
00289 def recv(self, buffer_size):
00290 assert self.retry_read is None and self.retry_write is None
00291 return self.tls.read(buffer_size)
00292
00293 def readable(self):
00294 return self.retry_read is not None or (self.retry_write is None and asynchat.async_chat.readable(self))
00295
00296 def writeable(self):
00297 return self.retry_write is not None or (self.retry_read is None and asynchat.async_chat.writeable(self))
00298
00299 def handle_read(self):
00300 assert self.retry_write is None
00301 if self.retry_read is not None:
00302 thunk = self.retry_read
00303 self.retry_read = None
00304 self.log("Retrying TLS read %r" % thunk)
00305 thunk()
00306 else:
00307 try:
00308 asynchat.async_chat.handle_read(self)
00309 except POW.WantReadError:
00310 self.retry_read = self.handle_read
00311 except POW.WantWriteError:
00312 self.retry_write = self.handle_read
00313 except POW.ZeroReturnError:
00314 self.log("ZeroReturn in handle_read()")
00315 self.close()
00316 except POW.SSLUnexpectedEOFError:
00317 self.log("SSLUnexpectedEOF in handle_read()")
00318 self.close(force = True)
00319
00320 def handle_write(self):
00321 assert self.retry_read is None
00322 if self.retry_write is not None:
00323 thunk = self.retry_write
00324 self.retry_write = None
00325 thunk()
00326 self.log("Retrying TLS write %r" % thunk)
00327 else:
00328 asynchat.async_chat.handle_write(self)
00329
00330 def initate_send(self):
00331 assert self.retry_read is None and self.retry_write is None
00332 try:
00333 asynchat.async_chat.initiate_send(self)
00334 except POW.WantReadError:
00335 self.retry_read = self.initiate_send
00336 except POW.WantWriteError:
00337 self.retry_write = self.initiate_send
00338 except POW.ZeroReturnError:
00339 self.log("ZeroReturn in initiate_send()")
00340 self.close()
00341 except POW.SSLUnexpectedEOFError:
00342 self.log("SSLUnexpectedEOF in initiate_send()")
00343 self.close(force = True)
00344
00345 def close(self, force = False):
00346 self.log("Close requested")
00347 assert self.retry_read is None and self.retry_write is None
00348 if self.tls is not None:
00349 try:
00350 ret = self.tls.shutdown()
00351 self.log("tls.shutdown() returned %s, force_shutdown %s" % (ret, force))
00352 if ret or force:
00353 self.tls = None
00354 asynchat.async_chat.close(self)
00355 self.handle_close()
00356 except POW.WantReadError:
00357 self.retry_read = self.close
00358 except POW.WantWriteError:
00359 self.retry_write = self.close
00360
00361 def log_cert(self, tag, x):
00362 if debug_tls_certs:
00363 self.log("HTTPS %s cert %r issuer %s [%s] subject %s [%s]" % (tag, x, x.getIssuer(), x.hAKI(), x.getSubject(), x.hSKI()))
00364
00365 class http_server(http_stream):
00366
00367 parse_type = http_request
00368
00369 def __init__(self, conn, handlers, cert = None, key = None, ta = (), dynamic_ta = None):
00370 self.log("Starting")
00371 self.handlers = handlers
00372 http_stream.__init__(self, conn = conn)
00373 self.expect_close = not want_persistent_server
00374
00375 self.log("cert %r key %r ta %r dynamic_ta %r" % (cert, key, ta, dynamic_ta))
00376
00377 self.tls = POW.Ssl(POW.TLSV1_SERVER_METHOD)
00378 self.log_cert("server", cert)
00379 self.tls.useCertificate(cert.get_POW())
00380 self.tls.useKey(key.get_POW())
00381 ta = rpki.x509.X509.normalize_chain(dynamic_ta() if dynamic_ta else ta)
00382 if not ta:
00383 raise RuntimeError, "No trust anchor(s) specified, this is unlikely to work"
00384 for x in ta:
00385 self.log_cert("trusted", x)
00386 self.tls.addTrust(x.get_POW())
00387 self.tls.setVerifyMode(POW.SSL_VERIFY_PEER | POW.SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
00388
00389 self.tls.setFd(self.fileno())
00390 self.tls_accept()
00391
00392 def tls_accept(self):
00393 try:
00394 self.tls.accept()
00395 except POW.WantReadError:
00396 self.retry_read = self.tls_accept
00397 except POW.WantWriteError:
00398 self.retry_write = self.tls_accept
00399
00400 def handle_no_content_length(self):
00401 self.handle_message()
00402
00403 def find_handler(self, path):
00404 """
00405 Helper method to search self.handlers.
00406 """
00407 for s, h in self.handlers:
00408 if path.startswith(s):
00409 return h
00410 return None
00411
00412 def handle_message(self):
00413 self.log("Received request %s %s" % (self.msg.cmd, self.msg.path))
00414 if not self.msg.persistent():
00415 self.expect_close = True
00416 handler = self.find_handler(self.msg.path)
00417 error = None
00418 if self.msg.cmd != "POST":
00419 error = 501, "No handler for method %s" % self.msg.cmd
00420 elif self.msg.headers["Content-Type"] != rpki_content_type:
00421 error = 415, "No handler for Content-Type %s" % self.headers["Content-Type"]
00422 elif handler is None:
00423 error = 404, "No handler for URL %s" % self.msg.path
00424 if error is None:
00425 try:
00426 handler(self.msg.body, self.msg.path, self.send_reply)
00427 except (rpki.async.ExitNow, SystemExit):
00428 raise
00429 except Exception, edata:
00430 rpki.log.traceback()
00431 self.send_error(500, "Unhandled exception %s" % edata)
00432 else:
00433 self.send_error(code = error[0], reason = error[1])
00434
00435 def send_error(self, code, reason):
00436 self.send_message(code = code, reason = reason)
00437
00438 def send_reply(self, code, body):
00439 self.send_message(code = code, body = body)
00440
00441 def send_message(self, code, reason = "OK", body = None):
00442 self.log("Sending response %s %s" % (code, reason))
00443 if code >= 400:
00444 self.expect_close = True
00445 msg = http_response(code = code, reason = reason, body = body,
00446 Content_Type = rpki_content_type,
00447 Connection = "Close" if self.expect_close else "Keep-Alive")
00448 self.push(msg.format())
00449 if self.expect_close:
00450 self.log("Closing")
00451 self.timer.cancel()
00452 self.close_when_done()
00453 else:
00454 self.log("Listening for next message")
00455 self.restart()
00456
00457 class http_listener(asyncore.dispatcher):
00458
00459 log = logger
00460
00461 def __init__(self, handlers, port = 80, host = "", cert = None, key = None, ta = None, dynamic_ta = None):
00462 self.log("Listener cert %r key %r ta %r dynamic_ta %r" % (cert, key, ta, dynamic_ta))
00463 asyncore.dispatcher.__init__(self)
00464 self.handlers = handlers
00465 self.cert = cert
00466 self.key = key
00467 self.ta = ta
00468 self.dynamic_ta = dynamic_ta
00469 try:
00470 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
00471 self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
00472 self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
00473 self.bind((host, port))
00474 self.listen(5)
00475 except (rpki.async.ExitNow, SystemExit):
00476 raise
00477 except:
00478 self.handle_error()
00479 self.log("Listening on %r, handlers %r" % ((host, port), handlers))
00480
00481 def handle_accept(self):
00482 self.log("Accepting connection")
00483 try:
00484 http_server(conn = self.accept()[0], handlers = self.handlers, cert = self.cert, key = self.key, ta = self.ta, dynamic_ta = self.dynamic_ta)
00485 except (rpki.async.ExitNow, SystemExit):
00486 raise
00487 except:
00488 self.handle_error()
00489
00490 def handle_error(self):
00491 if sys.exc_info()[0] is SystemExit:
00492 self.log("Caught SystemExit, propagating")
00493 raise
00494 else:
00495 self.log("Error in HTTP listener")
00496 rpki.log.traceback()
00497
00498 class http_client(http_stream):
00499
00500 parse_type = http_response
00501
00502 def __init__(self, queue, hostport, cert = None, key = None, ta = ()):
00503 self.log("Creating new connection to %s" % repr(hostport))
00504 self.log("cert %r key %r ta %r" % (cert, key, ta))
00505 http_stream.__init__(self)
00506 self.queue = queue
00507 self.hostport = hostport
00508 self.state = "opening"
00509 self.expect_close = not want_persistent_client
00510 self.cert = cert
00511 self.key = key
00512 self.ta = rpki.x509.X509.normalize_chain(ta)
00513
00514 def start(self):
00515 try:
00516 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
00517 self.connect(self.hostport)
00518 except (rpki.async.ExitNow, SystemExit):
00519 raise
00520 except:
00521 self.handle_error()
00522
00523 def handle_connect(self):
00524 self.log("Connected")
00525 self.set_state("idle")
00526
00527 self.tls = POW.Ssl(POW.TLSV1_CLIENT_METHOD)
00528 self.log_cert("client", self.cert)
00529 self.tls.useCertificate(self.cert.get_POW())
00530 self.tls.useKey(self.key.get_POW())
00531 if not self.ta:
00532 raise RuntimeError, "No trust anchor(s) specified, this is unlikely to work"
00533 for x in self.ta:
00534 self.log_cert("trusted", x)
00535 self.tls.addTrust(x.get_POW())
00536 self.tls.setVerifyMode(POW.SSL_VERIFY_PEER | POW.SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
00537
00538 self.tls.setFd(self.fileno())
00539 self.tls_connect()
00540
00541 def tls_connect(self):
00542 try:
00543 self.tls.connect()
00544 except POW.WantReadError:
00545 self.retry_read = self.tls_connect
00546 except POW.WantWriteError:
00547 self.retry_write = self.tls_connect
00548 else:
00549 self.queue.send_request()
00550
00551 def set_state(self, state):
00552 self.log("State transition %s => %s" % (self.state, state))
00553 self.state = state
00554
00555 def handle_no_content_length(self):
00556 self.set_terminator(None)
00557
00558 def send_request(self, msg):
00559 self.log("Sending request %r" % msg)
00560 assert self.state == "idle", "%r: state should be idle, is %s" % (self, self.state)
00561 self.set_state("request-sent")
00562 msg.headers["Connection"] = "Close" if self.expect_close else "Keep-Alive"
00563 self.push(msg.format())
00564 self.restart()
00565
00566 def handle_message(self):
00567 self.log("Message received, state %s" % self.state)
00568
00569 if not self.msg.persistent():
00570 self.expect_close = True
00571
00572 if self.state != "request-sent":
00573 if self.state == "closing":
00574 assert not self.msg.body
00575 self.log("Ignoring empty response received while closing")
00576 return
00577 raise RuntimeError, "%r received message while in unexpected state %s" % (self, self.state)
00578
00579 if self.expect_close:
00580 self.log("Closing")
00581 self.set_state("closing")
00582 self.queue.detach(self)
00583 self.close_when_done()
00584 else:
00585 self.log("Idling")
00586 self.set_state("idle")
00587 self.update_timeout()
00588
00589 if self.msg.code == 200:
00590 self.queue.return_result(self.msg)
00591 else:
00592 self.queue.return_result(rpki.exceptions.HTTPRequestFailed(
00593 "HTTPS request failed with status %s, reason %s, response %s" % (self.msg.code, self.msg.reason, self.msg.body)))
00594
00595 def handle_close(self):
00596 http_stream.handle_close(self)
00597 self.log("State %s" % self.state)
00598 self.queue.detach(self)
00599 if self.get_terminator() is None:
00600 self.handle_body()
00601 elif self.state == "request-sent":
00602 self.queue.return_result(rpki.exceptions.HTTPSClientAborted("HTTPS request aborted by close event"))
00603
00604 def handle_timeout(self):
00605 if self.state != "idle":
00606 self.log("Timeout while in state %s" % self.state)
00607 http_stream.handle_timeout(self)
00608 self.queue.detach(self)
00609
00610 def handle_error(self):
00611 http_stream.handle_error(self)
00612 self.queue.detach(self)
00613 etype, edata = sys.exc_info()[:2]
00614 if etype in (SystemExit, rpki.async.ExitNow):
00615 raise edata
00616 else:
00617 self.queue.return_result(edata)
00618
00619 class http_queue(object):
00620
00621 log = logger
00622
00623 def __init__(self, hostport, cert = None, key = None, ta = ()):
00624 self.log("Creating queue for %s" % repr(hostport))
00625 self.log("cert %r key %r ta %r" % (cert, key, ta))
00626 self.hostport = hostport
00627 self.client = None
00628 self.queue = []
00629 self.cert = cert
00630 self.key = key
00631 self.ta = ta
00632
00633 def request(self, *requests):
00634 self.log("Adding requests %r" % requests)
00635 self.queue.extend(requests)
00636
00637 def restart(self):
00638 if self.client is None:
00639 client = http_client(self, self.hostport, cert = self.cert, key = self.key, ta = self.ta)
00640 self.log("Attaching client %r" % client)
00641 self.client = client
00642 self.client.start()
00643 elif self.client.state == "idle":
00644 self.log("Sending request to existing client %r" % self.client)
00645 self.send_request()
00646 else:
00647 self.log("Client exists and is not idle")
00648
00649 def send_request(self):
00650 if self.queue:
00651 self.client.send_request(self.queue[0])
00652
00653 def detach(self, client):
00654 if client is self.client:
00655 self.log("Detaching client %r" % client)
00656 self.client = None
00657
00658 def return_result(self, result):
00659
00660 if not self.queue:
00661 self.log("No caller, this should not happen. Dropping result %r" % result)
00662
00663 req = self.queue.pop(0)
00664 self.log("Dequeuing request %r" % req)
00665
00666 try:
00667 if isinstance(result, http_response):
00668 self.log("Returning result %r to caller" % result)
00669 req.callback(result.body)
00670 else:
00671 assert isinstance(result, Exception)
00672 self.log("Returning exception %r to caller: %s" % (result, result))
00673 req.errback(result)
00674 except (rpki.async.ExitNow, SystemExit):
00675 raise
00676 except:
00677 self.log("Unhandled exception from callback")
00678 rpki.log.traceback()
00679
00680 self.log("Queue: %r" % self.queue)
00681
00682 if self.queue:
00683 self.restart()
00684
00685 client_queues = {}
00686
00687 def client(msg, client_key, client_cert, server_ta, url, callback, errback):
00688 """
00689 Open client HTTPS connection, send a message, set up callbacks to
00690 handle response.
00691 """
00692
00693 u = urlparse.urlparse(url)
00694
00695 if (u.scheme not in ("", "https") or
00696 u.username is not None or
00697 u.password is not None or
00698 u.params != "" or
00699 u.query != "" or
00700 u.fragment != ""):
00701 raise rpki.exceptions.BadClientURL, "Unusable URL %s" % url
00702
00703 rpki.log.debug("Contacting %s" % url)
00704
00705 request = http_request(
00706 cmd = "POST",
00707 path = u.path,
00708 body = msg,
00709 callback = callback,
00710 errback = errback,
00711 Host = u.hostname,
00712 Content_Type = rpki_content_type)
00713
00714 hostport = (u.hostname or "localhost", u.port or 80)
00715
00716 if debug:
00717 rpki.log.debug("Created request %r for %r" % (request, hostport))
00718 if not isinstance(server_ta, (tuple, list)):
00719 server_ta = (server_ta,)
00720 if hostport not in client_queues:
00721 client_queues[hostport] = http_queue(hostport, cert = client_cert, key = client_key, ta = server_ta)
00722 client_queues[hostport].request(request)
00723
00724
00725
00726
00727 if debug:
00728 rpki.log.debug("Scheduling connection startup for %r" % request)
00729 rpki.async.timer(client_queues[hostport].restart, errback).set(None)
00730
00731 def server(handlers, server_key, server_cert, port, host ="", client_ta = (), dynamic_https_trust_anchor = None):
00732 """
00733 Run an HTTPS server and wait (forever) for connections.
00734 """
00735
00736 if not isinstance(handlers, (tuple, list)):
00737 handlers = (("/", handlers),)
00738
00739 if not isinstance(client_ta, (tuple, list)):
00740 server_ta = (client_ta,)
00741
00742 http_listener(port = port, handlers = handlers, cert = server_cert, key = server_key, ta = client_ta, dynamic_ta = dynamic_https_trust_anchor)
00743 rpki.async.event_loop()
00744
00745 def build_https_ta_cache(certs):
00746 """
00747 Package up a collection of certificates into a form suitable for use
00748 as a dynamic HTTPS trust anchor set. Precise format of this
00749 collection is an internal conspiracy within the rpki.https module;
00750 at one point it was a POW.X509Store object, at the moment it's a
00751 Python set, what it will be tomorow is nobody else's business.
00752 """
00753
00754 return set(certs)