/* Declarations for wput.
   Copyright (C) 1989-1994, 1996-1999, 2001 
   This file is part of wput.

   The wput 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.1 of the License, or (at your option) any later version.

   The wput 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.

   You should have received a copy of the GNU General Public
   License along with the wput; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

/* This file contains several lower-level functions for dealing with sockets. */
#include "utils.h"
//#include "ftp.h"
#include "windows.h"
#include "wput.h"
#include "wpsocket.h"
#define ipaddr h_addr_list[0]

#ifndef WIN32
#include <netdb.h>
#endif

 
int get_ip_addr(char* hostname, int * ip){ 
	struct hostent *ht;
#ifdef WIN32
	*ip = inet_addr(hostname);
	/* If we have an IP address we cannot resolve it as a hostname, 
	but this doesn't matter since we just need the ip. */
	if(*ip && *ip != 0xffffffff)
		return 0;
	else
#endif
	  ht = gethostbyname(hostname);
	
	if(ht != 0x0){
		printout(vDEBUG, "IP of `%s' is `%s'\n",
                 hostname, printip( (unsigned char *) ht->h_addr_list[0]));
		memcpy(ip, ht->h_addr_list[0], 4);
		return 0;
	}
	return -1;
}
#if 0
/* get interface-address of network interface ifname     *
 * return ip-address or 0 on error */
unsigned long get_ifaddr(char *ifname)
{
	int s;
	struct ifreq ifr;
	struct sockaddr_in sa;
	
	strcpy(ifr.ifr_name,ifname);
	s=socket(AF_INET,SOCK_DGRAM,0);
	if(s>=0) {
		if(ioctl(s,SIOCGIFADDR,&ifr)) {
			closesocket(s);
			return 0;
		}
		closesocket(s);
		memcpy(&sa,&ifr.ifr_addr,16);
		return ntohl(sa.sin_addr.s_addr);
	}
	return 0;
}
#endif

int create_new_reply_sockfd(const int ip, const int port){

  int c_sock;
  struct sockaddr_in remote_addr;

  memset((char *)&remote_addr,0,sizeof(remote_addr));
  remote_addr.sin_family      = AF_INET;
  remote_addr.sin_addr.s_addr = ip;
  remote_addr.sin_port        = htons((unsigned short) port);

  /*
   * Open a TCP socket(an internet stream socket).
   */

  if( (c_sock = socket(AF_INET,SOCK_STREAM,0)) < 0)
#ifdef WIN32
  { printf("%d", GetLastError());
  exit(0); }
#else
  perror("client: can't open stream socket");
#endif
  printout(vDEBUG, "c_sock: %x\n", c_sock);
  /*
   * Connect to the server
   */
  return timeoutConnect(c_sock,(struct sockaddr *)&remote_addr,sizeof(remote_addr));
}
/* See the socks-proxy-rfc for reference to these codes */
/* as you can see, this code is quick and ugly. it shall simply work.
 * the intention was not to write a complete proxy-client but an piece
 * of ftp-software... */
/* error-levels: ERR_FAILED */
int proxy_init(void) {
	/* TODO NRV add further authentication-methods support */
	char t[4] = {5, 1, 0};
	int c_sock = create_new_reply_sockfd(opt.proxy_ip, opt.proxy_port);
	int res;
    printout(vDEBUG, "csock: %d\n", c_sock);
	if(c_sock == -1) {
		printout(vNORMAL, "failed.\n");
		printout(vLESS, "Error: Connection to proxy cannot be established.\n");
		return ERR_FAILED;
	}
    
    if(opt.proxy_user && opt.proxy_pass) {
        t[1] = 2;
        t[2] = 2;
        t[3] = 0;
    }
    
	send(c_sock, t, strlen(t)+1, 0);
	res = read_for_sure(c_sock, t, 2);
	if(res < 0) printout(vMORE, "read() failed: %d (%d: %s)\n", res, errno, strerror(errno));
	if(res < 0) return ERR_FAILED;

	if(t[0] != 5) {
		printout(vNORMAL, "failed.\n");
		printout(vLESS, "Error: Proxy version mismatch (%d)\n", t[0]);
		return ERR_FAILED;
	}
    if(t[1] == 2 && opt.proxy_user && opt.proxy_pass) {
        /* TODO USS it's a wonder that this code actually works. make it readable for heaven's sake. */
        char * p = malloc(strlen(opt.proxy_user) + strlen(opt.proxy_pass) + 3);
        p[0] = 5;
        p[1] = strlen(opt.proxy_user);
        strcpy(p+2, opt.proxy_user);
        p[2+p[1]] = strlen(opt.proxy_pass);
        strncpy(p+3+p[1], opt.proxy_pass, p[2+p[1]]);
        send(c_sock, p, 3+p[1]+p[2+p[1]], 0);
        read_for_sure(c_sock, p, 2);
        if(p[1] != 0) {
            /* TODO NRV abort or return error-level on proxy-auth-failure? 
			 * TODO NRV abort is ok, since all other connections to this proxy
			 * TODO NRV will fail, too... but this would imply, that we'll need
			 * TODO NRV to change other proxy-errors to abort as well... */
            wpAbort("Proxy authentication failure\n");
        }
        free(p);
    } else 
	if(t[1] != 0) {
		printout(vNORMAL, "failed.\n");
		printout(vLESS, "Error: Proxy method mismatch (%d)\n", t[1]);
		return ERR_FAILED;
	}

	return c_sock;
}
/* SOCKS v5 Proxys can listen on a port for us. Use this great feature */
int proxy_listen(unsigned int * ip, unsigned short * port) {
	/* v5, bind, rsv, ipv4, 0.0.0.0:0 */
	char t[10] = {5, 2, 0, 1, 0, 0, 0, 0, 0, 0};
	int c_sock = proxy_init();
	int res;
	if(c_sock == ERR_FAILED) return ERR_FAILED;
	printout(vLESS, "Warning: proxy-bind-support was never tested, so will most likely not work.\n"
			"         If you have a bind-capable socks5 proxy, please send\n"
			"         me its address, so that i can make this feature usable.\n\n");
	send(c_sock, t, 10, 0);
	res = read_for_sure(c_sock, t, 10);
	if(res < 0) return ERR_FAILED;
	
	if(t[1] != 0) {
		printout(vLESS, "Error: Proxy discarded listen-request. Error-Code: %d\n", t[1]);
		printout(vMORE, "Disabling listen-tries for proxy\n");
		opt.proxy_bind = 0;
		return ERR_FAILED;
	}
	*ip   = *(unsigned int *) (t+4);
	*port = *(unsigned short int *) (t+8);
	printout(vMORE,
         "Proxy is listening on %s:%d for incoming connections\n",
          printip((unsigned char *) ip), *port);

	return c_sock;
}
/* quick and ugly implementation of v5/http proxy */
/* TODO IMP make proxy-implementation more relieable and maybe more read-/understandable */
int proxy_connect(const int ip, const unsigned short port, const char * hostname) {
	int res;
    printout(vDEBUG, "Doing proxy connection\n");
	if(opt.proxy == PROXY_SOCKS) {
		int c_sock = proxy_init();
		char * t;
		if(c_sock == ERR_FAILED) return ERR_FAILED;
		printout(vMORE, "Using SOCKS5-Proxy %s:%d... ",
                 printip((unsigned char *) &opt.proxy_ip), opt.proxy_port);
        
		/* if we could not resolve the hostname, let the proxy do it */
		if(ip == 0) {
			t = malloc(6 + strlen(hostname) + 1);
			/* v5, connect, rsv, hostname */
			printout(vDEBUG, "hostname: %s\n", hostname);
			memcpy(t, "\5\1\0\3", 4);
			t[4] = strlen(hostname);
			strcpy(t+5, hostname);
			res = htons(port);
			memcpy(t+strlen(hostname)+5, (char *) &res, 2);
			send(c_sock, t, strlen(hostname)+7, 0);
			free(t);
			t = malloc(10);
		} else {
			t = malloc(10);
			/* v5, connect, rsv, ipv4, 0.0.0.0:0 */
			memcpy(t, "\5\1\0\1", 4);
            //res = htonl(ip);
			memcpy(t+4, &ip, 4);
            res = htons(port);
			memcpy(t+8, &res, 2);
			send(c_sock, t, 10, 0);
		}
		res = read_for_sure(c_sock, t, 10);
		if(res < 0) {
			free(t);
			return ERR_FAILED;
		}
		
		if(t[0] == 5 && t[1] == 0)
			printout(vMORE, "Proxy connection established.\n");
        else {
			printout(vLESS, "Error: Connection through proxy failed. Error-code: %d\n", t[1]);
			free(t);
			return ERR_FAILED;
	    }
        free(t);
		return c_sock;
	} else {
		/* HTTP-proxy
		 * TODO USS proxy: error-handling
		 * TODO USS Proxy-Authentication has not yet been checked. Could someone report, if it works or not?
		 * TODO NRV SSL */
		int c_sock = create_new_reply_sockfd(opt.proxy_ip, opt.proxy_port);
		char * userencoded = NULL;
		char * request;
        if(opt.proxy_user && opt.proxy_pass) {
            int len = strlen(opt.proxy_user) + strlen(opt.proxy_pass) + 3;
            char * userunencoded = (char *) malloc(len);
            strcpy(userunencoded, opt.proxy_user);
            strcpy(userunencoded+strlen(opt.proxy_user)+1, opt.proxy_pass);
            userunencoded[len-1] = 0; /* we need to have on char after our challenge to be 0 */
            userencoded = base64(userunencoded, len-1);
            free(userunencoded);
        }
        request = malloc(8 /* 'CONNECT ' */
            + (hostname ? strlen(hostname) : 15 /* '255.255.255.255' */)
            + (userencoded ? strlen(userencoded) + 29 : 0) /* '\r\nProxy-Authorization: Basic ' */
            + 10 + 5); /* ':' + ' HTTP/1.0' + '\r\n\r\n' + '\0' */
        strcpy(request, "CONNECT ");
		if(ip == 0)
            strcat(request, hostname);
		else
            strcat(request, printip( (unsigned char *) &ip));
        strcat(request, ":");
        {
            char * str_port = malloc(6);
            strcat(request, int64toa(port, str_port, 10));
            free(str_port);
        }
        strcat(request, " HTTP/1.0");
        if(userencoded) {
            strcat(request, "\r\nProxy-Authorization: Basic ");
            strcat(request, userencoded);
            free(userencoded);
        }
        strcat(request, "\r\n\r\n");
		printout(vDEBUG, "proxy-connect: '%s' (IP %x)\n", request, ip);
		send(c_sock, request, strlen(request), 0);
		free(request);
        
		request = malloc(40);
		request[0] = 0;
		res = read_for_sure(c_sock, request, 40);
		if(res < 0  || strncmp(request+9, "200", 3)) {
			printout(vLESS, "Error: Connection could not be established.\nProxy states '%s'", request);
			free(request);
			return ERR_FAILED;
		}
        free(request);
		return c_sock;
	}
}
/* reads a line from socket. terminating \r\n is replace by \0 */
/* error-levels: NULL (fatal read-error), ERR_TIMEOUT */
char * read_socket_line(int fd) {
	int length  = 0;
	int bufsize = 82;
	char * buf  = malloc(bufsize+1);
	int res;
	
	while( (res = read_for_sure(fd, buf+length, 1)) > 0) {
		/* if multiline-messages can be like 220 Everything fine\nGreat\r\n
		 * reading the next answer will certainly fail and we'll get bug-reports */
		if(buf[length] == '\n')
			/* rfc states that lines have to end with CRLF. so remove the \r too */
			if(buf[length-1] == '\r') {
				buf[length-1] = 0;
				return buf;
			}

		if(length == bufsize) {
			bufsize <<= 1;
			buf = realloc(buf, bufsize+1);
		}
		length++;
	}
	/* this should never be reached. 
	 * however, make sure there is some debug-output if reading fails */
	buf[length] = 0;
	
	if(res == ERR_TIMEOUT) {
		printout(vNORMAL, "Receive-Warning: read() timed out. Read '%s' so far.\n", buf);
		free(buf);
		return (char *) ERR_TIMEOUT;
	}
	
	printout(vLESS, "Receive-Error: read() failed. Read '%s' so far. (errno: %s (%d))\n", buf, strerror(errno), errno);
	free(buf);
	return NULL;
}
/* recv, but take care of read-timeouts and read-interuptions */
/* error-levels: ERR_TIMEOUT, ERR_FAILED */
int read_for_sure(int fd, void *buf, int len) {
  int res;
  
  if( !isDataReadable(fd, opt.timeout) ) {
    printout(vNORMAL, "Error: recv() timed out. No data received\n");
    return ERR_TIMEOUT;
  }
  do
    res = recv(fd, buf, len, 0);
  while( (res == -1 && errno == WPUT_EINTR) );
//  while ((res == -1 && errno == EINTR) || (res == -1 && errno == EINPROGRESS));
  if(res == 0) return ERR_FAILED;
  return res;
}

int initialize_server_master(int * s_sock, unsigned short * s_port){

  struct sockaddr_in serv_addr;

  /*
   * Open a TCP socket(an Internet STREAM socket)
   */
  if ((*s_sock =socket(AF_INET, SOCK_STREAM, 0))<0)
    perror("server: can't open new socket");
  /*
   * Bind out local address so that the client can send to us
   */
  memset((void *)&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family            = AF_INET;
  serv_addr.sin_port              = htons(*s_port);
  serv_addr.sin_addr.s_addr       = htonl(opt.bindaddr);

  if(bind(*s_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) <0){
    printout(vLESS,"Error: server:can't bind local address\n");
    exit(0);
  }
  if (!*s_port)
    {
      /* #### addrlen should be a 32-bit type, which int is not
         guaranteed to be.  Oh, and don't try to make it a size_t,
         because that can be 64-bit.  */

      /* 2004-12-08 SMS.
       * "X/Open specifies that these are size_t."
       */
      size_t addrlen = sizeof (struct sockaddr_in);
      if (getsockname (*s_sock, (struct sockaddr *)&serv_addr, &addrlen) < 0)
        {
          if(*s_sock != -1) {
            close (*s_sock);
            *s_sock = -1;
          }
	  printout(vDEBUG, "Failed to open server socket.\n");
          return -1;
        }
      *s_port = ntohs (serv_addr.sin_port);
    }

  /* TODO USS install a signal handler to clean up if user interrupt the server */
  /* TODO USS sighandler(*s_sock); */
  listen(*s_sock, 1);
  printout(vDEBUG, "Server socket ready to accept client connection.\n");

  return 0;
}

int create_data_sockfd(int s_sock){
  int newsockfd;
  size_t clilen;
  struct sockaddr_in client_addr;

  clilen = sizeof( client_addr);
  newsockfd = accept(s_sock, (struct sockaddr *)&client_addr, &clilen);
  if(newsockfd == -1){
    perror("accept error");
    exit(-1);
  }
  printout(vDEBUG, "Server socket accepted new connection from requesting client.\n");
  return newsockfd;

}

int get_local_ip(int sockfd, char * local_ip){
  struct sockaddr_in mysrv;
  struct sockaddr *myaddr;
  size_t addrlen = sizeof (mysrv); 
  myaddr = (struct sockaddr *) (&mysrv);
  if (getsockname (sockfd, myaddr, &addrlen) < 0)
    return -1;
  memcpy (local_ip, &mysrv.sin_addr, 4);
  return 0;
}

int blockSocket(int sock, unsigned char block) {
#ifdef WIN32
  unsigned long flags = !block;
  return ioctlsocket(sock,FIONBIO,&flags)?-1:0;
#else
  int flags = fcntl(sock, F_GETFL, 0);
  if(block)
  	flags = flags & (O_NONBLOCK ^ 0xFFFFFFFF);
  else
  	flags |= O_NONBLOCK;
  if (fcntl(sock, F_SETFL, flags) < 0)
    return ERR_FAILED;
  return 0;
#endif
}

/* check whether a specific file-descriptor has data that can be read or written within timeout seconds */
int isDataWriteable(int s, int timeout) {
      struct timeval t;
      fd_set inSet;
      FD_ZERO(&inSet);
      t.tv_sec = timeout / 10;
      t.tv_usec= (timeout % 10) * 100;
      FD_SET(s, &inSet);
      return select(s+1, NULL, &inSet, NULL, &t);
}
int isDataReadable(int s, int timeout) {
      struct timeval t;
      fd_set inSet;
      FD_ZERO(&inSet);
      t.tv_sec = timeout / 10;
      t.tv_usec= (timeout % 10) * 100;
      FD_SET(s, &inSet);
      return select(s+1, &inSet, NULL, NULL, &t);
}
/* this is for not getting hanged up when a connection cannot be established in time */
int timeoutConnect(int sock, struct sockaddr *remote_addr,int size) {
  int c = 0;
  printout(vDEBUG, "initiating timeout connect (%d) (%x)\n", opt.timeout, sock);
  blockSocket(sock, 0);
  c = connect(sock,remote_addr,size);
  /* DEBUG if(c == -1) perror("connect"); */
  blockSocket(sock, 1);
  c = isDataWriteable(sock, opt.timeout);
  return c < 1 ? -1 : sock;
}
