From 8135cbf65ffd7d1f15063110eb7583a414cddd2d Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 18:44:36 +0200 Subject: [PATCH 1/7] split handlers.py file --- pyproxy/handlers/__init__.py | 0 pyproxy/handlers/client.py | 96 ++++++++++++ pyproxy/handlers/http.py | 173 +++++++++++++++++++++ pyproxy/{handlers.py => handlers/https.py} | 166 +------------------- pyproxy/server.py | 2 +- 5 files changed, 274 insertions(+), 163 deletions(-) create mode 100644 pyproxy/handlers/__init__.py create mode 100644 pyproxy/handlers/client.py create mode 100644 pyproxy/handlers/http.py rename pyproxy/{handlers.py => handlers/https.py} (62%) diff --git a/pyproxy/handlers/__init__.py b/pyproxy/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyproxy/handlers/client.py b/pyproxy/handlers/client.py new file mode 100644 index 0000000..5553e41 --- /dev/null +++ b/pyproxy/handlers/client.py @@ -0,0 +1,96 @@ +""" +handlers.py + +This module defines the ProxyHandlers class used by the proxy server to process +HTTP and HTTPS client connections. It handles request forwarding, blocking, shortcut +redirection, custom headers, and optional SSL inspection. +""" + +import threading + +from pyproxy.handlers.http import HttpHandler +from pyproxy.handlers.https import HttpsHandler + +# pylint: disable=R0914,R0903 +class ProxyHandlers: + """ + ProxyHandlers manages client connections for a proxy server, + handling both HTTP and HTTPS requests. It processes request forwarding, + blocking, SSL inspection, and custom headers based on configuration settings. + """ + def __init__(self, html_403, logger_config, filter_config, ssl_config, + filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, + cancel_inspect_queue, cancel_inspect_result_queue, custom_header_queue, + custom_header_result_queue, console_logger, shortcuts, custom_header, + active_connections): + self.html_403 = html_403 + self.logger_config = logger_config + self.filter_config = filter_config + self.ssl_config = ssl_config + self.filter_queue = filter_queue + self.filter_result_queue = filter_result_queue + self.shortcuts_queue = shortcuts_queue + self.shortcuts_result_queue = shortcuts_result_queue + self.cancel_inspect_queue = cancel_inspect_queue + self.cancel_inspect_result_queue = cancel_inspect_result_queue + self.custom_header_queue = custom_header_queue + self.custom_header_result_queue = custom_header_result_queue + self.console_logger = console_logger + self.config_shortcuts = shortcuts + self.config_custom_header = custom_header + self.active_connections = active_connections + + def handle_client(self, client_socket): + """ + Handles an incoming client connection by processing the request. + + Args: + client_socket (socket): The socket object for the client connection. + """ + request = client_socket.recv(4096) + + if not request: + self.console_logger.debug("No request received, closing connection.") + client_socket.close() + self.active_connections.pop(threading.get_ident(), None) + return + + first_line = request.decode(errors='ignore').split("\n")[0] + + if first_line.startswith("CONNECT"): + client_https_handler = HttpsHandler( + html_403=self.html_403, + logger_config=self.logger_config, + filter_config=self.filter_config, + ssl_config=self.ssl_config, + filter_queue=self.filter_queue, + filter_result_queue=self.filter_result_queue, + shortcuts_queue=self.shortcuts_queue, + shortcuts_result_queue=self.shortcuts_result_queue, + cancel_inspect_queue=self.cancel_inspect_queue, + cancel_inspect_result_queue=self.cancel_inspect_result_queue, + custom_header_queue=self.custom_header_queue, + custom_header_result_queue=self.custom_header_result_queue, + console_logger=self.console_logger, + shortcuts=self.config_shortcuts, + custom_header=self.config_custom_header, + active_connections=self.active_connections + ) + client_https_handler.handle_https_connection(client_socket, first_line) + else: + client_http_handler = HttpHandler( + html_403=self.html_403, + logger_config=self.logger_config, + filter_config=self.filter_config, + filter_queue=self.filter_queue, + filter_result_queue=self.filter_result_queue, + shortcuts_queue=self.shortcuts_queue, + shortcuts_result_queue=self.shortcuts_result_queue, + custom_header_queue=self.custom_header_queue, + custom_header_result_queue=self.custom_header_result_queue, + console_logger=self.console_logger, + shortcuts=self.config_shortcuts, + custom_header=self.config_custom_header, + active_connections=self.active_connections + ) + client_http_handler.handle_http_request(client_socket, request) diff --git a/pyproxy/handlers/http.py b/pyproxy/handlers/http.py new file mode 100644 index 0000000..b96f2b4 --- /dev/null +++ b/pyproxy/handlers/http.py @@ -0,0 +1,173 @@ +""" +handlers.py + +This module defines the ProxyHandlers class used by the proxy server to process +HTTP and HTTPS client connections. It handles request forwarding, blocking, shortcut +redirection, custom headers. +""" + +import socket +import os +import threading + +from pyproxy.utils.http_req import extract_headers, parse_url + +# pylint: disable=R0914 +class HttpHandler: + """ + ProxyHandlers manages client connections for a proxy server, + handling both HTTP and HTTPS requests. It processes request forwarding, + blocking, and custom headers based on configuration settings. + """ + def __init__(self, html_403, logger_config, filter_config, + filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, + custom_header_queue, custom_header_result_queue, console_logger, shortcuts, + custom_header, active_connections): + self.html_403 = html_403 + self.logger_config = logger_config + self.filter_config = filter_config + self.filter_queue = filter_queue + self.filter_result_queue = filter_result_queue + self.shortcuts_queue = shortcuts_queue + self.shortcuts_result_queue = shortcuts_result_queue + self.custom_header_queue = custom_header_queue + self.custom_header_result_queue = custom_header_result_queue + self.console_logger = console_logger + self.config_shortcuts = shortcuts + self.config_custom_header = custom_header + self.active_connections = active_connections + + def handle_http_request(self, client_socket, request): + """ + Handles HTTP requests, checks if the URL is blocked, + and forwards the request to the target server. + + Args: + client_socket (socket): The socket object for the client connection. + request (bytes): The raw HTTP request sent by the client. + """ + first_line = request.decode(errors='ignore').split("\n")[0] + url = first_line.split(" ")[1] + + if self.config_custom_header and os.path.isfile(self.config_custom_header): + headers = extract_headers(request.decode(errors='ignore')) + self.custom_header_queue.put(url) + new_headers = self.custom_header_result_queue.get(timeout=5) + headers.update(new_headers) + + if self.config_shortcuts and os.path.isfile(self.config_shortcuts): + domain, _ = parse_url(url) + self.shortcuts_queue.put(domain) + shortcut_url = self.shortcuts_result_queue.get(timeout=5) + if shortcut_url: + response = ( + f"HTTP/1.1 302 Found\r\n" + f"Location: {shortcut_url}\r\n" + f"Content-Length: 0\r\n" + "\r\n" + ) + + client_socket.sendall(response.encode()) + client_socket.close() + self.active_connections.pop(threading.get_ident(), None) + return + + if not self.filter_config.no_filter: + self.filter_queue.put(url) + result = self.filter_result_queue.get(timeout=5) + if result[1] == "Blocked": + if not self.logger_config.no_logging_block: + self.logger_config.block_logger.info( + "%s - %s - %s", + client_socket.getpeername()[0], + url, + first_line + ) + with open(self.html_403, "r", encoding='utf-8') as f: + custom_403_page = f.read() + response = ( + f"HTTP/1.1 403 Forbidden\r\n" + f"Content-Length: {len(custom_403_page)}\r\n" + f"\r\n" + f"{custom_403_page}" + ) + client_socket.sendall(response.encode()) + client_socket.close() + self.active_connections.pop(threading.get_ident(), None) + return + server_host, _ = parse_url(url) + if not self.logger_config.no_logging_access: + self.logger_config.access_logger.info( + "%s - %s - %s", + client_socket.getpeername()[0], + f"http://{server_host}", + first_line + ) + + if self.config_custom_header and os.path.isfile(self.config_custom_header): + request_lines = request.decode(errors='ignore').split("\r\n") + request_line = request_lines[0] # GET / HTTP/1.1 + + header_lines = [f"{key}: {value}" for key, value in headers.items()] + reconstructed_headers = "\r\n".join(header_lines) + + if "\r\n\r\n" in request.decode(errors='ignore'): + body = request.decode(errors='ignore').split("\r\n\r\n", 1)[1] + else: + body = "" + + modified_request = f"{request_line}\r\n{reconstructed_headers}\r\n\r\n{body}".encode() + + self.forward_request_to_server(client_socket, modified_request, url) + + else: + self.forward_request_to_server(client_socket, request, url) + + def forward_request_to_server(self, client_socket, request, url): + """ + Forwards the HTTP request to the target server and sends the response back to the client. + + Args: + client_socket (socket): The socket object for the client connection. + request (bytes): The raw HTTP request sent by the client. + url (str): The target URL from the HTTP request. + """ + server_host, server_port = parse_url(url) + thread_id = threading.get_ident() + + if thread_id in self.active_connections: + self.active_connections[thread_id]["target_ip"] = server_host + self.active_connections[thread_id]["target_port"] = server_port + + try: + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.connect((server_host, server_port)) + server_socket.sendall(request) + server_socket.settimeout(5) + self.active_connections[thread_id]["bytes_sent"] += len(request) + + while True: + try: + response = server_socket.recv(4096) + if response: + client_socket.send(response) + self.active_connections[thread_id]["bytes_received"] += len(response) + else: + break + except socket.timeout: + break + except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e: + self.console_logger.error("Error connecting to the server %s : %s", server_host, e) + response = ( + f"HTTP/1.1 502 Bad Gateway\r\n" + f"Content-Length: {len('Bad Gateway')} \r\n" + "\r\n" + f"Bad Gateway" + ) + client_socket.sendall(response.encode()) + client_socket.close() + self.active_connections.pop(thread_id, None) + finally: + client_socket.close() + server_socket.close() + self.active_connections.pop(thread_id, None) diff --git a/pyproxy/handlers.py b/pyproxy/handlers/https.py similarity index 62% rename from pyproxy/handlers.py rename to pyproxy/handlers/https.py index 02103ed..5196c5c 100644 --- a/pyproxy/handlers.py +++ b/pyproxy/handlers/https.py @@ -13,10 +13,9 @@ import threading from pyproxy.utils.crypto import generate_certificate -from pyproxy.utils.http_req import extract_headers, parse_url # pylint: disable=R0914 -class ProxyHandlers: +class HttpsHandler: """ ProxyHandlers manages client connections for a proxy server, handling both HTTP and HTTPS requests. It processes request forwarding, @@ -44,163 +43,6 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, self.config_custom_header = custom_header self.active_connections = active_connections - def handle_client(self, client_socket): - """ - Handles an incoming client connection by processing the request. - - Args: - client_socket (socket): The socket object for the client connection. - """ - request = client_socket.recv(4096) - - if not request: - self.console_logger.debug("No request received, closing connection.") - client_socket.close() - self.active_connections.pop(threading.get_ident(), None) - return - - first_line = request.decode(errors='ignore').split("\n")[0] - - if first_line.startswith("CONNECT"): - self.handle_https_connection(client_socket, first_line) - else: - self.handle_http_request(client_socket, request) - - def handle_http_request(self, client_socket, request): - """ - Handles HTTP requests, checks if the URL is blocked, - and forwards the request to the target server. - - Args: - client_socket (socket): The socket object for the client connection. - request (bytes): The raw HTTP request sent by the client. - """ - first_line = request.decode(errors='ignore').split("\n")[0] - url = first_line.split(" ")[1] - - if self.config_custom_header and os.path.isfile(self.config_custom_header): - headers = extract_headers(request.decode(errors='ignore')) - self.custom_header_queue.put(url) - new_headers = self.custom_header_result_queue.get() - headers.update(new_headers) - - if self.config_shortcuts and os.path.isfile(self.config_shortcuts): - domain, _ = parse_url(url) - self.shortcuts_queue.put(domain) - shortcut_url = self.shortcuts_result_queue.get() - if shortcut_url: - response = ( - f"HTTP/1.1 302 Found\r\n" - f"Location: {shortcut_url}\r\n" - f"Content-Length: 0\r\n" - "\r\n" - ) - - client_socket.sendall(response.encode()) - client_socket.close() - self.active_connections.pop(threading.get_ident(), None) - return - - if not self.filter_config.no_filter: - self.filter_queue.put(url) - result = self.filter_result_queue.get() - if result[1] == "Blocked": - if not self.logger_config.no_logging_block: - self.logger_config.block_logger.info( - "%s - %s - %s", - client_socket.getpeername()[0], - url, - first_line - ) - with open(self.html_403, "r", encoding='utf-8') as f: - custom_403_page = f.read() - response = ( - f"HTTP/1.1 403 Forbidden\r\n" - f"Content-Length: {len(custom_403_page)}\r\n" - f"\r\n" - f"{custom_403_page}" - ) - client_socket.sendall(response.encode()) - client_socket.close() - self.active_connections.pop(threading.get_ident(), None) - return - server_host, _ = parse_url(url) - if not self.logger_config.no_logging_access: - self.logger_config.access_logger.info( - "%s - %s - %s", - client_socket.getpeername()[0], - f"http://{server_host}", - first_line - ) - - if self.config_custom_header and os.path.isfile(self.config_custom_header): - request_lines = request.decode(errors='ignore').split("\r\n") - request_line = request_lines[0] # GET / HTTP/1.1 - - header_lines = [f"{key}: {value}" for key, value in headers.items()] - reconstructed_headers = "\r\n".join(header_lines) - - if "\r\n\r\n" in request.decode(errors='ignore'): - body = request.decode(errors='ignore').split("\r\n\r\n", 1)[1] - else: - body = "" - - modified_request = f"{request_line}\r\n{reconstructed_headers}\r\n\r\n{body}".encode() - - self.forward_request_to_server(client_socket, modified_request, url) - - else: - self.forward_request_to_server(client_socket, request, url) - - def forward_request_to_server(self, client_socket, request, url): - """ - Forwards the HTTP request to the target server and sends the response back to the client. - - Args: - client_socket (socket): The socket object for the client connection. - request (bytes): The raw HTTP request sent by the client. - url (str): The target URL from the HTTP request. - """ - server_host, server_port = parse_url(url) - thread_id = threading.get_ident() - - if thread_id in self.active_connections: - self.active_connections[thread_id]["target_ip"] = server_host - self.active_connections[thread_id]["target_port"] = server_port - - try: - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.connect((server_host, server_port)) - server_socket.sendall(request) - server_socket.settimeout(5) - self.active_connections[thread_id]["bytes_sent"] += len(request) - - while True: - try: - response = server_socket.recv(4096) - if response: - client_socket.send(response) - self.active_connections[thread_id]["bytes_received"] += len(response) - else: - break - except socket.timeout: - break - except (socket.timeout, socket.gaierror, ConnectionRefusedError, OSError) as e: - self.console_logger.error("Error connecting to the server %s : %s", server_host, e) - response = ( - f"HTTP/1.1 502 Bad Gateway\r\n" - f"Content-Length: {len('Bad Gateway')} \r\n" - "\r\n" - f"Bad Gateway" - ) - client_socket.sendall(response.encode()) - client_socket.close() - self.active_connections.pop(thread_id, None) - finally: - client_socket.close() - server_socket.close() - self.active_connections.pop(thread_id, None) - # pylint: disable=too-many-locals,too-many-statements,too-many-branches,too-many-nested-blocks def handle_https_connection(self, client_socket, first_line): """ @@ -217,7 +59,7 @@ def handle_https_connection(self, client_socket, first_line): if not self.filter_config.no_filter: self.filter_queue.put(target) - result = self.filter_result_queue.get() + result = self.filter_result_queue.get(timeout=5) if result[1] == "Blocked": if not self.logger_config.no_logging_block: self.logger_config.block_logger.info( @@ -246,7 +88,7 @@ def handle_https_connection(self, client_socket, first_line): and os.path.isfile(self.ssl_config.cancel_inspect) ): self.cancel_inspect_queue.put(server_host) - not_inspect = self.cancel_inspect_result_queue.get() + not_inspect = self.cancel_inspect_result_queue.get(timeout=5) if self.ssl_config.ssl_inspect and not not_inspect: cert_path, key_path = generate_certificate( @@ -292,7 +134,7 @@ def handle_https_connection(self, client_socket, first_line): if not self.filter_config.no_filter: self.filter_queue.put(f"{server_host}{path}") - result = self.filter_result_queue.get() + result = self.filter_result_queue.get(timeout=5) if result[1] == "Blocked": if not self.logger_config.no_logging_block: self.logger_config.block_logger.info( diff --git a/pyproxy/server.py b/pyproxy/server.py index 35e8c1a..81051c4 100644 --- a/pyproxy/server.py +++ b/pyproxy/server.py @@ -16,7 +16,7 @@ from pyproxy.utils.version import __slim__ from pyproxy.utils.logger import configure_file_logger, configure_console_logger -from pyproxy.handlers import ProxyHandlers +from pyproxy.handlers.client import ProxyHandlers from pyproxy.modules.filter import filter_process from pyproxy.modules.cancel_inspect import cancel_inspect_process if not __slim__: From f2c2b65b00a8f5f8a25d3a8349317b1b8b5fbf5f Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 18:59:14 +0200 Subject: [PATCH 2/7] add proxy_host & proxy_port argument --- config.ini.example | 6 +++++- pyproxy.py | 8 ++++++-- pyproxy/handlers/client.py | 21 +++++++++++++++------ pyproxy/handlers/http.py | 21 +++++++++++---------- pyproxy/handlers/https.py | 20 ++++++++++++-------- pyproxy/server.py | 8 +++++++- pyproxy/utils/args.py | 2 ++ 7 files changed, 58 insertions(+), 28 deletions(-) diff --git a/config.ini.example b/config.ini.example index 0955071..b05f010 100644 --- a/config.ini.example +++ b/config.ini.example @@ -31,4 +31,8 @@ cancel_inspect = config/cancel_inspect.txt [Monitoring] flask_port = 5000 -flask_pass = password \ No newline at end of file +flask_pass = password + +[Proxy] +proxy_host = 127.0.0.1 +proxy_port = 8081 diff --git a/pyproxy.py b/pyproxy.py index 61a3ea6..cd4e40a 100644 --- a/pyproxy.py +++ b/pyproxy.py @@ -8,7 +8,7 @@ from pyproxy.utils.args import parse_args, load_config, get_config_value from pyproxy.utils.config import ProxyConfigLogger, ProxyConfigFilter, ProxyConfigSSL -# pylint: disable=C0301 +# pylint: disable=C0301,R0914 def main(): """ Main entry point of the proxy server. It parses command-line arguments, loads the configuration file, @@ -25,6 +25,8 @@ def main(): custom_header = get_config_value(args, config, 'custom_header', 'Options', "config/custom_header.json") flask_port = get_config_value(args, config, 'flask_port', 'Monitoring', 5000) flask_pass = get_config_value(args, config, 'flask_pass', 'Monitoring', "password") + proxy_host = get_config_value(args, config, 'proxy_host', 'Proxy', "127.0.0.1") + proxy_port = get_config_value(args, config, 'proxy_port', 'Proxy', 8081) logger_config = ProxyConfigLogger( access_log=get_config_value(args, config, 'access_log', 'Logging', "logs/access.log"), @@ -59,7 +61,9 @@ def main(): flask_pass=flask_pass, html_403=html_403, shortcuts=shortcuts, - custom_header=custom_header + custom_header=custom_header, + proxy_host=proxy_host, + proxy_port=proxy_port ) proxy.start() diff --git a/pyproxy/handlers/client.py b/pyproxy/handlers/client.py index 5553e41..309f728 100644 --- a/pyproxy/handlers/client.py +++ b/pyproxy/handlers/client.py @@ -1,5 +1,5 @@ """ -handlers.py +pyproxy.handlers.client.py This module defines the ProxyHandlers class used by the proxy server to process HTTP and HTTPS client connections. It handles request forwarding, blocking, shortcut @@ -14,15 +14,17 @@ # pylint: disable=R0914,R0903 class ProxyHandlers: """ - ProxyHandlers manages client connections for a proxy server, - handling both HTTP and HTTPS requests. It processes request forwarding, - blocking, SSL inspection, and custom headers based on configuration settings. + ProxyHandlers manages client connections for a proxy server, handling both HTTP + and HTTPS requests. It processes request forwarding, blocking, SSL inspection, + and custom headers based on configuration settings. This class is responsible + for dispatching the correct handler for HTTP or HTTPS requests and managing + connection-related operations. """ def __init__(self, html_403, logger_config, filter_config, ssl_config, filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, cancel_inspect_queue, cancel_inspect_result_queue, custom_header_queue, custom_header_result_queue, console_logger, shortcuts, custom_header, - active_connections): + active_connections, proxy_host, proxy_port): self.html_403 = html_403 self.logger_config = logger_config self.filter_config = filter_config @@ -38,11 +40,14 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, self.console_logger = console_logger self.config_shortcuts = shortcuts self.config_custom_header = custom_header + self.proxy_host=proxy_host + self.proxy_port=proxy_port self.active_connections = active_connections def handle_client(self, client_socket): """ - Handles an incoming client connection by processing the request. + Handles an incoming client connection by processing the request and forwarding + it to the appropriate handler based on whether the request is HTTP or HTTPS. Args: client_socket (socket): The socket object for the client connection. @@ -74,6 +79,8 @@ def handle_client(self, client_socket): console_logger=self.console_logger, shortcuts=self.config_shortcuts, custom_header=self.config_custom_header, + proxy_host=self.proxy_host, + proxy_port=self.proxy_port, active_connections=self.active_connections ) client_https_handler.handle_https_connection(client_socket, first_line) @@ -91,6 +98,8 @@ def handle_client(self, client_socket): console_logger=self.console_logger, shortcuts=self.config_shortcuts, custom_header=self.config_custom_header, + proxy_host=self.proxy_host, + proxy_port=self.proxy_port, active_connections=self.active_connections ) client_http_handler.handle_http_request(client_socket, request) diff --git a/pyproxy/handlers/http.py b/pyproxy/handlers/http.py index b96f2b4..47f7829 100644 --- a/pyproxy/handlers/http.py +++ b/pyproxy/handlers/http.py @@ -1,9 +1,8 @@ """ -handlers.py +pyproxy.handlers.http.py -This module defines the ProxyHandlers class used by the proxy server to process -HTTP and HTTPS client connections. It handles request forwarding, blocking, shortcut -redirection, custom headers. +This module defines the HttpHandler class used by the proxy server to process +HTTP client connections. It handles request forwarding, blocking, and custom headers. """ import socket @@ -15,14 +14,14 @@ # pylint: disable=R0914 class HttpHandler: """ - ProxyHandlers manages client connections for a proxy server, - handling both HTTP and HTTPS requests. It processes request forwarding, - blocking, and custom headers based on configuration settings. + HttpHandler manages client HTTP connections for a proxy server, + handling request forwarding, filtering, blocking, and custom header modification + based on configuration settings. """ def __init__(self, html_403, logger_config, filter_config, filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, custom_header_queue, custom_header_result_queue, console_logger, shortcuts, - custom_header, active_connections): + custom_header, active_connections, proxy_host, proxy_port): self.html_403 = html_403 self.logger_config = logger_config self.filter_config = filter_config @@ -35,12 +34,14 @@ def __init__(self, html_403, logger_config, filter_config, self.console_logger = console_logger self.config_shortcuts = shortcuts self.config_custom_header = custom_header + self.proxy_host=proxy_host + self.proxy_port=proxy_port self.active_connections = active_connections def handle_http_request(self, client_socket, request): """ - Handles HTTP requests, checks if the URL is blocked, - and forwards the request to the target server. + Processes an HTTP request, checks for URL filtering, applies shortcuts, + and forwards the request to the target server if not blocked. Args: client_socket (socket): The socket object for the client connection. diff --git a/pyproxy/handlers/https.py b/pyproxy/handlers/https.py index 5196c5c..1b35148 100644 --- a/pyproxy/handlers/https.py +++ b/pyproxy/handlers/https.py @@ -1,9 +1,9 @@ """ -handlers.py +pyproxy.handlers.https.py -This module defines the ProxyHandlers class used by the proxy server to process -HTTP and HTTPS client connections. It handles request forwarding, blocking, shortcut -redirection, custom headers, and optional SSL inspection. +This class handles HTTPS CONNECT requests, applies filtering rules, supports SSL inspection, +generates certificates dynamically, and logs access and blocked attempts. It can also +relay raw data when SSL inspection is disabled. """ import socket @@ -17,15 +17,17 @@ # pylint: disable=R0914 class HttpsHandler: """ - ProxyHandlers manages client connections for a proxy server, - handling both HTTP and HTTPS requests. It processes request forwarding, - blocking, SSL inspection, and custom headers based on configuration settings. + Handles HTTPS client connections for a proxy server. + + Supports SSL interception, filtering of targets, and custom logging. This handler + processes HTTPS `CONNECT` requests and either tunnels them directly to the destination + or performs SSL interception for inspection and filtering. """ def __init__(self, html_403, logger_config, filter_config, ssl_config, filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, cancel_inspect_queue, cancel_inspect_result_queue, custom_header_queue, custom_header_result_queue, console_logger, shortcuts, custom_header, - active_connections): + active_connections, proxy_host, proxy_port): self.html_403 = html_403 self.logger_config = logger_config self.filter_config = filter_config @@ -41,6 +43,8 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, self.console_logger = console_logger self.config_shortcuts = shortcuts self.config_custom_header = custom_header + self.proxy_host=proxy_host + self.proxy_port=proxy_port self.active_connections = active_connections # pylint: disable=too-many-locals,too-many-statements,too-many-branches,too-many-nested-blocks diff --git a/pyproxy/server.py b/pyproxy/server.py index 81051c4..cf49c3e 100644 --- a/pyproxy/server.py +++ b/pyproxy/server.py @@ -43,7 +43,7 @@ class ProxyServer: def __init__(self, host, port, debug, logger_config, filter_config, html_403, ssl_config, shortcuts, custom_header, - flask_port, flask_pass): + flask_port, flask_pass, proxy_host, proxy_port): """ Initialize the ProxyServer with configuration parameters. """ @@ -60,6 +60,10 @@ def __init__(self, host, port, debug, logger_config, filter_config, self.flask_port = flask_port self.flask_pass = flask_pass + # Proxy + self.proxy_host=proxy_host + self.proxy_port=proxy_port + # Process communication queues self.filter_proc = None self.filter_queue = multiprocessing.Queue() @@ -231,6 +235,8 @@ def start(self): console_logger=self.console_logger, shortcuts=self.config_shortcuts, custom_header=self.config_custom_header, + proxy_host=self.proxy_host, + proxy_port=self.proxy_port, active_connections=self.active_connections ) client_handler = threading.Thread( diff --git a/pyproxy/utils/args.py b/pyproxy/utils/args.py index fe28932..8be4fe5 100644 --- a/pyproxy/utils/args.py +++ b/pyproxy/utils/args.py @@ -49,6 +49,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--cancel-inspect", type=str, help="Path to the text file containing the list of URLs without ssl inspection") parser.add_argument("--flask-port", type=int, help="Port to listen on for monitoring interface") parser.add_argument("--flask-pass", type=int, help="Default password to Flask interface") + parser.add_argument("--proxy-host", type=int, help="Proxy IP to use after PyProxy") + parser.add_argument("--proxy-port", type=int, help="Proxy Port to use after PyProxy") return parser.parse_args() From 5e9f0d9771132bbf917e017d5fc8291b02dfb88d Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 21:13:24 +0200 Subject: [PATCH 3/7] proxify HTTP request --- config.ini.example | 1 + pyproxy.py | 2 ++ pyproxy/handlers/client.py | 5 ++++- pyproxy/handlers/http.py | 8 ++++++-- pyproxy/handlers/https.py | 3 ++- pyproxy/server.py | 4 +++- pyproxy/utils/args.py | 3 ++- 7 files changed, 20 insertions(+), 6 deletions(-) diff --git a/config.ini.example b/config.ini.example index b05f010..30192e6 100644 --- a/config.ini.example +++ b/config.ini.example @@ -34,5 +34,6 @@ flask_port = 5000 flask_pass = password [Proxy] +proxy_enable = false proxy_host = 127.0.0.1 proxy_port = 8081 diff --git a/pyproxy.py b/pyproxy.py index cd4e40a..6ef6fd4 100644 --- a/pyproxy.py +++ b/pyproxy.py @@ -25,6 +25,7 @@ def main(): custom_header = get_config_value(args, config, 'custom_header', 'Options', "config/custom_header.json") flask_port = get_config_value(args, config, 'flask_port', 'Monitoring', 5000) flask_pass = get_config_value(args, config, 'flask_pass', 'Monitoring', "password") + proxy_enable = get_config_value(args, config, 'proxy_enable', 'Proxy', False) proxy_host = get_config_value(args, config, 'proxy_host', 'Proxy', "127.0.0.1") proxy_port = get_config_value(args, config, 'proxy_port', 'Proxy', 8081) @@ -62,6 +63,7 @@ def main(): html_403=html_403, shortcuts=shortcuts, custom_header=custom_header, + proxy_enable=proxy_enable, proxy_host=proxy_host, proxy_port=proxy_port ) diff --git a/pyproxy/handlers/client.py b/pyproxy/handlers/client.py index 309f728..8c494f2 100644 --- a/pyproxy/handlers/client.py +++ b/pyproxy/handlers/client.py @@ -24,7 +24,7 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, cancel_inspect_queue, cancel_inspect_result_queue, custom_header_queue, custom_header_result_queue, console_logger, shortcuts, custom_header, - active_connections, proxy_host, proxy_port): + active_connections, proxy_enable, proxy_host, proxy_port): self.html_403 = html_403 self.logger_config = logger_config self.filter_config = filter_config @@ -40,6 +40,7 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, self.console_logger = console_logger self.config_shortcuts = shortcuts self.config_custom_header = custom_header + self.proxy_enable=proxy_enable self.proxy_host=proxy_host self.proxy_port=proxy_port self.active_connections = active_connections @@ -79,6 +80,7 @@ def handle_client(self, client_socket): console_logger=self.console_logger, shortcuts=self.config_shortcuts, custom_header=self.config_custom_header, + proxy_enable=self.proxy_enable, proxy_host=self.proxy_host, proxy_port=self.proxy_port, active_connections=self.active_connections @@ -98,6 +100,7 @@ def handle_client(self, client_socket): console_logger=self.console_logger, shortcuts=self.config_shortcuts, custom_header=self.config_custom_header, + proxy_enable=self.proxy_enable, proxy_host=self.proxy_host, proxy_port=self.proxy_port, active_connections=self.active_connections diff --git a/pyproxy/handlers/http.py b/pyproxy/handlers/http.py index 47f7829..7d260cb 100644 --- a/pyproxy/handlers/http.py +++ b/pyproxy/handlers/http.py @@ -21,7 +21,7 @@ class HttpHandler: def __init__(self, html_403, logger_config, filter_config, filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, custom_header_queue, custom_header_result_queue, console_logger, shortcuts, - custom_header, active_connections, proxy_host, proxy_port): + custom_header, active_connections, proxy_enable, proxy_host, proxy_port): self.html_403 = html_403 self.logger_config = logger_config self.filter_config = filter_config @@ -34,6 +34,7 @@ def __init__(self, html_403, logger_config, filter_config, self.console_logger = console_logger self.config_shortcuts = shortcuts self.config_custom_header = custom_header + self.proxy_enable=proxy_enable self.proxy_host=proxy_host self.proxy_port=proxy_port self.active_connections = active_connections @@ -133,7 +134,10 @@ def forward_request_to_server(self, client_socket, request, url): request (bytes): The raw HTTP request sent by the client. url (str): The target URL from the HTTP request. """ - server_host, server_port = parse_url(url) + if self.proxy_enable: + server_host, server_port = self.proxy_host, self.proxy_port + else: + server_host, server_port = parse_url(url) thread_id = threading.get_ident() if thread_id in self.active_connections: diff --git a/pyproxy/handlers/https.py b/pyproxy/handlers/https.py index 1b35148..aea0d5d 100644 --- a/pyproxy/handlers/https.py +++ b/pyproxy/handlers/https.py @@ -27,7 +27,7 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, filter_queue, filter_result_queue, shortcuts_queue, shortcuts_result_queue, cancel_inspect_queue, cancel_inspect_result_queue, custom_header_queue, custom_header_result_queue, console_logger, shortcuts, custom_header, - active_connections, proxy_host, proxy_port): + active_connections, proxy_enable, proxy_host, proxy_port): self.html_403 = html_403 self.logger_config = logger_config self.filter_config = filter_config @@ -43,6 +43,7 @@ def __init__(self, html_403, logger_config, filter_config, ssl_config, self.console_logger = console_logger self.config_shortcuts = shortcuts self.config_custom_header = custom_header + self.proxy_enable=proxy_enable self.proxy_host=proxy_host self.proxy_port=proxy_port self.active_connections = active_connections diff --git a/pyproxy/server.py b/pyproxy/server.py index cf49c3e..df1c9c5 100644 --- a/pyproxy/server.py +++ b/pyproxy/server.py @@ -43,7 +43,7 @@ class ProxyServer: def __init__(self, host, port, debug, logger_config, filter_config, html_403, ssl_config, shortcuts, custom_header, - flask_port, flask_pass, proxy_host, proxy_port): + flask_port, flask_pass, proxy_enable, proxy_host, proxy_port): """ Initialize the ProxyServer with configuration parameters. """ @@ -61,6 +61,7 @@ def __init__(self, host, port, debug, logger_config, filter_config, self.flask_pass = flask_pass # Proxy + self.proxy_enable=proxy_enable self.proxy_host=proxy_host self.proxy_port=proxy_port @@ -235,6 +236,7 @@ def start(self): console_logger=self.console_logger, shortcuts=self.config_shortcuts, custom_header=self.config_custom_header, + proxy_enable=self.proxy_enable, proxy_host=self.proxy_host, proxy_port=self.proxy_port, active_connections=self.active_connections diff --git a/pyproxy/utils/args.py b/pyproxy/utils/args.py index 8be4fe5..28e2a58 100644 --- a/pyproxy/utils/args.py +++ b/pyproxy/utils/args.py @@ -49,7 +49,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--cancel-inspect", type=str, help="Path to the text file containing the list of URLs without ssl inspection") parser.add_argument("--flask-port", type=int, help="Port to listen on for monitoring interface") parser.add_argument("--flask-pass", type=int, help="Default password to Flask interface") - parser.add_argument("--proxy-host", type=int, help="Proxy IP to use after PyProxy") + parser.add_argument("--proxy-enable", action="store_true", help="Enable proxy after PyProxy") + parser.add_argument("--proxy-host", type=str, help="Proxy IP to use after PyProxy") parser.add_argument("--proxy-port", type=int, help="Proxy Port to use after PyProxy") return parser.parse_args() From 38c640444bf53a0bcc097bd632b4d1cf26077f89 Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 21:20:59 +0200 Subject: [PATCH 4/7] proxify https connection if ssl inspection enable --- pyproxy/handlers/https.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/pyproxy/handlers/https.py b/pyproxy/handlers/https.py index aea0d5d..4ed4b97 100644 --- a/pyproxy/handlers/https.py +++ b/pyproxy/handlers/https.py @@ -119,10 +119,31 @@ def handle_https_connection(self, client_socket, first_line): ) ssl_client_socket.do_handshake() - server_socket = socket.create_connection((server_host, server_port)) + if self.proxy_enable: + next_proxy_socket = socket.create_connection((self.proxy_host, self.proxy_port)) + connect_command = f"CONNECT {server_host}:{server_port} HTTP/1.1\r\nHost: {server_host}:{server_port}\r\n\r\n" + next_proxy_socket.sendall(connect_command.encode()) + + response = b"" + while b"\r\n\r\n" not in response: + chunk = next_proxy_socket.recv(4096) + if not chunk: + raise Exception("Connection to next proxy failed") + response += chunk + + if b"200 Connection Established" not in response: + raise Exception("Next proxy refused CONNECT") + + server_socket = next_proxy_socket + else: + server_socket = socket.create_connection((server_host, server_port)) server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - server_context.load_default_certs() + if self.proxy_enable: + server_context.check_hostname = False + server_context.verify_mode = ssl.CERT_NONE + else: + server_context.load_default_certs() ssl_server_socket = server_context.wrap_socket( server_socket, From 508cde81cec8bf5332c166a6e07a0796066fb105 Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 21:27:15 +0200 Subject: [PATCH 5/7] README: add proxy chaining --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 97700b4..a00e8df 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ | Custom headers | ✅ | | Web interface monitoring | ✅ | | Lightweight Docker image | ✅ | +| Proxy chaining (multi-proxy forwarding) | ✅ | ## 📦 **Installation** From 7ac2f4d4b0f99614ae8cddb111635c4a9e0b9c34 Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 21:27:28 +0200 Subject: [PATCH 6/7] =?UTF-8?q?Bump=20version:=200.3.1=20=E2=86=92=200.3.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pyproxy/utils/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 7d43a83..8d3eec7 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.1 +current_version = 0.3.2 commit = True tag = True diff --git a/pyproxy/utils/version.py b/pyproxy/utils/version.py index 8112d65..27df6cb 100644 --- a/pyproxy/utils/version.py +++ b/pyproxy/utils/version.py @@ -7,7 +7,7 @@ import os -__version__ = "0.3.1" +__version__ = "0.3.2" if os.path.isdir("pyproxy/monitoring"): __slim__ = False From 1aabae21e2db16b56c5be373ac37f8db50f83a82 Mon Sep 17 00:00:00 2001 From: 6C656C65 <73671374+6C656C65@users.noreply.github.com> Date: Tue, 13 May 2025 22:37:30 +0200 Subject: [PATCH 7/7] fix pylint --- pyproxy/handlers/https.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyproxy/handlers/https.py b/pyproxy/handlers/https.py index 4ed4b97..ffdb1f8 100644 --- a/pyproxy/handlers/https.py +++ b/pyproxy/handlers/https.py @@ -121,18 +121,21 @@ def handle_https_connection(self, client_socket, first_line): if self.proxy_enable: next_proxy_socket = socket.create_connection((self.proxy_host, self.proxy_port)) - connect_command = f"CONNECT {server_host}:{server_port} HTTP/1.1\r\nHost: {server_host}:{server_port}\r\n\r\n" + connect_command = ( + f"CONNECT {server_host}:{server_port} HTTP/1.1\r\n" + f"Host: {server_host}:{server_port}\r\n\r\n" + ) next_proxy_socket.sendall(connect_command.encode()) response = b"" while b"\r\n\r\n" not in response: chunk = next_proxy_socket.recv(4096) if not chunk: - raise Exception("Connection to next proxy failed") + raise ConnectionError("Connection to next proxy failed") response += chunk if b"200 Connection Established" not in response: - raise Exception("Next proxy refused CONNECT") + raise ConnectionRefusedError("Next proxy refused CONNECT") server_socket = next_proxy_socket else: