#
#
-# Copyright (C) 2007, 2008 Google Inc.
+# Copyright (C) 2007, 2008, 2010 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
from cStringIO import StringIO
from ganeti import constants
-from ganeti import serializer
from ganeti import utils
HTTP_AUTHENTICATION_INFO = "Authentication-Info"
HTTP_ALLOW = "Allow"
+HTTP_APP_OCTET_STREAM = "application/octet-stream"
+HTTP_APP_JSON = "application/json"
+
_SSL_UNEXPECTED_EOF = "Unexpected EOF"
# Socket operations
code = 405
+class HttpNotAcceptable(HttpException):
+ """406 Not Acceptable
+
+ RFC2616, 10.4.7: The resource identified by the request is only capable of
+ generating response entities which have content characteristics not
+ acceptable according to the accept headers sent in the request.
+
+ """
+ code = 406
+
+
class HttpRequestTimeout(HttpException):
"""408 Request Timeout
code = 412
+class HttpUnsupportedMediaType(HttpException):
+ """415 Unsupported Media Type
+
+ RFC2616, 10.4.16: The server is refusing to service the request because the
+ entity of the request is in a format not supported by the requested resource
+ for the requested method.
+
+ """
+ code = 415
+
+
class HttpInternalServerError(HttpException):
"""500 Internal Server Error
code = 505
-class HttpJsonConverter: # pylint: disable-msg=W0232
- CONTENT_TYPE = "application/json"
-
- @staticmethod
- def Encode(data):
- return serializer.DumpJson(data)
-
- @staticmethod
- def Decode(data):
- return serializer.LoadJson(data)
-
-
-def WaitForSocketCondition(sock, event, timeout):
- """Waits for a condition to occur on the socket.
-
- @type sock: socket
- @param sock: Wait for events on this socket
- @type event: int
- @param event: ORed condition (see select module)
- @type timeout: float or None
- @param timeout: Timeout in seconds
- @rtype: int or None
- @return: None for timeout, otherwise occured conditions
-
- """
- check = (event | select.POLLPRI |
- select.POLLNVAL | select.POLLHUP | select.POLLERR)
-
- if timeout is not None:
- # Poller object expects milliseconds
- timeout *= 1000
-
- poller = select.poll()
- poller.register(sock, event)
- try:
- while True:
- # TODO: If the main thread receives a signal and we have no timeout, we
- # could wait forever. This should check a global "quit" flag or
- # something every so often.
- io_events = poller.poll(timeout)
- if not io_events:
- # Timeout
- return None
- for (_, evcond) in io_events:
- if evcond & check:
- return evcond
- finally:
- poller.unregister(sock)
-
-
def SocketOperation(sock, op, arg1, timeout):
"""Wrapper around socket functions.
else:
wait_for_event = event_poll
- event = WaitForSocketCondition(sock, wait_for_event, timeout)
+ event = utils.WaitForFdCondition(sock, wait_for_event, timeout)
if event is None:
raise HttpSocketTimeout()
"""
self.ssl_key_pem = utils.ReadFile(ssl_key_path)
self.ssl_cert_pem = utils.ReadFile(ssl_cert_path)
+ self.ssl_cert_path = ssl_cert_path
def GetKey(self):
return OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM,
self._ssl_key = None
self._ssl_cert = None
- def _CreateSocket(self, ssl_params, ssl_verify_peer):
+ def _CreateSocket(self, ssl_params, ssl_verify_peer, family):
"""Creates a TCP socket and initializes SSL if needed.
@type ssl_params: HttpSslParams
@type ssl_verify_peer: bool
@param ssl_verify_peer: Whether to require client certificate
and compare it with our certificate
+ @type family: int
+ @param family: socket.AF_INET | socket.AF_INET6
"""
- self._ssl_params = ssl_params
+ assert family in (socket.AF_INET, socket.AF_INET6)
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._ssl_params = ssl_params
+ sock = socket.socket(family, socket.SOCK_STREAM)
# Should we enable SSL?
self.using_ssl = ssl_params is not None
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2)
+ ciphers = self.GetSslCiphers()
+ logging.debug("Setting SSL cipher string %s", ciphers)
+ ctx.set_cipher_list(ciphers)
+
ctx.use_privatekey(self._ssl_key)
ctx.use_certificate(self._ssl_cert)
ctx.check_privatekey()
OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
self._SSLVerifyCallback)
+ # Also add our certificate as a trusted CA to be sent to the client.
+ # This is required at least for GnuTLS clients to work.
+ try:
+ # This will fail for PyOpenssl versions before 0.10
+ ctx.add_client_ca(self._ssl_cert)
+ except AttributeError:
+ # Fall back to letting OpenSSL read the certificate file directly.
+ ctx.load_client_ca(ssl_params.ssl_cert_path)
+
return OpenSSL.SSL.Connection(ctx, sock)
+ def GetSslCiphers(self): # pylint: disable-msg=R0201
+ """Returns the ciphers string for SSL.
+
+ """
+ return constants.OPENSSL_CIPHERS
+
def _SSLVerifyCallback(self, conn, cert, errnum, errdepth, ok):
"""Verify the certificate provided by the peer
self.start_line = None
self.headers = None
self.body = None
- self.decoded_body = None
class HttpClientToServerStartLine(object):
buf = self._ContinueParsing(buf, eof)
# Must be done only after the buffer has been evaluated
- # TODO: Connection-length < len(data read) and connection closed
+ # TODO: Content-Length < len(data read) and connection closed
if (eof and
self.parser_status in (self.PS_START_LINE,
self.PS_HEADERS)):
assert self.parser_status == self.PS_COMPLETE
assert not buf, "Parser didn't read full response"
+ # Body is complete
msg.body = self.body_buffer.getvalue()
- # TODO: Content-type, error handling
- if msg.body:
- msg.decoded_body = HttpJsonConverter().Decode(msg.body)
- else:
- msg.decoded_body = None
-
- if msg.decoded_body:
- logging.debug("Message body: %s", msg.decoded_body)
-
def _ContinueParsing(self, buf, eof):
"""Main function for HTTP message state machine.
if hdr_content_length:
try:
self.content_length = int(hdr_content_length)
- except ValueError:
+ except (TypeError, ValueError):
self.content_length = None
if self.content_length is not None and self.content_length < 0:
self.content_length = None