X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/1122eb25776ab935c1166dbcb3a4726a0e80117f..d24bc00093906f86c8f054557b4fe6869cb75885:/lib/http/__init__.py diff --git a/lib/http/__init__.py b/lib/http/__init__.py index c4b9ec2..9049829 100644 --- a/lib/http/__init__.py +++ b/lib/http/__init__.py @@ -1,7 +1,7 @@ # # -# 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 @@ -32,7 +32,6 @@ import errno from cStringIO import StringIO from ganeti import constants -from ganeti import serializer from ganeti import utils @@ -66,6 +65,9 @@ HTTP_AUTHORIZATION = "Authorization" 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 @@ -178,6 +180,17 @@ class HttpMethodNotAllowed(HttpException): 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 @@ -235,6 +248,17 @@ class HttpPreconditionFailed(HttpException): 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 @@ -299,54 +323,6 @@ class HttpVersionNotSupported(HttpException): code = 505 -class HttpJsonConverter: - CONTENT_TYPE = "application/json" - - def Encode(self, data): - return serializer.DumpJson(data) - - def Decode(self, 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. @@ -397,13 +373,13 @@ def SocketOperation(sock, op, arg1, timeout): 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() - if (op == SOCKOP_RECV and - event & (select.POLLNVAL | select.POLLHUP | select.POLLERR)): - return "" + if event & (select.POLLNVAL | select.POLLHUP | select.POLLERR): + # Let the socket functions handle these + break if not event & wait_for_event: continue @@ -574,6 +550,7 @@ class HttpSslParams(object): """ 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, @@ -594,7 +571,7 @@ class HttpBase(object): 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 @@ -602,11 +579,14 @@ class HttpBase(object): @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 @@ -620,6 +600,10 @@ class HttpBase(object): 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() @@ -629,8 +613,23 @@ class HttpBase(object): 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 @@ -638,6 +637,8 @@ class HttpBase(object): we do on our side. """ + # some parameters are unused, but this is the API + # pylint: disable-msg=W0613 assert self._ssl_params, "SSL not initialized" return (self._ssl_cert.digest("sha1") == cert.digest("sha1") and @@ -652,7 +653,6 @@ class HttpMessage(object): self.start_line = None self.headers = None self.body = None - self.decoded_body = None class HttpClientToServerStartLine(object): @@ -811,7 +811,7 @@ class HttpMessageReader(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)): @@ -823,17 +823,9 @@ class HttpMessageReader(object): 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. @@ -858,7 +850,7 @@ class HttpMessageReader(object): # the CRLF." if idx == 0: # TODO: Limit number of CRLFs/empty lines for safety? - buf = buf[:2] + buf = buf[2:] continue if idx > 0: @@ -1015,7 +1007,7 @@ class HttpMessageReader(object): 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