NOEXPORT void socks5_server(CLI *c) {
    SOCKS5_UNION socks;
    uint8_t host_len;
    char *host_name, *port_name;
    u_short port_number;
    SOCKADDR_UNION addr;
    int close_connection=1;

    /* parse request */
    memset(&socks, 0, sizeof socks);
    s_ssl_read(c, &socks, sizeof socks.req);
    if(socks.req.ver!=0x05) {
        s_log(LOG_ERR, "Invalid SOCKS5 message version 0x%02x", socks.req.ver);
        socks.resp.ver=0x05; /* response version 5 */
        socks.resp.rep=0x01; /* general SOCKS server failure */
    } else if(socks.req.cmd==0x01) { /* CONNECT */
        if(socks.req.atyp==0x01) { /* IP v4 address */
            c->connect_addr.num=1;
            c->connect_addr.addr=str_alloc(sizeof(SOCKADDR_UNION));
            c->connect_addr.addr[0].in.sin_family=AF_INET;
            s_ssl_read(c, &socks.v4.addr, 4+2);
            memcpy(&c->connect_addr.addr[0].in.sin_addr, &socks.v4.addr, 4);
            memcpy(&c->connect_addr.addr[0].in.sin_port, &socks.v4.port, 2);
            s_log(LOG_INFO, "SOCKS5 IPv4 address received");
            if(validate(c)) {
                socks.resp.rep=0x00; /* succeeded */
                close_connection=0;
            } else {
                socks.resp.rep=0x02; /* connection not allowed by ruleset */
            }
        } else if(socks.req.atyp==0x03) { /* DOMAINNAME */
            s_ssl_read(c, &host_len, sizeof host_len);
            host_name=str_alloc((size_t)host_len+1);
            s_ssl_read(c, host_name, host_len);
            host_name[host_len]='\0';
            s_ssl_read(c, &port_number, 2);
            port_name=str_printf("%u", ntohs(port_number));
            hostport2addrlist(&c->connect_addr, host_name, port_name);
            str_free(port_name);
            if(c->connect_addr.num) {
                s_log(LOG_INFO, "SOCKS5 resolved \"%s\" to %u host(s)",
                    host_name, c->connect_addr.num);
                if(validate(c)) {
                    socks.resp.rep=0x00; /* succeeded */
                    close_connection=0;
                } else {
                    socks.resp.rep=0x02; /* connection not allowed by ruleset */
                }
            } else {
                s_log(LOG_ERR, "SOCKS5 failed to resolve \"%s\"", host_name);
                socks.resp.rep=0x04; /* Host unreachable */
            }
            str_free(host_name);
#ifdef USE_IPv6
        } else if(socks.req.atyp==0x04) { /* IP v6 address */
            c->connect_addr.num=1;
            c->connect_addr.addr=str_alloc(sizeof(SOCKADDR_UNION));
            c->connect_addr.addr[0].in6.sin6_family=AF_INET6;
            s_ssl_read(c, &socks.v6.addr, 16+2);
            memcpy(&c->connect_addr.addr[0].in6.sin6_addr, &socks.v6.addr, 16);
            memcpy(&c->connect_addr.addr[0].in6.sin6_port, &socks.v6.port, 2);
            s_log(LOG_INFO, "SOCKS5 IPv6 address received");
            if(validate(c)) {
                socks.resp.rep=0x00; /* succeeded */
                close_connection=0;
            } else {
                socks.resp.rep=0x02; /* connection not allowed by ruleset */
            }
#endif
        } else {
            s_log(LOG_ERR,
                "Unsupported SOCKS5 address type 0x%02x", socks.req.atyp);
            socks.resp.rep=0x07; /* Address type not supported */
        }
    } else if(socks.req.cmd==0xf0) { /* RESOLVE (a TOR extension) */
        s_ssl_read(c, &host_len, sizeof host_len);
        host_name=str_alloc((size_t)host_len+1);
        s_ssl_read(c, host_name, host_len);
        host_name[host_len]='\0';
        s_ssl_read(c, &port_number, 2);
        port_name=str_printf("%u", ntohs(port_number));
        if(hostport2addr(&addr, host_name, port_name, 0)) {
            if(addr.sa.sa_family==AF_INET) {
                s_log(LOG_INFO, "SOCKS5/TOR resolved \"%s\" to IPv4", host_name);
                memcpy(&socks.v4.addr, &addr.in.sin_addr, 4);
                socks.resp.atyp=0x01; /* IP v4 address */
                socks.resp.rep=0x00; /* succeeded */
#ifdef USE_IPv6
            } else if(addr.sa.sa_family==AF_INET6) {
                s_log(LOG_INFO, "SOCKS5/TOR resolved \"%s\" to IPv6", host_name);
                memcpy(&socks.v6.addr, &addr.in6.sin6_addr, 16);
                socks.resp.atyp=0x04; /* IP v6 address */
                socks.resp.rep=0x00; /* succeeded */
#endif
            } else {
                s_log(LOG_ERR, "SOCKS5/TOR unsupported address type for \"%s\"",
                    host_name);
                socks.resp.rep=0x04; /* Host unreachable */
            }
        } else {
            s_log(LOG_ERR, "SOCKS5/TOR failed to resolve \"%s\"", host_name);
            socks.resp.rep=0x04; /* Host unreachable */
        }
        str_free(host_name);
        str_free(port_name);
    } else {
        s_log(LOG_ERR, "Unsupported SOCKS5 command 0x%02x", socks.req.cmd);
        socks.resp.rep=0x07; /* Command not supported */
    }

    /* send response */
    /* broken clients tend to expect the same address family for response,
     * so stunnel tries to preserve the address family if possible */
    if(socks.resp.atyp==0x04) { /* IP V6 address */
        s_ssl_write(c, &socks, sizeof socks.v6);
    } else {
        socks.resp.atyp=0x01; /* IP v4 address */
        s_ssl_write(c, &socks, sizeof socks.v4);
    }
    if(close_connection) /* request failed */
        throw_exception(c, 2); /* don't reset */
}