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 2508 2009-06-09 04:29:20Z 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, traceback, 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 = False
00050
00051
00052 debug = False
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 print traceback.format_exc()
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 print traceback.format_exc()
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 msg = http_response(code = code, reason = reason, body = body,
00444 Content_Type = rpki_content_type,
00445 Connection = "Close" if self.expect_close else "Keep-Alive")
00446 self.push(msg.format())
00447 if self.expect_close:
00448 self.log("Closing")
00449 self.timer.cancel()
00450 self.close_when_done()
00451 else:
00452 self.log("Listening for next message")
00453 self.restart()
00454
00455 class http_listener(asyncore.dispatcher):
00456
00457 log = logger
00458
00459 def __init__(self, handlers, port = 80, host = "", cert = None, key = None, ta = None, dynamic_ta = None):
00460 self.log("Listener cert %r key %r ta %r dynamic_ta %r" % (cert, key, ta, dynamic_ta))
00461 asyncore.dispatcher.__init__(self)
00462 self.handlers = handlers
00463 self.cert = cert
00464 self.key = key
00465 self.ta = ta
00466 self.dynamic_ta = dynamic_ta
00467 try:
00468 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
00469 self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
00470 self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
00471 self.bind((host, port))
00472 self.listen(5)
00473 except (rpki.async.ExitNow, SystemExit):
00474 raise
00475 except:
00476 self.handle_error()
00477 self.log("Listening on %r, handlers %r" % ((host, port), handlers))
00478
00479 def handle_accept(self):
00480 self.log("Accepting connection")
00481 try:
00482 http_server(conn = self.accept()[0], handlers = self.handlers, cert = self.cert, key = self.key, ta = self.ta, dynamic_ta = self.dynamic_ta)
00483 except (rpki.async.ExitNow, SystemExit):
00484 raise
00485 except:
00486 self.handle_error()
00487
00488 def handle_error(self):
00489 if sys.exc_info()[0] is SystemExit:
00490 self.log("Caught SystemExit, propagating")
00491 raise
00492 else:
00493 self.log("Error in HTTP listener")
00494 print traceback.format_exc()
00495
00496 class http_client(http_stream):
00497
00498 parse_type = http_response
00499
00500 def __init__(self, queue, hostport, cert = None, key = None, ta = ()):
00501 self.log("Creating new connection to %s" % repr(hostport))
00502 self.log("cert %r key %r ta %r" % (cert, key, ta))
00503 http_stream.__init__(self)
00504 self.queue = queue
00505 self.hostport = hostport
00506 self.state = "opening"
00507 self.expect_close = not want_persistent_client
00508 self.cert = cert
00509 self.key = key
00510 self.ta = rpki.x509.X509.normalize_chain(ta)
00511
00512 def start(self):
00513 try:
00514 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
00515 self.connect(self.hostport)
00516 except (rpki.async.ExitNow, SystemExit):
00517 raise
00518 except:
00519 self.handle_error()
00520
00521 def handle_connect(self):
00522 self.log("Connected")
00523 self.set_state("idle")
00524
00525 self.tls = POW.Ssl(POW.TLSV1_CLIENT_METHOD)
00526 self.log_cert("client", self.cert)
00527 self.tls.useCertificate(self.cert.get_POW())
00528 self.tls.useKey(self.key.get_POW())
00529 if not self.ta:
00530 raise RuntimeError, "No trust anchor(s) specified, this is unlikely to work"
00531 for x in self.ta:
00532 self.log_cert("trusted", x)
00533 self.tls.addTrust(x.get_POW())
00534 self.tls.setVerifyMode(POW.SSL_VERIFY_PEER | POW.SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
00535
00536 self.tls.setFd(self.fileno())
00537 self.tls_connect()
00538
00539 def tls_connect(self):
00540 try:
00541 self.tls.connect()
00542 except POW.WantReadError:
00543 self.retry_read = self.tls_connect
00544 except POW.WantWriteError:
00545 self.retry_write = self.tls_connect
00546 else:
00547 self.queue.send_request()
00548
00549 def set_state(self, state):
00550 self.log("State transition %s => %s" % (self.state, state))
00551 self.state = state
00552
00553 def handle_no_content_length(self):
00554 self.set_terminator(None)
00555
00556 def send_request(self, msg):
00557 self.log("Sending request %r" % msg)
00558 assert self.state == "idle", "%r: state should be idle, is %s" % (self, self.state)
00559 self.set_state("request-sent")
00560 msg.headers["Connection"] = "Close" if self.expect_close else "Keep-Alive"
00561 self.push(msg.format())
00562 self.restart()
00563
00564 def handle_message(self):
00565 self.log("Message received, state %s" % self.state)
00566
00567 if not self.msg.persistent():
00568 self.expect_close = True
00569
00570 if self.state != "request-sent":
00571 if self.state == "closing":
00572 assert not self.msg.body
00573 self.log("Ignoring empty response received while closing")
00574 return
00575 raise RuntimeError, "%r received message while in unexpected state %s" % (self, self.state)
00576
00577 if self.expect_close:
00578 self.log("Closing")
00579 self.set_state("closing")
00580 self.queue.detach(self)
00581 self.close_when_done()
00582 else:
00583 self.log("Idling")
00584 self.set_state("idle")
00585 self.update_timeout()
00586
00587 if self.msg.code == 200:
00588 self.queue.return_result(self.msg)
00589 else:
00590 self.queue.return_result(rpki.exceptions.HTTPRequestFailed(
00591 "HTTPS request failed with status %s, reason %s, response %s" % (self.msg.code, self.msg.reason, self.msg.body)))
00592
00593 def handle_close(self):
00594 http_stream.handle_close(self)
00595 self.log("State %s" % self.state)
00596 self.queue.detach(self)
00597 if self.get_terminator() is None:
00598 self.handle_body()
00599 elif self.state == "request-sent":
00600 self.queue.return_result(rpki.exceptions.HTTPSClientAborted("HTTPS request aborted by close event"))
00601
00602 def handle_timeout(self):
00603 if self.state != "idle":
00604 self.log("Timeout while in state %s" % self.state)
00605 http_stream.handle_timeout(self)
00606 self.queue.detach(self)
00607
00608 def handle_error(self):
00609 http_stream.handle_error(self)
00610 self.queue.detach(self)
00611 etype, edata = sys.exc_info()[:2]
00612 if etype in (SystemExit, rpki.async.ExitNow):
00613 raise edata
00614 else:
00615 self.queue.return_result(edata)
00616
00617 class http_queue(object):
00618
00619 log = logger
00620
00621 def __init__(self, hostport, cert = None, key = None, ta = ()):
00622 self.log("Creating queue for %s" % repr(hostport))
00623 self.log("cert %r key %r ta %r" % (cert, key, ta))
00624 self.hostport = hostport
00625 self.client = None
00626 self.queue = []
00627 self.cert = cert
00628 self.key = key
00629 self.ta = ta
00630
00631 def request(self, *requests):
00632 self.log("Adding requests %r" % requests)
00633 self.queue.extend(requests)
00634
00635 def restart(self):
00636 if self.client is None:
00637 client = http_client(self, self.hostport, cert = self.cert, key = self.key, ta = self.ta)
00638 self.log("Attaching client %r" % client)
00639 self.client = client
00640 self.client.start()
00641 elif self.client.state == "idle":
00642 self.log("Sending request to existing client %r" % self.client)
00643 self.send_request()
00644 else:
00645 self.log("Client exists and is not idle")
00646
00647 def send_request(self):
00648 if self.queue:
00649 self.client.send_request(self.queue[0])
00650
00651 def detach(self, client):
00652 if client is self.client:
00653 self.log("Detaching client %r" % client)
00654 self.client = None
00655
00656 def return_result(self, result):
00657
00658 if not self.queue:
00659 self.log("No caller, this should not happen. Dropping result %r" % result)
00660
00661 req = self.queue.pop(0)
00662 self.log("Dequeuing request %r" % req)
00663
00664 try:
00665 if isinstance(result, http_response):
00666 self.log("Returning result %r to caller" % result)
00667 req.callback(result.body)
00668 else:
00669 assert isinstance(result, Exception)
00670 self.log("Returning exception %r to caller: %s" % (result, result))
00671 req.errback(result)
00672 except (rpki.async.ExitNow, SystemExit):
00673 raise
00674 except:
00675 self.log("Unhandled exception from callback")
00676 rpki.log.error(traceback.format_exc())
00677
00678 self.log("Queue: %r" % self.queue)
00679
00680 if self.queue:
00681 self.restart()
00682
00683 client_queues = {}
00684
00685 def client(msg, client_key, client_cert, server_ta, url, callback, errback):
00686 """
00687 Open client HTTPS connection, send a message, set up callbacks to
00688 handle response.
00689 """
00690
00691 u = urlparse.urlparse(url)
00692
00693 if (u.scheme not in ("", "https") or
00694 u.username is not None or
00695 u.password is not None or
00696 u.params != "" or
00697 u.query != "" or
00698 u.fragment != ""):
00699 raise rpki.exceptions.BadClientURL, "Unusable URL %s" % url
00700
00701 rpki.log.debug("Contacting %s" % url)
00702
00703 request = http_request(
00704 cmd = "POST",
00705 path = u.path,
00706 body = msg,
00707 callback = callback,
00708 errback = errback,
00709 Host = u.hostname,
00710 Content_Type = rpki_content_type)
00711
00712 hostport = (u.hostname or "localhost", u.port or 80)
00713
00714 if debug:
00715 rpki.log.debug("Created request %r for %r" % (request, hostport))
00716 if not isinstance(server_ta, (tuple, list)):
00717 server_ta = (server_ta,)
00718 if hostport not in client_queues:
00719 client_queues[hostport] = http_queue(hostport, cert = client_cert, key = client_key, ta = server_ta)
00720 client_queues[hostport].request(request)
00721
00722
00723
00724
00725 if debug:
00726 rpki.log.debug("Scheduling connection startup for %r" % request)
00727 rpki.async.timer(client_queues[hostport].restart, errback).set(None)
00728
00729 def server(handlers, server_key, server_cert, port, host ="", client_ta = (), dynamic_https_trust_anchor = None):
00730 """
00731 Run an HTTPS server and wait (forever) for connections.
00732 """
00733
00734 if not isinstance(handlers, (tuple, list)):
00735 handlers = (("/", handlers),)
00736
00737 if not isinstance(client_ta, (tuple, list)):
00738 server_ta = (client_ta,)
00739
00740 http_listener(port = port, handlers = handlers, cert = server_cert, key = server_key, ta = client_ta, dynamic_ta = dynamic_https_trust_anchor)
00741 rpki.async.event_loop()
00742
00743 def build_https_ta_cache(certs):
00744 """
00745 Package up a collection of certificates into a form suitable for use
00746 as a dynamic HTTPS trust anchor set. Precise format of this
00747 collection is an internal conspiracy within the rpki.https module;
00748 at one point it was a POW.X509Store object, at the moment it's a
00749 Python set, what it will be tomorow is nobody else's business.
00750 """
00751
00752 return set(certs)