/* Josh Pieper, (c) 2000 */

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

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#ifndef WIN32
# include <sys/time.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <netinet/tcp.h>
# include <arpa/inet.h>
# include <sys/ioctl.h>
# include <unistd.h>
# include <net/if.h>
# include <netdb.h>
#endif

#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>
#include <regex.h>
#include <assert.h>

#include "conf.h"
#include "gnut_connection.h"
#include "gnut_lib.h"
#include "gnut_threads.h"
#include "gnut_if.h"
#include "gnut_net.h"
#include "gnut_ui.h"
#include "host.h"
#include "protocol.h"
#include "blacklist.h"

uchar local_ip[4];

void free_he(struct hostent **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

/* uint32 net_local_ip()
 *
 * returns the local ip address */
uchar *net_local_ip()
{
  return local_ip;
}

int gnut_net_host()
{
  char host[256];
  struct hostent *he;
  int ret;
  
  gd_s(2,"gnut_net_host entering\n");
  
  ret=gethostname(host,sizeof(host));
  
  if (ret<0) {
    perror("gnut_net_host, gethostname");
    return ret;
  }
  
  he=gethostbyname(host);
  if (he==0) {
    perror("gnut_net_host, gethostbyname");
    return ret;
  }
  
  memcpy(local_ip,he->h_addr_list[0],4);
  
  return 0;
}

/* int net_init(char *iface)
 *
 * Does misc. net init stuff.  Mostly just finds our local IP address */
int net_init(char *iface)
{
  struct sockaddr_in *sinptr = 0;
  
  gd_s(2, "net_init entering\n");
  
  if (iface) {
    if (inet_aton(iface,(struct in_addr *) local_ip)) {
      gd_s(1, "local was set by command to: ");
      gd_i(1, local_ip[0]);
      gd_s(1, ".");
      gd_i(1, local_ip[1]);
      gd_s(1, ".");
      gd_i(1, local_ip[2]);
      gd_s(1, ".");
      gd_i(1, local_ip[3]);
      gd_s(1, "\n");
      return 0;
    }
  }
  
  if ((sinptr = get_if_addr(iface)) == 0) {
    gd_s(1,"Can't get local IP address through interface, trying host name...\n");
    gnut_net_host();  
  } else {
    memcpy(local_ip,&(sinptr->sin_addr),sizeof(local_ip));
  }
  
  gd_s(1,"local ip is: ");
  gd_i(1, local_ip[0]);
  gd_s(1, ".");
  gd_i(1, local_ip[1]);
  gd_s(1, ".");
  gd_i(1, local_ip[2]);
  gd_s(1, ".");
  gd_i(1, local_ip[3]);
  gd_s(1, "\n");
  
  printf("VPN class %i\n", host_class(local_ip));
  
  return 0;
}

/* exactly like the real read, except that it returns -2
 * if no data was read before the timeout occurred... */
int timed_read(int sock, char *buf, int len, int secs)
{
  fd_set fsr;
  struct timeval tv;
  time_t a,b;
  int ret;
  int i=0;
  
  /* ret=fcntl(sock,F_SETFL,O_NONBLOCK); */

  a=time(0);
  b=time(0);
  
  do {
    if (i==1) usleep(100);
    FD_ZERO(&fsr);
    FD_SET(sock,&fsr);
    
    tv.tv_sec=secs-(b-a);
    tv.tv_usec=0;
    
    ret=select(sock+1, &fsr, 0, 0, &tv);
    if (ret<0) {
      perror("select");
    }
    
    if (ret==0) {
      return -2;
    }
    
    if (ret<0 && errno!=EINTR) {
      return ret;
    }
    b=time(0);
    i=1;
  } while (ret<0 && errno==EINTR && (b-a)<secs);
  
  if ((b-a)>secs) return -2;
  
  ret=read(sock,buf,len);
  
  /*  fcntl(sock,F_SETFL,0); */
  
  return ret;
}

/* exactly like the real recv, except that it returns -2
 * if no data was read before the timeout occurred... */
int timed_recv(int sock, char *buf, int len, int flags, int secs)
{
  fd_set fsr;
  struct timeval tv;
  time_t a,b;
  int ret;
  int i=0;
  
  /*  ret=fcntl(sock,F_SETFL,O_NONBLOCK); */
  
  a=time(0);
  b=time(0);
  
  do {
    if (i==1) {
      usleep(100);
    }
    FD_ZERO(&fsr);
    FD_SET(sock,&fsr);
    
    tv.tv_sec=secs- (b-a);
    tv.tv_usec=0;
    
    ret=select(sock+1, &fsr, 0, 0, &tv);
    
    if (ret==0) {
      return -2;
    }
    
    if (ret<0 && errno!=EINTR) {
      return ret;
    }
    b=time(0);
    i=1;
  } while (ret<0 && errno==EINTR && (b-a)<secs);

  if ((b-a)>secs) {
    return -2;
  }
  
  ret=recv(sock,buf,len,flags);
  
  /*  fcntl(sock,F_SETFL,0); */
  
  return ret;
}

/* timed_connect is just like connect except that it tells the
 * select(2) function to times out after "secs" seconds. returns -2 on
 * timeout, otherwise same as connect */
int timed_connect(int sockfd, struct sockaddr *sa, int addrlen, int secs,
		  gnut_transfer *gt)
{
  int ret;
  fd_set fsr;
  struct timeval tv;
  int val, len;
  int countdown, result;
 
  gd_s(1, "timed_conn entering sock=");
  gd_i(1, sockfd);
  gd_s(1, " secs=");
  gd_i(1, secs);
  gd_s(1, "\n");
  
  ret=fcntl(sockfd, F_SETFL, O_NONBLOCK);
  gd_s(5, "timed_conn fcntl returned ");
  gd_i(5, ret);
  gd_s(5, "\n");
  
  if (ret<0) {
    return ret;
  }

  ret=connect(sockfd, sa, addrlen);
  gd_s(5, "timed_conn connect returned ");
  gd_i(5, ret);
  gd_s(5, "\n");

#if 0
  if (ret==0) {
    /* wooah!  immediate connection */
    gd_s(0, "timed_conn immediate connection!\n");
    return -2;
  }
#endif
  
#if 0
  if (errno != EINPROGRESS) {
    perror("timed_conn, connect");
    return ret;
  }
#endif  

  FD_ZERO(&fsr);
  FD_SET(sockfd, &fsr);

  countdown = secs;
  if (countdown <= 0) {
    countdown = 1;
  }

  result = 0;
  while((result == 0) && (countdown > 0)) {
    tv.tv_sec = 1;
    tv.tv_usec = 0;

    ret=select(sockfd+1, 0, &fsr, 0, &tv);
    gd_s(5,"timed_conn select returned ");
    gd_i(5, ret);
    gd_s(5, "\n");

    if (ret==0) {
      /* timeout, keep on looping */
    } else {
      /* It connected -- make loop exit */
      result = 1;
    }

    /* Allow user to stop the connection */
    if (gt) {
      if (gt->state != STATE_CONNECTING) {
	if (gc_debug_opts & 8) {
	  printf("timed_conn detect cancel by user\n");
	}
	result = 2;
      }
    }

    countdown--;
  }

  if (result == 0) {
    /* timeout */
    gd_s(1, "timed_conn timeout\n");
    fcntl(sockfd, F_SETFL, 0);
    return -2;
  } else if (result == 2) {
    /* User forced connection to cancel */
    return -1;
  }

  len=4;
  ret=getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &val, &len);
  gd_s(5,"timed_conn getsockopt returned ");
  gd_i(5, ret);
  gd_s(5," val=");
  gd_i(5, val);
  gd_s(5, "\n");

  if (ret < 0) {
    gd_s(1,"timed_conn getsockopt returned: ");
    gd_i(1, ret);
    gd_s(1, "\n");
    return ret;
  }

  if (val != 0) {
    gd_s(3,"timed_conn returning failure!\n");
    return -1;
  }

  ret=fcntl(sockfd, F_SETFL, 0);

  gd_s(1,"timed_conn fcntl: ");
  gd_i(1, ret);
  gd_s(1, "\n");
  
  gd_s(3,"timed_conn returning success val=");
  gd_i(3, val);
  gd_s(3, "\n");
  return 0;
}

/* int create_listen_socket(int * port)
 *
 * attempts to create a socket to listen on port
 * returns the actual port bound to in port or <0 if not
 * successful */
int create_listen_socket(int *port)
{
  struct sockaddr_in sin;
  int sock;
  int ret,i;
  int val;
  
  gd_s(3, "create_listen_socket entering\n");
  
  sock=socket(AF_INET, SOCK_STREAM, 6);
  if (sock<0) {
    perror("create_listen_socket, socket");
    return sock;
  }
  
  val=1;
  ret=setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));

  /* Turn off the Nagle "tinygram prevention" algorithm. (CURRENTLY
   * INSIDE IF(0))
   * 
   * (Background: The Nagle algorithm is an option that applies to any
   * TCP connection. It causes data to be grouped into large blocks
   * before being sent, unless the other end is known to be waiting
   * for more data. The other end is waiting for data if we have
   * received ACKs for al as-yet-sent packets.)
   *
   * By turning off Nagle, we enable the sending of small packets even
   * when the other end has not yet acknowledged all data we have sent
   * it.
   *
   * Someone suggested this change, and I subsequently discovered that
   * it does not actually benefit anything except the startup time for
   * GnutellaNet connections -- so it is here but disabled. */
  if (0) {
    int flag;
	
    flag = 1;
    ret = setsockopt (sock,              /* socket affected */
		      IPPROTO_TCP,       /* set option at TCP level */
		      TCP_NODELAY,       /* name of option */
		      (char *) &flag,    /* the cast is historical cruft */
		      sizeof (int));      /* length of option value */
    if (ret<0) {
      perror ("setsockopt() - TCP_NODELAY");
      return (ret);
    }
  }
  
  i=0;
  ret=-1;
  while (ret<0) {
    sin.sin_addr.s_addr=INADDR_ANY;
    sin.sin_port=htons(*port);
    sin.sin_family=AF_INET;
    ret=bind(sock,(struct sockaddr *) &sin,sizeof(struct sockaddr_in));
    if (++i>50) {
      (*port)++;
      i=0;
    }
  }
  if (ret<0) {
    perror("create_listen_socket, bind");
    return ret;
  }
  
  ret=listen(sock,5);
  if (ret<0) {
    perror("create_listen_socket, listen");
    return ret;
  }
  
  gd_s(1, "create_listen_socket bound to port ");
  gd_i(1, *port);
  gd_s(1, "\n");
  
  /* printf("Bound to port: %i\n",*port); */
  
  ret=fcntl(sock,F_SETFL,O_NONBLOCK);
  if (ret<0) {
    perror("create_listen_socket, fcntl");
    return ret;
  }
  
  return sock;
}
      
/* int read_line(int sock, char *buf, int len)
 *
 * reads a line from sock into buf, not exceeding len chars
 * returns 0 on EOF, -1 on error, or num of characters read
 * and -2 if buffer length exceeded */
int read_line(int sock, char *buf, int len)
{
  int i,ret;
  char c;
  char *ptr;
  
  ptr=buf;
  gd_s(3,"read_line entering\n");
  fcntl(sock,F_SETFL,0);
  
  buf[0]=0;
  
  for (i=1;i<len;i++) {
  try_again:
    
    ret=timed_read(sock, &c, 1, 30);
    
    if (ret==1) {
      *ptr++=c;
      if (c=='\n') {
	break;
      }
    } else if (ret==0) {
      if (i==1) {
        return 0;
      } else {
	break;
      }
    } else {
      if (errno==EAGAIN) {
        goto try_again;
      }
      if (errno==EINTR) {
        goto try_again;
      }
      
      if (gnut_lib_debug>=1) {
	perror("read_line, read");
      }
      return -1;
    }
  }
  buf[i]=0;
  
  gd_s(3,"read_line returning success\n");
  return i;
}    

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

struct hostent *gnut_hostent_copy(struct hostent *he)
{
  struct hostent *n;
  int i,len;
  
  if (!he) return NULL;
  
  n=(struct hostent *) calloc(sizeof(struct hostent),1);
  
  memcpy(n,he,sizeof(struct hostent));
  
  if (he->h_name) n->h_name=strdup(he->h_name);
  
  for (len=0;he->h_addr_list[len];len++);
  n->h_addr_list=(char **) calloc(sizeof(char *) * (len+1),1);
  for (i=0;i<len;i++) {
    n->h_addr_list[i]=xmalloc(4);
    memcpy(n->h_addr_list[i],he->h_addr_list[i],4);
  }
  
  return n;
}

void gnut_hostent_delete(struct hostent *he)
{
  int i, len;

  gd_s(1,"gnut_hostent_delete entering\n");

  for (len=0; he->h_addr_list[len]; len++) ;

  gd_s(1, "gnut_hostent_delete len=");
  gd_i(1, len);
  gd_s(1, "\n");
  for (i=0;i<len;i++) {
    gd_s(1, "gnut_hostent_delete deleting i=");
    gd_i(1, i);
    gd_s(1, "\n");
    if (he->h_addr_list[i]) {
      free_str(&(he->h_addr_list[i]), 257);
    }
  }
  
  gd_s(1,"gnut_hostent_delete freeing h_name\n");
  if (he->h_name) {
    free_str(&(he->h_name), 258);
  }
  
  gd_s(1,"gnut_hostent_delete freeing h_addr_list\n");
  free_strl(&(he->h_addr_list), 259);
  
  gd_s(1,"gnut_hostent_delete freeing he\n");
  free_he(&he, 260);
  gd_s(1,"gnut_hostent_delete returning\n");
}
  

/* creates a socket, and attempts to make a connection to the host
 * named. It tries multiple resolved ip addresses if necessary,
 * returning the socket on success and storing the good ip number in ip
 * otherwise it returns < 0
 *
 * Notes: due to the fact that gethostbyname is not thread safe
 * we need to protect this function with a mutex  */
int make_connection(uchar *host, uint port, uchar ip[4], int for_transfer,
		    int type, gnut_transfer *gt, int *retry_enable)
{
  struct hostent *he;
  struct sockaddr_in sin;
  int i,sock=-1,ret;
  int timeout;
  char buf_ip[100];
  
  gd_s(1,"make_conn entering\n");
  
  pthread_mutex_lock(&make_conn_mutex);
  
  he=gnut_hostent_copy(gethostbyname(host));
  if (he==0) {
    gd_s(1, "make_conn No such host: ");
    gd_s(1, host);
    gd_s(1, "\n");
    pthread_mutex_unlock(&make_conn_mutex);
    return -1;
  }

  pthread_mutex_unlock(&make_conn_mutex);

  if (!gnut_blacklist_allow(BLACKLIST_TYPE_OUTGOING, 0, &(sin.sin_addr),
			    htons(port))) {
    return -1;
  }

  for (i=0, ret=-1; (he->h_addr_list[i]) && (ret < 0); i++) {
    sprintf(buf_ip, "%i.%i.%i.%i:%i",
	    (uchar) he->h_addr_list[i][0], (uchar) he->h_addr_list[i][1],
	    (uchar) he->h_addr_list[i][2], (uchar) he->h_addr_list[i][3],
	    port);
    gd_s(1, "make_conn trying host ");
    gd_s(1, buf_ip);
    gd_s(1, "\n");
    sock = socket(AF_INET, SOCK_STREAM, 6);
    if (sock<0) {
      perror("make_conn, socket");
      gnut_hostent_delete(he);
      return sock;
    }
	
    sin.sin_family=AF_INET;
    sin.sin_port=htons(port);
    memcpy(&sin.sin_addr.s_addr, he->h_addr_list[i], 4);
	
    timeout = 10;
    if (type == MT_FOR_DOWNLOAD) {
      timeout = conf_get_int("download_timeout");
    } else if (type == MT_FOR_PUSH) {
      timeout = 120;
    }
    ret=timed_connect(sock, (struct sockaddr *) &sin,
		      sizeof(struct sockaddr_in), timeout, gt);
    gd_s(5, "make_conn timed_conn returned: ");
    gd_i(5, ret);
    gd_s(5, "\n");
    if (ret<0) {
      if (for_transfer) {
	if (type == MT_FOR_PUSH) {
	  if (gc_debug_opts & 1) {
	    printf(UI_NT_CONN_FOR_PUSH);
	    printf(UI_NT_HOST_REFUSED, buf_ip);
	  }
	} else if (type == MT_FOR_DOWNLOAD) {
	  if(conf_get_int("verbose")&1) {
	    printf(UI_NT_CONN_FOR_DOWN);
	    printf(UI_NT_HOST_REFUSED, buf_ip);
	  }
	}
      }
      gd_s(1,"make_conn host bad, closing\n");
      close(sock);
    } else {
      /* Successful */
      break;
    }
  }

  if (gt && (gt->state != STATE_CONNECTING)) {
    if(gt->type == TYPE_RECV_PUSH) {
      if (conf_get_int("verbose") & 2) {
	printf("Detect PUSH connection stopped while opening\n");
      }
    } else {
      if (conf_get_int("verbose") & 1) {
	printf("Detect GET connection stopped while opening\n");
      }
    }
    if (retry_enable) {
      if (*retry_enable) {
	*retry_enable = 0;
      }
    }
    ret = -1;
    close(sock);
  }

  if ((ret < 0) || (he->h_addr_list[i] == 0)) {
    gd_s(1,"make_conn returning failure\n");
    gnut_hostent_delete(he);
    return -2;
  }
  gd_s(5, "make_conn about to copy ip from slot ");
  gd_i(5, i);
  gd_s(5, "\n");
  
  memcpy(ip, he->h_addr_list[i], 4);
  
  gd_s(4,"make_conn trying to unlock mutex\n");
  
  gnut_hostent_delete(he);
  
  gd_s(1,"make_conn returning success\n");
  
  return sock;
}

/* int send_packet (int sock, gnutella_packet *gpa)
 *
 * sends the packet described by gpa over socket sock
 * return 0 on success or <0 for error */
int send_packet(int sock, gnutella_packet *gpa)
{
  int ret;
  int t;

  gd_s(3,"send_packet entering\n");

  ret=write(sock,(char *) &gpa->gh, sizeof(gnutella_hdr));
  if (ret<0) {
    if (gnut_lib_debug>3)
	  perror("send_packet, write header");
    return ret;
  }
  
  memcpy(&t,gpa->gh.dlen,4);
  t=GUINT32_FROM_LE(t);
  
  if (t>0) {
    ret=write(sock,gpa->data,t);
    if (ret<0) {
      if (gnut_lib_debug>3) 
        perror("send_packet, write data");
      return ret;
    }
  }
  gd_s(3,"send_packet returning success\n");
  
  return 23+t;
}

/* int read_packet(int sock, gnutella_packet *gpa)
 *
 * reads a packet from the socket sock into the packet
 * structure of gpa.
 * returns 0 on success or <0 on error */
int read_packet(int sock, gnutella_packet *gpa)
{
  int ret;
  char *ptr;
  int left;
  uint dlen;
  int i;
  
  gd_s(3, "read_packet entering\n");
  
  ptr=(char *) &gpa->gh;
  for (left=sizeof(gnutella_hdr);left>0;) {
    ret=timed_read(sock,ptr,left,10);
    gd_s(6, "read_packet timed_read returned: ");
    gd_i(6, ret);
    gd_s(6, "\n");
    if (ret==-2) return 0;
    if (ret==0) {
      return -2;
    } else if (ret<0) {
      if (errno!=EINTR) {
        if (gnut_lib_debug>3) {
	  perror("read_packet, header");
	}
        return ret;
      } else ret=0;
    }
    
    ptr+=ret;
    left-=ret;
  }
  
  assert(left==0);
  memcpy(&dlen,gpa->gh.dlen,4);
  dlen=GUINT32_FROM_LE(dlen);
  
  while (dlen>32767) {
    /* We're out of sync!  I'm going to do large reads until
     * one returns 23, which is probably a ping packet.... */
    gd_s(2, "read_packet out of sync! func=");
    gd_i(2, gpa->gh.func);
    gd_s(2, " dlen=");
    gd_i(2, dlen);
    gd_s(2, "\n");
    ptr=(char *) xmalloc(100);
    
    ret=1;
    i=0;
    while (ret>0 && ret!=23 && ret!=49) {
      ret=timed_read(sock,ptr,60,2);
      gd_s(6, "read_packet timed_read returned: ");
      gd_i(6, ret);
      gd_s(6, "\n");
      if (ret==-2 || (++i>5)) {
        free_str(&ptr, 261);
        return 0;
      }
    }
    
    if (ret<=0) {
      free_str(&ptr, 262);
      return ret;
    }
    
    memcpy(&gpa->gh,ptr,23);
    memcpy(&dlen,gpa->gh.dlen,4);
    dlen=GUINT32_FROM_LE(dlen);
    free_str(&ptr, 263);
  }
  
  if (dlen>0) {
    gpa->data=(char *) calloc(dlen,1);
    ptr=gpa->data;
    for (left=dlen;left>0;) {
      ret=timed_read(sock,ptr,left,10);
      gd_s(6, "read_packet timed_read returned: ");
      gd_i(6, ret);
      gd_s(6, "\n");
      if (ret==-2) return 0;
      if (ret==0) return -2;
      if (ret<0) {
        if (gnut_lib_debug>3) perror("read_packet, data");
        return ret;
      }
      
      ptr+=ret;
      left-=ret;
    }
  }
  
  if (dlen>3000) {
    gd_s(2,"read_packet OVERSIZE packet! size: ");
    gd_i(2, dlen);
    gd_s(2, "\n");
  }
  gd_s(3,"read_packet returning success\n");
  return 23+dlen;
}
