def _secure(s, host, ssl_context, **config): # Secure the connection if an SSL context has been provided if ssl_context and SSL_AVAILABLE: log_debug("~~ [SECURE] %s", host) try: s = ssl_context.wrap_socket(s, server_hostname=host if HAS_SNI and host else None) except SSLError as cause: _force_close(s) error = SecurityError("Failed to establish secure connection to {!r}".format(cause.args[1])) error.__cause__ = cause raise error else: # Check that the server provides a certificate der_encoded_server_certificate = s.getpeercert(binary_form=True) if der_encoded_server_certificate is None: _force_close(s) raise ProtocolError("When using a secure socket, the server should always " "provide a certificate") trust = config.get("trust", default_config["trust"]) if trust == TRUST_ON_FIRST_USE: from neo4j.bolt.cert import PersonalCertificateStore store = PersonalCertificateStore() if not store.match_or_trust(host, der_encoded_server_certificate): _force_close(s) raise ProtocolError("Server certificate does not match known certificate " "for %r; check details in file %r" % (host, KNOWN_HOSTS)) else: der_encoded_server_certificate = None return s, der_encoded_server_certificate
def _handshake(s, resolved_address): """ :param s: :return: """ local_port = s.getsockname()[1] # Send details of the protocol versions supported supported_versions = [3, 0, 0, 0] handshake = [Bolt.MAGIC_PREAMBLE] + supported_versions log.debug("[#%04X] C: <MAGIC> 0x%08X", local_port, Bolt.MAGIC_PREAMBLE) log.debug("[#%04X] C: <HANDSHAKE> 0x%08X 0x%08X 0x%08X 0x%08X", local_port, *supported_versions) data = b"".join(struct_pack(">I", num) for num in handshake) s.sendall(data) # Handle the handshake response ready_to_read = False while not ready_to_read: ready_to_read, _, _ = select((s, ), (), (), 1) try: data = s.recv(4) except OSError: raise ServiceUnavailable("Failed to read any data from server {!r} " "after connected".format(resolved_address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select # response, the server has closed the connection log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ServiceUnavailable("Connection to %r closed without handshake " "response" % (resolved_address, )) if data_size != 4: # Some garbled data has been received log.debug("[#%04X] S: @*#!", local_port) s.close() raise ProtocolError("Expected four byte Bolt handshake response " "from %r, received %r instead; check for " "incorrect port number" % (resolved_address, data)) elif data == b"HTTP": log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ServiceUnavailable("Cannot to connect to Bolt service on {!r} " "(looks like HTTP)".format(resolved_address)) agreed_version = data[-1], data[-2] log.debug("[#%04X] S: <HANDSHAKE> 0x%06X%02X", local_port, agreed_version[1], agreed_version[0]) if agreed_version == (0, 0): log.debug("[#%04X] C: <CLOSE>", local_port) s.shutdown(SHUT_RDWR) s.close() elif agreed_version in ((3, 0), ): return s, agreed_version else: log.debug("[#%04X] S: <CLOSE>", local_port) s.close() raise ProtocolError("Unknown Bolt protocol version: " "{}".format(agreed_version))
def _handshake(s, resolved_address, der_encoded_server_certificate, error_handler, **config): """ :param s: :return: """ # Send details of the protocol versions supported supported_versions = [2, 1, 0, 0] handshake = [MAGIC_PREAMBLE] + supported_versions log_debug("C: [HANDSHAKE] 0x%X %r", MAGIC_PREAMBLE, supported_versions) data = b"".join(struct_pack(">I", num) for num in handshake) s.sendall(data) # Handle the handshake response ready_to_read, _, _ = select((s,), (), (), 0) while not ready_to_read: ready_to_read, _, _ = select((s,), (), (), 0) try: data = s.recv(4) except ConnectionResetError: raise ServiceUnavailable("Failed to read any data from server {!r} after connected".format(resolved_address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select # response, the server has closed the connection log_error("S: [CLOSE]") s.close() raise ProtocolError("Connection to %r closed without handshake response" % (resolved_address,)) if data_size != 4: # Some garbled data has been received log_error("S: @*#!") s.close() raise ProtocolError("Expected four byte handshake response, received %r instead" % data) agreed_version, = struct_unpack(">I", data) log_debug("S: [HANDSHAKE] %d", agreed_version) if agreed_version == 0: log_debug("~~ [CLOSE]") s.shutdown(SHUT_RDWR) s.close() elif agreed_version in (1, 2): connection = Connection(resolved_address, s, agreed_version, der_encoded_server_certificate=der_encoded_server_certificate, error_handler=error_handler, **config) connection.init() return connection elif agreed_version == 0x48545450: log_error("S: [CLOSE]") s.close() raise ServiceUnavailable("Cannot to connect to Bolt service on {!r} " "(looks like HTTP)".format(resolved_address)) else: log_error("S: [CLOSE]") s.close() raise ProtocolError("Unknown Bolt protocol version: {}".format(agreed_version))
def open(cls, address, *, auth=None, timeout=None, **config): """ Open a new Bolt connection to a given server address. :param address: :param auth: :param timeout: :param config: :return: """ config = PoolConfig.consume(config) s, config.protocol_version = connect(address, timeout=timeout, config=config) if config.protocol_version == (3, 0): from neo4j.io._bolt3 import Bolt3 connection = Bolt3(address, s, auth=auth, **config) else: log.debug("[#%04X] S: <CLOSE>", s.getpeername()[1]) s.shutdown(SHUT_RDWR) s.close() raise ProtocolError( "Driver does not support Bolt protocol version: 0x%06X%02X", config.protocol_version[0], config.protocol_version[1]) connection.hello() return connection
def _yield_messages(self, sock): try: buffer = UnpackableBuffer() chunk_loader = self._load_chunks(sock, buffer) unpacker = Unpacker(buffer) details = [] while True: unpacker.reset() details[:] = () chunk_size = -1 while chunk_size != 0: chunk_size = next(chunk_loader) summary_signature = None summary_metadata = None size, signature = unpacker.unpack_structure_header() if size > 1: raise ProtocolError("Expected one field") if signature == b"\x71": data = unpacker.unpack() details.append(data) else: summary_signature = signature summary_metadata = unpacker.unpack_map() yield details, summary_signature, summary_metadata except OSError as error: self.on_error(error)
def _fetch(self): """ Receive at least one message from the server, if available. :return: 2-tuple of number of detail messages and number of summary messages fetched """ if self.closed(): raise self.Error( "Failed to read from closed connection {!r}".format( self.server.address)) if self.defunct(): raise self.Error( "Failed to read from defunct connection {!r}".format( self.server.address)) if not self.responses: return 0, 0 self._receive() details, summary_signature, summary_metadata = self._unpack() if details: log_debug("S: RECORD * %d", len(details)) # TODO self.responses[0].on_records(details) if summary_signature is None: return len(details), 0 response = self.responses.popleft() response.complete = True if summary_signature == SUCCESS: log_debug("S: SUCCESS (%r)", summary_metadata) response.on_success(summary_metadata or {}) elif summary_signature == IGNORED: self._last_run_statement = None log_debug("S: IGNORED (%r)", summary_metadata) response.on_ignored(summary_metadata or {}) elif summary_signature == FAILURE: self._last_run_statement = None log_debug("S: FAILURE (%r)", summary_metadata) response.on_failure(summary_metadata or {}) else: self._last_run_statement = None raise ProtocolError( "Unexpected response message with signature %02X" % summary_signature) return len(details), 1
def driver(cls, uri, **config): """ Acquire a :class:`.Driver` instance for the given URI and configuration. The URI scheme determines the Driver implementation that will be returned. Options are: ``bolt`` Returns a :class:`.DirectDriver`. ``bolt+routing`` Returns a :class:`.RoutingDriver`. :param uri: URI for a graph database service :param config: configuration and authentication details (valid keys are listed below) `auth` An authentication token for the server, for example ``("neo4j", "password")``. `der_encoded_server_certificate` The server certificate in DER format, if required. `encrypted` A boolean flag to determine whether encryption should be used. Defaults to :const:`True`. `trust` Trust level: one of :attr:`.TRUST_ALL_CERTIFICATES` (default) or :attr:`.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES`. `user_agent` A custom user agent string, if required. for more config options see neo4j.config.default_config """ parsed = urlparse(uri) try: driver_class = cls.uri_schemes[parsed.scheme] except KeyError: raise ProtocolError("URI scheme %r not supported" % parsed.scheme) else: return driver_class(uri, **config)
def _unpack(self): unpacker = self.unpacker input_buffer = self.input_buffer details = [] summary_signature = None summary_metadata = None more = True while more: unpacker.attach(input_buffer.frame()) size, signature = unpacker.unpack_structure_header() if size > 1: raise ProtocolError("Expected one field") if signature == RECORD: data = unpacker.unpack_list() details.append(data) more = input_buffer.frame_message() else: summary_signature = signature summary_metadata = unpacker.unpack_map() more = False return details, summary_signature, summary_metadata
def _secure(s, host, ssl_context): local_port = s.getsockname()[1] # Secure the connection if an SSL context has been provided if ssl_context: log.debug("[#%04X] C: <SECURE> %s", local_port, host) try: sni_host = host if HAS_SNI and host else None s = ssl_context.wrap_socket(s, server_hostname=sni_host) except SSLError as cause: s.close() error = SecurityError("Failed to establish secure connection " "to {!r}".format(cause.args[1])) error.__cause__ = cause raise error else: # Check that the server provides a certificate der_encoded_server_certificate = s.getpeercert(binary_form=True) if der_encoded_server_certificate is None: s.close() raise ProtocolError("When using a secure socket, the server " "should always provide a certificate") return s
def fetch_message(self): """ Receive at least one message from the server, if available. :return: 2-tuple of number of detail messages and number of summary messages fetched """ if self._closed: raise ServiceUnavailable( "Failed to read from closed connection {!r} ({!r})".format( self.unresolved_address, self.server.address)) if self._defunct: raise ServiceUnavailable( "Failed to read from defunct connection {!r} ({!r})".format( self.unresolved_address, self.server.address)) if not self.responses: return 0, 0 # Receive exactly one message try: details, summary_signature, summary_metadata = next(self.inbox) except (IOError, OSError) as error: log.error("Failed to read data from connection " "{!r} ({!r}); ({!r})".format( self.unresolved_address, self.server.address, "; ".join(map(repr, error.args)))) if self.pool: self.pool.deactivate(self.unresolved_address) raise if details: log.debug("[#%04X] S: RECORD * %d", self.local_port, len(details)) # TODO self.responses[0].on_records(details) if summary_signature is None: return len(details), 0 response = self.responses.popleft() response.complete = True if summary_signature == b"\x70": log.debug("[#%04X] S: SUCCESS %r", self.local_port, summary_metadata) response.on_success(summary_metadata or {}) elif summary_signature == b"\x7E": log.debug("[#%04X] S: IGNORED", self.local_port) response.on_ignored(summary_metadata or {}) elif summary_signature == b"\x7F": log.debug("[#%04X] S: FAILURE %r", self.local_port, summary_metadata) try: response.on_failure(summary_metadata or {}) except (ServiceUnavailable, DatabaseUnavailableError): if self.pool: self.pool.deactivate(self.unresolved_address), raise except (NotALeaderError, ForbiddenOnReadOnlyDatabaseError): if self.pool: self.pool.on_write_failure(self.unresolved_address), raise else: raise ProtocolError("Unexpected response message with " "signature %02X" % summary_signature) return len(details), 1
def fail(metadata): raise ProtocolError("RESET failed %r" % metadata)
def connect(address, ssl_context=None, error_handler=None, **config): """ Connect and perform a handshake and return a valid Connection object, assuming a protocol version can be agreed. """ # Establish a connection to the host and port specified # Catches refused connections see: # https://docs.python.org/2/library/errno.html log_info("~~ [CONNECT] %s", address) s = None try: if len(address) == 2: s = socket(AF_INET) elif len(address) == 4: s = socket(AF_INET6) else: raise ValueError("Unsupported address {!r}".format(address)) t = s.gettimeout() s.settimeout( config.get("connection_timeout", default_config["connection_timeout"])) s.connect(address) s.settimeout(t) s.setsockopt( SOL_SOCKET, SO_KEEPALIVE, 1 if config.get("keep_alive", default_config["keep_alive"]) else 0) except SocketTimeout: if s: try: s.close() except: pass raise ServiceUnavailable( "Timed out trying to establish connection to {!r}".format(address)) except SocketError as error: if s: try: s.close() except: pass if error.errno in (61, 111, 10061): raise ServiceUnavailable( "Failed to establish connection to {!r}".format(address)) else: raise except ConnectionResetError: raise ServiceUnavailable( "Failed to establish connection to {!r}".format(address)) # Secure the connection if an SSL context has been provided if ssl_context and SSL_AVAILABLE: host = address[0] log_info("~~ [SECURE] %s", host) try: s = ssl_context.wrap_socket( s, server_hostname=host if HAS_SNI else None) except SSLError as cause: s.close() error = SecurityError( "Failed to establish secure connection to {!r}".format( cause.args[1])) error.__cause__ = cause raise error else: # Check that the server provides a certificate der_encoded_server_certificate = s.getpeercert(binary_form=True) if der_encoded_server_certificate is None: s.close() raise ProtocolError( "When using a secure socket, the server should always " "provide a certificate") trust = config.get("trust", default_config["trust"]) if trust == TRUST_ON_FIRST_USE: from neo4j.bolt.cert import PersonalCertificateStore store = PersonalCertificateStore() if not store.match_or_trust(host, der_encoded_server_certificate): s.close() raise ProtocolError( "Server certificate does not match known certificate " "for %r; check details in file %r" % (host, KNOWN_HOSTS)) else: der_encoded_server_certificate = None # Send details of the protocol versions supported supported_versions = [1, 0, 0, 0] handshake = [MAGIC_PREAMBLE] + supported_versions log_info("C: [HANDSHAKE] 0x%X %r", MAGIC_PREAMBLE, supported_versions) data = b"".join(struct_pack(">I", num) for num in handshake) s.sendall(data) # Handle the handshake response ready_to_read, _, _ = select((s, ), (), (), 0) while not ready_to_read: ready_to_read, _, _ = select((s, ), (), (), 0) try: data = s.recv(4) except ConnectionResetError: raise ServiceUnavailable( "Failed to read any data from server {!r} after connected".format( address)) data_size = len(data) if data_size == 0: # If no data is returned after a successful select # response, the server has closed the connection log_error("S: [CLOSE]") s.close() raise ProtocolError( "Connection to %r closed without handshake response" % (address, )) if data_size != 4: # Some garbled data has been received log_error("S: @*#!") s.close() raise ProtocolError( "Expected four byte handshake response, received %r instead" % data) agreed_version, = struct_unpack(">I", data) log_info("S: [HANDSHAKE] %d", agreed_version) if agreed_version == 0: log_info("~~ [CLOSE]") s.shutdown(SHUT_RDWR) s.close() elif agreed_version == 1: connection = Connection( address, s, der_encoded_server_certificate=der_encoded_server_certificate, error_handler=error_handler, **config) connection.init() return connection elif agreed_version == 0x48545450: log_error("S: [CLOSE]") s.close() raise ServiceUnavailable("Cannot to connect to Bolt service on {!r} " "(looks like HTTP)".format(address)) else: log_error("S: [CLOSE]") s.close() raise ProtocolError( "Unknown Bolt protocol version: {}".format(agreed_version))
def on_failure(self, metadata): raise ProtocolError("RESET failed")
def on_failure(self, metadata): raise ProtocolError("ACK_FAILURE failed")