/*
 *  netutils.c - functions common to all the protocols
 *
 *  Copyright (C) 1998-1999 Hugo Haas
 *
 *  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 <netdb.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <ctype.h>

#include <pthread.h>

#include "defines.h"
#include "netutils.h"
#include "log.h"

extern struct loginfo log;

/* Mutexes */
pthread_mutex_t service_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t dns_mutex = PTHREAD_MUTEX_INITIALIZER;

/*
 * host_lookup
 *
 * Performs a DNS query with caching
 *
 * Cache uses a hastable with open adressing and double hashing.
 * It has LEVELS levels. Each entry has a dirty flag to help to decide
 * which entry should be discarded if all the levels are occupied.
 *
 * Some results when run on a LAN:
 * Tue Feb  2 19:00:36 DEBUG cache: 910 calls - 86 misses
 *
 * Yes, I know, it is incredible! :-)
 * -- Hugo (2/2/99)
 */

#ifdef CACHE_DEBUG
unsigned int dbg_calls = 0, dbg_missed = 0;
#define DUMP_EVERY 10
#endif

/* Hashtable */

struct ht_entry_struct {
  __u32 ip;
  char *name;
  short dirty;
};
typedef struct ht_entry_struct ht_entry;
ht_entry table[TABLE_SIZE];

void initialize_dns_cache(void) {
  int i;
#ifdef CACHE_DEBUG
  int dbg_freed = 0;
#endif

  /* Tell that we should use UDP for resolving, and not TCP */
  sethostent(0);

  pthread_mutex_lock(&dns_mutex);

  for(i = 0; i < TABLE_SIZE; i++) {
    table[i].ip = 0;
    if (table[i].name != NULL) {
      free(table[i].name);
      table[i].name = NULL;
#ifdef CACHE_DEBUG
      dbg_freed++;
#endif
    }
  }

  pthread_mutex_unlock(&dns_mutex);

#ifdef CACHE_DEBUG
  log.log(log.level_or_fd, "DEBUG cache: cleared %d entries", dbg_freed);
#endif
}

int perform_lookup(char *host, __u32 in) {
  struct in_addr i;
  struct hostent *he;

  i.s_addr = in;
  he = gethostbyaddr((char *) &i, sizeof(struct in_addr), AF_INET);
  if (he) {
    strncpy(host, he->h_name, HOST_LENGTH-24);
    return TRUE;
  }
  return FALSE;
}

int host_lookup(char *host, __u32 in) {
  __u16 key, key1, key2, level;
  int res;

  pthread_mutex_lock(&dns_mutex);

#ifdef CACHE_DEBUG
  if (dbg_calls % DUMP_EVERY == 0)
    log.log(log.level_or_fd, "DEBUG cache: %d calls - %d misses", dbg_calls, dbg_missed);

  dbg_calls++;
#endif

  /* First pass: see if the value is stored or do a lookup if a slot is empty */

  key1 = HASH1(in);
  key2 = HASH2(in);
  key = key1;
  level = 0;
  do {
    if (table[key].ip == 0) {
      goto lookup;
    }
    if (table[key].ip == in) {
      strcpy(host, table[key].name);
      table[key].dirty = FALSE;
      goto exit_hl;
    }
    key = (key + key2) % TABLE_SIZE;
  } while(++level == LEVELS);

  /* If we are here, it means that all the slots are full and none of them contains the searched item */
  /* Second pass: look for an entry to replace; take the first dirty one */
  key = key1;
  level = 0;
  do {
    if (table[key].dirty == TRUE) {
      goto lookup;
    }
    else {
      table[key].dirty = TRUE;
    }
    key = (key + key2) % TABLE_SIZE;
  } while(++level == LEVELS);

  /* Do a lookup and store the result in */
 lookup:
#ifdef CACHE_DEBUG
  dbg_missed++;
#endif
  res = perform_lookup(host, in);
#ifdef CACHE_DEBUG
  log.log(log.level_or_fd, "DEBUG cache: result of lookup - %s", host);
  log.log(log.level_or_fd, "DEBUG cache: replacing (%d; %d; %d; %s)", key, table[key].ip, table[key].dirty, table[key].name);
#endif
  /* Now host contains the correct value; store it in the table */
  table[key].ip = in;
  table[key].dirty = FALSE;
  if (table[key].name != NULL)
    free(table[key].name);
  table[key].name = strdup(host);

  pthread_mutex_unlock(&dns_mutex);
  return res;

 exit_hl:
  pthread_mutex_unlock(&dns_mutex);
  return TRUE;
}

/*
 * host_print
 *
 * Prints into hostname IP and optionaly DNS name
 */
void host_print(char *hostname, __u32 in, int resolve) {
  char remote_hostname[HOST_LENGTH];
  struct in_addr i;

  i.s_addr = in;
  *remote_hostname = '\0';

  if (resolve && host_lookup(remote_hostname, in)) {
    snprintf(hostname, HOST_LENGTH, "%s [%.20s]",
             remote_hostname, inet_ntoa(i));
  } else {
    snprintf(hostname, 21, "%.20s", inet_ntoa(i));
  }
}

/*
 * get_details
 *
 * Display details about a packet header (source & destination port & address)
 * Based on Steffen Ullrich's (ccrlphr@xensei.com) code
 * " (xxx.xxx.xxx.xxx:xxxxx->xxx.xxx.xxx.xxx:xxxxx)" if ports are non-zero
 * " (xxx.xxx.xxx.xxx->xxx.xxx.xxx.xxx)" if ports are zero
 */

void get_details(char *details,
                 const __u32 src_addr, const __u16 src_port,
                 const __u32 dst_addr, const __u16 dst_port) {
  int sz;
  struct in_addr i;

  i.s_addr = src_addr;
  if ((src_port == 0) && (dst_port == 0)) {
    sz = snprintf(details, DETAILS_LENGTH, " (%s->", inet_ntoa(i));
    i.s_addr = dst_addr;
    snprintf(details+sz, DETAILS_LENGTH-sz, "%s)", inet_ntoa(i));
  }
  else {
    sz = snprintf(details, DETAILS_LENGTH, " (%s:%d->", inet_ntoa(i), ntohs(src_port));
    i.s_addr = dst_addr;
    snprintf(details+sz, DETAILS_LENGTH-sz, "%s:%d)", inet_ntoa(i), ntohs(dst_port));
  }
}

/*
 * service_lookup
 *
 * Get a service name for a specified protocol
 */

void service_lookup(char *proto, char *service, __u16 port) {
  struct servent *se;

  pthread_mutex_lock(&service_mutex);
  se = getservbyport(port, proto);
  if (se == NULL)
    snprintf(service, SERVICE_LENGTH, "port %d", ntohs(port));
  else {
    snprintf(service, SERVICE_LENGTH, "%s", se->s_name);
  }
  pthread_mutex_unlock(&service_mutex);
}

