Logo Search packages:      
Sourcecode: nessus-plugins version File versions

nessus_tcp_scanner.c

/* 
 * Copyright (C) 2004 Michel Arboi <mikhail@nessus.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#include <includes.h>

#ifndef GRAB_MAX_SOCK
# define GRAB_MAX_SOCK  1024
#endif
#ifndef GRAB_MIN_SOCK
# define GRAB_MIN_SOCK  64
#endif

PlugExport int plugin_init(struct arglist * desc)
{
 plug_set_id(desc, 10335);
 plug_set_version(desc, "$Revision: 1.4 $");
   
         
 plug_set_name(desc, "Nessus TCP scanner", NULL);
 plug_set_summary(desc, "Look for open TCP ports & services banners", NULL);
 plug_set_description(desc, "\
This plugin is a classical TCP port scanner\n\
It shall be reasonably quick even against a firewalled target.\n\
\n\
Once a TCP connection is open, it grabs any available banner\n\
for the service identification plugins\n\
\n\
Note that TCP scanners are more intrusive than \n\
SYN (half open) scanners\
", NULL);
 
 plug_set_copyright(desc, "(C) 2004 Michel Arboi <mikhail@nessus.org>", NULL);
 plug_set_category(desc, ACT_SCANNER);
 plug_set_family(desc, "Scanners de ports", "francais");
 plug_set_family(desc, "Port scanners", NULL);

 plug_set_dep(desc, "ping_host.nasl");
 return(0);
}
 

typedef struct {
  int             fd;
  time_t          tictac;           /* open time */
  unsigned short  port;
  unsigned char         state;
} grab_socket_t;

#define GRAB_SOCKET_UNUSED    0
#define GRAB_SOCKET_OPENING   1
#define GRAB_SOCKET_OPEN      2

#define GRAB_PORT_UNKNOWN     0
#define GRAB_PORT_CLOSED      1
#define GRAB_PORT_OPEN        2
#define GRAB_PORT_FILTERED    3
#define GRAB_PORT_NOT_TESTED  254
#define GRAB_PORT_TESTING     255
  

#ifdef DEBUG
# define DISPLAY
#endif

static int
my_socket_close(int s)
{
#ifndef SO_LINGER
  if (shutdown(s, 2) < 0)
#ifdef DEBUG
    perror("shutdown")
#endif
      ;
#endif
  return close(s);
}

static void
remember_filtered_port(struct arglist* desc, int port)
{
  char            k[80];
  snprintf(k, sizeof(k), "/tmp/ConnectTimeout/TCP/%d", port);
  plug_set_key(desc, k, ARG_INT, (void*)1); 
}

static int
banner_grab(const struct in_addr *pia, const char* portrange, 
          const int read_timeout,
          int           max_cnx,
          struct arglist *globals, 
          struct arglist *desc,
          struct arglist *hostinfos)
{
  char                  buf[8192];
  int             s, tcpproto;
  struct protoent *proto;
  fd_set          rfs, wfs, efs;
  struct timeval  timeout;
  struct sockaddr_in    sa;
  int             port = 23;
  int             imax, i, j, scanned_ports, x, opt, optsz;
  int             minport;
  unsigned char         ports_states[65536];
  grab_socket_t         sockets[GRAB_MAX_SOCK];
  int             open_sock_nb, open_sock_max, open_sock_max2;
  time_t          ti;
  int             unfiltered_ports_nb, filtered_ports_nb, timeout_nb;
  int             untested_ports_nb, total_ports_nb;


  proto = getprotobyname("tcp");
  if (proto == NULL)
    {
      perror("tcp");
      return -1;
    }
  tcpproto = proto->p_proto;

  for (i = 0; i < sizeof(ports_states) / sizeof(*ports_states); i ++)
    ports_states[i] = GRAB_PORT_NOT_TESTED;
  scanned_ports = 0;

  {
    char    *p, *q;
    int           po1, po2;
    p = (char*)portrange;
    untested_ports_nb = 0;

    if (p == NULL || *p == '\0' || strcmp(p, "default") == 0)
      {
      int   last_num = 0;
      unsigned short * nums = (unsigned short*)get_tcp_svcs(&last_num);

      if (nums == NULL)
        {
          fprintf(stderr, "Cannot get list of default services\n");
          return -1;
        }
      for (i = 0; i < last_num; i ++)
          {
            ports_states[nums[i]] = GRAB_PORT_UNKNOWN;
            untested_ports_nb ++;
          }
      efree(&nums);
      }
    else
      while (*p != '\0')
      {
        while (*p == ',')
          p ++;

        if (*p == '-')
          {
            po1 = 1;
            q = p + 1;
            po2 = strtol(q, &p, 10);
            if (q == p)
            {
              fprintf(stderr, "Cannot parse '%s'\n", p);
              return -1;
            }
          }
        else
          {
            po1 = strtol(p, &q, 10);
            if (q == p)
            {
              fprintf(stderr, "Cannot parse '%s'\n", p);
              return -1;
            }
            if (*q == ',')
            {
              p = q + 1;
              po2 = po1;
            }
            else if (*q == '\0')
            {
              p = q;
              po2 = po1;
            }
            else if (*q == '-')
            {
              if (q[1] == '\0')
                {
                  po2 = 65535;
                  p = q+1;
                }
              else
                {
                  po2 = strtol(q+1, &p, 10);
                  if (q+1 == p)
                  {
                    fprintf(stderr, "Cannot parse '%s'\n", p);
                    return -1;
                  }
                }
            }
          }
        for (i = po1; i <= po2; i ++)
          {
            ports_states[i] = GRAB_PORT_UNKNOWN;
            untested_ports_nb ++;
          }
      }
  }

  for (i = 0; i < max_cnx; i ++)
    {
      sockets[i].state = GRAB_SOCKET_UNUSED;
      sockets[i].fd = -1;
    }

  open_sock_nb = 0; 
  open_sock_max = GRAB_MIN_SOCK; open_sock_max2 = max_cnx;

  filtered_ports_nb = unfiltered_ports_nb = 0;

  minport = 1;
  while (scanned_ports < 65535)
    {
      total_ports_nb = unfiltered_ports_nb + filtered_ports_nb + untested_ports_nb;
      comm_send_status(globals, arg_get_value(hostinfos, "NAME"),"portscan", 
                   unfiltered_ports_nb + filtered_ports_nb, 
                   total_ports_nb);
#ifdef DEBUG
      fprintf(stderr, "%d / %d = %02d%% - %d ports remaining\n", 
            unfiltered_ports_nb + filtered_ports_nb,
            total_ports_nb,
            (unfiltered_ports_nb + filtered_ports_nb) * 100 / 
            (total_ports_nb > 0 ? total_ports_nb : 1),
            untested_ports_nb);
#endif
      while (open_sock_nb < open_sock_max)
      {
        for (port = minport; port <= 65535 && ports_states[port] != GRAB_PORT_UNKNOWN; port ++)
          ;
        if (port > 65535)
          break;
        minport = port;

        ports_states[port] = GRAB_PORT_TESTING;
#ifdef DEBUG
        fprintf(stderr, "Trying %d\n", port);
#endif
        s = socket(PF_INET, SOCK_STREAM, tcpproto);
        if (s < 0)
          {
            perror("socket");
            if (errno == ENFILE || errno == EMFILE)
            {
              open_sock_max = open_sock_max2 = open_sock_nb;
#ifdef DEBUG
              fprintf(stderr, "Reducing the number of maximum open connections to %d\n", open_sock_max);
#endif
              continue;
            }
            else
            return -1;
          }

        if (fcntl(s, F_SETFL, O_NONBLOCK) < 0)
          {
            perror("fcntl");
            return -1;
          }

#ifdef SO_LINGER
        {
          struct linger l;

          l.l_onoff = 0; l.l_linger = 0;
          if (setsockopt(s, SOL_SOCKET,  SO_LINGER,  &l, sizeof(l)) < 0)
            perror("setsockopt(SO_LINGER)");
        }
#endif

        sa.sin_addr = *pia;
        sa.sin_family = AF_INET;
        sa.sin_port = htons(port);

        if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) < 0)
          {
            switch (errno)
            {
            case EINPROGRESS:
            case EALREADY:
              sockets[open_sock_nb].fd = s;
              sockets[open_sock_nb].port = port;
              sockets[open_sock_nb].state = GRAB_SOCKET_OPENING;
              sockets[open_sock_nb].tictac = time(NULL);
              open_sock_nb ++;
              break;
              
            case EAGAIN:
              open_sock_max = open_sock_nb - 1;
#ifdef DEBUG
              fprintf(stderr, "Reducing the number of maximum open connections to %d\n", open_sock_max);
#endif
              continue;

            case ECONNREFUSED:
              ports_states[port] = GRAB_PORT_CLOSED;
              my_socket_close(s);
              unfiltered_ports_nb ++;
              untested_ports_nb --;
              continue;
              
            case ENETUNREACH:
              ports_states[port] = GRAB_PORT_FILTERED;
              my_socket_close(s);
              filtered_ports_nb ++;
              untested_ports_nb --;
              continue;

            default:
              perror("connect");
              return -1;
            }
          }
        else                  /* This shoud not happen! */
          {
            sockets[open_sock_nb].fd = s;
            sockets[open_sock_nb].port = port;
            sockets[open_sock_nb].state = GRAB_SOCKET_OPEN;
            sockets[open_sock_nb].tictac = time(NULL);
            open_sock_nb ++;
            ports_states[port] = GRAB_PORT_OPEN;
            unfiltered_ports_nb ++;
            untested_ports_nb --;
            scanner_add_port(desc, port, "tcp");
          }
      }

      if (open_sock_max2 <= 0)      /* file table is full */
      return -1;

      if (open_sock_nb == 0)
      {
        if (untested_ports_nb > 0)
          {
#if 1
            fprintf(stderr, "No more open socket?\n");
#endif
            return -1;
          }
        else
          return 0;
      }

      FD_ZERO(&rfs); FD_ZERO(&wfs); FD_ZERO(&efs);
      imax = -1;
      for (i = 0; i < open_sock_nb; i ++)
      {
        if (sockets[i].fd >= 0)
          {
            switch (sockets[i].state)
            {
            case GRAB_SOCKET_OPEN:
              FD_SET(sockets[i].fd, &rfs);
              break;
            case GRAB_SOCKET_OPENING:
              FD_SET(sockets[i].fd, &wfs);
              break;
            default:
#if 1
              fprintf(stderr, "Bad status %d - s=%d\n", 
                    sockets[i].state, sockets[i].fd);
#endif
              break;
            }
            if (sockets[i].fd > imax)
            imax = sockets[i].fd;
          }
      }

      if (imax < 0)
      {
        if (untested_ports_nb > 0)
          {
#if 1
            fprintf(stderr, "No socket! %d ports remaining\n", untested_ports_nb);
#endif
            return -1;
          }
        else
          {
#ifdef DEBUG
            fprintf(stderr, "No socket! %d ports remaining\n", untested_ports_nb);
#endif
            return 0;
          }
      }

      timeout.tv_sec = read_timeout; /* * 2 ? */
      timeout.tv_usec = 0;
      i = 0;
      do
      x = select(imax + 1, &rfs, &wfs, NULL, &timeout);
      while (i ++ < 10 && x < 0 && errno == EINTR);

      if (x < 0)
      {
        perror("select");
        return -1;
      }
      else if (x == 0)        /* timeout */
      {
#if DEBUG
        fprintf(stderr, "select: timeout on all (%d) sockets!\n", imax - 1);
#endif
        for (i = 0; i < open_sock_nb; i ++)
          {
            if (sockets[i].fd > 0)
            {
              my_socket_close(sockets[i].fd);
              sockets[i].fd = -1;
              if (sockets[i].state == GRAB_SOCKET_OPENING)
                {
                  ports_states[sockets[i].port] = GRAB_PORT_FILTERED;
                  filtered_ports_nb ++;
                  untested_ports_nb --;
                  remember_filtered_port(desc, sockets[i].port);
                }
              
            }
            sockets[i].state = GRAB_SOCKET_UNUSED;
          }
      }
      else              /* something to do */
      {
        for (i = 0; i < open_sock_nb; i ++)
          {
            if (sockets[i].fd > 0)
            if (FD_ISSET(sockets[i].fd, &wfs))
              {
                opt = 0; optsz = sizeof(opt);
                if (getsockopt(sockets[i].fd, SOL_SOCKET, SO_ERROR, &opt, &optsz) < 0)
                  {
                  perror("getsockopt");
                  return -1;
                  }

                if (opt != 0)
                  {
                  errno = opt;
#ifdef DEBUG
                  perror("select->getsockopt");
#endif
                  ports_states[sockets[i].port] = GRAB_PORT_CLOSED;
                  my_socket_close(sockets[i].fd);
                  sockets[i].fd = -1;
                  sockets[i].state = GRAB_SOCKET_UNUSED;
                  unfiltered_ports_nb ++;
                  untested_ports_nb --;
#ifdef DISPLAY
                  printf(">> %d: CLOSED\n", sockets[i].port);
#endif
                  }
                else
                  {
                  sockets[i].state = GRAB_SOCKET_OPEN;
#ifdef DISPLAY
                  printf(">> %d: OPEN\n", sockets[i].port);
#endif
                  untested_ports_nb --;
                  ports_states[sockets[i].port] = GRAB_PORT_OPEN;
                  scanner_add_port(desc, sockets[i].port, "tcp");
                  }
              }
            else if (FD_ISSET(sockets[i].fd, &rfs))
              {
                x = read(sockets[i].fd, buf, sizeof(buf)-1);
                if (x > 0)
                  {
                  char  kb[64];
                  buf[x] = '\0';
                  sprintf(kb, "Banner/%d", sockets[i].port);
                  plug_set_key(desc, kb, ARG_STRING, buf);
#ifdef DISPLAY
                  printf("Banner for port %d: %s\n", sockets[i].port, buf);
#endif
                  
                  }
                else
#ifdef DEBUG
                  perror("read");
#endif
                my_socket_close(sockets[i].fd);
                sockets[i].fd = -1;
                sockets[i].state = GRAB_SOCKET_UNUSED;
              }
          }
      }

      ti = time(NULL);
      timeout_nb = 0;
      for (i = 0; i < open_sock_nb; i ++)
      if (sockets[i].fd >= 0 && ti - sockets[i].tictac >= read_timeout)
        {
          switch(sockets[i].state)
            {
            case GRAB_SOCKET_OPEN:
#ifdef DISPLAY
            printf(">> %d: NO BANNER\n", sockets[i].port);
#endif
            timeout_nb ++;
            break;
            case GRAB_SOCKET_OPENING:
#ifdef DISPLAY
            printf(">> %d: TIMEOUT\n", sockets[i].port);
#endif
            ports_states[sockets[i].port] = GRAB_PORT_FILTERED;
            filtered_ports_nb ++;
            untested_ports_nb --;
            remember_filtered_port(desc, sockets[i].port);
            break;
            }
          my_socket_close(sockets[i].fd); sockets[i].fd = -1;
          sockets[i].state = GRAB_SOCKET_UNUSED;
        }

      x = open_sock_max;
      open_sock_max += filtered_ports_nb;
      open_sock_max += timeout_nb;
      if (open_sock_max > open_sock_max2)
      open_sock_max = open_sock_max2;
#ifdef DEBUG
      if (x != open_sock_max)
      fprintf(stderr, "open_sock_max=%d\n", open_sock_max);
#endif
      for (i = 0; i < open_sock_nb; )
      if (sockets[i].state == GRAB_SOCKET_UNUSED || sockets[i].fd < 0)
        {
          for (j = i +1;  
             j < open_sock_nb && (sockets[j].state == GRAB_SOCKET_UNUSED || sockets[j].fd < 0);
             j ++)
            ;
          if (j < open_sock_nb)
            memmove(sockets+i, sockets+j, sizeof(*sockets) * (max_cnx - j));
          open_sock_nb -= j - i;
        }
      else
        i ++;
    }
  return 0;
}

PlugExport int plugin_run(struct arglist * desc)
{
  struct arglist * globals = arg_get_value(desc, "globals");
  struct arglist * preferences = arg_get_value(desc, "preferences");
  struct arglist * hostinfos = arg_get_value(desc, "HOSTNAME");
  char * port_range = arg_get_value(preferences, "port_range");
  char * p;
  struct in_addr *p_addr;
  int timeout = 0, max_cnx;


  p =  arg_get_value(preferences, "checks_read_timeout");
  if (p != NULL) timeout = atoi(p);
  if (timeout <= 0)
    timeout = 30;

  {
    int           max_host = 0, max_sys_fd = 0;
    FILE    *fp;

    p = arg_get_value(preferences, "max_hosts");
    if (p != NULL) max_host = atoi(p);
    if (max_host <= 0) max_host = 15;
#if 0
    p = arg_get_value(preferences, "max_checks");
    if (p != NULL) max_checks = atoi(p);
    if (max_checks <= 0) max_checks = 10;
#endif
    fp = popen("sysctl fs.file-max", "r");
    if (fp != NULL)
      {
      fscanf(fp, "%d", &max_sys_fd);
      fclose(fp);
      }
    if (max_sys_fd <= 0)
      {
      fp = popen("sysctl kern.maxfiles", "r");
      if (fp != NULL)
        {
          fscanf(fp, "%d", &max_sys_fd);
          fclose(fp);
        }
      }
    if (max_sys_fd <= 0) max_sys_fd = 16384; /* reasonable default */
    max_cnx = max_sys_fd / max_host;
#ifdef DEBUG
    fprintf(stderr, "max_cnx = %d\n", max_cnx);
#endif
    if (max_cnx > GRAB_MAX_SOCK) max_cnx = GRAB_MAX_SOCK;
    if (max_cnx < GRAB_MIN_SOCK) max_cnx = GRAB_MIN_SOCK;
  }
  
  p_addr = arg_get_value(hostinfos, "IP");
  if( p_addr == NULL )
    return -1;
  if (banner_grab(p_addr, port_range, timeout, max_cnx, globals, desc, hostinfos) < 0)
    return -1;
  comm_send_status(globals, arg_get_value(hostinfos, "NAME"),"portscan", 65535, 65535);
  plug_set_key(desc, "Host/scanned", ARG_INT, (void*)1);
  return 0;
}



Generated by  Doxygen 1.6.0   Back to index