user304539
user304539

Reputation: 17

How to bind a http.server to a remote socks proxy?

I've a webserver that is going to answer requests at a remote socks5 proxy.

I want the webserver to connect to the proxy, send the bind command, and listen to a connection.

How can I set up a http.server to bind and listen on a remote port?

I have tried to override server_bind without success:

class TestHTTPServer ( http.server.HTTPServer ):
    def __init__ ( self, server_address, RequestHandlerClass, proxy_host=None, proxy_port=None, bind_and_activate=True ):
        if ':' in (str)(server_address[0]):
            self.address_family = socket.AF_INET6
        else:
            self.address_family = socket.AF_INET
        
        self.proxy_host = proxy_host
        self.proxy_port = proxy_port
        
        super().__init__ ( server_address, RequestHandlerClass, bind_and_activate )
    
    def serve_forever ( self, poll_interval = 0.5 ):
        self.socket.settimeout ( poll_interval )
        while not stop_unittest_event.is_set ():
            try:
                self.handle_request ()
            except OSError:
                pass

    def server_bind ( self ):
        if self.proxy_host is None or self.proxy_port is None:
            super().server_bind ()
        else:
            self.socket = socks.socksocket()
            self.socket.set_proxy(socks.SOCKS5, self.server_address[0], self.server_address[1])
            self.socket.bind(self.server_address)

It seems as set_proxy only handles client requests (not servers). How can this code be changed to listen on a socks5 port?

It is something an FTP-server does in active mode.

Upvotes: 0

Views: 31

Answers (1)

user304539
user304539

Reputation: 17

class TestHTTPServerServer ( http.server.HTTPServer ):
    def __init__ ( self, server_address, RequestHandlerClass, bind_and_activate=True, **kwargs ):
        self.proxy_args = kwargs.copy ()
        
        if bool ( self.proxy_args ) is False : # standard way of initalization
            if ':' in (str)(server_address[0]):
                self.address_family = socket.AF_INET6
            else:
                self.address_family = socket.AF_INET
            
            self.allow_reuse_address = True
            
            super().__init__ ( server_address, RequestHandlerClass, bind_and_activate )
        else:
            self.proxy_args = kwargs.copy ()
            
            self.RequestHandlerClass = RequestHandlerClass
            
            self.socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
            self.server_socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
            
            self.server_address = ( "0.0.0.0", 0 )
    
    
    def serve_forever ( self, poll_interval = 0.5 ):
        if  bool ( self.proxy_args ) is False:
            self.socket.settimeout ( poll_interval )
            
            while not test_unittests_stop_event.is_set ():
                try:
                    self.handle_request ()
                except OSError:
                    pass
        else:
            d = {}
            
            while True:
                if ':' in (str)(self.proxy_args [ "proxy_host" ] ):
                    self.proxy_socket = socket.socket ( socket.AF_INET6, socket.SOCK_STREAM )
                else:
                    self.proxy_socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
                
                self.proxy_socket.setsockopt ( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
                
                self.proxy_socket.settimeout ( 10 )
                
                self.proxy_socket.connect ( ( self.proxy_args [ "proxy_host" ], self.proxy_args [ "proxy_port" ] ) )
                
                if "username" in self.proxy_args and "password" in self.proxy_args:
                    d [ "username" ] = self.proxy_args [ "username" ]
                    d [ "password" ] = self.proxy_args [ "password" ]
                
                server_socks5_client_send_greet ( self.proxy_socket, d )
                server_socks5_client_recv_choice ( self.proxy_socket, d )
                
                server_socks5_client_send_authorization_request ( self.proxy_socket, d )
                server_socks5_client_recv_authorization_response ( self.proxy_socket, d )
                
                d [ "cmd" ] = 2 # bind
                
                if "bind.ip.version" in d:
                    d [ "ip.version" ] = d [ "bind.ip.version" ]
                else:
                    d [ "ip.version" ] = 4
                    
                if "bind.ip" in d:
                    d [ "ip" ] = d [ "bind.ip" ]
                else:
                    d [ "ip" ] = "127.0.0.1"
                
                if "bind.port" in d:
                    d [ "port" ] = d [ "bind.port" ]
                else:
                    d [ "port" ] = 0
                
                # print ( d )
                
                server_socks5_client_send_connect_request ( self.proxy_socket, d )
                server_socks5_client_recv_connect_response ( self.proxy_socket, d )
                
                if "bind.ip" in d:
                    self.bind_host = d [ "bind.ip" ]
                
                if "bind.domain" in d:
                    self.bind_host = d [ "bind.domain" ]
                
                if "bind.port" in d:
                    self.bind_port = d [ "bind.port" ]
                
                print ( f"SOCKS5 Proxy listening on {self.bind_host}:{self.bind_port}" )
                
                self.proxy_socket.settimeout ( 18 )
                
                rlist, _, _ = select.select ( [ self.proxy_socket, ], [], [] )
                if self.proxy_socket in rlist:
                    l = {}
                    
                    server_socks5_client_recv_connect_response ( self.proxy_socket, l )
                    
                    address_0 = None
                    address_1 = None
                    
                    if "bind.ip" in l:
                        address_0 = l [ "bind.ip" ]
                    
                    if "bind.domain" in l:
                        address_0 = l [ "bind.domain" ]
                    
                    if "bind.port" in l:
                        address_1 = l [ "bind.port" ]
                    
                    client_address = ( address_0, address_1 )
                    
                    print ( f"Accepted connection from {address_0}:{address_1}" )
                    
                    self.RequestHandlerClass ( self.proxy_socket, client_address, self)
                    self.shutdown_request ( self.proxy_socket )
def server_socks5_client_send_greet ( s : socket.socket, d : dict ):
    # Client greeting   VER NAUTH       AUTH
    # Byte count          1     1   variable

    # VER
    #     SOCKS version (0x05)
    # NAUTH
    #     Number of authentication methods supported, uint8
    # AUTH
    #     Authentication methods, 1 byte per method supported
    #     The authentication methods supported are numbered as follows:

    #         0x00: No authentication
    #         0x01: GSSAPI (RFC 1961)
    #         0x02: Username/password (RFC 1929)
    #         0x03–0x7F: methods assigned by IANA[17]
    #             0x03: Challenge–Handshake Authentication Protocol
    #             0x04: Unassigned
    #             0x05: Challenge–Response Authentication Method
    #             0x06: Secure Sockets Layer
    #             0x07: NDS Authentication
    #             0x08: Multi-Authentication Framework
    #             0x09: JSON Parameter Block
    #             0x0A–0x7F: Unassigned
    #         0x80–0xFE: methods reserved for private use

    methods =  [ 0x00 ]
    
    if "username" in d and "password" in d:
        methods.append ( 0x02 )
        
    transmit = struct.pack ( "BB", 0x05, len ( methods ) )
    transmit += bytes ( methods )
    
    s.sendall ( transmit )


def server_socks5_client_recv_choice ( s : socket.socket, d : dict ):
    # Server choice
    #               VER CAUTH
    # Byte count      1     1

    # VER
    #     SOCKS version (0x05)
    # CAUTH
    #     chosen authentication method, or 0xFF if no acceptable methods were offered
    
    version, cauth = struct.unpack ( "!BB", s.recv ( 2 ) )
    
    if version != 0x5:
        raise SocksChainException ( "Unsupported socks version", version )
    
    if cauth == 0xff:
        raise AuthenticationChainException ( "No accepted AUTH type" )
    
    d [ "cauth" ] = cauth


def server_socks5_client_send_authorization_request ( s : socket.socket, d : dict ):
    #  Client authentication request, 0x02
    #               VER IDLEN       ID  PWLEN       PW
    # Byte count      1     1  (1–255)      1  (1–255)

    # VER
    #     0x01 for current version of username/password authentication
    # IDLEN, ID
    #     Username length, uint8; username as bytestring
    # PWLEN, PW
    #     Password length, uint8; password as bytestring
    
    if d [ "cauth" ] == 0x00:
        return
    
    if d [ "cauth" ] != 0x02:
        raise AuthenticationChainException ( "No accepted AUTH type" )
        
    if not "username" in d or not "password" in d:
        raise AuthenticationChainException ( "No AUTH without username and password not accepted" )
    
    transmit = b'\x01' # username/password authentication
    transmit += int.to_bytes ( len ( d [ "username" ] ) )
    transmit += str.encode ( d [ "username" ] )
    
    transmit += int.to_bytes ( len ( d [ "password" ] ) )
    transmit += str.encode ( d [ "password" ] )
    
    s.sendall ( transmit )


def server_socks5_client_recv_authorization_response ( s : socket.socket, d : dict ):
    # Server response, 0x02
    #               VER STATUS
    # Byte count      1      1

    # VER
    #     0x01 for current version of username/password authentication
    # STATUS
    #     0x00 success, otherwise failure, connection must be closed
    
    if d [ "cauth" ] == 0x00:
        return d
    
    version, status = struct.unpack ( "!BB", s.recv ( 2 ) )
    
    if version != 0x01:
        raise SocksChainException ( "Wrong socks username and password", version )
    
    if status != 0x00:
        raise AuthenticationChainException ( "Access denied", json.dumps ( d ) )


def server_socks5_client_send_connect_request ( s : socket.socket, d : dict ):
    # Client connection request
    #             VER CMD RSV  DSTADDR    DSTPORT
    # Byte Count    1   1   1 Variable          2
    
    # VER
    #     SOCKS version (0x05)
    # CMD
    #     command code:
    
    #         0x01: establish a TCP/IP stream connection
    #         0x02: establish a TCP/IP port binding
    #         0x03: associate a UDP port
    
    # RSV
    #     reserved, must be 0x00
    # DSTADDR
    #    SOCKS5 address  TYPE       ADDR
    #    Byte Count         1   variable
    
    #    TYPE
    #        type of the address. One of:
    
    #            0x01: IPv4 address
    #            0x03: Domain name
    #            0x04: IPv6 address
    # DSTPORT
    #     port number in a network byte order
    
    # print ( sys._getframe().f_lineno, d, file=sys.stderr )
    
    transmit = b'\x05'  # SOCKS version 5
    transmit += d [ "cmd" ].to_bytes ()
    transmit += b'\x00'  # Reserved
    
    if "ip.version" in d and d [ "ip.version" ] == 4:  # IPv4
        transmit += b'\x01'
        transmit += socket.inet_pton ( socket.AF_INET, d [ "ip" ] )
    elif "ip.version" in d and d [ "ip.version" ] == 6:  # IPv6
        transmit += b'\x04'
        transmit += socket.inet_pton ( socket.AF_INET6, d [ "ip" ] )
    elif "domain" in d:  # Domain name
        transmit += b'\x03'
        transmit += len ( d [ "domain" ] ).to_bytes ( 1, byteorder='big' )  # Length of domain
        transmit += d [ "domain" ].encode ( 'utf-8' )
    
    transmit += struct.pack('!H', d [ "port" ] )  # Port number
    
    s.sendall ( transmit )


def server_socks5_client_recv_connect_response ( s : socket.socket, d : dict ):
    # Response packet from server
    #               VER STATUS  RSV  BNDADDR    BNDPORT
    # Byte Count      1      1    1 variable          2
    
    # VER
    #     SOCKS version (0x05)
    # STATUS
    #     status code:
    
    #         0x00: request granted
    #         0x01: general failure
    #         0x02: connection not allowed by ruleset
    #         0x03: network unreachable
    #         0x04: host unreachable
    #         0x05: connection refused by destination host
    #         0x06: TTL expired
    #         0x07: command not supported / protocol error
    #         0x08: address type not supported
    
    # RSV
    #     reserved, must be 0x00
    # BNDADDR
    #    SOCKS5 address  TYPE       ADDR
    #    Byte Count         1   variable
    
    #    TYPE
    #        type of the address. One of:
    
    #            0x01: IPv4 address
    #            0x03: Domain name
    #            0x04: IPv6 address
    # BNDPORT
    #     server bound port number in a network byte order
    
    version, status, reserved, address_type = struct.unpack("!BBBB", s.recv(4))
    
    # Check for successful response
    
    if version != 0x05:
        raise SocksChainException ( "Unsupported socks version", version )
    
    if status != 0x00:
        raise SocksChainException ( "Socket chain error", status )
        
    # Parse the remaining response based on the address type
    if address_type == 0x01:  # IPv4
        d [ "bind.ip" ] = socket.inet_ntoa ( s.recv ( 4 ) )
        d [ "bind.ip.version" ] = 4
        d [ "bind.port" ] = struct.unpack ( "!H", s.recv ( 2 ) )[ 0 ]
    elif address_type == 0x03:  # Domain name
        domain_len = struct.unpack("!B", s.recv(1))[0]
        d [ "bind.domain" ] = s.recv ( domain_len ).decode ()
        d [ "bind.port" ] = struct.unpack ( "!H", s.recv ( 2 ) )[ 0 ]
    elif address_type == 0x04:  # IPv6
        d [ "bind.ip" ] = socket.inet_ntop ( socket.AF_INET6, s.recv ( 16 ) )
        d [ "bind.ip.version" ] = 6
        d [ "bind.port" ] = struct.unpack ( "!H", s.recv ( 2 ) )[ 0 ]
    else:
        raise Exception("Unknown address type")

Start http-proxy-server:

test_http_servers_listen_addresses = [
    { "proxy_host": '127.0.0.1', 'proxy_port': 1084 },
    { "proxy_host": '::1', 'proxy_port': 1076, "username": "username2", "password": "password5" },
]

for i in test_http_servers_listen_addresses:
    httpd = TestHTTPServerServer ( None, TestHTTPServerHandler, **i )
    
    server_thread = threading.Thread ( target=test_run_http_server, args = ( httpd, ) )
    server_thread.start ()

Tested with gost.

Upvotes: 0

Related Questions