/*
 * nmap_tcp_connect plugin
 *
 * This code is mostly (c) Fyodor <fyodor@dhp.com>. Several changes
 * have been made by Renaud Deraison <deraison@worldnet.fr>.
 *
 * This plugin is distributed under the GPL
 *
 */

#include <includes.h>
#include <stdarg.h>
#include "nmap.h"

#ifndef NTP_10
#define NTP_10 10
#endif

#ifndef NTP_11
#define NTP_11 11
#endif


#define NAME "Nmap tcp connect() scan"
#define DESCRIPTION "\
This is the most basic form of TCP\n\
scanning. The connect() system call provided by your operating\n\
system is used to open a connection to every interesting port on\n\
the machine. If the port is listening, connect() will succeed,\n\
otherwise the port isn't reachable. One strong advantage to this\n\
technique is that you don't need any special privileges. Any user\n\
on most UNIX boxes is free to use this call. Another advantage is\n\
speed. While making a separate connect() call for every targeted\n\
port in a linear fashion would take ages over a slow connection,\n\
you can hasten the scan by using many sockets in parallel. Using\n\
non-blocking I/O allows you to set a low time-out period and watch\n\
all the sockets at once. This is the fastest scanning method\n\
supported by nmap, and is available with the -t (TCP) option. The\n\
big downside is that this sort of scan is easily detectable and\n\
filterable. The target hosts logs will show a bunch of connection\n\
and error messages for the services which take the connection and\n\
then have it immediately shutdown."

#define COPYRIGHT "Taken from Fyodor's Nmap"
#define SUMMARY "Performs a noisy TCP scan"

/*
 * auth_printf()
 *
 * Writes data to the global socket of the thread
 */
 
 
   
/*
 * Sends the status of an action
 */
void 
comm_send_status(globals, hostname, action, current,max)
  struct arglist * globals;
  char * hostname;
  char * action;
  int current, max;
{
  int ntp_version = (int)arg_get_value(globals, "ntp_version");
  if(ntp_version == NTP_10)
  {
   auth_printf(globals, "SERVER <|> STAT <|> %s <|> %d/%d <|> SERVER\n",
	      hostname, current, max);
  }
  else auth_printf(globals, "SERVER <|> STATUS <|> %s <|> %s <|> %d/%d <|> SERVER\n",
  	      hostname, action, current, max);
}


PlugExport int plugin_init(struct arglist * desc)
{
 plug_set_name(desc, NAME);
 plug_set_summary(desc, SUMMARY);
 plug_set_description(desc, DESCRIPTION);
 plug_set_copyright(desc, COPYRIGHT);
 plug_set_category(desc, ACT_SCANNER);
 plug_set_family(desc, "Port scanners");
 return(0);
}



PlugExport int plugin_run(struct arglist * env)
{
  short *ports;
  struct arglist * globals = arg_get_value(env, "globals");
  struct arglist * hostinfos = arg_get_value(env, "hostinfos");
  char * port_range = arg_get_value(env, "port_range");
  portlist openports = NULL;
  struct in_addr *p_addr;
  

  if(!strcmp("-1", port_range))return(0);
  else arg_add_value(hostinfos, "scanned", ARG_INT, sizeof(int), (void *)1);
  
  ports = getpts(port_range);
  arg_add_value(hostinfos, "SCAN_RANGE", ARG_PTR, sizeof(ports), ports);
  p_addr = arg_get_value(hostinfos, "IP");
  if(!p_addr)return(-1);
  tcp_scan(globals, p_addr, ports, &openports, hostinfos);
  comm_send_status(globals, arg_get_value(hostinfos, "FQDN"),"portscan",  1,1);
  return 0;
}


void init_socket(int sd) {
struct linger l;

l.l_onoff = 1;
l.l_linger = 0;

if (setsockopt(sd, SOL_SOCKET, SO_LINGER,  (void *) &l, sizeof(struct linger)))
  {
   fprintf(stderr, "Problem setting socket SO_LINGER, errno: %d\n", errno);
   perror("setsockopt");
  }
}



int block_socket(int sd) {
int options;
options = (~O_NONBLOCK) & fcntl(sd, F_GETFL);
fcntl(sd, F_SETFL, options);
return 1;
}


int unblock_socket(int sd) {
int options;
/*Unblock our socket to prevent recvfrom from blocking forever
  on certain target ports. */
options = O_NONBLOCK | fcntl(sd, F_GETFL);
fcntl(sd, F_SETFL, options);
return 1;
}



/* Convert a string like "-100,200-1024,3000-4000,60000-" into an array 
   of port numbers*/
unsigned short *getpts(char *origexpr) {
int exlen = strlen(origexpr);
char *p,*q;
unsigned short *tmp, *ports;
int i=0, j=0,start,end;
char *expr = strdup(origexpr);
char *mem = expr;

ports = emalloc(65536 * sizeof(short));
for(;j < exlen; j++) 
  if (expr[j] != ' ') expr[i++] = expr[j]; 
expr[i] = '\0';
exlen = i;
i=0;
while((p = strchr(expr,','))) {
  *p = '\0';
  if (*expr == '-') {start = 1; end = atoi(expr+ 1);}
  else {
    start = end = atoi(expr);
    if ((q = strchr(expr,'-')) && *(q+1) ) end = atoi(q + 1);
    else if (q && !*(q+1)) end = 65535;
  }
  if (start < 1 || start > end) return(NULL);
  for(j=start; j <= end; j++) 
    ports[i++] = j;
  expr = p + 1;
}
if (*expr == '-') {
  start = 1;
  end = atoi(expr+ 1);
}
else {
  start = end = atoi(expr);
  if ((q =  strchr(expr,'-')) && *(q+1) ) end = atoi(q+1);
  else if (q && !*(q+1)) end = 65535;
}
if (start < 1 || start > end) return(NULL);
for(j=start; j <= end; j++) 
  ports[i++] = j;
ports[i++] = 0;
#ifndef NESSUSNT /* WindowsNT doesn't like realloc() */
tmp = realloc(ports, i * sizeof(short));
  efree(&mem);
  return tmp;
#else
  tmp = NULL;
  return(ports);
#endif
}

portlist tcp_scan(globals, target,portarray, ports,hostdata) 
     struct arglist * globals;
     struct in_addr *target;
     unsigned short *portarray;
     portlist *ports;
     struct arglist * hostdata;	
{
int starttime, current_out = 0, res , deadindex = 0, i=0, j=0, k=0, max=0; 
struct sockaddr_in sock, stranger;
int sockaddr_in_len = sizeof(struct sockaddr_in);
int seconds, seconds2;  /* Store time temporarily for timeout purposes */
int *sockets;   /* All socket descriptors */
int *deadstack; /* Stack of dead descriptors (indexes to sockets[] */
unsigned short *portno;  /* port numbers of sd's, parallel to sockets[] */
int *times; /* initial connect() times of sd's, parallel to sockets[].  For timeout information. */
int *retrystack; /* sd's that need to be retried */
static int *retries; /* nr. or retries for this port */
int retryindex = -1;
int numretries = 2; /* How many retries before we give up on a connection */
char *owner, *buf; 
int timeout = 10;
int current_socket = -1;
fd_set fds_read, fds_write;
struct timeval nowait = {0,0},  longwait = {7,0}; 
int timeouts=0;
int num_scanned = 0;
int end_port = 0;
int ntp_version = (int)arg_get_value(globals, "ntp_version");
j = 0;
while(portarray[j++])end_port++;
j = 0;

sockets = emalloc(sizeof(int)*(MAX_SOCKETS_ALLOWED+1));
deadstack = emalloc(sizeof(int)*(MAX_SOCKETS_ALLOWED+1));
portno = emalloc(sizeof(unsigned short)*(MAX_SOCKETS_ALLOWED+1));
times = emalloc(sizeof(int)*(MAX_SOCKETS_ALLOWED+1));
retrystack = emalloc(sizeof(int)*(MAX_SOCKETS_ALLOWED+1));
retries = emalloc(sizeof(int)*65536);
owner = emalloc(513);
buf = emalloc(65536);
signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE so our 'write 0 bytes' test
			     doesn't crash our program!*/
owner[0] = '\0';
starttime = time(NULL);
bzero((char *)&sock,sizeof(struct sockaddr_in));
sock.sin_addr.s_addr = target->s_addr;
sock.sin_family=AF_INET;
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
comm_send_status(globals, arg_get_value(hostdata, "FQDN"),"portscan",  0,end_port);

/* Initially, all of our sockets are "dead" */
for(i = 0 ; i < MAX_SOCKETS_ALLOWED; i++) {
  deadstack[deadindex++] = i;
  portno[i] = 0;
}

deadindex--; 
/* deadindex always points to the most recently added dead socket index */

while(portarray[j] || retryindex >= 0 || current_out != 0) {
  longwait.tv_sec = timeout;
  longwait.tv_usec = nowait.tv_sec = nowait.tv_usec = 0;
  seconds = time(NULL);
  for(i=current_out; i < MAX_SOCKETS_ALLOWED && (portarray[j] || retryindex >= 0); i++,
  	num_scanned++) {
  	if(num_scanned==100){
	comm_send_status(globals, arg_get_value(hostdata, "FQDN"), 
        "portscan",
	portarray[j]>0?portarray[j]-1:0, end_port);
        num_scanned = 0;
        }      
    current_socket = deadstack[deadindex--];
    if ((sockets[current_socket] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
      {perror("Socket troubles"); EXIT(1);}
    if (sockets[current_socket] > max) max = sockets[current_socket]; 
    current_out++;
    unblock_socket(sockets[current_socket]);
    init_socket(sockets[current_socket]);
    if (retryindex < 0) {
      portno[current_socket] = portarray[j++];
    }
    else { /* we have retries to do ...*/
      portno[current_socket] = retrystack[retryindex--];
    }
    sock.sin_port = htons(portno[current_socket]);
    times[current_socket] = seconds;
    if ((res = connect(sockets[current_socket],(struct sockaddr *)&sock,sizeof(struct sockaddr)))!=-1) 
    {
      struct servent * service;
      
      service = getservbyport(htons(portno[current_socket]), "tcp");
      
	/* This can happen on localhost, successful connection immediately
           in non-blocking mode */
      host_add_port(hostdata, portno[current_socket], 1, "");
	  if(ntp_version <= NTP_10)
	  {
      auth_printf(globals, "SERVER <|> PORT <|> %s <|> %d <|> SERVER\n",
			  arg_get_value(hostdata, "FQDN"),portno[current_socket]);
	  }
	  else
		auth_printf(globals, "SERVER <|> PORT <|> %s <|> %s (%d/tcp) <|> SERVER\n",
			  arg_get_value(hostdata, "FQDN"),
                          service ? service->s_name : "unknown", portno[current_socket]);
	  
      if (max == sockets[current_socket])
	max--;
      FD_CLR(sockets[current_socket], &fds_read);
      FD_CLR(sockets[current_socket], &fds_write);
      deadstack[++deadindex] = current_socket;
      current_out--;
      portno[current_socket] = 0;
      close(sockets[current_socket]);
    }
    else {  /* Connect() failed, normal case */
      switch(errno) {
      case EINPROGRESS: /* The one I always see */
      case EAGAIN:
	block_socket(sockets[current_socket]); 
	FD_SET(sockets[current_socket], &fds_write);
	FD_SET(sockets[current_socket], &fds_read);
	break;
      default:
	printf("Strange error from connect (%d):", errno);
	perror(""); /*falling through intentionally*/
      case ECONNREFUSED:
	if (max == sockets[current_socket]) max--;
	deadstack[++deadindex] = current_socket;
	current_out--;
	portno[current_socket] = 0;
	close(sockets[current_socket]);
	timeouts = 0; /* We may not want to give up on this host */
	break;
      }
    }
  }
  /*  if (!portarray[j] && retryindex < 0) sleep(2); *//*If we are done, wait a second for any last packets*/
  while((res = select(max + 1, &fds_read, &fds_write, NULL, 
		      (current_out < MAX_SOCKETS_ALLOWED)?
		      &nowait : &longwait)) > 0) {
    fflush(stdout);
    for(k=0; k < MAX_SOCKETS_ALLOWED; k++)
      if (portno[k]) {
	if (FD_ISSET(sockets[k], &fds_write)
	    && FD_ISSET(sockets[k], &fds_read)) {
	  /*printf("Socket at port %hi is selectable for r & w.", portno[k]);*/
	  res = recvfrom(sockets[k], buf, 65536, 0, (struct sockaddr *)
			 & stranger, &sockaddr_in_len);
	  if (res >= 0) { 
           struct servent * service;
           service = getservbyport(htons(portno[k]), "tcp");
	  host_add_port(hostdata, portno[k], 1, "");	    
	  if(ntp_version > NTP_10)
	    auth_printf(globals, "SERVER <|> PORT <|> %s <|> %s (%d/tcp) <|> SERVER\n",
			  arg_get_value(hostdata, "FQDN"),
                          service ? service->s_name : "unknown", portno[k]);
	  else
		  auth_printf(globals, "SERVER <|> PORT <|> %s <|> %d <|> SERVER\n", 
			   arg_get_value(hostdata, "FQDN"), portno[k]);
	  }
	  if (max == sockets[k])
	    max--;
	  FD_CLR(sockets[k], &fds_read);
	  FD_CLR(sockets[k], &fds_write);
	  deadstack[++deadindex] = k;
	  current_out--;
	  portno[k] = 0;
	  close(sockets[k]);
	}
	else if(FD_ISSET(sockets[k], &fds_write)) {
	  /*printf("Socket at port %hi is selectable for w only.VERIFYING\n",
	    portno[k]);*/
	  res = send(sockets[k], buf, 0, 0);
	  if (res < 0 ) {
	    signal(SIGPIPE, SIG_IGN);
	  }
	  else {	    
          struct servent * service = getservbyport(htons(portno[k]), "tcp");
	  host_add_port(hostdata, portno[k], 1, "");
	  if(ntp_version > NTP_10)
	  
	  auth_printf(globals, "SERVER <|> PORT <|> %s <|> %s (%d/tcp) <|> SERVER\n",
			  arg_get_value(hostdata, "FQDN"),
                          service ? service->s_name:"unknown", portno[k]);	 
	   else
		   auth_printf(globals, "SERVER <|> PORT <|> %s <|> %d <|> SERVER\n",
			   arg_get_value(hostdata, "FQDN"), portno[k]);
	  }
	  if (max == sockets[k]) max--;
	  FD_CLR(sockets[k], &fds_write);
	  deadstack[++deadindex] = k;
	  current_out--;
	  portno[k] = 0;
	  close(sockets[k]);
	}
	else if ( FD_ISSET(sockets[k], &fds_read) ) {       
	  printf("Socket at port %hi is selectable for r only.  This is very wierd.\n", portno[k]);
	  if (max == sockets[k]) max--;
	  FD_CLR(sockets[k], &fds_read);
	  deadstack[++deadindex] = k;
	  current_out--;
	  portno[k] = 0;
	  close(sockets[k]);
	}
	else { /* neither read nor write selected */
	  if (time(NULL) - times[k] < 10) {
	  /*printf("Socket at port %hi not selecting, readding.\n",portno[k]);*/
	  FD_SET(sockets[k], &fds_write);
	  FD_SET(sockets[k], &fds_read);
	  }
	  else { /* time elapsed */
	    if (retries[portno[k]] < numretries  && 
		(portarray[j] || retryindex >= 0)) {
	    /* don't readd if we are done with all other ports */ 
	      retries[portno[k]]++;
	      retrystack[++retryindex] = portno[k];
	    }
	    else {
	      timeouts++;	      
	    }	  	    
	    if (max == sockets[k]) max--;
	    FD_CLR(sockets[k], &fds_write);
	    FD_CLR(sockets[k], &fds_read);
	    deadstack[++deadindex] = k;
	    current_out--;
	    portno[k] = 0;
	    close(sockets[k]);
	  }
	}
      }
  longwait.tv_sec = timeout;
  longwait.tv_usec = 0;
  }
  /* If we can't send anymore packets (because full or out of ports) */
  if (current_out == MAX_SOCKETS_ALLOWED || (!portarray[j] && retryindex < 0)) {
    int z;
    seconds2 = time(NULL);
    for(z=0; z < MAX_SOCKETS_ALLOWED; z++) {
      if (portno[z] && seconds2 - times[z] >= 10) { /* Timed out, dr0p it */
	if (retries[portno[z]] < numretries && 
	    (portarray[j] || retryindex >= 0)) { /* don't re-add if we
						    are done with all other
						    ports */
	  retries[portno[z]]++;
	  retrystack[++retryindex] = portno[z];
	}
	else {
	  timeouts++;	      
	  if (max == sockets[z]) max--;
	  FD_CLR(sockets[z], &fds_write);
	  FD_CLR(sockets[z], &fds_read);
	  deadstack[++deadindex] = z;
	  current_out--;
	  portno[z] = 0;
	  close(sockets[z]);
	}
      }
    }
  }
}



for(k=0; k < MAX_SOCKETS_ALLOWED; k++) {
  if (portno[k]) {
    printf("Almost missed port %d\n", portno[k]);
    close(sockets[k]);
  }
}

/*
free(sockets);
free(deadstack); 
free(portno); 
free(times);
free(retrystack); 
free(retries); 
free(owner);
free(buf);
*/
return NULL;
}
