/*
  Copyright (c) 1999 Rafal Wojtczuk <nergal@avet.com.pl>. All rights reserved.
  See the file COPYING for license details.
*/

#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#ifndef WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include <syslog.h>
#endif
#include <netinet/in_systm.h>
#include <stdlib.h>
#include <string.h>
#include <pcap.h>

#include "checksum.h"
#include "ip_fragment.h"
#include "scan.h"
#include "tcp.h"
#include "util.h"
#include "nids.h"

#ifndef WIN32
#define int_ntoa(x)	inet_ntoa(*((struct in_addr *)&x))
#endif

extern int ip_options_compile(char *);

static void nids_syslog(int, int, struct ip *, void *);
static int nids_ip_filter(struct ip *);

static struct proc_node *ip_frag_procs;
static struct proc_node *ip_procs;
struct proc_node *tcp_procs;
static int linkoffset = 0;
static int linktype;
static pcap_t *desc = 0;

char nids_errbuf[PCAP_ERRBUF_SIZE];

char *nids_warnings[] =
{
  "Murphy - you never should see this message !",
  "Oversized IP packet",
  "Invalid IP fragment list: fragment over size",
  "Overlapping IP fragments",
  "Invalid IP header",
  "Source routed IP frame",
  "Max number of TCP streams reached",
  "Invalid TCP header",
  "Too much data in TCP receive queue",
  "Invalid TCP flags"
};

#ifdef WIN32
struct nids_prm nids_params = {
  1024,			/* n_tcp_streams */
  256,			/* n_hosts */
  NULL,			/* device */
  168,			/* sk_buff_size */
  -1,			/* dev_addon */
  256,			/* scan_num_hosts */
  3000,			/* scan_delay */
  10,			/* scan_num_ports */
  nids_no_mem,		/* no_mem() */
  nids_ip_filter,	/* ip_filter() */
  NULL			/* pcap_filter */
};
#else
struct nids_prm nids_params = {
  1024,			/* n_tcp_streams */
  256,			/* n_hosts */
  NULL,			/* device */
  168,			/* sk_buff_size */
  -1,			/* dev_addon */
  nids_syslog,          /* syslog() */
  LOG_ALERT,            /* syslog_level */
  256,			/* scan_num_hosts */
  3000,			/* scan_delay */
  10,			/* scan_num_ports */
  nids_no_mem,		/* no_mem() */
  nids_ip_filter,	/* ip_filter() */
  NULL			/* pcap_filter */
};
#endif

static int
nids_ip_filter(struct ip * x)
{
  return 1;
}

#ifndef WIN32
static void
nids_syslog(int type, int errnum, struct ip * iph, void *data)
{
  char saddr[20], daddr[20];
  char buf[1024];
  struct host *this_host;
  unsigned char flagsand = 255, flagsor = 0;
  int i;
  
  switch (type) {

  case NIDS_WARN_IP:
    if (errnum != NIDS_WARN_IP_HDR) {
      strcpy(saddr, int_ntoa(iph->ip_src.s_addr));
      strcpy(daddr, int_ntoa(iph->ip_dst.s_addr));
      syslog(nids_params.syslog_level,
	   "%s, packet (apparently) from %s to %s\n", nids_warnings[errnum],
	     saddr, daddr);
    }
    else
      syslog(nids_params.syslog_level, "%s\n", nids_warnings[errnum]);
    break;

  case NIDS_WARN_TCP:
    strcpy(saddr, int_ntoa(iph->ip_src.s_addr));
    strcpy(daddr, int_ntoa(iph->ip_dst.s_addr));
    if (errnum != NIDS_WARN_TCP_HDR)
      syslog(nids_params.syslog_level,
	     "%s,from %s:%hi to  %s:%hi\n", nids_warnings[errnum], saddr,
	     ntohs(((struct tcphdr *) data)->th_sport), daddr,
	     ntohs(((struct tcphdr *) data)->th_dport));
    else
      syslog(nids_params.syslog_level, "%s,from %s to %s\n",
	     nids_warnings[errnum], saddr, daddr);
    break;

  case NIDS_WARN_SCAN:
    this_host = (struct host *) data;
    sprintf(buf, "Scan from %s. Scanned ports: ", int_ntoa(this_host->addr));
    for (i = 0; i < this_host->n_packets; i++) {
      strcat(buf, int_ntoa(this_host->packets[i].addr));
      sprintf(buf + strlen(buf), ":%hi,", this_host->packets[i].port);
      flagsand &= this_host->packets[i].flags;
      flagsor |= this_host->packets[i].flags;
    }
    if (flagsand == flagsor) {
      i = flagsand;
      switch (flagsand) {
      case 2:
	strcat(buf, "scan type: SYN");
	break;
      case 0:
	strcat(buf, "scan type: NULL");
	break;
      case 1:
	strcat(buf, "scan type: FIN");
	break;
      default:
	sprintf(buf + strlen(buf), "flags=0x%x", i);
      }
    }
    else
      strcat(buf, "various flags");
    syslog(nids_params.syslog_level, "%s", buf);
    break;

  default:
    syslog(nids_params.syslog_level, "Unknown warning number ?\n");
  }
}
#endif

static void
pcap_hand(u_char *par, struct pcap_pkthdr *hdr, u_char *data)
{
  struct proc_node *i;

  /* Only handle IP packets below. */
  if (linktype == DLT_EN10MB && (data[12] != 8 || data[13] != 0))
    return;
  
  for (i = ip_frag_procs; i; i = i->next)
    (i->item) (data + linkoffset, hdr->len - linkoffset);
}

static void
gen_ip_frag_proc(u_char *data, int len)
{
  struct proc_node *i;
  struct ip *iph = (struct ip *)data;
  int need_free = 0;
  int skblen;
  
  if (!nids_params.ip_filter(iph))
    return;
  
  if (len < sizeof(struct ip) || iph->ip_hl < 5 || iph->ip_v != 4 ||
      ip_fast_csum((unsigned char *) iph, iph->ip_hl) != 0 ||
      len < ntohs(iph->ip_len) ||
      ntohs(iph->ip_len) < iph->ip_hl << 2) {
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_IP, NIDS_WARN_IP_HDR, iph, 0);
#endif
    return;
  }
  if (iph->ip_hl > 5 && ip_options_compile(data)) {
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_IP, NIDS_WARN_IP_SRR, iph->ip_hl, 0);
#endif
    return;
  }
  switch (ip_defrag_stub((struct ip *)data, &iph)) {
  case IPF_ISF:
    return;
  case IPF_NOTF:
    need_free = 0;
    iph = (struct ip *) data;
    break;
  case IPF_NEW:
    need_free = 1;
    break;
  default:;
  }
  skblen = ntohs(iph->ip_len) + 16;
  if (!need_free)
    skblen += nids_params.dev_addon;
  skblen = (skblen + 15) & ~15;
  skblen += nids_params.sk_buff_size;
  
  for (i = ip_procs; i; i = i->next)
    (i->item) (iph, skblen);
  if (need_free)
    free(iph);
}

static void
process_udp(char *x)
{
  return;
}

static void
gen_ip_proc(u_char * data, int skblen)
{
  switch (((struct ip *) data)->ip_p) {
  case IPPROTO_TCP:
    process_tcp(data, skblen);
    break;
  case IPPROTO_UDP:
    process_udp(data);
    break;
  case IPPROTO_ICMP:
    process_icmp(data);
    break;
  default:
    break;
  }
}

static void
init_procs()
{
  ip_frag_procs = mknew(struct proc_node);
  ip_frag_procs->item = gen_ip_frag_proc;
  ip_frag_procs->next = 0;
  ip_procs = mknew(struct proc_node);
  ip_procs->item = gen_ip_proc;
  ip_procs->next = 0;
  tcp_procs = 0;
}

void
nids_register_ip(void (*x))
{
  struct proc_node *ipp = mknew(struct proc_node);

  ipp->item = x;
  ipp->next = ip_procs;
  ip_procs = ipp;
}

void
nids_register_ip_frag(void (*x))
{
  struct proc_node *ipp = mknew(struct proc_node);

  ipp->item = x;
  ipp->next = ip_frag_procs;
  ip_procs = ipp;
}

int
nids_init()
{
  char *device;
#ifdef WIN32
  char *devicet = NULL;
  int i;
#endif

  if (nids_params.device == NULL)
    nids_params.device = pcap_lookupdev(nids_errbuf);
  if (nids_params.device == NULL)
    return 0;

#ifdef WIN32
   	  if(nids_params.device[0]=='0' && nids_params.device[1]==0){
			strcpy(nids_errbuf, "Invalid interface");
			return 0;
	  }
			
	  if((i=atoi(nids_params.device))!=0){
		if(i<0){
	    	strcpy(nids_errbuf, "Invalid interface");
			return 0;
		}
		
		devicet = pcap_lookupdev(nids_errbuf);
			if (devicet == NULL){
				strcpy(nids_errbuf, "Invalid interface");
				return 0;
			}

		device = NULL;
		device=GetAdapterFromList(devicet,i);
			if (device == NULL){
		    	strcpy(nids_errbuf, "Invalid interface");
				return 0;
			}
	}
else 
#endif
device = nids_params.device;

  if ((desc = pcap_open_live(device, 16384, 1, 1024, nids_errbuf)) == NULL)
    return 0;
  
  if (nids_params.pcap_filter != NULL) {
    u_int net, mask;
    struct bpf_program fcode;
    
    if (pcap_lookupnet(device, &net, &mask, nids_errbuf) == -1)
      return 0;
    if (pcap_compile(desc, &fcode, nids_params.pcap_filter, 1, mask) < 0)
      return 0;
    if (pcap_setfilter(desc, &fcode) == -1)
      return 0;
  }
  switch ((linktype = pcap_datalink(desc))) {
  case DLT_EN10MB:
    linkoffset = 14;
    break;
  case DLT_PPP:
    linkoffset = 4;
    break;
  case DLT_RAW:
    linkoffset = 0;
    break;
  default:
    fprintf(stderr, "link type unknown, defaulting to ethernet.\n");
    linktype = DLT_EN10MB;
    linkoffset = 14;
    break;
  }
  if (nids_params.dev_addon == -1) {
    if (linktype == DLT_EN10MB)
      nids_params.dev_addon = 16;
    else
      nids_params.dev_addon = 0;
  }
#ifndef WIN32
  if (nids_params.syslog == nids_syslog)
    openlog("libnids", 0, LOG_LOCAL0);
#endif

  init_procs();
  tcp_init(nids_params.n_tcp_streams);
  ip_frag_init(nids_params.n_hosts);
  scan_init();
  
  return 1;
}

void
nids_run()
{
  if (!desc) {
    strcpy(nids_errbuf, "Libnids not initialized");
    return;
  }
  pcap_loop(desc, -1, (pcap_handler) pcap_hand, 0);
  strcpy(nids_errbuf, "loop:");
  strncpy(nids_errbuf + 5, pcap_geterr(desc), sizeof(nids_errbuf - 7));
  nids_errbuf[sizeof(nids_errbuf) - 1] = 0;
  pcap_close(desc);
}

int
nids_getfd()
{
  if (!desc) {
    strcpy(nids_errbuf, "Libnids not initialized");
    return -1;
  }
  return pcap_fileno(desc);
}

int
nids_next()
{
  struct pcap_pkthdr h;
  char *data;

  if (!desc) {
    strcpy(nids_errbuf, "Libnids not initialized");
    return 0;
  }
  if (!(data = (char *) pcap_next(desc, &h))) {
    strcpy(nids_errbuf, "next:");
    strncpy(nids_errbuf + 5, pcap_geterr(desc), sizeof(nids_errbuf - 7));
    nids_errbuf[sizeof(nids_errbuf) - 1] = 0;
    return 0;
  }
  pcap_hand(0, &h, data);
  return 1;
}

#ifdef WIN32
void* GetAdapterFromList(void* device,int index)
{
        DWORD dwVersion;
        DWORD dwWindowsMajorVersion;
        char* Adapter95;
        WCHAR* Adapter;
        int i;

        dwVersion=GetVersion();
        dwWindowsMajorVersion =  (DWORD)(LOBYTE(LOWORD(dwVersion)));
		/* Windows 95. */
        if (dwVersion >= 0x80000000 && dwWindowsMajorVersion >= 4)
        {
                Adapter95=device;
                for(i=0;i<index-1;i++){
                        while(*Adapter95++!=0);
                        if(*Adapter95==0)return NULL;
                }
                return  Adapter95;
        }
        else{
                Adapter=(WCHAR*)device;
                for(i=0;i<index-1;i++){
                        while(*Adapter++!=0);
                        if(*Adapter==0)return NULL;
                }
                return  Adapter;
        }

}
#endif
