#!/usr/bin/env python3
"""Logging SOCKS5 server (RFC 1928): accepts the greeting, prints the address type and
address of each CONNECT request, then answers "general SOCKS server failure".
Usage: python3 socks5_log.py [PORT]   (listens on 127.0.0.1, default port 1080)"""
import ipaddress, socketserver, sys

ATYP = {1: "IPv4", 3: "DOMAIN", 4: "IPv6"}

def read(sock, n):
    data = b""
    while len(data) < n:
        chunk = sock.recv(n - len(data))
        if not chunk:
            raise ConnectionError("client closed the connection")
        data += chunk
    return data

class Handler(socketserver.BaseRequestHandler):
    def handle(self):
        s = self.request
        try:
            version, nmethods = read(s, 2)               # greeting: VER, NMETHODS, METHODS
            if version != 5:
                return print(f"NOT-SOCKS5 first byte 0x{version:02x}", flush=True)
            read(s, nmethods)
            s.sendall(b"\x05\x00")                       # method 0x00: no authentication
            _, command, _, atyp = read(s, 4)             # request: VER, CMD, RSV, ATYP
            if atyp == 3:                                # DOMAIN: one length byte, then the name
                address = read(s, read(s, 1)[0]).decode("ascii", "replace")
            else:                                        # IPv4: 4 bytes, IPv6: 16 bytes
                address = str(ipaddress.ip_address(read(s, 4 if atyp == 1 else 16)))
            port = int.from_bytes(read(s, 2), "big")
            print(f"CMD={command} {ATYP.get(atyp, atyp)} {address} port={port}", flush=True)
            s.sendall(b"\x05\x01\x00\x01" + bytes(6))    # REP 0x01: general SOCKS server failure
        except ConnectionError as error:
            print(f"INCOMPLETE {error}", flush=True)

socketserver.ThreadingTCPServer.allow_reuse_address = True
port = int(sys.argv[1]) if len(sys.argv) > 1 else 1080
with socketserver.ThreadingTCPServer(("127.0.0.1", port), Handler) as server:
    print(f"LISTENING 127.0.0.1:{port}", flush=True)
    server.serve_forever()
