Ginés Martínez Sánchez avatar Ginés Martínez Sánchez committed 7f2c7ff Draft

developing http server

Comments (0)

Files changed (3)

ginsfsm/c_sock.py

 #   Code inspired in asyncore.py, waitress and tornado.
 # ======================================================================
 
+try:
+    import ssl  # Python 2.6+
+except ImportError:
+    ssl = None
+
 import select
 import socket
 import time
     EISCONN,
     EBADF,
     ECONNABORTED,
+    EAGAIN,
 #    EADDRNOTAVAIL,
     errorcode
     )
     'ip': [str, '', 0, None, ""],
     'host': [str, '', 0, None, "server or client host (ip or name)"],
     'port': [int, 0, 0, None, "server or client port"],
+    'ssl_options': [dict, None, 0, None, "ssl options"],
     'tx_buffer_size': [int, 4096, 0, None, ""],
     'connected_event_name': [str, 'EV_CONNECTED', 0, None,
         "Must be empty if you don't want receive this event"],
             self.subscriber = self.parent
         self.subscribe_event(None, self.subscriber)
 
+        # Verify the SSL options. Otherwise we don't get errors until clients
+        # connect. This doesn't verify that the keys are legitimate, but
+        # the SSL module doesn't do that until there is a connected socket
+        # which seems like too much work
+        if self.ssl_options is not None:
+            # Only certfile is required: it can contain both keys
+            if 'certfile' not in self.ssl_options:
+                raise KeyError('missing key "certfile" in ssl_options')
+
+            if not os.path.exists(self.ssl_options['certfile']):
+                raise ValueError('certfile "%s" does not exist' %
+                    self.ssl_options['certfile'])
+            if ('keyfile' in self.ssl_options and
+                    not os.path.exists(self.ssl_options['keyfile'])):
+                raise ValueError('keyfile "%s" does not exist' %
+                    self.ssl_options['keyfile'])
+
     def set_clisrv_socket(self, sock):
         # Set to nonblocking just to make sure for cases where we
         # get a socket from a blocking source.
     def send(self, data):
         try:
             result = self.socket.send(data)
+            if result == 0:
+                # With OpenSSL, if we couldn't write the entire buffer,
+                # the very same string object must be used on the
+                # next call to send.  Therefore we suppress
+                # merging the write buffer after an incomplete send.
+                # A cleaner solution would be to set
+                # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER, but this is
+                # not yet accessible from python
+                # (http://bugs.python.org/issue8240)
+                pass
             return result
         except socket.error as why:
             if why.args[0] == EWOULDBLOCK:
         self.__trace_dump = value
 
 
+class GSSLSock(GSock):
+    """A GSock class to write to and read from a non-blocking SSL socket.
+
+    If the socket passed to the constructor is already connected,
+    it should be wrapped with::
+
+        ssl.wrap_socket(sock, do_handshake_on_connect=False, **kwargs)
+
+    Unconnected sockets will be wrapped when connect is finished.
+    """
+
+    def __init__(self):
+        GSock.__init__(self)
+
+    def start_up(self):
+        """ Initialization zone.
+        """
+        self._ssl_accepting = True
+        self._handshake_reading = False
+        self._handshake_writing = False
+        self._ssl_connect_callback = None
+
+    def writable(self):
+        #"predicate for inclusion in the writable for select()"
+        return self._handshake_reading or super(GSSLSock, self).reading()
+
+    def readable(self):
+        #"predicate for inclusion in the readable for select()"
+        return self._handshake_writing or super(GSSLSock, self).writing()
+
+    def _do_ssl_handshake(self):
+        # Based on code from test_ssl.py in the python stdlib
+        try:
+            self._handshake_reading = False
+            self._handshake_writing = False
+            self.socket.do_handshake()
+        except ssl.SSLError, err:
+            if err.args[0] == ssl.SSL_ERROR_WANT_READ:
+                self._handshake_reading = True
+                return
+            elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
+                self._handshake_writing = True
+                return
+            elif err.args[0] in (ssl.SSL_ERROR_EOF,
+                                 ssl.SSL_ERROR_ZERO_RETURN):
+                return self.close()
+            elif err.args[0] == ssl.SSL_ERROR_SSL:
+                try:
+                    peer = self.socket.getpeername()
+                except:
+                    peer = '(not connected)'
+                logging.warning("SSL Error on %d %s: %s",
+                                self.socket.fileno(), peer, err)
+                return self.close()
+            raise
+        except socket.error, err:
+            if err.args[0] in (errno.ECONNABORTED, errno.ECONNRESET):
+                return self.close()
+        else:
+            self._ssl_accepting = False
+            if self._ssl_connect_callback is not None:
+                callback = self._ssl_connect_callback
+                self._ssl_connect_callback = None
+                self._run_callback(callback)
+
+    def handle_read(self):
+        if self._ssl_accepting:
+            self._do_ssl_handshake()
+            return
+        super(GSSLSock, self).handle_read()
+
+    def handle_write(self):
+        if self._ssl_accepting:
+            self._do_ssl_handshake()
+            return
+        super(GSSLSock, self).handle_write()
+
+    def handle_connect(self):
+        # When the connection is complete, wrap the socket for SSL
+        # traffic.  Note that we do this by overriding _handle_connect
+        # instead of by passing a callback to super().connect because
+        # user callbacks are enqueued asynchronously on the IOLoop,
+        # but since _handle_events calls _handle_connect immediately
+        # followed by _handle_write we need this to be synchronous.
+        self.socket = ssl.wrap_socket(self.socket,
+                                      do_handshake_on_connect=False,
+                                      **self._ssl_options)
+        super(GSSLSock, self).handle_connect()
+
+    # TODO para revisar
+
+    def recv(self):
+        while True:
+            # Read from the socket until we get EWOULDBLOCK or equivalent.
+            # SSL sockets do some internal buffering, and if the data is
+            # sitting in the SSL object's buffer select() and friends
+            # can't see it; the only way to find out if it's there is to
+            # try to read it.
+            if not super(GSSLSock, self).recv():
+                break
+
+
+
+
+        if self._ssl_accepting:
+            # If the handshake hasn't finished yet, there can't be anything
+            # to read (attempting to read may or may not raise an exception
+            # depending on the SSL version)
+            return None
+        try:
+            # SSLSocket objects have both a read() and recv() method,
+            # while regular sockets only have recv().
+            # The recv() method blocks (at least in python 2.6) if it is
+            # called when there is nothing to read, so we have to use
+            # read() instead.
+            chunk = self.socket.read(self.read_chunk_size)
+        except ssl.SSLError, e:
+            # SSLError is a subclass of socket.error, so this except
+            # block must come first.
+            if e.args[0] == ssl.SSL_ERROR_WANT_READ:
+                return None
+            else:
+                raise
+        except socket.error, e:
+            if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+                return None
+            else:
+                raise
+        if not chunk:
+            self.close()
+            return None
+        return chunk
+
+
 # ---------------------------------------------------------------------------
 # used for debugging.
 # ---------------------------------------------------------------------------

ginsfsm/c_srv_sock.py

     :members:
 
 """
+try:
+    import ssl  # Python 2.6+
+except ImportError:
+    ssl = None
+
 import logging
+import errno
 import socket
 from ginsfsm.c_sock import (
     GSock,
+    GSSLSock
 )
 
 GSERVERSOCK_FSM = {  # ONLY for documentation. Partial use of GSOCK_FSM.
         ],
     'host': [str, '', 0, None, "server host (ip or name)"],
     'port': [int, 0, 0, None, "server port"],
-    'connected_event_name': [str, 'EV_CONNECTED', 0, None, ""],
+    'ssl_options': [dict, None, 0, None, "ssl options"],
 }
 
 
     but it uses the server behaviour of a sock.
     It creates a listening sock at configured (host, port) attributes.
 
+    `GServerSock` can serve SSL traffic with Python 2.6+ and OpenSSL.
+    To make this server serve SSL traffic, send the ssl_options dictionary
+    argument with the arguments required for the `ssl.wrap_socket` method,
+    including "certfile" and "keyfile"::
+
+       ssl_options={
+           "certfile": os.path.join(data_dir, "mydomain.crt"),
+           "keyfile": os.path.join(data_dir, "mydomain.key"),
+       }
+
     Each incoming connection
     will create a new :class:`ginsfsm.c_sock.GSock` :term:`gobj`,
     that it will be child of the :attr:`subscriber` `gobj`
         self.listen(1024)
 
     def handle_accepted(self, sock, addr):
-        # The socket options to set on receiving a connection.  It is a list of
-        # (level, optname, value) tuples.  TCP_NODELAY disables the Nagle
-        # algorithm for writes (Waitress already buffers its writes).
+        # The socket options to set on receiving a connection.
+        # It is a list of (level, optname, value) tuples.
+        # TCP_NODELAY disables the Nagle algorithm for writes
+        # (Waitress already buffers its writes).
         # TODO: check origins for permitted source ip.
         socket_options = [
             (socket.SOL_TCP, socket.TCP_NODELAY, 1),
         for (level, optname, value) in socket_options:
             sock.setsockopt(level, optname, value)
 
+        # copied from tornado.netutil.py
+        if self.ssl_options is not None:
+            assert ssl, "Python 2.6+ and OpenSSL required for SSL"
+            try:
+                sock = ssl.wrap_socket(
+                    sock,
+                    server_side=True,
+                    do_handshake_on_connect=False,
+                    **self.ssl_options
+                )
+            except ssl.SSLError, err:
+                if err.args[0] == ssl.SSL_ERROR_EOF:
+                    return sock.close()
+                else:
+                    raise
+            except socket.error, err:
+                if err.args[0] == errno.ECONNABORTED:
+                    return sock.close()
+                else:
+                    raise
+
         self._n_clisrv += 1
         if self.name:
             prefix_name = self.name
         else:
             prefix_name = None
         channel = '.clisrv-gsock-%x' % self._n_clisrv
+
+        if self.ssl_options is not None:
+            gsock_class = GSSLSock
+        else:
+            gsock_class = GSock
         clisrv = self.create_gobj(
             prefix_name + channel if prefix_name else None,
-            GSock,
+            gsock_class,
             self.subscriber,
         )
         clisrv.set_clisrv_socket(sock)

ginsfsm/protocols/http/server/c_http_server.py

     :members:
 
 """
+try:
+    import ssl  # Python 2.6+
+except ImportError:
+    ssl = None
 
 from ginsfsm.gobj import GObj
 from ginsfsm.c_srv_sock import GServerSock
 
     def getsockname(self):
         return self.serversock.socket.getsockname()
+
+    def get_ssl_certificate(self, binary_form=False):
+        """Returns the client's SSL certificate, if any.
+
+        To use client certificates, the HTTPServer must have been constructed
+        with cert_reqs set in ssl_options, e.g.::
+
+            server = HTTPServer(app,
+                ssl_options=dict(
+                    certfile="foo.crt",
+                    keyfile="foo.key",
+                    cert_reqs=ssl.CERT_REQUIRED,
+                    ca_certs="cacert.crt"))
+
+        By default, the return value is a dictionary (or None, if no
+        client certificate is present).  If ``binary_form`` is true, a
+        DER-encoded form of the certificate is returned instead.  See
+        SSLSocket.getpeercert() in the standard library for more
+        details.
+        http://docs.python.org/library/ssl.html#sslsocket-objects
+        """
+        try:
+            return self.connection.stream.socket.getpeercert(
+                binary_form=binary_form)
+        except ssl.SSLError:
+            return None
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.