/* OpenVAS
 * $Id$
 * Description: IDS stressing functions.
 *
 * ids_send() sends data spliced into several packets, with bad packets
 * between them, thus making bad NIDSes reassemble the tcp stream awkwardly;
 *
 * ids_open_sock_tcp() opens a tcp socket and immediately sends a badly
 * formed RST packet to the remote host, thus making bad NIDSes think
 * the connection was immediately dropped on our end.
 *
 * Authors:
 * Renaud Deraison <deraison@nessus.org> (Original pre-fork development)
 *
 * Copyright:
 * Based on work Copyright (C) 1998 - 2002 Renaud Deraison
 *
 * 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>

#include "arglists.h"
#include "bpf_share.h"
#include "resolve.h"
#include "ids_send.h"
#include "network.h"
#include "pcap_openvas.h"
#include "plugutils.h"
#include "system.h"

/** @todo: It still needs to be taken care
 * BSD_BYTE_ORDERING gets here if defined (e.g. by config.h) */
#ifdef BSD_BYTE_ORDERING
# define FIX(n) (n)
# define UNFIX(n) (n)
#else
# define FIX(n) htons(n)
# define UNFIX(n) ntohs(n)
#endif

/*
 * We define our own packet structs (they'll be defined in libnasl later
 * on, and I feel lazy today)
 */
struct ip_packet {
#if !WORDS_BIGENDIAN
	u_char	ip_hl:4,		/* header length */
		ip_v:4;			/* version */
#else
	 u_char	ip_v:4,			/* version */
		ip_hl:4;		/* header length */
#endif
	u_char	ip_tos;			/* type of service */
	u_short	ip_len;			/* total length */
	u_short	ip_id;			/* identification */
	u_short	ip_off;			/* fragment offset field */
	u_char	ip_ttl;			/* time to live */
	u_char	ip_p;			/* protocol */
	u_short	ip_sum;			/* checksum */
	struct	in_addr ip_src,ip_dst;	/* source and dest address */
};

struct tcp_packet {
	u_short	th_sport;		/* source port */
	u_short	th_dport;		/* destination port */
	u_long	th_seq;			/* sequence number */
	u_long	th_ack;			/* acknowledgement number */
#if !WORDS_BIGENDIAN
	u_int	th_x2:4,		/* (unused) */
		th_off:4;		/* data offset */
#endif
#if WORDS_BIGENDIAN
	u_int	th_off:4,		/* data offset */
		th_x2:4;		/* (unused) */
#endif
	u_char	th_flags;

	u_short	th_win;			/* window */
	u_short	th_sum;			/* checksum */
	u_short	th_urp;			/* urgent pointer */
};


struct ipv6_header{
	union  {
		struct ip6_hdrctl {
			uint32_t ip6_un1_flow;   /* 24 bits of flow-ID */
			uint16_t ip6_un1_plen;   /* payload length */
			uint8_t  ip6_un1_nxt;    /* next header */
			uint8_t  ip6_un1_hlim;   /* hop limit */
		} ip6_un1;
		uint8_t ip6_un2_vfc;       /* 4 bits version, 4 bits priority */
	} ip6_ctlun;
	struct in6_addr ip6_src;      /* source address */
	struct in6_addr ip6_dst;      /* destination address */
} ;


/*
 * our own definition of the tcp flags
 */
#define TCP_FLAG_RST  0x0004
#define TCP_FLAG_ACK  0x0010
#define TCP_FLAG_PUSH 0x0008


struct pseudohdr
{
        struct in_addr saddr;
        struct in_addr daddr;
        u_char zero;
        u_char protocol;
        u_short length;
        struct tcp_packet tcpheader;
};


/*
 * This function returns the TTL we should use when forging our own
 * IP packets. If <method & OPENVAS_CNX_IDS_EVASION_SHORT_TTL>, then
 * it returns an estimation of the number of hops between us and the
 * remote host (see comment), minus 1. 
 * 
 * By default, it returns the TTL our OS usually returns (to be improved)
 *
 */
static 
int which_ttl(method, old_ttl)
 int method, old_ttl;
{
 int ttl;
 
 if(method & OPENVAS_CNX_IDS_EVASION_SHORT_TTL)
 {
  /*
   * XXXX
   * To find out the number of hops to the remote host,
   * we assume that the TTL set remotely is one of {32,64,128,255}.
   * (by default, I know of no OS which uses a different value)
   * 
   * We could fine grain that by reading Host/OS or even by
   * computing the traceroute by ourselves (especially since we're
   * not sure that our packets will go through the same path as those
   * we receive) however this will do for now.
   */
  int num_hops = old_ttl;
  if(num_hops < 32)
  {
   ttl = 32 - num_hops;
  }
  else if(num_hops < 64)
  {
   ttl = 64 - num_hops;
  }
  else if(num_hops < 128)
  {
   ttl = 128 - num_hops;
  }
  else ttl = 255 - num_hops;
 }
 /*
  * We try to set up a 'normal' TTL
  */
 else /* if(method & OPENVAS_CNX_IDS_EVASION_INJECT) */
 {
#ifdef LINUX
 int f;
 f = open("/proc/sys/net/ipv4/ip_default_ttl", O_RDONLY);
 if(f >= 0)
 {
  char rd[20];

  /* RATS issues the following warning for this line:
   * "Check buffer boundaries if calling this function in a loop and make sure
   * you are not in danger of writing past the allocated space."
   * This warning can IMHO be ignored as long as the buffer size of rd is
   * hardcoded since we will read at most sizeof(rd)-1 bytes into rd.
   *   -mwiegand, 20090205
   */
  /* RATS: ignore */
  read(f, rd, sizeof(rd) - 1);
  close(f);
  ttl = atoi(rd);
 }
#else
 /*
  * TODO :
  * XXX sysctl support (BSD)
  * XXX Solaris support...
  */
 ttl = 64;
#endif
 }
 
 return ttl;
}


static
int in_cksum(p,n)
u_short *p; int n;
{
  register u_short answer;
  register long sum = 0;
  u_short odd_byte = 0;

  while( n > 1 )  { sum += *p++; n -= 2; }

  /* mop up an odd byte, if necessary */
  if( n == 1 ) {
      *(u_char *)(&odd_byte) = *(u_char *)p;
      sum += odd_byte;
  }

  sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
  sum += (sum >> 16);			/* add carry */
  answer = (int)~sum;			/* ones-complement, truncate*/
  return (answer);
}


static 
int tcp_cksum(packet, len)
 char* packet;
 int len;
{
 struct pseudohdr pseudoheader;
 char * tcpsumdata = (char *)emalloc(sizeof( struct pseudohdr ) + 
 				    ( len % 2 ? len + 1 : 0 ) );
 struct in_addr source, dest;
 struct ip_packet * ip = (struct ip_packet*)packet;
 struct tcp_packet * tcp = (struct tcp_packet*)(packet + ip->ip_hl*4); 
 char * data = (char*)(packet + ip->ip_hl*4 + tcp->th_off*4);
     
  source.s_addr = ip->ip_src.s_addr;
  dest.s_addr = ip->ip_dst.s_addr;
  
  bzero(&pseudoheader, 12+sizeof(struct tcp_packet));
  pseudoheader.saddr.s_addr=source.s_addr;
  pseudoheader.daddr.s_addr=dest.s_addr;
 
  pseudoheader.protocol=IPPROTO_TCP;
  pseudoheader.length=htons(sizeof(struct tcp_packet)+len);
  bcopy((char *) tcp,(char *) &pseudoheader.tcpheader,sizeof(struct tcp_packet));
  /* fill tcpsumdata with data to checksum */
  bcopy((char *) &pseudoheader, tcpsumdata ,sizeof(struct pseudohdr));
  bcopy((char *) data, tcpsumdata + sizeof(struct pseudohdr), len);
  tcp->th_sum = in_cksum((unsigned short *)tcpsumdata,12+sizeof(struct tcp_packet) + len );
  efree(&tcpsumdata);
  return 0;
}


/*
 * This function injects a tcp packet in a stream. If
 * method & OPENVAS_CNX_IDS_EVASION_SHORT_TTL != 0, then 
 * the injected tcp packet will be completely valid but will
 * have a short TTL (so that it does not reach the remote host).
 *
 * If method & OPENVAS_CNX_IDS_EVASION_INJECT != 0, then 
 * the injected tcp packet will have a normal TTL but will
 * have a bad checksum.
 *
 * <orig_packet> is the capture of the last packet sent by the
 * remote host (hopefully a ACK). We use it to obtain the th_seq
 * and th_ack elements that are being waited for by the remote
 * host.
 *
 * <packet_len> is the size of the captured packet
 *
 *
 * We also use the ttl of the packet to determine the number
 * of hops between us and the remote host (see the function
 * which_ttl()).
 *
 * <flags> are the flags of the tcp packet we should send (RST, ...)
 *
 * <data> is the data we should append to our tcp packet. This can
 * be NULL
 * <data_len> is the length of the data. 
 *
 *
 * Note that this function opens a raw socket each time it's called, which
 * is highly inefficient.
 */
static int 
inject(orig_packet, packet_len, method, flags, data, data_len)
 char * orig_packet;
 int packet_len;
 int method;
 int flags;
 char * data;
 int data_len;
{
 int soc;
 char * packet;
 struct ip_packet * ip, * old_ip;
 struct tcp_packet * tcp, * old_tcp;
 int tot_len = sizeof(struct ip_packet) + sizeof(struct tcp_packet) + data_len;
 int i;
 int one = 1;
 struct sockaddr_in sockaddr; 
 
 if(packet_len < sizeof(struct ip_packet) + sizeof(struct tcp_packet))
  return -1;
 
 
 
 old_ip = (struct ip_packet*)orig_packet;
 
 if((old_ip->ip_hl * 4)+sizeof(struct tcp_packet) > packet_len)
 	return -1;
	
 old_tcp = (struct tcp_packet*)(orig_packet + (old_ip->ip_hl * 4));
 
 
 soc = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
 if(soc < 0)
   return -1;

#ifdef IP_HDRINCL
 setsockopt(soc, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
#endif


 packet =  emalloc(tot_len);

 /*
  * Copy data, if any
  */
 for(i=0;i<data_len;i++)packet[i+sizeof(struct ip_packet)] = data[i];
 
 ip = (struct ip_packet*)packet;
 tcp = (struct tcp_packet*)(packet + sizeof(struct ip_packet));
 
 /* 
  * for the sake of code shortness, we copy the header of the
  * received packet into the packet we forge, and we'll change
  * some stuff in it.
  */
 memcpy(ip, old_ip, sizeof(struct ip_packet));
 
 ip->ip_len = FIX(tot_len);
 ip->ip_hl = 5;
 ip->ip_off = 0;
 ip->ip_src.s_addr = old_ip->ip_dst.s_addr;
 ip->ip_dst.s_addr = old_ip->ip_src.s_addr;
 ip->ip_id = rand();
 ip->ip_ttl = which_ttl(method, old_ip->ip_ttl);
 ip->ip_sum = in_cksum((u_short *)packet, sizeof(struct ip_packet));
 
 memcpy(tcp, old_tcp, sizeof(struct tcp_packet));
 tcp->th_flags = flags;
 if((flags & TCP_FLAG_RST) && (method & OPENVAS_CNX_IDS_EVASION_FAKE_RST))
 	tcp->th_ack = htonl(ntohl(old_tcp->th_seq) + 1);
 else	
 	tcp->th_ack = old_tcp->th_seq;
	
 tcp->th_seq = old_tcp->th_ack;
 tcp->th_sport = old_tcp->th_dport;
 tcp->th_dport = old_tcp->th_sport;
 tcp->th_off = 5;
 tcp->th_sum = 0;
 if(method & OPENVAS_CNX_IDS_EVASION_SHORT_TTL)
  tcp_cksum(packet, data_len);
 else 
  tcp->th_sum = rand(); /* bad checksum - packet will be dropped */
  

 /*
  * Sending the packet now
  */
 bzero(&sockaddr, sizeof(sockaddr));
 sockaddr.sin_family = AF_INET;
 sockaddr.sin_addr.s_addr = ip->ip_dst.s_addr;
 
 
 if(sendto(soc, packet, tot_len, 0, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) < 0)
 {
  perror("openvas-libraries : libopenvas : ids_send.c : inject() : sendto() ");
 }
 efree(&packet);
 close(soc);
 return 0;
}


static int injectv6(orig_packet, packet_len, method, flags, data, data_len)
 char * orig_packet;
 int packet_len;
 int method;
 int flags;
 char * data;
 int data_len;
{
  int soc;
  char * packet;
  struct ipv6_header * ip6, * old_ip6;
  struct tcp_packet * tcp, * old_tcp;
  int tot_len = sizeof(struct ipv6_header) + sizeof(struct tcp_packet) + data_len;
  int i;
  struct sockaddr_in6 sockaddr6;

  if(packet_len < sizeof(struct ipv6_header) + sizeof(struct tcp_packet))
    return -1;

  old_ip6 = (struct ipv6_header*)orig_packet;
  old_tcp = (struct tcp_packet*)(orig_packet + 40);

  soc = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
  if(soc < 0)
    return -1;

  packet =  emalloc(tot_len);

  /*
   * Copy data, if any
   */
  for(i = 0; i < data_len; i++){
    packet[i+sizeof(struct ipv6_header)] = data[i];
  }

  ip6 = (struct ipv6_header*)packet;
  tcp = (struct tcp_packet*)(packet + sizeof(struct ipv6_header));

 /*
  * for the sake of code shortness, we copy the header of the
  * received packet into the packet we forge, and we'll change
  * some stuff in it.
  */
  memcpy(ip6, old_ip6, sizeof(struct ipv6_header));

  ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = old_ip6->ip6_ctlun.ip6_un1.ip6_un1_flow;
  ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = data_len;
  ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = old_ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt;
  ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = which_ttl(method, old_ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim);
  memcpy(&ip6->ip6_src, &old_ip6->ip6_dst, sizeof(struct in6_addr));
  memcpy(&ip6->ip6_dst, &old_ip6->ip6_src, sizeof(struct in6_addr));

  memcpy(tcp, old_tcp, sizeof(struct tcp_packet));
  tcp->th_flags = flags;
  if((flags & TCP_FLAG_RST) && (method & OPENVAS_CNX_IDS_EVASION_FAKE_RST))
    tcp->th_ack = htonl(ntohl(old_tcp->th_seq) + 1);
  else
    tcp->th_ack = old_tcp->th_seq;

  tcp->th_seq = old_tcp->th_ack;
  tcp->th_sport = old_tcp->th_dport;
  tcp->th_dport = old_tcp->th_sport;
  tcp->th_off = 5;
  tcp->th_sum = 0;
  if(method & OPENVAS_CNX_IDS_EVASION_SHORT_TTL)
    tcp_cksum(packet, data_len);
  else
    tcp->th_sum = rand(); /* bad checksum - packet will be dropped */


 /*
  * Sending the packet now
  */
  bzero(&sockaddr6, sizeof(sockaddr6));
  sockaddr6.sin6_family = AF_INET6;
  sockaddr6.sin6_addr = ip6->ip6_dst;

  if(sendto(soc, packet, tot_len, 0, (struct sockaddr*)&sockaddr6, sizeof(sockaddr6)) < 0)
  {
    perror("openvas-libraries : libopenvas : ids_send.c : inject() : sendto() ");
  }
  efree(&packet);
  close(soc);
  return 0;
}


int
ids_send(fd, buf0, n, method)
 int fd;
 void * buf0;
 int n;
 int method;
{
  struct in_addr  dst, src;
  struct in6_addr dst6, src6;
  struct sockaddr_in6 sockaddr6;
  struct sockaddr_in  *saddr;
  struct sockaddr *sa;
  char * iface;
  char filter[255];
  char * src_host, * dst_host;
  int port;
  int ret = 0;
  int len;
  char * buf = (char*)buf0;
  unsigned int sz  = sizeof(sockaddr6);
  int e;
  unsigned char * packet;
  int bpf;
  char hostname[INET6_ADDRSTRLEN];
  int family;

  bzero(&sockaddr6, sizeof(sockaddr6));
  if(getpeername(fd, (struct sockaddr*)&sockaddr6, &sz) < 0)
  {
    perror("getpeername() ");
  }
  sa = (struct sockaddr *)&sockaddr6;
  if(sa->sa_family == AF_INET)
  {
    family = AF_INET;
    saddr = (struct sockaddr_in *)&sockaddr6;
    port = ntohs(saddr->sin_port);
    dst.s_addr = saddr->sin_addr.s_addr;
    src.s_addr = 0;
    iface = routethrough(&dst, &src);

    src_host = estrdup(inet_ntoa(src));
    dst_host = estrdup(inet_ntoa(dst));

    snprintf(filter, sizeof(filter), "tcp and (src host %s and dst host %s and src port %d)",
        dst_host,
        src_host,
        port);
    efree(&src_host);
    efree(&dst_host);
  }
  else
  {
    family = AF_INET6;
    port = ntohs(sockaddr6.sin6_port);
    memcpy(&dst6,&sockaddr6.sin6_addr,sizeof(struct in6_addr));
    bzero(&src6, sizeof(src6));
    iface = v6_routethrough(&dst6, &src6);

    src_host = estrdup(inet_ntop(AF_INET6,&src6, hostname, sizeof(hostname)));
    dst_host = estrdup(inet_ntop(AF_INET6,&dst6, hostname,sizeof(hostname)));
    snprintf(filter, sizeof(filter), "tcp and (src host %s and dst host %s and src port %d)",
        dst_host,
        src_host,
        port);
    efree(&src_host);
    efree(&dst_host);
  }

  bpf = bpf_open_live(iface, filter);
  if(bpf >= 0)
  {
    e = send(fd, buf+ret, 1, 0);
    packet = bpf_next(bpf, &len);
    if(e < 0)
      return -1;
    else
      ret+=e;
    /*
     * We can start to send stuff now
     */
    while(ret < n)
    {
      if(packet)
      {
        char *pkt_ip;
        int num_before = (rand() / 1000) % 3;
        int num_after = (rand() / 1000) % 3;
        int i;

        if(!num_before && !num_after)
        {
          if(rand() % 2)num_before = 1;
          else num_after = 1;
        }
        pkt_ip = (char*)(packet + get_datalink_size(bpf_datalink(bpf)));

        /*
         * send bogus data before
         */
        for(i=0;i<num_before;i++)
        {
          int j;
          char data[10];
          for(j=0;j<10;j++)data[j]=rand();
          if(family == AF_INET)
            inject(pkt_ip, len - get_datalink_size(bpf_datalink(bpf)), method, TCP_FLAG_ACK|TCP_FLAG_PUSH, data, (rand()%9)+1);
          else
            injectv6(pkt_ip, len - get_datalink_size(bpf_datalink(bpf)), method, TCP_FLAG_ACK|TCP_FLAG_PUSH, data, (rand()%9)+1);

        }
        e = send(fd, buf+ret, 1, 0);
        packet = bpf_next(bpf, &len);
        /*
         * send bogus data after
         */
        for(i=0;i<num_after;i++)
        {
          int j;
          char data[10];
          for(j=0;j<10;j++)data[j]=rand();
          if(family == AF_INET)
            inject(pkt_ip, len - get_datalink_size(bpf_datalink(bpf)), method, TCP_FLAG_ACK|TCP_FLAG_PUSH, data, (rand()%9)+1);
          else
            injectv6(pkt_ip, len - get_datalink_size(bpf_datalink(bpf)), method, TCP_FLAG_ACK|TCP_FLAG_PUSH, data, (rand()%9)+1);
        }
      }
      else
      {
        e = send(fd, buf+ret, 1, 0);
        packet = bpf_next(bpf, &len);
      }
      if(e < 0)
        return -1;
      else
        ret+=e;
    }
    bpf_close(bpf);
    return ret;
  }
  else return send(fd, buf, n, 0);
}


int ids_open_sock_tcp(args, port, method, timeout)
 struct arglist * args;
 int port;
 int method;
 int timeout;
{
 int bpf;
 struct in_addr dst, src;
 struct in6_addr *dst6, *src6;
 char * iface;
 char filter[255];
 char * src_host, * dst_host;
 int ret = 0;
 int len;
 char hostname[INET6_ADDRSTRLEN];
 int family;

 dst6 = plug_get_host_ip(args);
 if(!dst6)
 {
  fprintf(stderr, "Error - no address associated with name\n");
  return -1;
 }
 if(IN6_IS_ADDR_V4MAPPED(dst6))
 {
   family = AF_INET;
   dst.s_addr = dst6->s6_addr32[3];
   src.s_addr = 0;
   iface = routethrough(&dst, &src);
   src_host = estrdup(inet_ntoa(src));
   dst_host = estrdup(inet_ntoa(dst));
 }
 else
 {
   family = AF_INET6;
   bzero(src6, sizeof(*src6));
   iface = v6_routethrough(dst6, src6);
   src_host = estrdup(inet_ntop(AF_INET6,src6, hostname, sizeof(hostname)));
   dst_host = estrdup(inet_ntop(AF_INET6,dst6, hostname,sizeof(hostname)));
 }

 snprintf(filter, sizeof(filter), "tcp and (src host %s and dst host %s and src port %d)",
       dst_host, src_host, port);

 efree(&src_host);
 efree(&dst_host);


 bpf = bpf_open_live(iface, filter);
 if(bpf >= 0)
 {
  ret = open_sock_tcp(args, port, timeout);
  if(ret >= 0)
  {
   unsigned char * packet = bpf_next(bpf, &len);
   if(packet)
   {
   char *pkt_ip;
   pkt_ip = (char*)(packet + get_datalink_size(bpf_datalink(bpf)));

   if(family == AF_INET)
    inject(pkt_ip, len - get_datalink_size(bpf_datalink(bpf)), method, TCP_FLAG_RST, NULL, 0);
   else
    injectv6(pkt_ip, len - get_datalink_size(bpf_datalink(bpf)), method, TCP_FLAG_RST, NULL, 0);
   }
  }
  bpf_close(bpf);
  return ret;
 }
 else
  return open_sock_tcp(args, port, timeout);
}
