/* Copyright (C) 2000-2013 Boris Wesslowski */
/* $Id: response.c 731 2013-05-17 14:15:23Z bw $ */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <syslog.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "response.h"
#include "main.h"
#include "utils.h"

struct known_hosts *first_host = NULL;
extern struct options opt;
extern struct conn_data *first;

void check_for_ipchains()
{
  char buf[BUFSIZE];
  unsigned int found = 0;
  FILE *fd;
  int retval;

  char chain[10], src_dst[36], interface[16];
  unsigned int fw_flg, fw_invflg, protocol;
  char rest[80];

  fd = fopen("/proc/net/ip_fwchains", "r");
  if (fd == NULL) {
    syslog(LOG_NOTICE, "fopen /proc/net/ip_fwchains: %s", strerror(errno));
    log_exit(EXIT_FAILURE);
  }

  while (fgets(buf, BUFSIZE, fd)) {
    retval = sscanf(buf, "%10s %36s %16s %6X %6X %5u %80s\n", chain, src_dst, interface, &fw_flg, &fw_invflg, &protocol, rest);
    if (retval == 7) {
      if (fw_flg & IP_FW_F_PRN) {
	found++;
      }
    }
  }

  retval = fclose(fd);
  if (retval == EOF) {
    syslog(LOG_NOTICE, "fclose /proc/net/ip_fwchains: %s", strerror(errno));
  }

  if (found > 0) {
    if (found == 1) {
      syslog(LOG_NOTICE, _("One logging ipchains firewall rule defined"));
    } else {
      syslog(LOG_NOTICE, _("%u logging ipchains firewall rules defined"), found);
    }
  } else {
    syslog(LOG_NOTICE, _("No logging ipchains firewall rules defined, format was requested"));
    log_exit(EXIT_FAILURE);
  }
}

void check_script_perms(char *name)
{
  int retval;
  struct stat *buf;

  buf = xmalloc(sizeof(struct stat));

  retval = stat(name, buf);
  if (retval == -1) {
    syslog(LOG_NOTICE, "stat %s: %s", name, strerror(errno));
    free(buf);
    log_exit(EXIT_FAILURE);
  }

  if ((getuid() == 0) || (geteuid() == 0)) {
    if ((buf->st_mode & (S_IWGRP | S_IWOTH)) != 0) {
      syslog(LOG_NOTICE, _("%s is group/world writable"), name);
      free(buf);
      log_exit(EXIT_FAILURE);
    }
  }

  free(buf);
}

void modify_firewall(unsigned char action)
{
  char buf[BUFSIZE];

  if (action == FW_START) {
    snprintf(buf, BUFSIZE, "%s start", opt.respond_script);
    run_command(buf);
  } else if (action == FW_STOP) {
    snprintf(buf, BUFSIZE, "%s stop", opt.respond_script);
    run_command(buf);
  }
}

void react(unsigned char mode, struct known_hosts *this_host)
{
  char buf[BUFSIZE], buf2[BUFSIZE];

  if (mode == EX_NOTIFY) {
    xstrncpy(buf, opt.notify_script, BUFSIZE);
  } else {
    xstrncpy(buf, opt.respond_script, BUFSIZE);
    if (mode == EX_RESPOND_ADD) {
      strncat(buf, " add", BUFSIZE - strlen(buf) - 1);
    } else {
      strncat(buf, " remove", BUFSIZE - strlen(buf) - 1);
    }
  }

  snprintf(buf2, BUFSIZE, " %d %s", this_host->count, my_inet_ntop(&this_host->shost));
  strncat(buf, buf2, BUFSIZE - strlen(buf) - 1);

  if (opt.dst_ip) {
    snprintf(buf2, BUFSIZE, " %s", my_inet_ntop(&this_host->dhost));
    strncat(buf, buf2, BUFSIZE - strlen(buf) - 1);
  } else {
    strncat(buf, " -", BUFSIZE - strlen(buf) - 1);
  }

  if (opt.proto) {
    snprintf(buf2, BUFSIZE, " %d", this_host->protocol);
    strncat(buf, buf2, BUFSIZE - strlen(buf) - 1);
  } else {
    strncat(buf, " -", BUFSIZE - strlen(buf) - 1);
  }

  if (opt.src_port) {
    snprintf(buf2, BUFSIZE, " %d", this_host->sport);
    strncat(buf, buf2, BUFSIZE - strlen(buf) - 1);
  } else {
    strncat(buf, " -", BUFSIZE - strlen(buf) - 1);
  }

  if (opt.dst_port) {
    snprintf(buf2, BUFSIZE, " %d", this_host->dport);
    strncat(buf, buf2, BUFSIZE - strlen(buf) - 1);
  } else {
    strncat(buf, " -", BUFSIZE - strlen(buf) - 1);
  }

  run_command(buf);
}

void remove_old(unsigned char mode)
{
  time_t now;
  unsigned char is_first;

  now = time(NULL);

  if (mode & RESP_REMOVE_OPC) {
    struct conn_data *prev, *this;

    prev = this = first;
    is_first = 1;
    while (this != NULL) {
      if ((now - this->end_time) >= opt.recent) {
	if (opt.verbose == 2)
	  syslog(LOG_NOTICE, _("Deleting packet cache entry (%s)"), my_inet_ntop(&this->shost));
	if (is_first == 1) {
	  prev = this->next;
	  free(this->hostname);
	  free(this->chainlabel);
	  free(this->branchname);
	  free(this->interface);
	  free(this);
	  first = this = prev;
	} else {
	  this = this->next;
	  free(prev->next->hostname);
	  free(prev->next->chainlabel);
	  free(prev->next->branchname);
	  free(prev->next->interface);
	  free(prev->next);
	  prev->next = this;
	}
      } else {
	prev = this;
	this = this->next;
	is_first = 0;
      }
    }
  }

  if (mode & RESP_REMOVE_OHS) {
    struct known_hosts *prev_host, *this_host;

    prev_host = this_host = first_host;
    is_first = 1;
    while (this_host != NULL) {
      if ((this_host->time != 0) && ((now - this_host->time) >= opt.recent)) {
	if (opt.verbose == 2)
	  syslog(LOG_NOTICE, _("Deleting host status entry (%s)"), my_inet_ntop(&this_host->shost));
	if (opt.response & OPT_RESPOND)
	  react(EX_RESPOND_REMOVE, this_host);
	if (is_first == 1) {
	  prev_host = this_host->next;
	  free(this_host);
	  first_host = this_host = prev_host;
	} else {
	  this_host = this_host->next;
	  free(prev_host->next);
	  prev_host->next = this_host;
	}
      } else {
	prev_host = this_host;
	this_host = this_host->next;
	is_first = 0;
      }
    }
  }
}

struct known_hosts *is_known(struct conn_data *host)
{
  struct known_hosts *this_host;
  int i;
  struct in6_addr testhost;

  this_host = first_host;
  while (this_host != NULL) {
    for (i = 0; i < 16; i++)
      testhost.s6_addr[i] = host->shost.s6_addr[i] & this_host->netmask.s6_addr[i];
    if (memcmp(&this_host->shost, &testhost, sizeof(struct in6_addr)) != 0) {
      goto no_match;
    }
    if (this_host->time == 0)
      return this_host;
    if ((opt.dst_ip) && (compare_ipv6_equal(&this_host->dhost, &host->dhost) != 0)) {
      goto no_match;
    }
    if ((opt.dst_port) && (this_host->dport != host->dport)) {
      goto no_match;
    }
    if ((opt.src_port) && (this_host->sport != host->sport)) {
      goto no_match;
    }
    if ((opt.proto) && (this_host->protocol != host->protocol)) {
      goto no_match;
    }
    break;
  no_match:
    this_host = this_host->next;
  }
  return this_host;
}

void look_for_alert()
{
  struct conn_data *this;
  unsigned char modified = 0;

  this = first;
  while (this != NULL) {
    if (this->count >= opt.threshold) {
      struct known_hosts *this_host;
      this_host = is_known(this);
      if (this_host == NULL) {
	this_host = xmalloc(sizeof(struct known_hosts));
	this_host->time = time(NULL);
	this_host->count = (this->count / opt.threshold) * opt.threshold;
	this_host->shost = this->shost;
	memset(&this_host->netmask, 0xff, sizeof(struct in6_addr));
	this_host->protocol = this->protocol;
	this_host->dhost = this->dhost;
	this_host->sport = this->sport;
	this_host->dport = this->dport;
	this_host->id = opt.global_id++;
	this_host->next = first_host;
	first_host = this_host;
	syslog(LOG_NOTICE, _("ALERT: %d attempts from %s"), this_host->count, my_inet_ntop(&this_host->shost));
	if (opt.response & OPT_NOTIFY)
	  react(EX_NOTIFY, this_host);
	if (opt.response & OPT_RESPOND)
	  react(EX_RESPOND_ADD, this_host);
      } else {
	this_host->count = this_host->count + ((this->count / opt.threshold) * opt.threshold);
	if (this_host->time != 0)
	  this_host->time = time(NULL);
      }
      this->count = (this->count % opt.threshold);
      if (this->count == 0) {
	this->end_time = 1;
	modified = 1;
      }
    }
    this = this->next;
  }
  if (modified)
    remove_old(RESP_REMOVE_OPC);
}

unsigned char hs_compare(struct known_hosts *op1, struct known_hosts *op2)
{
  unsigned char cond = 0;
  time_t now;

  switch (opt.sortfield) {
  case SORT_COUNT:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (op1->count > op2->count)
	cond++;
    } else {
      if (op1->count < op2->count)
	cond++;
    }
    break;
  case SORT_START_TIME:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (op1->time > op2->time)
	cond++;
    } else {
      if (op1->time < op2->time)
	cond++;
    }
    break;
  case SORT_END_TIME:
    now = time(NULL);
    if (opt.sortmode == ORDER_ASCENDING) {
      if ((now - op1->time) < (now - op2->time))
	cond++;
    } else {
      if ((now - op1->time) > (now - op2->time))
	cond++;
    }
    break;
  case SORT_PROTOCOL:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (op1->protocol > op2->protocol)
	cond++;
    } else {
      if (op1->protocol < op2->protocol)
	cond++;
    }
    break;
  case SORT_SOURCEHOST:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (compare_ipv6(&op1->shost, &op2->shost) == -1)
	cond++;
    } else {
      if (compare_ipv6(&op1->shost, &op2->shost) == 1)
	cond++;
    }
    break;
  case SORT_SOURCEPORT:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (op1->sport > op2->sport)
	cond++;
    } else {
      if (op1->sport < op2->sport)
	cond++;
    }
    break;
  case SORT_DESTHOST:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (compare_ipv6(&op1->dhost, &op2->dhost) == -1)
	cond++;
    } else {
      if (compare_ipv6(&op1->dhost, &op2->dhost) == 1)
	cond++;
    }
    break;
  case SORT_DESTPORT:
    if (opt.sortmode == ORDER_ASCENDING) {
      if (op1->dport > op2->dport)
	cond++;
    } else {
      if (op1->dport < op2->dport)
	cond++;
    }
  }

  return cond;
}

struct known_hosts *fwlw_hs_mergesort(struct known_hosts *list)
{
  struct known_hosts *p, *q, *e, *tail;
  int size, merges, psize, qsize, i;

  switch (opt.sortfield) {
  case SORT_COUNT:
  case SORT_START_TIME:
  case SORT_END_TIME:
  case SORT_PROTOCOL:
  case SORT_SOURCEHOST:
  case SORT_SOURCEPORT:
  case SORT_DESTHOST:
  case SORT_DESTPORT:
    if (list != NULL) {
      size = 1;
      while (1) {
	p = list;
	list = tail = NULL;
	merges = 0;
	while (p != NULL) {
	  merges++;
	  q = p;
	  psize = 0;
	  for (i = 0; i < size; i++) {
	    psize++;
	    q = q->next;
	    if (q == NULL)
	      break;
	  }
	  qsize = size;
	  while (psize > 0 || ((qsize > 0) && (q != NULL))) {
	    if (psize == 0) {
	      e = q;
	      q = q->next;
	      qsize--;
	    } else if (qsize == 0 || (q == NULL)) {
	      e = p;
	      p = p->next;
	      psize--;
	    } else if (hs_compare(p, q) <= 0) {
	      e = p;
	      p = p->next;
	      psize--;
	    } else {
	      e = q;
	      q = q->next;
	      qsize--;
	    }
	    if (tail != NULL) {
	      tail->next = e;
	    } else {
	      list = e;
	    }
	    tail = e;
	  }
	  p = q;
	}
	tail->next = NULL;
	if (merges <= 1)
	  return list;
	size *= 2;
      }
    } else {
      return NULL;
    }
    break;
  default:
    return list;
  }
}
