/*
 * active port forwarder - software for secure forwarding
 * Copyright (C) 2003,2004,2005 jeremian <jeremian [at] poczta.fm>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#include <config.h>

#include "inet_ntop.h"
#include "network.h"
#include "activefor.h"
#include "stats.h"
#include "logging.h"
#include <string.h>
#include <errno.h>
#include <zlib.h>

int
ip_listen(int* sockfd, const char *host, const char *serv, socklen_t *addrlenp, const char type)
{
#if defined(HAVE_GETADDRINFO) && defined(AF_INET6)
	int			 n;
	const int		on = 1;
	struct addrinfo	hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	if (type & 0x02) {
		hints.ai_family = AF_INET;
	}
	else if (type & 0x04) {
		hints.ai_family = AF_INET6;
	}
	else {
		hints.ai_family = AF_UNSPEC;
	}
	
	if (type & 0x01) {
		hints.ai_socktype = SOCK_STREAM;
	}
	else {
		hints.ai_socktype = SOCK_DGRAM;
	}

	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) {
		return n;
	}
	ressave = res;

	do {
		(*sockfd) = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if ((*sockfd) < 0) {
			continue;		/* error, try next one */
		}

		if (type & 0x01) { /* tcp_listen */
			setsockopt((*sockfd), SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
		}
		if (bind((*sockfd), res->ai_addr, res->ai_addrlen) == 0) {
			break;			/* success */
		}

		close((*sockfd));	/* bind error, close and try next one */
	} while ( (res = res->ai_next) != NULL);

	if (res == NULL) {	/* errno from final socket() or bind() */
		return 1;
	}

	if (type & 0x01) { /* tcp_listen */
		listen((*sockfd), 1);
	}

	if (addrlenp) {
		*addrlenp = res->ai_addrlen;	/* return size of protocol address */
	}

	freeaddrinfo(ressave);
#else
  struct sockaddr_in servaddr;
  struct hostent* hostaddr;
  int port;
  
  if (type & 0x01) {
    (*sockfd) = socket(AF_INET, SOCK_STREAM, 0);
  } 
  else {
    (*sockfd) = socket(AF_INET, SOCK_DGRAM, 0);
  } 
  
  if ((*sockfd) == -1) {
    return 1;
  } 
  port = atoi(serv);
  
  if (host) {
    hostaddr = gethostbyname(host);
    if (hostaddr == NULL) {
      return 2;
    }
  }
  
  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  if (host) {
    memcpy(&servaddr.sin_addr.s_addr, hostaddr->h_addr_list[0], hostaddr->h_length);
  }
  else {
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  }
  servaddr.sin_port = htons(port);
  
  if (bind((*sockfd), (struct sockaddr*) &servaddr, sizeof(servaddr))){
    printf("bind failed\n");
    return 4;
  } 
  
  if (listen((*sockfd), 5)){
    return 5;
  } 
#endif
  
  return(0);
}

int
ip_connect(int* sockfd, const char *host, const char *serv, const char type,
    const char *lhost, const char *lserv)
{
#if defined(HAVE_GETADDRINFO) && defined(AF_INET6)
	int				n;
  int    bindFailed;
	struct addrinfo	hints, *res, *ressave;
	struct addrinfo	lhints, *lres, *lressave = NULL;

	bzero(&hints, sizeof(struct addrinfo));
	if (type & 0x02) {
		hints.ai_family = AF_INET;
	}
	else if (type & 0x04) {
		hints.ai_family = AF_INET6;
	}
	else {
		hints.ai_family = AF_UNSPEC;
	}
	if (type & 0x01) {
		hints.ai_socktype = SOCK_STREAM;
	}
	else {
		hints.ai_socktype = SOCK_DGRAM;
	}

  lhints = hints;
  
  if (lhost || lserv) {
    if ( (n = getaddrinfo(lhost, lserv, &lhints, &lres)) != 0) {
      return n;
    }
    lressave = lres;
  }
  
	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) {
		return n;
	}
	ressave = res;

	do {
		(*sockfd) = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if ((*sockfd) < 0) {
			continue;	/* ignore this one */
		}

    bindFailed = 0;
    if (lhost || lserv) {
      bindFailed = 1;
      lres = lressave;
      do {
        if (bind((*sockfd), lres->ai_addr, lres->ai_addrlen) == 0) {
          bindFailed = 0;
          break;			/* success */
        }
      } while ( (lres = lres->ai_next) != NULL);
    }

    if (bindFailed == 1) {
      close((*sockfd));	/* ignore this one */
      continue;
    }

    if (connect((*sockfd), res->ai_addr, res->ai_addrlen) == 0) {
			break;		/* success */
		}

		close((*sockfd));	/* ignore this one */
	} while ( (res = res->ai_next) != NULL);

	if (res == NULL) {	/* errno set from final connect() */
		return 1;
	}

  if (lhost || lserv) {
	  freeaddrinfo(lressave);
  }
	freeaddrinfo(ressave);
#else
  struct sockaddr_in servaddr, lservaddr;
  struct hostent* hostaddr;
  struct hostent* lhostaddr;
  int port, lport;

  if (type & 0x01) {
    (*sockfd) = socket(AF_INET, SOCK_STREAM, 0);
  }
  else {
    (*sockfd) = socket(AF_INET, SOCK_DGRAM, 0);
  }

  if ((*sockfd) == -1) {
    return 1;
  }
  port = atoi(serv);

  hostaddr = gethostbyname(host);
  if (hostaddr == NULL) {
    return 2;
  }

  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(port);
  memcpy(&servaddr.sin_addr.s_addr, hostaddr->h_addr_list[0], hostaddr->h_length);

  if (lhost || lserv) {
    memset(&lservaddr, 0, sizeof(lservaddr));
    lservaddr.sin_family = AF_INET;
    if (lserv) {
      lport = atoi(lserv);
      lservaddr.sin_port = htons(lport);
    }
    if (lhost) {
      lhostaddr = gethostbyname(lhost);
      if (lhostaddr == NULL) {
        return 3;
      }
      memcpy(&lservaddr.sin_addr.s_addr, lhostaddr->h_addr_list[0], lhostaddr->h_length);
    }
    else {
      lservaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    }
    if (bind((*sockfd), (struct sockaddr*) &lservaddr, sizeof(lservaddr))){
      return 4;
    }
  }
  
  if (connect((*sockfd), (struct sockaddr*) &servaddr, sizeof(servaddr))){
    return 5;
  }
#endif

	return(0);
}

char *
sock_ntop(const struct sockaddr *sa, socklen_t salen, char* namebuf, char* portbuf, char type)
{
    char		portstr[7];
    static char str[136];		/* Unix domain is largest */

    switch (sa->sa_family) {
	case AF_INET: {
		struct sockaddr_in	*sin = (struct sockaddr_in *) sa;

    if (type) {
#ifdef HAVE_GETNAMEINFO
      if (getnameinfo(sa, salen, str, 128, NULL, 0, 0)) {
        return NULL;
      }
#else
      struct hostent* hostname;
      if ((hostname = gethostbyaddr(&sin->sin_addr, sizeof(struct in_addr), AF_INET))) {
        strncpy(str, hostname->h_name, 127);
        str[127] = 0;
      }
      else {
        if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) {
          return NULL;
        }
      }
#endif

    }
    else {
  		if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) {
  			return NULL;
  		}
    }
 		if (namebuf) {
      memcpy(namebuf, str, 128);
 		}
		if (ntohs(sin->sin_port) != 0) {
			snprintf(portstr, sizeof(portstr), ".%d", ntohs(sin->sin_port));
			if (portbuf) {
				snprintf(portbuf, 7, "%d", ntohs(sin->sin_port));
			}
			strcat(str, portstr);
		}
		return(str);
	}
#ifdef AF_INET6
	case AF_INET6: {
		struct sockaddr_in6	*sin6 = (struct sockaddr_in6 *) sa;

    if (type) {
#ifdef HAVE_GETNAMEINFO
      if (getnameinfo(sa, salen, str, 128, NULL, 0, 0)) {
        return NULL;
      }
#else
      struct hostent* hostname;
      if ((hostname = gethostbyaddr(&sin6->sin6_addr, sizeof(struct in6_addr), AF_INET6))) {
        strncpy(str, hostname->h_name, 127);
        str[127] = 0;
      }
      else {
        if (inet_ntop(AF_INET6, &sin6->sin6_addr, str, sizeof(str)) == NULL) {
          return NULL;
        }
      }
#endif

    }
    else {
  		if (inet_ntop(AF_INET6, &sin6->sin6_addr, str, sizeof(str)) == NULL) {
  			return NULL;
  		}
    }
		if (namebuf) {
      memcpy(namebuf, str, 128);
		}
		if (ntohs(sin6->sin6_port) != 0) {
			snprintf(portstr, sizeof(portstr), ".%d", ntohs(sin6->sin6_port));
			if (portbuf) {
				snprintf(portbuf, 7, "%d", ntohs(sin6->sin6_port));
			}
			strcat(str, portstr);
		}
		return(str);
	}
#endif
	default: {
		snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d", sa->sa_family, salen);
		return(str);
	}
    }
    return NULL;
}

int
SSL_writen(SSL* fd, unsigned char* buf, int amount)
{
	int sent, n;
	sent = 0;
	while (sent < amount) {
		n = SSL_write(fd, buf+sent, amount - sent);
		if (n != -1) {
			sent += n;
		}
		if (n == -1) {
			if (errno != EAGAIN)
				return 0;
		}
	}
	return amount;
}

int
SSL_readn(SSL* fd, unsigned char* buf, int amount)
{
	int sent, n;
	sent = 0;
	while (sent < amount) {
		n = SSL_read(fd, buf+sent, amount - sent);
		if (n != -1) {
			sent += n;
		}
		if (n == 0)
			return 0;
		if (n == -1) {
			if (errno != EAGAIN)
				return 0;
		}
	}
	return amount;
} 

int
writen(int fd, unsigned char* buf, int amount)
{
	int sent, n;
	sent = 0;
	while (sent < amount) {
		n = write(fd, buf+sent, amount - sent);
		if (n != -1) {
			sent += n;
		}
		if (n == -1) {
			if (errno != EAGAIN)
				return 0;
		}
	}
	return amount;
}

int
readn(int fd, unsigned char* buf, int amount)
{
	int sent, n;
	sent = 0;
	while (sent < amount) {
		n = read(fd, buf+sent, amount - sent);
		if (n != -1) {
			sent += n;
		}
		if (n == 0)
			return 0;
		if (n == -1) {
			if (errno != EAGAIN)
				return 0;
		}
	}
	return amount;

}
