/* $Id: ncat_core.c 11806 2009-01-22 00:38:21Z david $ */

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

#ifndef WIN32
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <assert.h>

struct sockaddr_storage srcaddr;
size_t srcaddrlen;
struct sockaddr_storage targetss;
size_t targetsslen;

struct sockaddr_storage httpconnect;
struct sockaddr_storage socksconnect;

int verbose_flag;

/* Global options structure. */
struct options o;

/* Initializes global options to their default values. */
void options_init(void) {
    o.af = AF_INET;
    o.broker = 0;
    o.listen = 0;
    o.sendonly = 0;
    o.recvonly = 0;
    o.telnet = 0;
    o.udp = 0;
    o.linedelay = 0;
    o.talk = 0;
    o.nodns = 0;
    o.normlogfd = -1;
    o.hexlogfd = -1;
    o.idletimeout = 0;
    o.crlf = 0;
    o.allow = NULL;
    o.allowfile = NULL;
    o.deny = NULL;
    o.denyfile = NULL;
    o.httpserver = 0;

    o.numsrcrtes = 0;
    o.srcrteptr = 4;

    o.conn_limit = 0;
    o.conntimeout = DEFAULT_CONNECT_TIMEOUT;

    o.cmdexec = NULL;
    o.shellexec = 0;
    o.proxy_auth = NULL;
    o.proxytype = NULL;

#ifdef HAVE_OPENSSL
    o.ssl = 0;
    o.sslcert = NULL;
    o.sslkey = NULL;
#endif
}

/* Tries to resolve the given name (or literal IP) into a sockaddr
   structure.  The af should be PF_INET (for IPv4) or PF_INET6.  Returns 0
   if hostname cannot be resolved.  It is OK to pass in a sockaddr_in or 
   sockaddr_in6 casted to a sockaddr_storage as long as you use the matching 
   pf.*/
int resolve(char *hostname, struct sockaddr_storage *ss, size_t *sslen,
	    int pf) {

  struct addrinfo hints;
  struct addrinfo *result;
  int rc;

  if (o.nodns && !isip(hostname)) {
      Fprintf(stderr, "Refusing to resolve %s\n", hostname);
      return 0;
  }

  assert(ss);
  assert(sslen);
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = pf;
  rc = getaddrinfo(hostname, NULL, &hints, &result);
  if (rc != 0 || result == NULL)
      return 0;
  assert(result->ai_addrlen > 0 && result->ai_addrlen <= (int) sizeof(struct sockaddr_storage));
  *sslen = result->ai_addrlen;
  memcpy(ss, result->ai_addr, *sslen);
  freeaddrinfo(result);
  return 1;
}


/* Take a file descriptor to redirect to and a command to execute.
 * Redirect file descriptor, stdin & stdout, not stderr.
 */
int netexec(int fd, char *cmdexec)
{
    char *token,    *newtoken,  **cmdargs,  *cmdbin;
    char *cmdexec_local,    *cmdexec_path;
    int x = 1,  arg_count = 0,  y = 0,  path_count = 0, maxlen = 0;

    if (verbose_flag > 1)
        Fprintf(stderr, "DEBUG: Executing: %s\n", cmdexec);

    /* rearrange stdin/stdout/stderr */
    Dup2(fd, STDIN_FILENO);
    Dup2(fd, STDOUT_FILENO);
    Dup2(fd, STDERR_FILENO);
    /*
    Dup2(fd, 0);
    Close(fd);
    Dup2(0, 1);
    Dup2(0, 2);
    */

    /* If we're executing through /bin/sh */
    if (o.shellexec) {
        execl("/bin/sh", "sh", "-c", cmdexec, NULL);
        bye("Cannot exec /bin/sh!");
    }

    /* FIXME fix this command parsing code it is not pretty */
    maxlen = strlen(cmdexec);
    cmdexec_local = Strdup(cmdexec);
    cmdexec_path = Strdup(cmdexec);

    /* parse command line into cmd + args */
    if ((token = strtok(cmdexec_path, " ")) != NULL) {
        do {
            /* position of the end of token */
            y = (strlen(token)) - 1;
	    
            /* if token ends with an escape */
            if (token[y] == '\\') {
                path_count++;
            } else {
                arg_count++;
            }
        } while ((token = strtok(NULL, " ")) != NULL);
    }

    /* malloc space based on supplied command/arguments */
    cmdbin = (char *)Malloc((path_count + 2) * sizeof(cmdbin));
    cmdargs = (char **)Malloc((arg_count + 2) * sizeof(cmdargs));
    newtoken = (char *)Calloc(((maxlen + arg_count + path_count) * 4), sizeof(char));

    /* assemble arg vector */
    if ((token = strtok(cmdexec_local, " ")) != NULL) {
        int ar = 0;
	
        do {
            /* craft the path & executable name, handling whitespaces. */
            if (ar <= path_count) {
                y = (strlen(token)) - 1;
		
                if (token[y] == '\\') {
                    strncat(newtoken, token, (strlen(token) - 1));
                    strcat(newtoken, " ");
                } else
                    strncat(newtoken, token, (strlen(token)));
		
                if (verbose_flag > 1)
                    Fprintf(stderr, "DEBUG: Executable path: %s\n", newtoken);
                ar++;
            } else {
                /* craft the arguments to the command */
		
                if (verbose_flag > 1)
                    Fprintf(stderr, "DEBUG: Command argument: %s\n", token);
		
                cmdargs[x++] = (char *) token;
            }
        } while ((token = strtok(NULL, " ")) != NULL);
        cmdargs[0] = newtoken;
    }
    
    cmdargs[x] = NULL;

    /* debugging */
    if (verbose_flag > 0)
        Fprintf(stderr, "DEBUG: Executing redirected command %s\n",
                    cmdexec);

    /* finally execute the command */
    return execv(newtoken, (char *const *) cmdargs);
}

/* Do telnet WILL/WONT DO/DONT negotiations */
void dotelnet(int s, unsigned char *buf, size_t bufsiz)
{
	unsigned char *end = buf + bufsiz, *p;
	unsigned char tbuf[3];

	for (p = buf; buf < end; p++) {
		if (*p != 255) /* IAC */
			break;

		tbuf[0] = *p++;

		/* Answer DONT for WILL or WONT */
		if (*p == 251 || *p == 252)
			tbuf[1] = 254;

		/* Answer WONT for DO or DONT */
		else if (*p == 253 || *p == 254)
			tbuf[1] = 252;

		tbuf[2] = *++p;

		send(s, (const char *) tbuf, 3, 0);
	}
}

/* Set verbosity level for Ncat and nsock */
int verbosity(int verbose_flag, nsock_pool mypool)
{
    int tracelevel = 0;

    /* nsock tracelevel for -v */
    if (verbose_flag == 1)
	    tracelevel = 1;

    /* nsock tracelevel for -vv and -vvv */
    else if (verbose_flag >= 2)
	    tracelevel = 10;

    /* Set the verbosity of nsock, based on user's verbosity requirement */
    nsp_settrace(mypool, tracelevel, nsock_gettimeofday());

    return 1;
}

/* Return 1 if user is root, otherwise 0. */
int ncat_checkuid()
{
#ifdef WIN32
	return 1;
#else
    return (getuid() == 0 || geteuid() == 0);
#endif
}

/* sleep(), usleep(), msleep(), Sleep() -- all together now, "portability".
 * 
 * There is no upper or lower limit to the delayval, so if you pass in a short 
 * length of time <100ms, then you're likely going to get odd results. 
 * This is because the Linux timeslice is 10ms-200ms. So don't expect 
 * it to return for atleast that long.
 * 
 * Block until the specified time has elapsed, then return 1.
 */
int ncat_delay_timer(int delayval)
{
    struct timeval s;

    s.tv_sec = delayval / 1000;
    s.tv_usec = (delayval % 1000) * (long) 1000;

    select(0, NULL, NULL, NULL, &s);
    return 1;
}

/* Open a logfile for writing.
 * Return the open file descriptor. */
int ncat_openlog(char *logfile)
{
    return Open(logfile, O_WRONLY | O_CREAT | O_TRUNC, 0664);
}

/* Convert session data to a neat hexdump logfile */
int ncat_hexdump(int logfd, char *data, int len)
{
    char *p = data;
    unsigned char c;
    int i;
    char bytestr[4] = { 0 };
    char addrstr[10] = { 0 };
    char hexstr[16 * 3 + 5] = { 0 };
    char charstr[16 * 1 + 5] = { 0 };
    char outstr[80] = { 0 };

    /* FIXME: needs to be audited closer */
    for (i = 1; i <= len; i++) {
        if (i % 16 == 1) {
            /* Hex address output */
            Snprintf(addrstr, sizeof(addrstr), "%.4x", (u_int)(p - data));
        }
	
        c = *p;
	
        /* If the character isn't printable. spaces, etc. */
        if (isprint(c) == 0)
            c = '.';

        /* hex for output */
	    Snprintf(bytestr, sizeof(bytestr), "%02X ", *p);
	    strncat(hexstr, bytestr, sizeof(hexstr) - strlen(hexstr) - 1);

	    /* char for output */
	    Snprintf(bytestr, sizeof(bytestr), "%c", c);
	    strncat(charstr, bytestr, sizeof(charstr) - strlen(charstr) - 1);

	    if (i % 16 == 0) {
	        /* neatly formatted output */
	        Snprintf(outstr, sizeof(outstr), "[%4.4s]   %-50.50s  %s\n",
		                addrstr, hexstr, charstr);

            Write(logfd, outstr, strlen(outstr));
	        zmem(outstr, sizeof(outstr));

            hexstr[0] = 0;
            charstr[0] = 0;
        } else if (i % 8 == 0) {
	        /* cat whitespaces where necessary */
	        strncat(hexstr, "  ", sizeof(hexstr) - strlen(hexstr) - 1);
	        strncat(charstr, " ", sizeof(charstr) - strlen(charstr) - 1);
	    }

	    /* get the next byte */
	    p++;
    }

    /* if there's still data left in the buffer, print it */
    if (strlen(hexstr) > 0) {
        Snprintf(outstr, sizeof(outstr), "[%4.4s]   %-50.50s  %s\n",
                    addrstr, hexstr, charstr);
	
        Write(logfd, outstr, strlen(outstr));
        zmem(outstr, sizeof(outstr));
    }

    return 1;
}

int ncat_hostaccess(char *matchaddr, char *filename, char *remoteip)
{
    char    buf[DEFAULT_BUF_LEN];

    if (!remoteip)
	    return -1;

    if (matchaddr && !filename) {
	    return (ncat_hostmatch(matchaddr, remoteip));
    } else if (filename && !matchaddr) {
        FILE *fp;

        fp = Fopen(filename, "r");

        while (Fgets(buf, sizeof(buf), fp) != NULL) {
            /* Allow commented lines */
            if (buf[0] == '#')
                continue;
	    
            if ((ncat_hostmatch(buf, remoteip))) {
                Fclose(fp);
                return 1;
            }
        }
	
        Fclose(fp);
        return 0;
    } else
        return -1;
}
