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)
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()
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
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