/* Josh Pieper, (c) 2000 */

/* This file is distributed under the GPL, see file COPYING for details */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif

#include "conf.h"
#include "host.h"
#include "gnut_net.h"
#include "Gnut_List.h"
#include "Gnut_Hash.h"

Gnut_Hash *host_list=NULL;
uint hosts_num = 0;
uint64 hosts_files = 0;
uint64 hosts_size = 0;

#ifndef PTHREADS_DRAFT4
pthread_mutex_t host_mutex=PTHREAD_MUTEX_INITIALIZER;
#else
pthread_mutex_t host_mutex;
#endif

int host_class(uchar *ip)
{
  /* The VPN filtering needs to be conditional: If a VPN host
   * is found, we can try connecting to it and if successful, can use it.
   * But we shouldn't blindly add all VPN addresses because a lot come
   * in from other VPNs elsewhere on the Internet and those should be
   * ignored */
  if (ip[0] == 10) {
    /* Class A virtual private network */
    return VPN_A;
  }

  if (ip[0] == 172) {
    if ((ip[1] >= 16) && (ip[1] <= 31)) {
      /* Class B virtual private network */
      return VPN_B;
    }
  }

  if ((ip[0] == 192) && (ip[1] == 168)) {
    /* Class C virtual private network */
    return VPN_C;
  }

  if ((ip[0] == 169) && (ip[1] == 254)) {
    /* APIPA address */
    return VPN_P;
  }

  if (ip[0] == 127) {
    /* Local loopback range */
    return BAD_ADDR;
  }

  if (ip[0] == 0) {
    /* class A network 0 -- old-style broadcast */
    return BAD_ADDR;
  }

  if ((ip[0] == 192) && (ip[1] == 0) && (ip[2] == 2)) {
    /* TEST-NET */
    return BAD_ADDR;
  }

  if (ip[0] >= 224) {
    /* Class D multicast and class E reserved
     * %%% not sure about this -- parts of this range might be another type
     * of VPN address within certain institutional networks */
    return BAD_ADDR;
  }

  return 0;
}

int can_connect(uchar *from_ip, uchar *to_ip)
{
  if (gc_no_rfc_1597) {
    return 1;
  }
  if (host_class(to_ip)) {
    /* to_ip is a VPN address: they're connectable only if in the same
     * VPN class as from_ip (this happens when both are within the same
     * institution's private IP network) */
    if (host_class(from_ip) & host_class(to_ip)) {
      return 1;
    }
    return 0;
  }
  /* Else: to_ip is a normal address */
  return 1;
}

int host_ok_for_get(uchar *ip)
{
  return(can_connect(net_local_ip(), ip));
}

int host_ok_for_push(uchar *ip)
{
  return(can_connect(ip, net_local_ip()));
}

int host_ok_for_either(uchar *ip)
{
  return(can_connect(net_local_ip(), ip) || can_connect(ip, net_local_ip()));
}

int host_ok_for_both(uchar *ip)
{
  return(can_connect(net_local_ip(), ip) && can_connect(ip, net_local_ip()));
}

int host_ok_for_neither(uchar *ip)
{
  if(can_connect(net_local_ip(), ip) || can_connect(ip, net_local_ip())) {
	return 0;
  }
  return 1;
}

uchar host_hash(void *a)
{
  int i;
  uchar b=0;
  
  host_entry *he;
  he=a;
  
  for (i=0;i<4;i++) {
    b ^= he->ip[i];
  }
  b ^= he->port[0];
  b ^= he->port[1];
  return b;
}

int host_compare(void *a, void *b)
{
  host_entry *he1,*he2;
  he1=a;
  he2=b;

  if ((memcmp(he1->ip,he2->ip,4)==0) && (memcmp(he1->port,he2->port,2)==0)) {
    return 0;
  }
  return -1;
}

int host_init()
{
  gd_s(3, "host_init entering\n");
  host_list = gnut_hash_new(host_hash, host_compare);
  return 0;
}

/* host_restore loads the host list file (usually ~/.gnut_hosts)
 * by calling host_add() on each entry to add the host IPs to the
 * host IP hashtable. */
int host_restore(char *fname)
{
  FILE *fp;
  host_entry *he;
  char buf[40];
  int a[4];
  char *ptr;
  uint16 st;
  
  gd_s(2, "host_restore entering\n");

  fp=fopen(fname,"r");
  
  if (fp == 0) {
    /* perror("host_restore, fopen"); */
    gd_s(1, "host_restore couldn't open file: ");
    gd_s(1, fname);
    gd_s(1, "\n");
    return -1;
  }
  
  host_clear();
  
  gd_s(3, "host_restore host_list=");
  gd_p(3, host_list);
  gd_s(3, "\n");
  
  gd_s(2,"host_restore starting to read hosts...\n");
  while (fgets(buf,sizeof(buf),fp)) {
    he=(host_entry *) calloc(sizeof(host_entry),1);
    ptr=buf;
    if ((ptr=strchr(buf,' '))) {
      *(ptr++)=0;
    } else if ((ptr=strchr(buf,':'))) {
      *(ptr++)=0;
    } else {
      printf("In ~/.gnut_hosts, ignoring %s\n",buf);
      continue;
    }
    
    if (4 != sscanf(buf,"%i.%i.%i.%i",&a[0],&a[1],&a[2],&a[3])) {
      printf("In ~/.gnut_hosts, no ip in %s %s",buf,ptr);
      continue;
    }
    
    if (0 == (st=atoi(ptr)) && (*ptr != '0')) {
      printf("In ~/.gnut_hosts, no port in %s %s\n",buf,ptr);
      continue;
    }
    
    he->ip[0]=a[0];
    he->ip[1]=a[1];
    he->ip[2]=a[2];
    he->ip[3]=a[3];
    st=atoi(ptr);
    st=GUINT16_TO_LE(st);
    memcpy(he->port,&st,2);
	
    if (host_ok_for_gnet(he->ip)) {
      host_add(he);
    }
  }
  
  fclose(fp);
  
  printf("Got %i addresses from hosts file.\n", hosts_num);
  
  gd_s(2,"host_restore returning\n");
  return 0;
}

int host_write_line(void *a, void *b)
{
  host_entry *he;
  FILE *fp;
  uint16 st;
  
  he=a;
  fp=b;
    
  memcpy(&st,he->port,2);
  st=GUINT16_FROM_LE(st);
  fprintf(fp,"%i.%i.%i.%i %i\n",he->ip[0],he->ip[1],he->ip[2],he->ip[3],st);
  return 0;
}

int host_save(char *fname)
{
  FILE *fp;
  
  /* if nothing to save, we want to keep old list */
  if (!hosts_num)
	return 0;
  
  unlink(fname);
  
  fp=fopen(fname,"w");
  if (fp==NULL)
	return -1;
  
  gd_s(3, "host_save opened file\n");
  
  pthread_mutex_lock(&host_mutex);
  
  gnut_hash_foreach(host_list,host_write_line,fp);
  gd_s(3, "host_save locked mutex\n");
  
  pthread_mutex_unlock(&host_mutex);
  
  fclose(fp);
  return 0;
}

int host_remove(uchar ip[4], uchar port[2])
{
  host_entry he, *he2;
  int i;
  
  gd_s(1,"host_remove entering ");
  gd_i(1, ip[0]);
  gd_s(1, ".");
  gd_i(1, ip[1]);
  gd_s(1, ".");
  gd_i(1, ip[2]);
  gd_s(1, ".");
  gd_i(1, ip[3]);
  gd_s(1, "\n");
  
  pthread_mutex_lock(&host_mutex);
  
  memcpy(he.ip, ip, 4);
  memcpy(he.port, port, 2);

  he2=gnut_hash_find(host_list,&he);
  if (!he2) {
    pthread_mutex_unlock(&host_mutex);
	/* This happens if the same host was connected twice and both
	 * connections close */
    gd_s(1, "host_remove  host not found!\n");
    return -1;
  }
  
  i=gnut_hash_remove(host_list,he2);
  hosts_num--;
  
  pthread_mutex_unlock(&host_mutex);
  
  return 0;
}

int host_totals(uint *num, uint64 *files, uint64 *size)
{
  gd_s(2, "host_totals num=");
  gd_i(2, hosts_num);
  gd_s(2, " files=");
  gd_li(2, hosts_files);
  gd_s(2, " size=");
  gd_li(2, hosts_size);
  gd_s(2, "\n");
  *num = hosts_num;
  *files = hosts_files;
  *size = hosts_size;
  return 0;
}

int host_add(host_entry *he)
{
  gd_s(3, "host_add entering host_list=");
  gd_p(3, host_list);
  gd_s(3, " he=");
  gd_p(3, he);
  gd_s(3, "\n");

  pthread_mutex_lock(&host_mutex);
  
  gnut_hash_insert(host_list,he);
  
  hosts_num++;
  hosts_files += he->files;
  hosts_size += (he->bytes/1024);
  
  pthread_mutex_unlock(&host_mutex);
  
  gd_s(3,"host_add returning\n");
  return 0;
}

int host_find(uchar ip[4], uchar port[2])
{
  host_entry he,*ret;
  gd_s(3,"host_find entering ");
  gd_i(3, ip[0]);
  gd_s(3, ".");
  gd_i(3, ip[1]);
  gd_s(3, ".");
  gd_i(3, ip[2]);
  gd_s(3, ".");
  gd_i(3, ip[3]);
  gd_s(3, "\n");
  
  pthread_mutex_lock(&host_mutex);
  
  memcpy(he.ip,ip,4);
  memcpy(he.port,port,2);
  
  ret=gnut_hash_find(host_list,&he);
  
  pthread_mutex_unlock(&host_mutex);
  
  if (ret) {
    gd_s(2,"found ip ");
    gd_i(2, ret->ip[0]);
    gd_s(2, ".");
    gd_i(2, ret->ip[1]);
    gd_s(2, ".");
    gd_i(2, ret->ip[2]);
    gd_s(2, ".");
    gd_i(2, ret->ip[3]);
    gd_s(2, "\n");
    return 0;
  }
  return -1;
}

int host_clear()
{
  pthread_mutex_lock(&host_mutex);
  
  gd_s(3, "host_clear entering\n");
  gnut_hash_free(host_list);
  
  host_list=gnut_hash_new(host_hash,host_compare);
  
  gd_s(3, "host_clear new host_list=");
  gd_p(3, host_list);
  gd_s(3, "\n");
    
  hosts_num = 0;
  hosts_files = 0;
  hosts_size = 0;
  
  pthread_mutex_unlock(&host_mutex);
  return 0;
}

int host_lock()
{
  pthread_mutex_lock(&host_mutex);
  return 0;
}

int host_unlock()
{
  pthread_mutex_unlock(&host_mutex);
  return 0;
}

Gnut_Hash * host_retrieve()
{
  /*  gd_s(-1,"BAD BAD BAD!!!\n"); */
  return host_list;
}
