/* $Id: ncat_main.c 11813 2009-01-22 21:43:27Z david $ */

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

#include <getopt.h>

#ifndef WIN32
#include <unistd.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#ifndef WIN32
#include <netdb.h>
#endif
#include <fcntl.h>

#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif

static int ncat_connect_mode(void);
static int ncat_listen_mode(void);

/* Determines if it's parsing HTTP or SOCKS by looking at defport */
static void parseproxy(char *str, struct sockaddr_storage *ss, unsigned short defport)
{
	char *c = strrchr(str, ':'), *ptr;
	int httpproxy = (defport == DEFAULT_PROXY_PORT);
	unsigned short portno;
	size_t sslen;

	ptr = str;

	if (c)
		*c = 0;

	if (!resolve(ptr, ss, &sslen, o.af)) {
		Fprintf(stderr,
			"%s: Could not resolve proxy \"%s\". ",
			NCAT_NAME, ptr);
		if (o.af == AF_INET6 && httpproxy)
			Fprintf(stderr,
				"Did you specify the port number? It's required for IPv6. ");
		Fprintf(stderr, "QUITTING.\n");
		exit(EXIT_FAILURE);
	}

	if (c && strlen((ptr = c + 1)))
		portno = htons((unsigned short) atoi(ptr));
	else
		portno = htons(defport);

	if (o.af == AF_INET)
		((struct sockaddr_in *) ss)->sin_port = portno;
#ifdef HAVE_IPV6
	else
		((struct sockaddr_in6 *) ss)->sin6_port = portno;
#endif
}

int main(int argc, char *argv[])
{
    struct sockaddr_in *sin = (struct sockaddr_in *) &targetss;
#ifdef HAVE_IPV6
    static struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &targetss;
#endif

    int srcport = -1;
    char *source = NULL;
    char *proxyaddr = NULL;

    /* Set default options. */
    options_init();

    struct option long_options[] = {
	{"4",			no_argument,		NULL,		'4'},
	{"6",			no_argument,		NULL,		'6'},
	{"crlf",		no_argument,		NULL,		'C'},
	{"g",			required_argument,	NULL,		'g'},
	{"G",			required_argument,	NULL,		'G'},
	{"exec",		required_argument,	NULL,		'e'},
	{"sh-exec",		required_argument,	NULL,		'c'},
	{"max-conns",		required_argument,	NULL,		'm'},
	{"help",		no_argument,		NULL,		'h'},
	{"delay",		required_argument,	NULL,		'd'},
	{"listen",		no_argument,		NULL,		'l'},
	{"output",		required_argument,	NULL,		'o'},
	{"hex-dump",		required_argument,	NULL,		'x'},
	{"idle-timeout",	required_argument,	NULL,		'i'},
	{"recv-only",		no_argument,		&o.recvonly,	1},
	{"source-port",		required_argument,	NULL,		'p'},
	{"source",		required_argument,	NULL,		's'},
	{"send-only",		no_argument,		&o.sendonly,	1},
	{"broker",		no_argument,		&o.broker,	1},
	{"talk",		no_argument,		NULL,		0},
	{"deny",		required_argument,	NULL,		0},
	{"denyfile",		required_argument,	NULL,		0},
	{"allow",		required_argument,	NULL,		0},
	{"allowfile",		required_argument,	NULL,		0},
	{"telnet",		no_argument,		NULL,		't'},
	{"udp",			no_argument,		NULL,		'u'},
	{"version",		no_argument,		NULL,		0},
	{"verbose",		no_argument,		NULL,		'v'},
	{"wait",		required_argument,	NULL,		'w'},
	{"nodns",		no_argument,		NULL,		'n'},
	{"proxy",		required_argument,	NULL,		0},
	{"proxy-type",		required_argument,	NULL,		0},
	{"proxy-auth",		required_argument,	NULL,		0},
#ifdef HAVE_OPENSSL
	{"ssl",			no_argument,		&o.ssl,		1},
	{"ssl-cert",		required_argument,	NULL,		0},
	{"ssl-key",		required_argument,	NULL,		0},
#endif
	{0, 0, 0, 0}
    };
	
#ifdef WIN32
    windows_init();
#endif

    while (1) {
	/* handle command line arguments */
	int option_index;
	int c = getopt_long(argc, argv, "46Cc:e:g:G:i:m:hp:d:lo:x:ts:uvw:n", long_options,
			    &option_index);

	/* That's the end of the options. */
	if (c == -1)
	    break;

	switch (c) {
	case '4':
	    o.af = AF_INET;
	    break;
	case '6':
#ifdef HAVE_IPV6
	    o.af = AF_INET6;
#else
	    bye("-6 chosen when IPv6 wasn't compiled in. QUITTING.");
#endif
	    break;
        case 'C':
            o.crlf = 1; /* not an arbitrary number */
            break;
	case 'c':
	    o.shellexec = 1;
	    /* fall through */
	case 'e':
#ifndef WIN32
	    o.cmdexec = optarg;
#else
	    bye("Sorry, the -%c option is unavailable on Windows due to lack of fork()", c);
#endif
	    break;
	case 'g': {
	    char *a = strtok(optarg, ",");
	    do {
	        struct sockaddr_in addr;
	        size_t sslen;
	        if (!resolve(a, (struct sockaddr_storage *) &addr, &sslen, AF_INET))
	            bye("Sorry, could not resolve source route hop %s", a);
	        o.srcrtes[o.numsrcrtes] = addr.sin_addr;
	    } while (o.numsrcrtes++ <= 8 && (a = strtok(NULL, ",")));
	    if (o.numsrcrtes > 8)
	        bye("Sorry, you gave too many source route hops");
	    break;
	}
	case 'G':
	    o.srcrteptr = atoi(optarg);
	    if (o.srcrteptr < 4 || (o.srcrteptr % 4) || o.srcrteptr > 28)
	        bye("Invalid source-route hop pointer %d", o.srcrteptr);
	    break;
	case 'm':
	    o.conn_limit = atoi(optarg);
	    break;
	case 'd':
	    o.linedelay = tval2msecs(optarg);
	    if (o.linedelay <= 0)
	        bye("Invalid -d delay (must be greater than 0)", optarg);
	    break;
	case 'o':
	    o.normlogfd = ncat_openlog(optarg);
	    break;
	case 'x':
	    o.hexlogfd = ncat_openlog(optarg);
	    break;
	case 'p':
	    srcport = atoi(optarg);
	    if (srcport < 0 || srcport > 0xffff)
	        bye("Invalid source port %d", srcport);
	    break;
	case 'i':
	    o.idletimeout = tval2msecs(optarg);
	    if (o.idletimeout <= 0)
	        bye("Invalid -i timeout (must be greater than 0)");
	    break;
	case 's':
	    source = optarg;
	    break;
	case 'l':
            o.listen = 1;
	    break;
	case 'u':
	    o.udp = 1;
	    break;
	case 'v':
	    verbose_flag++;
	    break;
	case 'n':
	    o.nodns = 1;
	    break;
	case 'w':
	    o.conntimeout = tval2msecs(optarg);
	    if (o.conntimeout <= 0)
	        bye("Invalid -w timeout (must be greater than 0)");
	    break;
	case 't':
	    o.telnet = 1;
	    break;
	case 0:
	    if (strcmp(long_options[option_index].name, "version") == 0) {
	        Fprintf(stderr, "%s version %s ( %s )\n", NCAT_NAME, NCAT_VERSION, NCAT_URL);
	        exit(EXIT_SUCCESS);
	    }
	    else if (strcmp(long_options[option_index].name, "proxy") == 0)
	    {
		if (proxyaddr)
			bye("You can't specify more than one --proxy");
		proxyaddr = strdup(optarg);
            }
	    else if (strcmp(long_options[option_index].name, "proxy-type") == 0)
	    {
		if (o.proxytype)
			bye("You can't specify more than one --proxy-type");
		o.proxytype = strdup(optarg);
	    }
	    else if (strcmp(long_options[option_index].name, "proxy-auth") == 0)
	    {
		if (o.proxy_auth)
			bye("You can't specify more than one --proxy-auth");
		o.proxy_auth = (unsigned char *) strdup(optarg);
	    }
	    else if (strcmp(long_options[option_index].name, "talk") == 0)
	    {
		o.talk = 1;
	    }
	    else if (strcmp(long_options[option_index].name, "allow") == 0)
	    {
		o.allow = optarg;
	    }
	    else if (strcmp(long_options[option_index].name, "allowfile") == 0)
	    {
		o.allowfile = optarg;
	    }
	    else if (strcmp(long_options[option_index].name, "deny") == 0)
	    {
		o.deny = optarg;
	    }
	    else if (strcmp(long_options[option_index].name, "denyfile") == 0)
	    {
		o.denyfile = optarg;
	    }
#ifdef HAVE_OPENSSL
	    else if (strcmp(long_options[option_index].name, "ssl-cert") == 0)
	    {
		o.sslcert = Strdup(optarg);
	    }
	    else if (strcmp(long_options[option_index].name, "ssl-key") == 0)
	    {
		o.sslkey = Strdup(optarg);
	    }
#endif
	    break;
	case 'h':
	    Printf("%s %s ( %s )\n", NCAT_NAME, NCAT_VERSION, NCAT_URL);
	    Printf("Usage: ncat [options] [hostname] [port]\n");
	    Printf("\n");
	    Printf("Options taking a time assume milliseconds, unless you append an 's'\n");
	    Printf("(seconds), 'm' (minutes), or 'h' (hours) to the value (e.g. 30s)\n");
	    Printf("  -4                         Use IPv4 only\n");
	    Printf("  -6                         Use IPv6 only\n");
	    Printf("  -C, --crlf                 Use CRLF for EOL sequence\n");
#ifndef WIN32
	    Printf("  -c, --sh-exec <command>    Executes specified command via /bin/sh\n");
	    Printf("  -e, --exec <command>       Executes specified command\n");
#endif
	    Printf("  -g hop1[,hop2,...]         Loose source routing hop points (8 max)\n");
	    Printf("  -G n                       Loose source routing hop pointer (4, 8, 12, ...)\n");
	    Printf("  -m, --max-conns n          Maximum n simultaneous connections\n");
	    Printf("  -h, --help                 Display this help screen\n");
	    Printf("  -d, --delay <time>         Wait between read/writes\n");
	    Printf("  -o, --output               Dump session data to a file\n");
	    Printf("  -x, --hex-dump             Dump session data as hex to a file\n");
	    Printf("  -i, --idle-timeout <time>  Idle read/write timeout\n");
	    Printf("  -p, --source-port port     Specify source port to use (doesn't affect -l)\n");
	    Printf("  -s, --source addr          Specify source address to use (doesn't affect -l)\n");
	    Printf("  -l, --listen               Bind and listen for incoming connections\n");
	    Printf("  -n, --nodns                Do not resolve hostnames via DNS\n");
	    Printf("  -t, --telnet               Answer Telnet negotiations\n");
	    Printf("  -u, --udp                  Use UDP instead of default TCP\n");
	    Printf("  -v, --verbose              Set verbosity level (can be used up to 3 times)\n");
	    Printf("  -w, --wait <time>          Connect timeout\n");
	    Printf("      --send-only            Only send data, ignoring received\n");
	    Printf("      --recv-only            Only receive data, never send anything\n");
	    Printf("      --allow                Allow specific hosts to connect to Ncat\n");
	    Printf("      --allowfile            A file of hosts allowed to connect to Ncat\n");
	    Printf("      --deny                 Hosts to be denied from connecting to Ncat\n");
	    Printf("      --denyfile             A file of hosts denied from connecting to Ncat\n");
	    Printf("      --broker               Enable Ncat's Connection Brokering mode\n");
	    Printf("      --talk                 Used with --broker to chat with other connected users\n");
	    Printf("      --proxy <addr[:port]>  Specify address of host to proxy through\n");
	    Printf("      --proxy-type <type>    Specify proxy type (\"http\" or \"socks4\")\n");
	    Printf("      --proxy-auth <auth>    Authenticate with HTTP or SOCKS proxy server\n");
#ifdef HAVE_OPENSSL
	    Printf("      --ssl                  Connect or listen with SSL\n");
	    Printf("      --ssl-cert             Specify SSL certificate file (PEM) for listening\n");
	    Printf("      --ssl-key              Specify SSL private key (PEM) for listening\n");
#endif
	    Printf("      --version              Display Ncat's version information and exit\n");
	    Printf("\n");
	    Printf("See the ncat(1) manpage for full options, descriptions and usage examples\n");
	    exit(EXIT_SUCCESS);
	case '?':
	    /* Consider unrecognised parameters/arguments as fatal. */
	    bye("%s: Try `--help' or man(1) ncat for more information, usage options and help.", NCAT_NAME);
	default:
	    /* We consider an unrecognised option fatal. */
	    bye("%s: Unrecognised option.", NCAT_NAME);
	}
    }

    /* Set the default to IPv4 if not explicitly specified. */
    if (o.af != AF_INET && o.af != AF_INET6)
        o.af = AF_INET;

    /* Will be AF_INET or AF_INET6 when valid */
    memset(&targetss, 0, sizeof(targetss));
    targetss.ss_family = AF_UNSPEC;
    httpconnect = socksconnect = srcaddr = targetss;

    if (proxyaddr) {
      if (!o.proxytype)
          o.proxytype = strdup("http");

      if (!strcmp(o.proxytype, "http")) {
          /* Parse HTTP proxy address and temporarily store it in httpconnect.  If
           * the proxy server is given as an IPv6 address (not hostname), the port
           * number MUST be specified as well or parsing will break (due to the
           * colons in the IPv6 address and host:port separator).
           */

          parseproxy(proxyaddr, &httpconnect, DEFAULT_PROXY_PORT);
      } else if (!strcmp(o.proxytype, "socks4") || !strcmp(o.proxytype, "4")) {
          /* Parse SOCKS proxy address and temporarily store it in socksconnect */

          parseproxy(proxyaddr, &socksconnect, DEFAULT_SOCKS4_PORT);
      } else {
          bye("Invalid proxy type \"%s\"", o.proxytype);
      }

      free(o.proxytype);
      free(proxyaddr);
    } else {
      if (o.proxytype) {
        if (!o.listen)
          bye("Proxy type (--proxy-type) specified without proxy address (--proxy)");
        if (strcmp(o.proxytype, "http"))
          bye("Invalid proxy type \"%s\"", o.proxytype);
      }
    }

    /* Resolve the given source address */
    if (source) {
        if (o.listen)
            bye("-l and -s are incompatible.  Specify the address and port to bind to like you would a host to connect to");

        if (!resolve(source, &srcaddr, &srcaddrlen, o.af))
            bye("%s: Could not resolve source address %s. QUITTING.", NCAT_NAME, source);
    }

    if (srcport != -1) {
        if (srcaddr.ss_family == AF_UNSPEC)
            srcaddr.ss_family = o.af;
        if (o.af == AF_INET) {
            ((struct sockaddr_in *) &srcaddr)->sin_port = htons((unsigned short) srcport);
            if (!srcaddrlen)
                srcaddrlen = sizeof(struct sockaddr_in);
        }
#ifdef HAVE_IPV6
        else {
            ((struct sockaddr_in6 *) &srcaddr)->sin6_port = htons((unsigned short) srcport);
            if (!srcaddrlen)
                srcaddrlen = sizeof(struct sockaddr_in6);
        }
#endif
    }

    if (optind == argc) {
        /* Listen defaults to any address and DEFAULT_NCAT_PORT */
        if (!o.listen)
            bye("You must specify a host to connect to");
    } else {
        /* Resolve hostname if we're given one */
        if (strspn(argv[optind], "0123456789") != strlen(argv[optind])) {
            /* resolve hostname */
            if (!resolve(argv[optind], &targetss, &targetsslen, o.af))
                bye("%s: Could not resolve hostname %s. QUITTING.", NCAT_NAME, argv[optind]);

            optind++;
        } else {
            if (!o.listen)
                bye("You must specify a host to connect to");
        }
    }

    if (optind < argc) {
        char *port_in = argv[optind];
        long long_port = Strtol(port_in, NULL, 10);

        if (long_port <= 0 || long_port > 65535)
	       bye("%s: Invalid port number. QUITTING.", NCAT_NAME);

        o.portno = (unsigned short) long_port;
    } else {
        /* Default port */
        o.portno = DEFAULT_NCAT_PORT;
    }

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

    /* Since the host we're actually *connecting* to is the proxy server, we
     * need to reverse these address structures to avoid any further confusion
     */
    if (httpconnect.ss_family != AF_UNSPEC) {
        struct sockaddr_storage tmp = targetss;
        targetss = httpconnect;
        httpconnect = tmp;
    } else if (socksconnect.ss_family != AF_UNSPEC) {
        struct sockaddr_storage tmp = targetss;
        targetss = socksconnect;
        socksconnect = tmp;
    }

    if (o.listen)
	return ncat_listen_mode();
    else
	return ncat_connect_mode();
}

/* connect error handling and operations. */
static int ncat_connect_mode(void) {
    struct sockaddr_in *sin = (struct sockaddr_in *) &targetss;
#ifdef HAVE_IPV6
    static struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &targetss;
#endif

    nsock_pool mypool;
    struct conn_state cs;
    nsock_event_id ev;

    /* Create an nsock pool */
    if ((mypool = nsp_new(NULL)) == NULL)
        bye("%s: Failed to create nsock_pool. QUITTING.", NCAT_NAME);

    /* set ncat/nsock verbosity levels */
    verbosity(verbose_flag, mypool);

    /* 
     * allow/deny commands with connect make no sense. If you don't want to 
     * connect to a host, don't try to.
     */
    if (o.allow || o.allowfile || o.deny || o.denyfile)
	bye("%s: Invalid option combination: allow/deny with connect. QUITTING.", NCAT_NAME);

    /* o.conn_limit with 'connect' doesn't make any sense. */
    if (o.conn_limit)
	bye("%s: Invalid option combination: `--max-conns' with connect. QUITTING.", NCAT_NAME);

    /* connection brokering only applies in listen mode. */
    if (o.broker)
	bye("%s: Invalid option combination: `--broker' with connect. QUITTING.", NCAT_NAME);

    /* create an iod for a new socket */
    if ((cs.sock_nsi = nsi_new(mypool, NULL)) == NULL)
	bye("%s: Failed to create nsock_iod. QUITTING.", NCAT_NAME);

    if (srcaddr.ss_family != AF_UNSPEC)
	nsi_set_localaddr(cs.sock_nsi, &srcaddr, srcaddrlen);

    if (o.numsrcrtes) {
        char *ipopts = NULL;
        size_t ipoptslen = 0;

        if (o.af != AF_INET)
            bye("Sorry, -g can only currently be used with IPv4");
        ipopts = buildsrcrte(sin->sin_addr, o.srcrtes, o.numsrcrtes, o.srcrteptr, &ipoptslen);

	nsi_set_ipoptions(cs.sock_nsi, ipopts, ipoptslen);
	free(ipopts); /* Nsock has its own copy */
    }

    if (o.udp) {
	ev = nsock_connect_udp(mypool, cs.sock_nsi, connect_evt_handler,
			       &cs, (struct sockaddr *) &targetss, targetsslen,
			       inet_port(&targetss));
    }
#ifdef HAVE_OPENSSL
    else if (o.ssl) {
	cs.ssl_session = NULL;
	ev = nsock_connect_ssl(mypool, cs.sock_nsi, connect_evt_handler,
			       o.conntimeout, &cs,
			       (struct sockaddr *) &targetss, targetsslen,
			       inet_port(&targetss), cs.ssl_session);
    }
#endif
    else {
	ev = nsock_connect_tcp(mypool, cs.sock_nsi, connect_evt_handler,
			       o.conntimeout, &cs,
			       (struct sockaddr *) &targetss, targetsslen,
			       inet_port(&targetss));
    }

    /* connect */
    return nsock_loop(mypool, -1);
}

static int ncat_listen_mode(void) {
    struct sockaddr_in *sin = (struct sockaddr_in *) &targetss;
#ifdef HAVE_IPV6
    static struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &targetss;
#endif

    /* Can't 'listen' AND 'connect' to a proxy server at the same time. */
    if (httpconnect.ss_family != AF_UNSPEC || socksconnect.ss_family != AF_UNSPEC)
	bye("%s: Invalid option combination: --proxy and -l. QUITTING.", NCAT_NAME);

    /* If a non-root user tries to bind to a privileged port, Exit. */
    if (o.portno < 1024 && !ncat_checkuid())
	bye("%s: Attempted a non-root bind() to a port <1024. QUITTING.", NCAT_NAME);

    /* Set the default maximum simultaneous TCP connection limit. */
    if (!o.conn_limit)
	o.conn_limit = DEFAULT_MAX_CONNS;

    /* 
     * Ncat obviously just redirects stdin/stdout/stderr, so have no clue what is going on with
     * the underlying application. If it foobars, it's your problem.
     */
    if (o.cmdexec && o.normlogfd != -1)
	bye("%s: Invalid option combination: `-e' and `-o'", NCAT_NAME);
    
    /* ditto. */
    if (o.cmdexec && o.hexlogfd != -1)
	bye("%s: Invalid option combination: `-e' and `-x'", NCAT_NAME);

#ifndef WIN32
    /* See if the shell is executable before we get deep into this */
    if (o.shellexec && access("/bin/sh", X_OK) == -1)
	bye("%s: /bin/sh is not executable, so `-c' won't work.", NCAT_NAME);
#endif

    /* If we weren't given a specific address to listen on, we accept
     * any incoming connections
     */
    if (targetss.ss_family == AF_UNSPEC) {
	targetss.ss_family = o.af;

	if (o.af == AF_INET)
	    sin->sin_addr.s_addr = INADDR_ANY;
#ifdef HAVE_IPV6
	else
	    sin6->sin6_addr = in6addr_any;
#endif
    }

    /* Actually set our source address */
    srcaddr = targetss;

    if (srcaddr.ss_family == AF_INET)
	srcaddrlen = sizeof(struct sockaddr_in);
#ifdef HAVE_IPV6
    else
	srcaddrlen = sizeof(struct sockaddr_in6);
#endif

    /* If --broker was supplied, go into connection brokering mode. */
    if (o.broker)
	return ncat_broker();

    if (o.proxytype && !strcmp(o.proxytype, "http"))
	o.httpserver = 1;

    /* Fire the listen/select dispatcher for bog-standard listen operations. */
    return ncat_listen();
}
