/*
 *  filter.c - Filtering mechanism
 *
 *  Copyright (C) 1998-1999 Hugo Haas
 *
 *  Added a function to do the DNS queries again if necessary
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 <stdlib.h>
#include <ctype.h>

#include <fnmatch.h>

#include <pthread.h>

#include <netdb.h>
#include <netinet/in.h>

#include "defines.h"
#include "filter.h"
#include "netutils.h"
#include "configuration.h"

struct filter_entry *filter = NULL;

#ifdef FILTER_DEBUG
#include "log.h"
extern struct loginfo log;
#endif

/* Configuration variables */

extern unsigned short use_ident;
extern unsigned short resolve_protocols;
extern unsigned short icmp_format;
extern unsigned short tcp_format;
extern unsigned short udp_format;
extern unsigned short log_closing;

/* Mutexes: if the filter has to be updated, no filtering */
pthread_mutex_t r_mux = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t w_mux = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t w_cond = PTHREAD_COND_INITIALIZER;
int readers = 0;

/*
 * display_info
 *
 * Show info structure (Debug)
 */

#ifdef FILTER_DEBUG
void display_info(struct log_info *info, int entries) {

  log.log(log.level_or_fd, "DBG: (e:%d) log:%d ident:%d resolve:%d closing:%d format:%d", entries, info->log, info->ident, info->resolve, info->logclosing, info->logformat);
}
#endif

/*
 * check_range
 *
 * Check if a number is in a particular range
 */

int check_range(const __u16 number, const long min, const long max, const short protocol) {
  __u16 n;

  if ((min == NOT_DEFINED) && (max == NOT_DEFINED))
    return TRUE;

  if (protocol != IPPROTO_ICMP)
    n = ntohs(number);
  else
    n = number;

  if (min == NOT_DEFINED) {
    if (n <= max)
      return TRUE;
  }
  else {
    if (max == NOT_DEFINED) {
      if (n >= min)
	return TRUE;
    }
    else {
      if ((n <= max) && (n >= min))
	return TRUE;
    }
  }

  return FALSE;
}

/*
 * check_address
 *
 * Check if an address is described by address/mask
 */

int check_address(const __u32 host, const __u32 address, const __u32 mask, char *name) {

  if (address == 0) {
    if (name == NULL)
      return TRUE;
    else
      return FALSE;
  }

  if (((address ^ host) & mask) == 0)
    return TRUE;

  return FALSE;
}

/*
 * check_name
 *
 * Checks if a hostname matches a pattern
 */

int check_name(__u32 address, const char *pattern) {
  char remote_host[HOST_LENGTH];

  *remote_host = '\0';
  host_lookup(remote_host, address);

  if (!(pattern && *pattern))
    return TRUE;

  if (!*remote_host)
    return FALSE;

  if (!fnmatch(pattern, remote_host, FNM_NOESCAPE))
    return TRUE;

  return FALSE;
}

/*
 * do_log
 * plus mutex handlers
 *
 * Returns TRUE if the packet needs to be logged, FALSE otherwise
 */

void add_reader(void) {

  pthread_mutex_lock(&r_mux);
  readers++;
  pthread_mutex_unlock(&r_mux);
}

void remove_reader(void) {

  pthread_mutex_lock(&r_mux);
  readers--;
  if (readers == 0)
    pthread_cond_signal(&w_cond);
  pthread_mutex_unlock(&r_mux);
}

void set_defaults(int protocol, struct log_info *info) {
  if (info->logformat == -1) {
    switch (protocol) {
    case IPPROTO_ICMP:
      info->logformat = icmp_format;
      break;
    case IPPROTO_TCP:
      info->logformat = tcp_format;
      break;
    case IPPROTO_UDP:
      info->logformat = udp_format;
      break;
    }
  }
  if (info->resolve == -1) {
    switch (protocol) {
    case IPPROTO_ICMP:
      info->resolve = resolve_protocols & RUN_ICMP;
      break;
    case IPPROTO_TCP:
      info->resolve = resolve_protocols & RUN_TCP;
      break;
    case IPPROTO_UDP:
      info->resolve = resolve_protocols & RUN_UDP;
      break;
    }
  }
}

struct log_info do_log(const __u32 from, const __u32 to, const __u16 type, const __u16 srctype, const short protocol) {
  struct filter_entry *p;
  struct log_info info;
#ifdef FILTER_DEBUG
  int entries = 0;
#endif

  pthread_mutex_lock(&w_mux);

  add_reader();

  pthread_mutex_unlock(&w_mux);

  for(p = filter; p != NULL; p = p-> next) {

#ifdef FILTER_DEBUG
    entries++;
#endif

    if ((protocol == IPPROTO_ICMP && !(p->protocol & RUN_ICMP)) ||
        (protocol == IPPROTO_TCP && !(p->protocol & RUN_TCP)) ||
        (protocol == IPPROTO_UDP && !(p->protocol & RUN_UDP))) continue;

#ifdef FILTER_DEBUG
    log.log(log.level_or_fd, "DBG: protocol:%d matches p->protocol:%d", protocol, p->protocol);
#endif

    if (protocol != IPPROTO_ICMP) {
      if (check_range(type, p->loginfo.portranges.dst.range_min,
                      p->loginfo.portranges.dst.range_max, protocol) &&
          check_range(srctype, p->loginfo.portranges.src.range_min,
                      p->loginfo.portranges.src.range_max, protocol) &&
          check_address(from, p->fromdesc.address, p->fromdesc.mask,
                        p->fromdesc.hostname) &&
          (p->fromdesc.hostmask == NULL ||
           check_name(from, p->fromdesc.hostmask)) &&
          check_address(to, p->todesc.address, p->todesc.mask,
                        p->todesc.hostname)) {
        remove_reader();
        info.log = p->log;
        info.ident = p->ident;
        info.resolve = p->resolve;
        info.logformat = p->logformat;
        info.logclosing = p->logclosing;
        set_defaults(protocol, &info);
#ifdef FILTER_DEBUG
        display_info(&info, entries);
#endif
        return info;
      }
    } else {
      if (((p->loginfo.icmptype == NO_ICMP_TYPE)
           || type == p->loginfo.icmptype) &&
          check_address(from, p->fromdesc.address, p->fromdesc.mask,
                        p->fromdesc.hostname) &&
          (p->fromdesc.hostmask == NULL ||
           check_name(from, p->fromdesc.hostmask)) &&
          check_address(to, p->todesc.address, p->todesc.mask,
                        p->todesc.hostname)) {
        remove_reader();
        info.log = p->log;
        info.ident = p->ident;
        info.resolve = p->resolve;
        info.logformat = p->logformat;
        set_defaults(protocol, &info);
#ifdef FILTER_DEBUG
        display_info(&info, entries);
#endif
        return info;
      }
    }
  }

  remove_reader();
  
  info.log = TRUE;
  info.ident = use_ident;
  info.logclosing = log_closing;
  info.logformat = info.resolve = -1;
  set_defaults(protocol, &info);

#ifdef FILTER_DEBUG
  display_info(&info, entries);
#endif
  return info;
}

/*
 * refresh_filter
 *
 * Used to periodically do DNS queries for entries in the filter
 */

void refresh_filter() {
  struct filter_entry *p;
  struct hostent * host;

  pthread_mutex_lock(&w_mux);

  pthread_mutex_lock(&r_mux);
  while (readers > 0)
    pthread_cond_wait(&w_cond, &r_mux);

  for(p = filter; p != NULL; p = p-> next) {
    if (p->fromdesc.hostname != NULL) {
      if ((host = gethostbyname((const char *) p->fromdesc.hostname)) == 0) {
        p->fromdesc.address = 0;
        p->fromdesc.mask = 0;
      }
      else {
        memcpy(&(p->fromdesc.address), host->h_addr, host->h_length);
        p->fromdesc.mask = 0xffffffff;
      }
    }
  }

  pthread_mutex_unlock(&r_mux);

  pthread_mutex_unlock(&w_mux);
}

/*
 * destroy_filter
 *
 * Purge the filter
 */

void destroy_filter() {
  struct filter_entry *p;

  /* The mutexes should not be necessary, but let's lock them just in case */
  pthread_mutex_lock(&w_mux);
  pthread_mutex_lock(&r_mux);
  while (readers > 0)
    pthread_cond_wait(&w_cond, &r_mux);

  while ((p = filter) != NULL) {
    filter = filter->next;
    if (p->fromdesc.hostname != NULL)
      free(p->fromdesc.hostname);
    if (p->fromdesc.hostmask != NULL)
      free(p->fromdesc.hostmask);
    if (p->todesc.hostname != NULL)
      free(p->todesc.hostname);
    if (p->todesc.hostmask != NULL)
      free(p->todesc.hostmask);
    free(p);
  }

  pthread_mutex_unlock(&r_mux);
  pthread_mutex_unlock(&w_mux);
}
