/* $Id: ncat_proxy.c 12829 2009-04-01 19:07:05Z david $ */

#include "nsock.h"
#include "ncat.h"
#include "sys_wrap.h"

#ifndef WIN32
#include <unistd.h>
#endif

/* SIG_CHLD handler */
static void proxyreaper(int signo)
{
    while (Waitpid(-1, NULL, WNOHANG) > 0);
}

/* send a '\0'-terminated string. */
static int send_string(int sock, const char *s)
{
    return send(sock, s, strlen(s), 0);
}

static void http_server_handler(int c);
static char *http_code2str(int code);
static ssize_t read_header(int sd, char *buf, size_t len);
static int parse_header(const char *header, size_t len,
                        struct sockaddr_storage *ss, size_t *sslen);

/* Simple forking HTTP proxy. This proxy uses the HTTP CONNECT method,
 * documented at
 *
 * http://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01
 *
 * This type of proxy is not limited to HTTP, but is potentially capable of
 * connecting to any TCP port on any host. The proxy connection is requested
 * with an HTTP request, but after that, the proxy does no interpretation of the
 * data passing through it. See section 6 of the above mentioned draft for the
 * security implications.
 *
 * Literal IPv6 addresses may be enclosed in [] to help distinguish the address
 * from the following port. See RFC 4038, section 5.1 for a recommendation of
 * this practice.
 */
int ncat_http_server(void)
{
    int c, s;
    size_t sslen;
    struct sockaddr_storage conn;

#ifndef WIN32
    Signal(SIGCHLD, proxyreaper);
#endif

    s = do_listen(SOCK_STREAM);

    for (;;) {
        sslen = sizeof(conn);

        c = accept(s, (struct sockaddr *) &conn, (socklen_t *) &sslen);

        if (c == -1) {
            if (errno == EINTR)
                continue;
            die("accept");
        }

        if (!allow_access(&conn)) {
            Close(c);
            continue;
        }

        if (Fork() == 0) {
            Close(STDIN_FILENO);
            Close(STDOUT_FILENO);
            Close(STDERR_FILENO);
            Close(s);

            http_server_handler(c);
            exit(0);
        }

        Close(c);
    }

    return 0;
}

static void http_server_handler(int c)
{
    char header[DEFAULT_BUF_LEN];
    char buf[DEFAULT_TCP_BUF_LEN];
    struct sockaddr_storage ss;
    size_t sslen = sizeof(ss);
    fd_set m, r;
    int s, code;
    int len, maxfd, numready;

    if (read_header(c, header, sizeof(header)) == -1) {
        send_string(c, http_code2str(400));
        Close(c);
        return;
    }

    code = parse_header(header, sizeof(header), &ss, &sslen);
    if (code != 0) {
        send_string(c, http_code2str(code));
        Close(c);
        return;
    }

    s = Socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP);

    if (connect(s, (struct sockaddr *) &ss, sslen) == -1) {
        send_string(c, http_code2str(404));
        Close(s);
        Close(c);
        return;
    }

    send_string(c, http_code2str(200));

    maxfd = c < s ? s : c;
    FD_ZERO(&m);
    FD_SET(c, &m);
    FD_SET(s, &m);

    errno = 0;

    while (!errno || errno == EINTR) {
        r = m;

        numready = Select(maxfd + 1, &r, NULL, NULL, NULL);

        zmem(buf, sizeof(buf));

        if (FD_ISSET(c, &r)) {
            if ((len = recv(c, buf, sizeof(buf), 0)) < 0)
                continue;

            if (!len)
                break;

            if (send(s, buf, len, 0) < 0)
                continue;
        }

        if (FD_ISSET(s, &r)) {
            if ((len = recv(s, buf, sizeof(buf), 0)) < 0)
                continue;

            if (!len)
                break;

            if (send(c, buf, len, 0) < 0)
                continue;
        }
    }

    close(c);
    close(s);
}

static char *http_code2str(int code)
{
    switch (code) {
    case 200:
        return "HTTP/1.0 200 OK\r\n\r\n";
    case 400:
        return "HTTP/1.0 400 Bad Request\r\n\r\n";
    case 404:
        return "HTTP/1.0 404 Not Found\r\n\r\n";
    default:
        return "ERROR";
    }

    return NULL;
}

/* Read an HTTP header from sd into buf. The header is terminated by two
   consecutive line endings, which may be CRLF or LF. Returns the number of
   bytes in the header, or -1 on error. Anything read past the end of the header
   is discarded. The buffer is null-terminated if there was no error. */
static ssize_t read_header(int sd, char *buf, size_t len)
{
    char *p;
    size_t n;
    int eol_count;

    p = buf;
    /* n is the number of bytes read so far. */
    n = 0;
    /* Search for two consecutive line endings. */
    eol_count = 0;
    while (p - buf < len - 1 && eol_count < 2) {
        if (p - buf >= n) {
            /* Read another chunk. */
            ssize_t i;
            do {
                errno = 0;
                i = recv(sd, p, len - (p - buf), 0);
            } while (i == -1 && errno == EINTR);
            if (i <= 0)
                return -1;
            n += i;
        }

        if (*p == '\n')
            eol_count++;
        else if (*p == '\r' && p - buf > 0 && *(p - 1) == '\n')
            ;
        else
            eol_count = 0;
        p++;
    }
    if (eol_count < 2)
        return -1;

    *p = '\0';

    return p - buf;
}

/* Parse a destination host:port specification. The host part may optionally be
   enclosed in square brackets. This is meant for IPv6 addresses but it works
   for any kind of address, and is not required for IPv6 (the colon and port are
   required so there is no ambiguity with colons in the address). */
static int parse_dest(const char *dest,
                      struct sockaddr_storage *ss, size_t *sslen)
{
    const char *portsep;
    char addr[DEFAULT_BUF_LEN];
    char *tail;
    long port;

    portsep = strrchr(dest, ':');
    if (portsep == NULL)
        return 400;
    errno = 0;
    port = parse_long(portsep + 1, &tail);
    if (errno != 0 || *tail != '\0' || port < 1 || port > 65535)
        return 400;

    if (dest[0] == '[') {
        /* IPv6 address in brackets. */
        if (portsep == dest || *(portsep - 1) != ']')
            return 400;
        memcpy(addr, dest + 1, portsep - dest - 2);
        addr[portsep - dest - 2] = '\0';
    } else {
        memcpy(addr, dest, portsep - dest);
        addr[portsep - dest] = '\0';
    }

    if (!resolve(addr, port, ss, sslen, AF_UNSPEC))
        return 404;

    return 0;
}

/* Parse an HTTP CONNECT method header and record the destination address and
   port. Returns 0 on success or an HTTP status code on failure. */
static int parse_header(const char *header, size_t len,
                        struct sockaddr_storage *ss, size_t *sslen)
{
    const char *p;
    char method[32];
    char dest[DEFAULT_BUF_LEN];
    int i;

    /* Get the method and make sure it's CONNECT. */
    p = header;
    i = 0;
    while (i < sizeof(method) - 1 && p - header < len && *p != ' ')
        method[i++] = *p++;
    method[i] = '\0';
    if (p - header >= len || *p != ' ' || strcmp(method, "CONNECT") != 0)
        return 400;

    /* Get the destination host:port. */
    p++;
    i = 0;
    while (i < sizeof(dest) - 1 && p - header < len && !isspace(*p))
        dest[i++] = *p++;
    dest[i] = '\0';
    if (p - header >= len || !isspace(*p))
        return 400;

    /* Skip over the HTTP version. */
    p++;
    while (p - header < len && *p != '\n')
        p++;
    if (p - header >= len)
        return 400;

    /* Ignore all header fields. */

    return parse_dest(dest, ss, sslen);
}

static char *b64enc(const unsigned char *data, int len);

/*
 * Return the HTTP/1.1 proxy request to send to an
 * HTTP proxy server. See docs/HTTP-PROXY for more
 * information about this HTTP request and what to
 * do if you find it doesn't work for you or your
 * proxy server.
 *
 * Return finalized HTTP/1.1 proxy request.
 *
 * If proxy_auth is NULL, HTTP Proxy-Authorization
 * headers are not included in the request.
 *
 */
char *ncat_http_proxy(unsigned char *proxy_auth)
{
    char *b64_auth;
    static char proxy_request[DEFAULT_BUF_LEN];
    int pos;
    const char *proxyhost;
    char hostbuf[INET6_ADDRSTRLEN + 9]; /* [ + address + ] + : + port + \0 */
    const char *host = inet_socktop(&httpconnect);
    unsigned short port = inet_port(&httpconnect);
    char *s6s = o.af == AF_INET6 ? "[" : "", *s6e = o.af == AF_INET6 ? "]" : "";

    if (port)
        Snprintf(hostbuf, sizeof hostbuf, "%s%s%s:%hu", s6s, host, s6e, port);
    else
        Snprintf(hostbuf, sizeof hostbuf, "%s%s%s", s6s, host, s6e);

    proxyhost = inet_socktop(&targetss);

    if (o.debug)
        logdebug("Proxy CONNECT target: %s\n", hostbuf);

    pos = Snprintf(proxy_request, sizeof(proxy_request),
                   "CONNECT %s HTTP/1.1\r\n", hostbuf);

    if (proxy_auth != NULL) {
        b64_auth = b64enc(proxy_auth, strlen((char *)proxy_auth));

        if (o.debug)
            logdebug("Proxy auth (base64enc): %s\n", b64_auth);

        pos += Snprintf(proxy_request + pos, sizeof(proxy_request) - pos,
                        "Proxy-Authorization: Basic %s\r\n", b64_auth);

        free(b64_auth);
    }

    Snprintf(proxy_request + pos, sizeof(proxy_request) - pos,
             "Host: %s\r\n\r\n", proxyhost);

    return (proxy_request);
}

static int b64enc_internal(const unsigned char *data, int len, char *dest)
{
    /* base64 alphabet, taken from rfc3548 */
    char *b64alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    char *buf = dest;

    /* Encode three bytes per iteration ala rfc3548. */
    while (len >= 3) {
        buf[0] = b64alpha[(data[0] >> 2) & 0x3f];
        buf[1] = b64alpha[((data[0] << 4) & 0x30) | ((data[1] >> 4) & 0xf)];
        buf[2] = b64alpha[((data[1] << 2) & 0x3c) | ((data[2] >> 6) & 0x3)];
        buf[3] = b64alpha[data[2] & 0x3f];
        data += 3;
        buf += 4;
        len -= 3;
    }

    /* Pad the remaining bytes. len is 0, 1, or 2 here. */
    if (len > 0) {
        buf[0] = b64alpha[(data[0] >> 2) & 0x3f];
        if (len > 1) {
            buf[1] = b64alpha[((data[0] << 4) & 0x30) | ((data[1] >> 4) & 0xf)];
            buf[2] = b64alpha[(data[1] << 2) & 0x3c];
        } else {
            buf[1] = b64alpha[(data[0] << 4) & 0x30];
            buf[2] = '=';
        }
        buf[3] = '=';
        buf += 4;
    }

    /*
     * As mentioned in rfc3548, we need to be careful about
     * how we null terminate and handle embedded null-termination.
     */
    *buf = '\0';

    return (buf - dest);
}

/* Take in plain text and encode into base64. */
static char *b64enc(const unsigned char *data, int len)
{
    char *dest;

    /* malloc enough space to do something useful */
    dest = (char*)Malloc(4 * len / 3 + 4);

    dest[0] = '\0';

    /* Call internal function to base64 encode data */
    b64enc_internal(data, len, dest);

    return (dest);
}

/*
 * Handle SOCKS4 CD field error reporting.
 * Return the error message to be used in
 * the final Ncat output. (It's final because
 * these are all fatal errors.)
 *
 * See: http://archive.socks.permeo.com/protocol/socks4.protocol
 *
 * These error messages are taken verbatim from socks4.protocol (above)
 */
char *socks4_error(char cd)
{
    switch (cd) {
        case SOCKS_CONN_REF:
            return "request rejected or failed";
            break;

        case SOCKS_CONN_IDENT:
            return "request rejected because SOCKS4 server cannot connect to identd";
            break;

        case SOCKS_CONN_IDENTDIFF:
            return "request rejected because SOCKS4 client and identd reported different userid's";
            break;

        default:
            return "Invalid SOCKS4 error code. Broken SOCKS4 implementation?";
    }
}
