Beispiel #1
0
    def __call__(self):
        self.connect()

        if not self.ignore:
            flow = models.TCPFlow(self.client_conn, self.server_conn, self)
            self.channel.ask("tcp_open", flow)

        buf = memoryview(bytearray(self.chunk_size))

        client = self.client_conn.connection
        server = self.server_conn.connection
        conns = [client, server]

        try:
            while not self.channel.should_exit.is_set():
                r = netlib.tcp.ssl_read_select(conns, 10)
                for conn in r:
                    dst = server if conn == client else client

                    size = conn.recv_into(buf, self.chunk_size)
                    if not size:
                        conns.remove(conn)
                        # Shutdown connection to the other peer
                        if isinstance(conn, SSL.Connection):
                            # We can't half-close a connection, so we just close everything here.
                            # Sockets will be cleaned up on a higher level.
                            return
                        else:
                            dst.shutdown(socket.SHUT_WR)

                        if len(conns) == 0:
                            return
                        continue

                    tcp_message = tcp.TCPMessage(dst == server,
                                                 buf[:size].tobytes())
                    if not self.ignore:
                        flow.messages.append(tcp_message)
                        self.channel.ask("tcp_message", flow)
                    dst.sendall(tcp_message.content)

        except (socket.error, netlib.exceptions.TcpException, SSL.Error) as e:
            if not self.ignore:
                flow.error = models.Error(
                    "TCP connection closed unexpectedly: {}".format(repr(e)))
                self.channel.tell("tcp_error", flow)
        finally:
            if not self.ignore:
                self.channel.tell("tcp_close", flow)
Beispiel #2
0
    def run(self):
        r = self.flow.request
        first_line_format_backup = r.first_line_format
        server = None
        try:
            self.flow.response = None

            # If we have a channel, run script hooks.
            if self.channel:
                request_reply = self.channel.ask("request", self.flow)
                if isinstance(request_reply, models.HTTPResponse):
                    self.flow.response = request_reply

            if not self.flow.response:
                # In all modes, we directly connect to the server displayed
                if self.config.options.mode == "upstream":
                    server_address = self.config.upstream_server.address
                    server = models.ServerConnection(
                        server_address, (self.config.options.listen_host, 0))
                    server.connect()
                    if r.scheme == "https":
                        connect_request = models.make_connect_request(
                            (r.data.host, r.port))
                        server.wfile.write(
                            http1.assemble_request(connect_request))
                        server.wfile.flush()
                        resp = http1.read_response(server.rfile,
                                                   connect_request,
                                                   body_size_limit=self.config.
                                                   options.body_size_limit)
                        if resp.status_code != 200:
                            raise exceptions.ReplayException(
                                "Upstream server refuses CONNECT request")
                        server.establish_ssl(self.config.clientcerts,
                                             sni=self.flow.server_conn.sni)
                        r.first_line_format = "relative"
                    else:
                        r.first_line_format = "absolute"
                else:
                    server_address = (r.host, r.port)
                    server = models.ServerConnection(
                        server_address, (self.config.options.listen_host, 0))
                    server.connect()
                    if r.scheme == "https":
                        server.establish_ssl(self.config.clientcerts,
                                             sni=self.flow.server_conn.sni)
                    r.first_line_format = "relative"

                server.wfile.write(http1.assemble_request(r))
                server.wfile.flush()
                self.flow.server_conn = server
                self.flow.response = models.HTTPResponse.wrap(
                    http1.read_response(
                        server.rfile,
                        r,
                        body_size_limit=self.config.options.body_size_limit))
            if self.channel:
                response_reply = self.channel.ask("response", self.flow)
                if response_reply == exceptions.Kill:
                    raise exceptions.Kill()
        except (exceptions.ReplayException,
                netlib.exceptions.NetlibException) as e:
            self.flow.error = models.Error(str(e))
            if self.channel:
                self.channel.ask("error", self.flow)
        except exceptions.Kill:
            # Kill should only be raised if there's a channel in the
            # first place.
            from ..proxy.root_context import Log
            self.channel.tell("log", Log("Connection killed", "info"))
        except Exception:
            from ..proxy.root_context import Log
            self.channel.tell("log", Log(traceback.format_exc(), "error"))
        finally:
            r.first_line_format = first_line_format_backup
            self.flow.live = False
            if server:
                server.finish()
Beispiel #3
0
    def __call__(self):
        if self.mode == "transparent":
            self.__initial_server_tls = self.server_tls
            self.__initial_server_conn = self.server_conn
        while True:
            try:
                request = self.get_request_from_client()
                self.log("request", "debug", [repr(request)])

                # Handle Proxy Authentication
                # Proxy Authentication conceptually does not work in transparent mode.
                # We catch this misconfiguration on startup. Here, we sort out requests
                # after a successful CONNECT request (which do not need to be validated anymore)
                if self.mode != "transparent" and not self.authenticate(
                        request):
                    return

                # Make sure that the incoming request matches our expectations
                self.validate_request(request)

                # Regular Proxy Mode: Handle CONNECT
                if self.mode == "regular" and request.first_line_format == "authority":
                    self.handle_regular_mode_connect(request)
                    return

            except netlib.exceptions.HttpReadDisconnect:
                # don't throw an error for disconnects that happen before/between requests.
                return
            except netlib.exceptions.NetlibException as e:
                self.send_error_response(400, repr(e))
                six.reraise(
                    exceptions.ProtocolException,
                    exceptions.ProtocolException(
                        "Error in HTTP connection: %s" % repr(e)),
                    sys.exc_info()[2])

            try:
                flow = models.HTTPFlow(self.client_conn,
                                       self.server_conn,
                                       live=self)
                flow.request = request
                # set upstream auth
                if self.mode == "upstream" and self.config.upstream_auth is not None:
                    flow.request.headers[
                        "Proxy-Authorization"] = self.config.upstream_auth
                self.process_request_hook(flow)

                if not flow.response:
                    self.establish_server_connection(flow)
                    self.get_response_from_server(flow)
                else:
                    # response was set by an inline script.
                    # we now need to emulate the responseheaders hook.
                    flow = self.channel.ask("responseheaders", flow)

                self.log("response", "debug", [repr(flow.response)])
                flow = self.channel.ask("response", flow)
                self.send_response_to_client(flow)

                if self.check_close_connection(flow):
                    return

                # Handle 101 Switching Protocols
                # It may be useful to pass additional args (such as the upgrade header)
                # to next_layer in the future
                if flow.response.status_code == 101:
                    layer = self.ctx.next_layer(self)
                    layer()
                    return

                # Upstream Proxy Mode: Handle CONNECT
                if flow.request.first_line_format == "authority" and flow.response.status_code == 200:
                    self.handle_upstream_mode_connect(flow.request.copy())
                    return

            except (exceptions.ProtocolException,
                    netlib.exceptions.NetlibException) as e:
                self.send_error_response(502, repr(e))

                if not flow.response:
                    flow.error = models.Error(str(e))
                    self.channel.ask("error", flow)
                    self.log(traceback.format_exc(), "debug")
                    return
                else:
                    six.reraise(
                        exceptions.ProtocolException,
                        exceptions.ProtocolException(
                            "Error in HTTP connection: %s" % repr(e)),
                        sys.exc_info()[2])
            finally:
                if flow:
                    flow.live = False
Beispiel #4
0
    def __call__(self):
        if self.mode == "transparent":
            self.__initial_server_tls = self.server_tls
            self.__initial_server_conn = self.server_conn
        while True:
            try:
                request = self.get_request_from_client()
                # Make sure that the incoming request matches our expectations
                self.validate_request(request)
            except netlib.exceptions.HttpReadDisconnect:
                # don't throw an error for disconnects that happen before/between requests.
                return
            except netlib.exceptions.HttpException as e:
                # We optimistically guess there might be an HTTP client on the
                # other end
                self.send_error_response(400, repr(e))
                six.reraise(
                    exceptions.ProtocolException,
                    exceptions.ProtocolException(
                        "HTTP protocol error in client request: {}".format(e)
                    ),
                    sys.exc_info()[2]
                )

            self.log("request", "debug", [repr(request)])

            # Handle Proxy Authentication
            # Proxy Authentication conceptually does not work in transparent mode.
            # We catch this misconfiguration on startup. Here, we sort out requests
            # after a successful CONNECT request (which do not need to be validated anymore)
            if not (self.http_authenticated or self.authenticate(request)):
                return

            flow = models.HTTPFlow(self.client_conn, self.server_conn, live=self)
            flow.request = request

            try:
                # Regular Proxy Mode: Handle CONNECT
                if self.mode == "regular" and request.first_line_format == "authority":
                    self.handle_regular_mode_connect(request)
                    return
            except (exceptions.ProtocolException, netlib.exceptions.NetlibException) as e:
                # HTTPS tasting means that ordinary errors like resolution and
                # connection errors can happen here.
                self.send_error_response(502, repr(e))
                flow.error = models.Error(str(e))
                self.channel.ask("error", flow)
                return

            # update host header in reverse proxy mode
            if self.config.options.mode == "reverse":
                if six.PY2:
                    flow.request.headers["Host"] = self.config.upstream_server.address.host.encode()
                else:
                    flow.request.headers["Host"] = self.config.upstream_server.address.host

            # set upstream auth
            if self.mode == "upstream" and self.config.upstream_auth is not None:
                flow.request.headers["Proxy-Authorization"] = self.config.upstream_auth
            self.process_request_hook(flow)

            try:
                if websockets.check_handshake(request.headers) and websockets.check_client_version(request.headers):
                    # We only support RFC6455 with WebSockets version 13
                    # allow inline scripts to manipulate the client handshake
                    self.channel.ask("websockets_handshake", flow)

                if not flow.response:
                    self.establish_server_connection(
                        flow.request.host,
                        flow.request.port,
                        flow.request.scheme
                    )
                    self.get_response_from_server(flow)
                else:
                    # response was set by an inline script.
                    # we now need to emulate the responseheaders hook.
                    flow = self.channel.ask("responseheaders", flow)

                self.log("response", "debug", [repr(flow.response)])
                flow = self.channel.ask("response", flow)
                self.send_response_to_client(flow)

                if self.check_close_connection(flow):
                    return

                # Handle 101 Switching Protocols
                if flow.response.status_code == 101:
                    return self.handle_101_switching_protocols(flow)

                # Upstream Proxy Mode: Handle CONNECT
                if flow.request.first_line_format == "authority" and flow.response.status_code == 200:
                    self.handle_upstream_mode_connect(flow.request.copy())
                    return

            except (exceptions.ProtocolException, netlib.exceptions.NetlibException) as e:
                self.send_error_response(502, repr(e))
                if not flow.response:
                    flow.error = models.Error(str(e))
                    self.channel.ask("error", flow)
                    return
                else:
                    six.reraise(exceptions.ProtocolException, exceptions.ProtocolException(
                        "Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2])
            finally:
                if flow:
                    flow.live = False