/* $Id: ncat_proxy.c 11830 2009-01-23 17:49:06Z david $ */

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

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

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;
    }

    if (len > 0) {
	    buf[0] = b64alpha[(data[0] >> 2) & 0x3f];
	    buf[1] = b64alpha[(data[0] << 4) & 0x30];

        if (len > 1) {
            buf[1] += (data[1] >> 4) & 0xf;
            buf[2] = b64alpha[(data[1] << 2) & 0x3c];
        } else
            /* pad it out. */
            buf[2] = '=';

        /* pad. */
        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);
}

/* 
 * 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 + 7]; /* 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 (verbose_flag > 1)
        Fprintf(stderr, "DEBUG: 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 (verbose_flag > 1)
            Fprintf(stderr, "DEBUG: Proxy auth (base64enc): %s\n", b64_auth);

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

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

    return (proxy_request);
}

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

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;
}

static void http_server_handler(int c, int s)
{
	fd_set m, r;
	char buf[DEFAULT_TCP_BUF_LEN];
	int len, maxfd = c < s ? s : c, numready;

	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);
}

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

/* Simple forking HTTP proxy
 *
 * IPv6 address requests should be within [].  This is to help deal with
 * the host:port separator.  E.g. [::1]:80
 *
 */
int ncat_http_server(void)
{
	char rbuf[DEFAULT_BUF_LEN], cbuf[DEFAULT_BUF_LEN];
	char *p, *x;
	int alen, c, f, s;
	size_t sslen;
	struct sockaddr_storage conn, ss;
	struct sockaddr_in *sin = (struct sockaddr_in *) &ss;
#ifdef HAVE_IPV6
	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &ss;
#endif

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

	s = do_listen(SOCK_STREAM);

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

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

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

		if (!allow_access(&conn)) {
			Close(c);
			continue;
		}
            
		recv(c, rbuf, sizeof(rbuf), 0);

		if (sscanf(rbuf, "CONNECT %s HTTP/1.", cbuf) != 1) {
badreq:
			send_string(c, http_code2str(400));
			Close(c);
			continue;
		}

		alen = strlen(cbuf);

		zmem(&ss, sizeof(ss));

		p = cbuf;

		/* IPv6 address */
		if (*p == '[') {
			x = strchr(p, ']');

			if (!x)
				goto badreq;

			*x = 0;

			p++;
		} else {
			x = strchr(p, ':');

			if (x)
				*x = 0;
		}

		if (!resolve(p, &ss, &sslen, AF_INET) &&
		    !resolve(p, &ss, &sslen, AF_INET6)) {
			send_string(c, http_code2str(404));
			Close(c);
			continue;
		}

		p += strlen(p) + 1;

		/* Left-over from IPv6 address */
		if (*p == ':')
			p++;

		if (p < cbuf + alen) {
			unsigned short port = htons((unsigned short) atoi(p));

			if (o.af == AF_INET)
				sin->sin_port = port;
#ifdef HAVE_IPV6
			else
				sin6->sin6_port = port;
#endif
		}

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

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

		send_string(c, http_code2str(200));

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

			http_server_handler(c, f);
		}

		Close(f);
		Close(c);
	}

	return 0;
}

/* 
 * 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?";
    }
}

