Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
modes.py304 linesDownload Raw Back to layers
1from __future__ import annotations2 3import socket4import struct5import sys6from abc import ABCMeta7from collections.abc import Callable8from dataclasses import dataclass9 10from mitmproxy import connection11from mitmproxy.proxy import commands12from mitmproxy.proxy import events13from mitmproxy.proxy import layer14from mitmproxy.proxy.commands import StartHook15from mitmproxy.proxy.mode_specs import ReverseMode16from mitmproxy.proxy.utils import expect17 18if sys.version_info < (3, 11):19    from typing_extensions import assert_never20else:21    from typing import assert_never22 23 24class HttpProxy(layer.Layer):25    @expect(events.Start)26    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:27        child_layer = layer.NextLayer(self.context)28        self._handle_event = child_layer.handle_event29        yield from child_layer.handle_event(event)30 31 32class HttpUpstreamProxy(layer.Layer):33    @expect(events.Start)34    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:35        child_layer = layer.NextLayer(self.context)36        self._handle_event = child_layer.handle_event37        yield from child_layer.handle_event(event)38 39 40class DestinationKnown(layer.Layer, metaclass=ABCMeta):41    """Base layer for layers that gather connection destination info and then delegate."""42 43    child_layer: layer.Layer44 45    def finish_start(self) -> layer.CommandGenerator[str | None]:46        if (47            self.context.options.connection_strategy == "eager"48            and self.context.server.address49            and self.context.server.transport_protocol == "tcp"50        ):51            err = yield commands.OpenConnection(self.context.server)52            if err:53                self._handle_event = self.done  # type: ignore54                return err55 56        self._handle_event = self.child_layer.handle_event  # type: ignore57        yield from self.child_layer.handle_event(events.Start())58        return None59 60    @expect(events.DataReceived, events.ConnectionClosed)61    def done(self, _) -> layer.CommandGenerator[None]:62        yield from ()63 64 65class ReverseProxy(DestinationKnown):66    @expect(events.Start)67    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:68        spec = self.context.client.proxy_mode69        assert isinstance(spec, ReverseMode)70        self.context.server.address = spec.address71 72        self.child_layer = layer.NextLayer(self.context)73 74        # For secure protocols, set SNI if keep_host_header is false75        match spec.scheme:76            case "http3" | "quic" | "https" | "tls" | "dtls":77                if not self.context.options.keep_host_header:78                    self.context.server.sni = spec.address[0]79            case "tcp" | "http" | "udp" | "dns":80                pass81            case _:  # pragma: no cover82                assert_never(spec.scheme)83 84        err = yield from self.finish_start()85        if err:86            yield commands.CloseConnection(self.context.client)87 88 89class TransparentProxy(DestinationKnown):90    @expect(events.Start)91    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:92        assert self.context.server.address, "No server address set."93        self.child_layer = layer.NextLayer(self.context)94        err = yield from self.finish_start()95        if err:96            yield commands.CloseConnection(self.context.client)97 98 99SOCKS5_VERSION = 0x05100 101SOCKS5_METHOD_NO_AUTHENTICATION_REQUIRED = 0x00102SOCKS5_METHOD_USER_PASSWORD_AUTHENTICATION = 0x02103SOCKS5_METHOD_NO_ACCEPTABLE_METHODS = 0xFF104 105SOCKS5_ATYP_IPV4_ADDRESS = 0x01106SOCKS5_ATYP_DOMAINNAME = 0x03107SOCKS5_ATYP_IPV6_ADDRESS = 0x04108 109SOCKS5_REP_HOST_UNREACHABLE = 0x04110SOCKS5_REP_COMMAND_NOT_SUPPORTED = 0x07111SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED = 0x08112 113 114@dataclass115class Socks5AuthData:116    client_conn: connection.Client117    username: str118    password: str119    valid: bool = False120 121 122@dataclass123class Socks5AuthHook(StartHook):124    """125    Mitmproxy has received username/password SOCKS5 credentials.126 127    This hook decides whether they are valid by setting `data.valid`.128    """129 130    data: Socks5AuthData131 132 133class Socks5Proxy(DestinationKnown):134    buf: bytes = b""135 136    def socks_err(137        self,138        message: str,139        reply_code: int | None = None,140    ) -> layer.CommandGenerator[None]:141        if reply_code is not None:142            yield commands.SendData(143                self.context.client,144                bytes([SOCKS5_VERSION, reply_code])145                + b"\x00\x01\x00\x00\x00\x00\x00\x00",146            )147        yield commands.CloseConnection(self.context.client)148        yield commands.Log(message)149        self._handle_event = self.done150 151    @expect(events.Start, events.DataReceived, events.ConnectionClosed)152    def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:153        if isinstance(event, events.Start):154            pass155        elif isinstance(event, events.DataReceived):156            self.buf += event.data157            yield from self.state()158        elif isinstance(event, events.ConnectionClosed):159            if self.buf:160                yield commands.Log(161                    f"Client closed connection before completing SOCKS5 handshake: {self.buf!r}"162                )163            yield commands.CloseConnection(event.connection)164        else:165            raise AssertionError(f"Unknown event: {event}")166 167    def state_greet(self) -> layer.CommandGenerator[None]:168        if len(self.buf) < 2:169            return170 171        if self.buf[0] != SOCKS5_VERSION:172            if self.buf[:3].isupper():173                guess = "Probably not a SOCKS request but a regular HTTP request. "174            else:175                guess = ""176            yield from self.socks_err(177                guess + "Invalid SOCKS version. Expected 0x05, got 0x%x" % self.buf[0]178            )179            return180 181        n_methods = self.buf[1]182        if len(self.buf) < 2 + n_methods:183            return184 185        if "proxyauth" in self.context.options and self.context.options.proxyauth:186            method = SOCKS5_METHOD_USER_PASSWORD_AUTHENTICATION187            self.state = self.state_auth188        else:189            method = SOCKS5_METHOD_NO_AUTHENTICATION_REQUIRED190            self.state = self.state_connect191 192        if method not in self.buf[2 : 2 + n_methods]:193            method_str = (194                "user/password"195                if method == SOCKS5_METHOD_USER_PASSWORD_AUTHENTICATION196                else "no"197            )198            yield from self.socks_err(199                f"Client does not support SOCKS5 with {method_str} authentication.",200                SOCKS5_METHOD_NO_ACCEPTABLE_METHODS,201            )202            return203        yield commands.SendData(self.context.client, bytes([SOCKS5_VERSION, method]))204        self.buf = self.buf[2 + n_methods :]205        yield from self.state()206 207    state: Callable[..., layer.CommandGenerator[None]] = state_greet208 209    def state_auth(self) -> layer.CommandGenerator[None]:210        if len(self.buf) < 3:211            return212 213        # Parsing username and password, which is somewhat atrocious214        user_len = self.buf[1]215        if len(self.buf) < 3 + user_len:216            return217        pass_len = self.buf[2 + user_len]218        if len(self.buf) < 3 + user_len + pass_len:219            return220        user = self.buf[2 : (2 + user_len)].decode("utf-8", "backslashreplace")221        password = self.buf[(3 + user_len) : (3 + user_len + pass_len)].decode(222            "utf-8", "backslashreplace"223        )224 225        data = Socks5AuthData(self.context.client, user, password)226        yield Socks5AuthHook(data)227        if not data.valid:228            # The VER field contains the current **version of the subnegotiation**, which is X'01'.229            yield commands.SendData(self.context.client, b"\x01\x01")230            yield from self.socks_err("authentication failed")231            return232 233        yield commands.SendData(self.context.client, b"\x01\x00")234        self.buf = self.buf[3 + user_len + pass_len :]235        self.state = self.state_connect236        yield from self.state()237 238    def state_connect(self) -> layer.CommandGenerator[None]:239        # Parse Connect Request240        if len(self.buf) < 5:241            return242 243        if self.buf[:3] != b"\x05\x01\x00":244            yield from self.socks_err(245                f"Unsupported SOCKS5 request: {self.buf!r}",246                SOCKS5_REP_COMMAND_NOT_SUPPORTED,247            )248            return249 250        # Determine message length251        atyp = self.buf[3]252        message_len: int253        if atyp == SOCKS5_ATYP_IPV4_ADDRESS:254            message_len = 4 + 4 + 2255        elif atyp == SOCKS5_ATYP_IPV6_ADDRESS:256            message_len = 4 + 16 + 2257        elif atyp == SOCKS5_ATYP_DOMAINNAME:258            message_len = 4 + 1 + self.buf[4] + 2259        else:260            yield from self.socks_err(261                f"Unknown address type: {atyp}", SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED262            )263            return264 265        # Do we have enough bytes yet?266        if len(self.buf) < message_len:267            return268 269        # Parse host and port270        msg, self.buf = self.buf[:message_len], self.buf[message_len:]271 272        host: str273        if atyp == SOCKS5_ATYP_IPV4_ADDRESS:274            host = socket.inet_ntop(socket.AF_INET, msg[4:-2])275        elif atyp == SOCKS5_ATYP_IPV6_ADDRESS:276            host = socket.inet_ntop(socket.AF_INET6, msg[4:-2])277        else:278            host_bytes = msg[5:-2]279            host = host_bytes.decode("ascii", "replace")280 281        (port,) = struct.unpack("!H", msg[-2:])282 283        # We now have all we need, let's get going.284        self.context.server.address = (host, port)285        self.child_layer = layer.NextLayer(self.context)286 287        # this already triggers the child layer's Start event,288        # but that's not a problem in practice...289        err = yield from self.finish_start()290        if err:291            yield commands.SendData(292                self.context.client, b"\x05\x04\x00\x01\x00\x00\x00\x00\x00\x00"293            )294            yield commands.CloseConnection(self.context.client)295        else:296            yield commands.SendData(297                self.context.client, b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00"298            )299            if self.buf:300                yield from self.child_layer.handle_event(301                    events.DataReceived(self.context.client, self.buf)302                )303                del self.buf304 
codekingpro/portable-devtools · Team Ai