/* Nessuslib -- the Nessus Library
 * Copyright (C) 1998 - 2002 Renaud Deraison
 * SSL Support Copyright (C) 2001 Michel Arboi
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Network Functions
 */ 

#define EXPORTING
#include <includes.h>
#include <stdarg.h>
#include "libnessus.h"
#include "network.h"
#include "resolve.h"
#include "ids_send.h"

#include <setjmp.h>

#ifdef HAVE_SSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#endif

#define TIMEOUT 20

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif







extern int plug_get_port_transport(struct arglist*, int);
extern void plug_set_port_transport(struct arglist*, int, int);


/*----------------------------------------------------------------*
 * Low-level connection management                                *
 *----------------------------------------------------------------*/
 
/* Nessus "FILE" structure */
typedef struct {
 int fd;		/* socket number, or whatever */
 int transport;	/* "transport" layer code when stream is encapsultated. 
		 * Negative transport signals a free descriptor */
 int timeout;	  /* timeout, in seconds
		   * special values: -2 for default */
 int options;			/* Misc options - see libnessus.h */
  
 int port;			 
#ifdef HAVE_SSL
  SSL_CTX* 	ssl_ctx;	/* SSL context 	*/
  SSL_METHOD* 	ssl_mt;		/* SSL method   */
  SSL* 		ssl;		/* SSL handler  */
#endif
 pid_t		pid;		/* Owner - for debugging only */
} nessus_connection;

/* 
 * The role of this offset is:
 * 1. To detect bugs when the program tries to write to a bad fd
 * 2. See if a fd is a real socket or a "nessus descriptor". This is a
 * quick & dirty hack and should be changed!!!
 */
#define NESSUS_FD_MAX 1024
#define NESSUS_FD_OFF 1000000

static nessus_connection connections[NESSUS_FD_MAX];


/*
 * NESSUS_STREAM(x) is TRUE if <x> is a Nessus-ified fd
 */
#define NESSUS_STREAM(x) (((x - NESSUS_FD_OFF) < NESSUS_FD_MAX) && ((x - NESSUS_FD_OFF) >=0))

/*
 * Same as perror(), but prefixes the data by our pid
 */
static int 
nessus_perror(error)
 const char* error;
{
  fprintf(stderr, "[%d] %s : %s\n", getpid(), error, strerror(errno));
  return 0;
}

/*
 * Returns the amount of data waiting to be read in the socket
 * or -1 in case of an error (errno will be set)
 */
#if 0
static int data_left(soc)
 int soc;
{
 int len = 0;
 int ret = ioctl(soc, FIONREAD, &len);
 if (ret >= 0) {
  if(len <= 0)
   return 0;
  else 
   return len;
 }
 else {
  nessus_perror("ioctl(FIONREAD)");
  return -1;
 }
} /* data_left */
#endif

/*
 * Returns a free file descriptor
 */
static int
get_connection_fd()
{
 int i;
 
 for ( i = 0; i < NESSUS_FD_MAX ; i++)
 {
  if(connections[i].transport <= 0) /* Not used */
  {
   bzero(&(connections[i]),  sizeof(connections[i]));
   connections[i].pid = getpid();
   return i + NESSUS_FD_OFF;
  }
 }
 fprintf(stderr, "%s:%d : Out of Nessus file descriptors\n", __FILE__, __LINE__);
 errno = EMFILE;
 return -1;
}

static int
release_connection_fd(fd)
 int fd;
{
 nessus_connection *p;
 
 if(!NESSUS_STREAM(fd))
    {
     errno = EINVAL;
     return -1;
    }
    
    
 p = &(connections[fd - NESSUS_FD_OFF]);

#ifdef HAVE_SSL
 if (p->ssl != NULL)
  SSL_free(p->ssl);
 if (p->ssl_ctx != NULL)
  SSL_CTX_free(p->ssl_ctx);
#endif

/* 
 * So far, fd is always a socket. If this is changed in the future, this
 * code shall be fixed
 */
if (p->fd >= 0)
 {
  if (shutdown(p->fd, 2) < 0)
    {
#if DEBUG_SSL > 1
    /*
     * It's not uncommon to see that one fail, since a lot of
     * services close the connection before we ask them to
     * (ie: http), so we don't show this error by default
     */
    nessus_perror("release_connection_fd: shutdown()");
#endif    
    }
  if (close(p->fd)  < 0)
    nessus_perror("release_connection_fd: close()");
 }
 bzero(p, sizeof(*p));
 p->transport = -1; 
 return 0;
}

/* ******** Compatibility function ******** */

ExtFunc int
nessus_register_connection(s, ssl)
     int	s;
#ifdef HAVE_SSL
     SSL	*ssl;
#else
     void	*ssl;
#endif
{
  int			fd;
  nessus_connection	*p;

  if((fd = get_connection_fd()) < 0)
    return -1;
  p = connections + (fd - NESSUS_FD_OFF);
#ifdef HAVE_SSL 
  p->ssl_ctx = NULL;
  p->ssl_mt = NULL;		/* shall be freed elsewhere */
  p->ssl = ssl;			/* will be freed on close */
#endif  
  p->timeout = TIMEOUT;		/* default value */
  p->port = 0;			/* just used for debug */
  p->fd = s;
  p->transport = (ssl != NULL) ? NESSUS_ENCAPS_SSLv23 : NESSUS_ENCAPS_IP;
  return fd;
}

ExtFunc int
nessus_deregister_connection(fd)
 int fd;
{
 nessus_connection * p;
 if(!NESSUS_STREAM(fd))
 {
  errno = EINVAL;
  return -1;
 }
 
 p = connections +  (fd - NESSUS_FD_OFF);
 bzero(p, sizeof(*p));
 p->transport = -1; 
 return 0;
}

/*----------------------------------------------------------------*
 * High-level connection management                               *
 *----------------------------------------------------------------*/


static int __port_closed;
static volatile int __timeout;
static void
connect_alarm_handler()
{
 __timeout = 1;
}


static void
sig_alrm(int timeout)
{
#ifdef HAVE_SIGACTION
  struct sigaction sa;
  sa.sa_handler = connect_alarm_handler;
  sa.sa_flags = 0;
  sigemptyset(&sa.sa_mask);
  sigaction(SIGALRM,&sa,(struct sigaction *) 0);
#else
  signal(SIGALRM, connect_alarm_handler);
#endif
  __timeout = 0;
  alarm(timeout);
}

/*
 * Initialize the SSL library (error strings and algorithms) and try
 * to set the pseudo random generator to something less silly than the
 * default value: 1 according to SVID 3, BSD 4.3, ISO 9899 :-(
 */

#ifdef HAVE_SSL
/* Adapted from stunnel source code */
ExtFunc
void sslerror2(txt, err)
     char	*txt;
     int	err;
{
  char string[120];

  ERR_error_string(err, string);
  fprintf(stderr, "%s[%d]: %s\n", txt, getpid(), string);
}

void
sslerror(txt)
     char	*txt;
{
  sslerror2(txt, ERR_get_error());
}
#endif

ExtFunc int
nessus_SSL_init(path)
     char	*path;		/* entropy pool file name */
{
#ifdef HAVE_SSL
  SSL_library_init();
  SSL_load_error_strings();

#ifdef HAVE_RAND_STATUS
  if (RAND_status() == 1)
    {
    /* The random generator is happy with its current entropy pool */
    return 0;
   }
#endif


  /*
   * Init the random generator
   *
   * OpenSSL provides nice functions for this job.
   * OpenSSL also ensures that each thread uses a different seed.
   * So this function should be called *before* forking.
   * Cf. http://www.openssl.org/docs/crypto/RAND_add.html#
   *
   * On systems that have /dev/urandom, SSL uses it transparently to seed 
   * its PRNG
   */

 
#if 0
  RAND_screen();	/* Only available under MSWin */
#endif

#ifdef EGD_PATH
  /*
   * We have the entropy gathering daemon.
   * However, OpenSSL automatically query it if it is not in some odd place
   */
  if(RAND_egd(EGD_PATH) > 0)
	  return 0;
#endif

   if (path != NULL)
    {
    (void) RAND_load_file(path, -1);
    RAND_write_file(path);
    }
   else
   {
    /*
     * Try with the default path
     */
    char path[1024];
    if(RAND_file_name(path, sizeof(path) - 1) == 0)
	    return -1;
    path[sizeof(path) - 1] = '\0';
    if(RAND_load_file(path, -1) < 0)
	    return -1;
    RAND_write_file(path);	
    return 0;
   } 
#endif
   return -1;
}

#ifdef HAVE_SSL
# if 0
ExtFunc void
nessus_print_SSL_certificate(cert)
     X509* cert;    
{
 BIO * b;
 BUF_MEM * bptr;
 char * ret = NULL;
 int	i;

 if(cert == NULL)
   return;

 b = BIO_new(BIO_s_mem());
 if(X509_print(b, cert) > 0)
   {
     BIO_get_mem_ptr(b, &bptr);
     printf("* Peer certificate *\n");
     for(i = 0; i < bptr->length; i ++)
       putchar(bptr->data[i]);
     printf("\n********************\n");
   }
 BIO_free(b);
}

ExtFunc void
nessus_print_peer_SSL_certificate(ssl)
     SSL* ssl;
{
  X509 * cert = SSL_get_peer_certificate(ssl);
  nessus_print_SSL_certificate(cert);
}
# endif
#endif


static int
nessus_SSL_password_cb(buf, size, rwflag, userdata)
     char *buf;
     int size;
     int rwflag;
     void *userdata;
{
  if (userdata != NULL)
    {
      buf[size - 1] = '\0';
      strncpy(buf, userdata, size - 1);
      return strlen(buf);
    }
  else
    {
#if DEBUG_SSL > 1
      fprintf(stderr, "nessus_SSL_password_cb: returning empty password\n");
#endif
      *buf = '\0';
      return 0;
    }
}

ExtFunc int
nessus_get_socket_from_connection(fd)
     int	fd;
{
  nessus_connection	*fp;

  if (!NESSUS_STREAM(fd))
    {
      fprintf(stderr,
	      "[%d] nessus_get_socket_from_connection: bad fd <%d>\n", getpid(), fd);
      fflush(stderr);
      return fd;
    }
  fp = connections + (fd - NESSUS_FD_OFF);
  if(fp->transport <= 0)
    {
      fprintf(stderr, "nessus_get_socket_from_connection: fd <%d> is closed\n", fd);
      return -1;
    }
  return fp->fd;
}


#ifdef HAVE_SSL
ExtFunc void
nessus_install_passwd_cb(ssl_ctx, pass)
     SSL_CTX	*ssl_ctx;
     char	*pass;
{
  SSL_CTX_set_default_passwd_cb(ssl_ctx, nessus_SSL_password_cb);
  SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, pass);
}

static int
open_SSL_connection(fp, timeout, cert, key, passwd, cert_names)
     nessus_connection	*fp;
     int		timeout;
     char		*cert, *key, *passwd; 
     STACK_OF(X509_NAME)	*cert_names;
{
  int		ret;


  nessus_SSL_init(NULL);

  switch (fp->transport)
    {
    case NESSUS_ENCAPS_SSLv2:
      fp->ssl_mt = SSLv2_client_method();
      break;
    case NESSUS_ENCAPS_SSLv3:
      fp->ssl_mt = SSLv3_client_method();
      break;
    case NESSUS_ENCAPS_TLSv1:
      fp->ssl_mt = TLSv1_client_method();
      break;
    case NESSUS_ENCAPS_SSLv23:	/* Compatibility mode */
      fp->ssl_mt = SSLv23_client_method();
      break;
      
    default:
#if DEBUG_SSL > 0
      fprintf(stderr, "*Bug* at %s:%d. Unknown transport %d\n",
	      __FILE__, __LINE__, fp->transport);
#endif
      fp->ssl_mt = SSLv23_client_method();
      break;
    }

  if((fp->ssl_ctx = SSL_CTX_new(fp->ssl_mt)) == NULL)
    return -1;

  if (SSL_CTX_set_options(fp->ssl_ctx, SSL_OP_ALL) < 0)
    sslerror("SSL_CTX_set_options(SSL_OP_ALL)");

  if ((fp->ssl = SSL_new(fp->ssl_ctx)) == NULL)
    return -1;

  /* Client certificates should not be used if we are in SSLv2 */
  if (fp->transport != NESSUS_ENCAPS_SSLv2) {
  SSL_CTX_set_default_passwd_cb(fp->ssl_ctx, nessus_SSL_password_cb);
  SSL_CTX_set_default_passwd_cb_userdata(fp->ssl_ctx, passwd);

  if (cert != NULL)
    SSL_use_certificate_file(fp->ssl, cert, SSL_FILETYPE_PEM);
  if (key != NULL)
#if 1
    SSL_use_PrivateKey_file(fp->ssl, key, SSL_FILETYPE_PEM);
#else
    SSL_use_RSAPrivateKey_file(fp->ssl, key, SSL_FILETYPE_PEM);
#endif

    if (cert_names != NULL)
      SSL_CTX_set_client_CA_list(fp->ssl_ctx, cert_names);
    }

  if(! (ret = SSL_set_fd(fp->ssl, fp->fd)))
    {
#if DEBUG_SSL > 0
      sslerror("SSL_set_fd");
#endif    
      return -1;
    }
    
  sig_alrm(timeout);
  ret = SSL_connect(fp->ssl);
  alarm(0);
  if(__timeout)
    {
#if DEBUG_SSL > 1
      fprintf(stderr,
	      "open_stream_connection: SSL_connection timed out (%d s)\n", 
	      timeout);
#endif	
      return -1;
    }
     
#if DEBUG_SSL > 0
  if (ret <= 0)
    sslerror("SSL_set_fd");
#endif	
  return ret;
}
#endif


static void
set_ids_evasion_mode(args, fp)
     struct arglist	*args;
     nessus_connection	*fp;
{
 char		*ids_evasion_split = plug_get_key(args, "NIDS/TCP/split");
 char 		*ids_evasion_inject = plug_get_key(args, "NIDS/TCP/inject");
 char 		*ids_evasion_short_ttl = plug_get_key(args,"NIDS/TCP/short_ttl");
 char		*ids_evasion_fake_rst = plug_get_key(args, "NIDS/TCP/fake_rst");
 int option = 0;
 
 
 /*
  * These first three options are mutually exclusive
  */
 if(ids_evasion_split != NULL && strcmp(ids_evasion_split, "yes") == 0)
 	option = NESSUS_CNX_IDS_EVASION_SPLIT;

 if(ids_evasion_inject != NULL && strcmp(ids_evasion_inject, "yes") == 0)
 	option = NESSUS_CNX_IDS_EVASION_INJECT;
 
 if(ids_evasion_short_ttl != NULL && strcmp(ids_evasion_short_ttl, "yes") == 0)
 	option = NESSUS_CNX_IDS_EVASION_SHORT_TTL;


 /*
  * This is not exclusive with the above
  */
 if(ids_evasion_fake_rst != NULL && strcmp(ids_evasion_fake_rst, "yes") == 0)
 	option |= NESSUS_CNX_IDS_EVASION_FAKE_RST;

 if(option)
   {
#ifdef SO_SNDLOWAT
     int		n = 1;
     (void) setsockopt(fp->fd, SOL_SOCKET, SO_SNDLOWAT, (void*)&n, sizeof(n));
#endif
     fp->options |= option;
   }
}

ExtFunc int
open_stream_connection(args, port, transport, timeout)
 struct arglist * args;
 unsigned int port;
 int transport, timeout;
{
 int			fd;
 nessus_connection * fp;
#ifdef HAVE_SSL
 char * cert   = NULL;
 char * key    = NULL;
 char * passwd = NULL;
 char * cafile = NULL;
 STACK_OF(X509_NAME)	*cert_names = NULL;
#endif

#if DEBUG_SSL > 2
 fprintf(stderr, "open_stream_connection: TCP:%d transport:%d timeout:%d\n",
       port, transport, timeout);
#endif

 if(timeout == -2)
  timeout = TIMEOUT;
  
 switch(transport)
 {
  case NESSUS_ENCAPS_IP:
#ifdef HAVE_SSL
  case NESSUS_ENCAPS_SSLv2:
  case NESSUS_ENCAPS_SSLv23:
  case NESSUS_ENCAPS_SSLv3:
  case NESSUS_ENCAPS_TLSv1:
#endif 
   break;
  default:
   fprintf(stderr, "open_stream_connection(): unsupported transport layer %d\n",
   	transport);
   errno = EINVAL;
   return -1;
 }
 
 if((fd = get_connection_fd()) < 0)
  return -1;
 
 fp = &(connections[fd - NESSUS_FD_OFF]);
 
 
 fp->transport = transport;
 fp->timeout   = timeout;
 fp->port      = port;
 set_ids_evasion_mode(args, fp);

 if(fp->options & NESSUS_CNX_IDS_EVASION_FAKE_RST)
   fp->fd = ids_open_sock_tcp(args, port, fp->options, timeout);
 else
   fp->fd = open_sock_tcp(args, port, timeout);
	
  if(fp->fd < 0)
	  goto failed;

 switch(transport)
 {
  case NESSUS_ENCAPS_IP:
    break;
#ifdef HAVE_SSL
  case NESSUS_ENCAPS_SSLv23:
  case NESSUS_ENCAPS_SSLv3:
  case NESSUS_ENCAPS_TLSv1:
    cert   = plug_get_key(args, "SSL/cert");
    key    = plug_get_key(args, "SSL/key");
    passwd = plug_get_key(args, "SSL/password");

    cafile = plug_get_key(args, "SSL/CA");

    if ((cafile != NULL) && strlen(cafile))
      {
	cert_names = SSL_load_client_CA_file(cafile);
	if (cert_names == NULL)
	  {
	    char	msg[512];
	    snprintf(msg, sizeof(msg), "SSL_load_client_CA_file(%s)", cafile);
	    sslerror(msg);
	  }
     }
   
  case NESSUS_ENCAPS_SSLv2:
    /* We do not need a client certificate in this case */

    if (open_SSL_connection(fp, timeout, cert, key, passwd, cert_names) <= 0)
    goto failed;
  break;
#endif
 }
 
 return fd;

failed:
 release_connection_fd(fd);
 return -1;
}


ExtFunc int
open_stream_connection_unknown_encaps(args, port, timeout, p)
 struct arglist * args;
 unsigned int  port;
 int timeout, * p;
{
 int fd;
 int i;
#ifdef HAVE_SSL
 static int encaps[] = {
   NESSUS_ENCAPS_TLSv1,
   NESSUS_ENCAPS_SSLv3,
   NESSUS_ENCAPS_SSLv2 };
#endif
 
#if DEBUG_SSL > 2
 fprintf(stderr, "open_stream_connection_unknown_encaps: TCP:%d; %d\n",
       port,timeout);
#endif

#ifdef HAVE_SSL
 for (i = 0; i < sizeof(encaps) / sizeof(*encaps); i ++)
   if ((fd = open_stream_connection(args, port, encaps[i], timeout)) >= 0)
     {
       *p = encaps[i];
       return fd;
     }

 /* 
  * No need to try to connect to the remote port twice
  */
 if(__port_closed)
  return -1;
#endif 
 if ((fd = open_stream_connection(args, port, NESSUS_ENCAPS_IP, timeout)) >= 0)
 {
  *p = NESSUS_ENCAPS_IP;
  return fd;
 }
 
 return -1;
}


ExtFunc int
open_stream_auto_encaps(args, port, timeout)
 struct arglist * args;
 unsigned int     port;
 int              timeout;
{
 int trp = plug_get_port_transport(args, port);
 int fd;
 
 if(trp == 0)
 {
  if ((fd = open_stream_connection_unknown_encaps(args, port, timeout, &trp)) < 0)
   return -1;
  plug_set_port_transport(args, port, trp);
  return fd;
 }
 else
 {
  fd = open_stream_connection(args, port, trp, timeout);
  return fd;
 }
 /*NOTREACHED*/
}


#ifdef HAVE_SSL
ExtFunc SSL*
stream_get_ssl(fd)
     int	fd;
{
 nessus_connection * fp;
 if(!NESSUS_STREAM(fd))
    {
     errno = EINVAL;
     return NULL;
    }
  fp = &(connections[fd - NESSUS_FD_OFF]);
  if (fp->transport <= 0)
    return NULL;
  else
    return fp->ssl;
}
#endif

ExtFunc int
stream_set_timeout(fd, timeout)
 int fd;
 int timeout;
{
 int old;
 nessus_connection * fp;
 if(!NESSUS_STREAM(fd))
    {
     errno = EINVAL;
     return 0;
    }
  fp = &(connections[fd - NESSUS_FD_OFF]);
  old = fp->timeout;
  fp->timeout = timeout;
  return old;
}

ExtFunc int
stream_set_options(fd, reset_opt, set_opt)
     int	fd, reset_opt, set_opt;
{
 nessus_connection * fp;
 if(!NESSUS_STREAM(fd))
    {
     errno = EINVAL;
     return -1;
    }
  fp = &(connections[fd - NESSUS_FD_OFF]);
  fp->options &= ~reset_opt;
  fp->options |= set_opt;
  return 0;
}


ExtFunc int 
read_stream_connection_min(fd, buf0, min_len, max_len)
 int fd;
 void* buf0;
 int min_len, max_len;
{
  int			ret, errcode, realfd, trp, t;
  int			total = 0, flag = 0, timeout = TIMEOUT, waitall = 0;
  unsigned char		* buf = (unsigned char*)buf0;
  nessus_connection	*fp = NULL;
  fd_set		fds;
  struct timeval	tv;
 
#if 0
  fprintf(stderr, "read_stream_connection(%d, 0x%x, %d, %d)\n",
	  fd, buf, min_len, max_len);
#endif

  if (NESSUS_STREAM(fd))
    {
      fp = &(connections[fd - NESSUS_FD_OFF]);
      trp = fp->transport;
      realfd = fp->fd;
      if (fp->timeout != -2)
	timeout = fp->timeout;
    }
  else
    {
#if 0
      fprintf(stderr, "read_stream_connection[%d] : supposedly bad fd %d\n",
	      getpid(), fd);
#endif
      trp = NESSUS_ENCAPS_IP;
      if(fd < 0 || fd > 1024)
	      	{
			errno = EBADF;
			return -1;
		}
      realfd = fd;
    }

#ifndef INCR_TIMEOUT
# define INCR_TIMEOUT	1
#endif

#ifdef MSG_WAITALL
  if (min_len == max_len || timeout <= 0)
    waitall = MSG_WAITALL;
#endif

  if(trp == NESSUS_ENCAPS_IP)
    {
      for (t = 0; total < max_len && (timeout <= 0 || t < timeout); )
	{
	  tv.tv_sec = INCR_TIMEOUT; /* Not timeout! */
	  tv.tv_usec = 0;
	  FD_ZERO(&fds);
	  FD_SET(realfd, &fds);
	  if(select(realfd + 1, &fds, NULL, NULL,
		    timeout > 0 ? &tv : NULL) <= 0)
	    {
	      t += INCR_TIMEOUT;
	      if (min_len <= 0)
		{
		  /* Try to be smart */
		  if (total > 0 && flag) 
		    return total;
		  else
		    flag ++;
		}
	      else
		if (total > min_len)
		  return total;
	    }
	  else
	    {
	      errno = 0;
	      ret = recv(realfd, buf + total, max_len - total, waitall);
	      if (ret < 0)
		if (errno != EINTR)
		  return total;
		else
		  ret = 0;
	      else if (ret == 0) /* EOF */
		return total;
	      /*ret > 0*/
	      total += ret; 
	      flag = 0;
	    }
	}
      return total;
    }

  FD_ZERO(&fds);
  FD_SET(realfd, &fds);

  switch(trp)
    {
      /* NESSUS_ENCAPS_IP was treated before with the non-Nessus fd */
#ifdef HAVE_SSL
    case NESSUS_ENCAPS_SSLv2:
    case NESSUS_ENCAPS_SSLv23:
    case NESSUS_ENCAPS_SSLv3:
    case NESSUS_ENCAPS_TLSv1:
# if DEBUG_SSL > 0
      if (getpid() != fp->pid)
	{
	  fprintf(stderr, "PID %d tries to use a SSL connection established by PID %d\n", getpid(), fp->pid);
	  errno = EINVAL;
	  return -1;
	}
# endif

      for (t = 0; timeout <= 0 || t < timeout; t += INCR_TIMEOUT)
	{
	  if (timeout > 0)
	    sig_alrm(INCR_TIMEOUT); /* Not timeout! */
	  ret = SSL_read(fp->ssl, buf + total, max_len - total);
	  alarm(0);
	  if (ret > 0)
	    total += ret;
	  if (total >= max_len)
	    return total;
	  if (__timeout)
	   {
	    if (min_len <= 0)
	      {
		/* Be smart */
		if (total > 0 && flag)
		  return total;
		else
		  flag ++;
	      }
	    else
	      if (total > min_len)
		return total;
	      else			/* No timeout */
		if(ret <= 0)
		  {
		    errcode = SSL_get_error(fp->ssl, ret);
#if DEBUG_SSL > 0
		    sslerror("SSL_read");
#endif
		    if(ret == 0 || errcode != SSL_ERROR_WANT_READ)
		      break;
		  }
	    }
	}
      break;
#endif
    default :
      fprintf(stderr, "Severe bug! Unhandled transport layer %d (fd=%d)\n",
	      fp->transport, fd);
      errno = EINVAL;
      return -1;
    }
    
  return total;
}

ExtFunc int 
read_stream_connection(fd, buf0, len)
 int fd;
 void* buf0;
 int len;
{
 return read_stream_connection_min(fd, buf0, -1, len);
}



ExtFunc int
write_stream_connection(fd, buf0, n) 
 int fd;
 void * buf0;
 int n;
{
 int ret, count, errcode;
 unsigned char* buf = (unsigned char*)buf0;
 nessus_connection * fp;

 if(!NESSUS_STREAM(fd))
   {
#if DEBUG_SSL > 0
     fprintf(stderr, "write_stream_connection: fd <%d> invalid\n", fd);
# if 0
     abort();
# endif
#endif
     errno = EINVAL;
     return -1;
    }

 fp = &(connections[fd - NESSUS_FD_OFF]);
 
#if DEBUG_SSL > 8
 fprintf(stderr, "> write_stream_connection(%d, 0x%x, %d) \tE=%d 0=0x%x\n",
	 fd, buf, n, fp->transport, fp->options);
#endif

 switch(fp->transport)
 {
  case NESSUS_ENCAPS_IP:
   for(count = 0; count < n;)
   {
     if ((fp->options & NESSUS_CNX_IDS_EVASION_SEND_MASK) != 0)
     {
      if(fp->options & NESSUS_CNX_IDS_EVASION_SPLIT)
       /* IDS evasion */
       ret = send(fp->fd, buf + count, 1, 0);
     else 
     	ret = ids_send(fp->fd, buf + count, n - count, fp->options);
     }
     else
       ret = send(fp->fd, buf + count, n - count, 0);

      
          
    if(ret <= 0)
      break;
     
     count += ret;
    }
    break;
#ifdef HAVE_SSL
  case NESSUS_ENCAPS_SSLv2:
  case NESSUS_ENCAPS_SSLv23:
  case NESSUS_ENCAPS_SSLv3:
  case NESSUS_ENCAPS_TLSv1:
    for(count = 0; count < n;)
    { 
     sig_alrm(TIMEOUT);
     ret = SSL_write(fp->ssl, buf + count, n - count);
     alarm(0);
     if(__timeout){
#if DEBUG_SSL > 2
      fprintf(stderr, "write_stream_connection: time out\n");
#endif    
      break;
     }
     if(ret <= 0)
     { 
      errcode = SSL_get_error(fp->ssl, ret);
#if DEBUG_SSL > 0
      sslerror("SSL_write");
#endif      
      if (ret == 0
    		|| errcode != SSL_ERROR_WANT_WRITE  /* ret = -1 */ )
  	break;
     }
     else 
       count += ret;
     }
    break;
#endif
   default:
     fprintf(stderr, "Severe bug! Unhandled transport layer %d (fd=%d)\n",
	     fp->transport, fd);
     errno =EINVAL;
     return -1;
  }
  
  
  if(count == 0 && n > 0)
   return -1;
  else 
   return count;
}


ExtFunc int
nsend (fd, data, length, i_opt)
 int fd;
 void * data;
 int length, i_opt;
{
 if(NESSUS_STREAM(fd))
 {
  if(connections[fd - NESSUS_FD_OFF].fd < 0)
   fprintf(stderr, "Nessus file descriptor %d closed ?!\n", fd);
  else 
   return write_stream_connection(fd, data, length);
 }
#if DEBUG_SSL > 1
 else
   fprintf(stderr, "nsend[%d]: fd=%d\n", getpid(), fd);
#endif
#if 1
 {
   int	i;
   for (i = 0; i < NESSUS_FD_MAX; i ++)
     if (connections[i].fd == fd && connections[i].transport > 0)
       {
	 /* Fixing a severe bug! */
	 fprintf(stderr, "nsend: fd=%d used by Nessus FD %d\n",
		 fd, i + NESSUS_FD_OFF);
	 return write_stream_connection(i + NESSUS_FD_OFF, data, length);
       }
 }
#endif
 /* Trying OS's send() */
 {
   int	n;
sendagain :   
   n = send(fd, data, length, i_opt);
   if (n < 0)
     {
      if ( errno == EINTR ) goto sendagain;
      if ( errno != 0 ) fprintf(stderr, "[%d] nsend():send %s\n", getpid(), strerror(errno));
     }
   return n;
 }
}
 
ExtFunc int
nrecv (fd, data, length, i_opt)
 int fd;
 void * data;
 int length, i_opt;
{
#if DEBUG_SSL > 8
   fprintf(stderr, "nrecv: fd=%d len=%d\n", fd, length);
#endif
 if(NESSUS_STREAM(fd))
 {
  if(connections[fd - NESSUS_FD_OFF].fd < 0)
   fprintf(stderr, "Nessus file descriptor %d closed ?!\n", fd);
  else 
    return read_stream_connection(fd, data, length);
 }
 /* Trying OS's recv() */
 return recv(fd, data, length, i_opt);
}
 
ExtFunc int
close_stream_connection(fd)
 int fd;
{
#if DEBUG_SSL > 2
 nessus_connection * fp;
 if(!NESSUS_STREAM(fd))
    {
     errno = EINVAL;
     return -1;
    }
  fp = &(connections[fd - NESSUS_FD_OFF]);
  fprintf(stderr, "close_stream_connection TCP:%d\n", fp->port);
#endif

  if(!NESSUS_STREAM(fd))	/* Will never happen if debug is on! */
   {
    if ( fd < 0 || fd > 1024 )
    {
	   errno = EINVAL;
	   return -1;
    }
   shutdown(fd, 2);
   return close(fd);
   }
  else
   return release_connection_fd(fd);
}


ExtFunc int
get_encaps(fd)
 int fd;
{
 if(!NESSUS_STREAM(fd))
 {
   fprintf(stderr, "get_encaps() : bad argument\n");
   return -1;
 }
 return connections[fd - NESSUS_FD_OFF].transport;
}


 
ExtFunc const char *
get_encaps_name(code)
 int code;
{
 static char str[100];
 switch(code)
 {
  case NESSUS_ENCAPS_IP:
   return "IP";
  case NESSUS_ENCAPS_SSLv2:
    return "SSLv2";
  case NESSUS_ENCAPS_SSLv23:
    return "SSLv23";
  case NESSUS_ENCAPS_SSLv3:
    return "SSLv3";
  case NESSUS_ENCAPS_TLSv1:
    return "TLSv1";
  default:
   snprintf(str, sizeof(str), "[unknown transport layer - code %d (0x%x)]", code, code);
   return str;
 }
}

ExtFunc  const char *
get_encaps_through(code)
 int code;
{
 static char str[100];
 switch(code)
 {
  case NESSUS_ENCAPS_IP:
   return "";
  case NESSUS_ENCAPS_SSLv2:
  case NESSUS_ENCAPS_SSLv23:
  case NESSUS_ENCAPS_SSLv3:
  case NESSUS_ENCAPS_TLSv1:
    return " through SSL";
  default:
    snprintf(str, sizeof(str), " through unknown transport layer - code %d (0x%x)", code, code);
    return str;
 }
}

static void 
sighand_alarm()
{
 printf("could not open the connection : timeout\n");
}

ExtFunc 
int open_sock_opt_hn(hostname, port, type, protocol, timeout)
 const char * hostname; 
 unsigned int port; 
 int type;
 int protocol;
 int timeout;
{
 struct sockaddr_in addr;
 int soc;
  
  __port_closed = 0;
  bzero((void*)&addr, sizeof(addr));
  addr.sin_family=AF_INET;
  addr.sin_port=htons((unsigned short)port);
  addr.sin_addr = nn_resolve(hostname);
  if (addr.sin_addr.s_addr == INADDR_NONE || addr.sin_addr.s_addr == 0)
    return(-1);
    
  if ((soc = socket(AF_INET, type, protocol)) < 0)
    return -1;   

  if (timeout == -2)
    timeout = TIMEOUT;
  if (timeout > 0)
    sig_alrm(TIMEOUT);
  if (connect(soc, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
      __port_closed = 1;
      closesocket(soc);
      alarm(0);
      return  -1;
    }
   
  if (timeout > 0)
    {
      signal(SIGALRM, SIG_IGN);
      alarm(0); 
    }
  return soc;
}


ExtFunc
int open_sock_tcp_hn(hostname, port)
 const char * hostname;
 unsigned int port;
{
  return open_sock_opt_hn(hostname, port, SOCK_STREAM, IPPROTO_TCP, TIMEOUT);
}


ExtFunc
int open_sock_tcp(args, port, timeout)
 struct arglist * args; 
 unsigned int port;
 int timeout;
{
  return open_sock_option(args, port, SOCK_STREAM,IPPROTO_TCP, timeout);
}


ExtFunc
int open_sock_udp(args, port)
 struct arglist * args;
 unsigned int port;
{
  return open_sock_option(args, port, SOCK_DGRAM, IPPROTO_UDP, 0);
}


ExtFunc 
struct in_addr _socket_get_next_source_addr(struct in_addr * addr)
{
  static struct in_addr * src_addrs = NULL;
  static int current_src_addr = 0;
  static pid_t current_src_addr_pid = 0;
  static int num_addrs = 0;
  struct in_addr ret;
  pid_t mypid;
  
  if( current_src_addr < 0 )
  {
   ret.s_addr = INADDR_ANY;
   return ret;
  }
  
  
  
  if ( src_addrs == NULL && current_src_addr == 0 )
  {
    src_addrs = addr;
    if( src_addrs == NULL ) 
    {
     ret.s_addr = INADDR_ANY;
     current_src_addr = -1;
     return ret;
    }    	
   
   num_addrs = -1;
   while(src_addrs[++num_addrs].s_addr != 0 ) ;
  }
  
  
  mypid = getpid();
  if ( current_src_addr_pid != mypid )
   {
    current_src_addr_pid = mypid;
    current_src_addr = lrand48() % ( num_addrs ) ;
    if ( src_addrs[current_src_addr].s_addr == 0 ) current_src_addr = 0;
   }
  
  return src_addrs[current_src_addr];
}

ExtFunc
struct in_addr socket_get_next_source_addr()
{
 return _socket_get_next_source_addr(NULL);
}
ExtFunc 
int set_socket_source_addr(int soc, int port)
{ 
  struct sockaddr_in bnd;
  int opt = 1;  
  
  struct in_addr src = _socket_get_next_source_addr(NULL);
  
  if( src.s_addr == INADDR_ANY && port == 0 ) /* No need to bind() */
  	return 0;
   
  setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(int));
  bzero(&bnd, sizeof(bnd));
  
   
   
   bnd.sin_port = htons(port);
   bnd.sin_addr = src;
   bnd.sin_family = AF_INET;
  
  if( bind(soc, (struct sockaddr*)&bnd, sizeof(bnd)) < 0 )
  { 
   return -1;
  }
  
  return 0;
}

ExtFunc  void socket_source_init(struct in_addr * addr)
{
 (void) _socket_get_next_source_addr(addr);
}


ExtFunc
int open_sock_option(args, port, type, protocol, timeout)
 struct arglist * args;
 unsigned int port;
 int type;
 int protocol;
 int timeout;
{
  struct sockaddr_in addr;
  struct in_addr * t;
  int soc;

  
  

  
  
  if(host_get_port_state(args, port)<=0)return(-1);
  bzero((void*)&addr, sizeof(addr));
  addr.sin_family=AF_INET;
  addr.sin_port=htons((unsigned short)port);
  t = plug_get_host_ip(args);
  if(!t)
  {
   fprintf(stderr, "ERROR ! NO ADDRESS ASSOCIATED WITH NAME\n");
   arg_dump(args, 0);
   return(-1);
  }
  addr.sin_addr = *t;
  if (addr.sin_addr.s_addr == INADDR_NONE)
    return(-1);
    
  if ((soc = socket(AF_INET, type, protocol)) < 0)
    {
      nessus_perror("socket");
      return -1;  
    }
    
   set_socket_source_addr(soc, 0);

  if (timeout == -2)
    timeout = TIMEOUT;
  if (timeout > 0)
    sig_alrm(timeout);
  if (connect(soc, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
      /* nessus_perror("connect"); */
      closesocket(soc);
      alarm(0);
      return  -1;
    }
  alarm(0); 
  return soc;
}



/* This function reads a text from the socket stream into the
   argument buffer, always appending a '\0' byte.  The return
   value is the number of bytes read, without the trailing '\0'.
 */


ExtFunc
int recv_line(soc, buf, bufsiz)
 int soc;
 char * buf;
 size_t bufsiz;
{
  int n, ret = 0;
  
  /*
   * Dirty SSL hack
   */
  if(NESSUS_STREAM(soc))
  {
   buf[0] = '\0';
   
   do
   {
    n = read_stream_connection_min (soc, buf + ret, 1, 1);
    switch (n)
    {
     case -1 :
       if(ret == 0)
        return -1;
       else 
        return ret;
       break;
     
     case 0:
       return ret;
       break;
      
      default :
      	ret ++;
    }
   }
   while (buf [ret-1] != '\0' && buf [ret-1] != '\n' && ret < bufsiz) ;
   
   if(ret > 0 )
   {
   if (buf[ret - 1] != '\0')
     buf[ ret ] = '\0';
   }
   return ret;  
  }
  
  
  else
  {
   fd_set rd;
   struct timeval tv;
   
   do
   {
      int e;
 again:
      FD_ZERO(&rd);
      FD_SET(soc, &rd);
      tv.tv_sec = 5;
      tv.tv_usec = 0;
      e = select(soc+1, &rd, NULL, NULL, &tv); 
      if( e < 0 && errno == EINTR) goto again;
      if( e > 0 )
      {
       n = recv(soc, buf + ret, 1, 0);
       switch(n)
       {
        case -1 :
	 if ( errno == EINTR ) continue;
	 if(ret == 0)
	  return -1;
	 else
	  return ret;
	 break;  
       case 0 :
         return ret;
       	 break;
       default:
         ret ++;	
       }
      } 
      else break;
      tv.tv_sec = 1;
      tv.tv_usec = 0;
    } while(buf[ret -1 ] != '\0' && buf[ret -1 ] != '\n' && ret < bufsiz);
    
    if(ret > 0)
    {
    if(buf[ret - 1] != '\0')
      buf[ret] = '\0';
    }
  }
  return ret;
} 

ExtFunc void
socket_close(soc)
int soc;
{
  closesocket(soc);
}

/*
 * auth_printf()
 *
 * Writes data to the global socket of the thread
 */
ExtFunc void 
auth_printf(struct arglist * globals, char * data, ...)
{
  va_list param;
  char buffer[65535];
  
  bzero(buffer, sizeof(buffer));

  va_start(param, data);
  vsnprintf(buffer, sizeof(buffer) - 1, data, param);
  
  va_end(param);
  auth_send(globals, buffer);
}                    


ExtFunc void
auth_send(struct arglist * globals, char * data)
{
 int soc = (int)arg_get_value(globals, "global_socket");
 int confirm = (int)arg_get_value(globals, "confirm");
 int n = 0;
 int length;
 int sent = 0;

 if(soc < 0)
  return;

#ifndef NESSUSNT
 signal(SIGPIPE, _exit);
#endif
 length = strlen(data);
 while(sent < length)
 {
 n = nsend(soc, data+sent, length-sent, 0);
 if(n < 0)
 {
  if((errno == ENOMEM)
#ifdef ENOBUFS  
   ||(errno==ENOBUFS)
#endif   
   )
   n = 0;
  else
   {
   nessus_perror("nsend");
   goto out;
   }
 }
 else sent+=n;
 }
 
 if(confirm)
 {
  /*
   * If confirm is set, then we are a son
   * trying to report some message to our busy
   * father. So we wait until he told us he
   * took care of it
   */
  char n;
  read_stream_connection_min(soc, &n, 1, 1);
 }
out:
#ifndef NESSUSNT
  signal(SIGPIPE, SIG_IGN);
#else
 ;
#endif
}

/*
 * auth_gets()
 *
 * Reads data from the global socket of the thread
 */
ExtFunc char * 
auth_gets(globals, buf, bufsiz)
     struct arglist * globals;
     char * buf;
     size_t bufsiz;
{
  int soc = (int)arg_get_value(globals, "global_socket");
  int n;
  /* bzero(buf, bufsiz); */
  n = recv_line(soc, buf, bufsiz);
  if(n <= 0)
	  return NULL;
  
  return(buf);
}


/*
 * Select() routines
 */
 
ExtFunc int
stream_zero(set)
 fd_set * set;
{ 
 FD_ZERO(set);
 return 0;
}

ExtFunc int
stream_set(fd, set)
 int fd;
 fd_set * set;
{
 int soc = nessus_get_socket_from_connection(fd);
 if(soc >= 0)
  FD_SET(soc, set);
 return soc;
}

ExtFunc int
stream_isset(fd, set)
 int fd;
 fd_set * set;
{
 return FD_ISSET(nessus_get_socket_from_connection(fd), set);
}

ExtFunc int
fd_is_stream(fd)
     int	fd;
{
  return NESSUS_STREAM(fd);	/* Should probably be smarter... */
}
