/*
 * active port forwarder - software for secure forwarding
 * Copyright (C) 2003-2006 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>
#include <assert.h>

/*
 * Function name: ip_listen
 * Description: Creates the listening socket.
 * Arguments: sockfd - the created socket
 *            host - the name of the host
 *            serv - the name of the service (port)
 *            addrlenp - pointer to the length of the sockaddr structure
 *            type - the type of the socket
 * Returns: 0 - success,
 *          !0 - failure.
 */

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

  aflog(LOG_T_INIT, LOG_I_DDEBUG,
      "ip_listen: host=[%s] serv=[%s], type=[%d]", host, serv, type);
  
  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));
      setsockopt((*sockfd), SOL_SOCKET, SO_KEEPALIVE, &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 = NULL;
  int port;
  
  aflog(LOG_T_INIT, LOG_I_DDEBUG,
      "ip_listen: host=[%s] serv=[%s], type=[%d]", host, serv, type);
  
  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 (type & 0x01) { /* tcp_listen */
    setsockopt((*sockfd), SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    setsockopt((*sockfd), SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
  }
  
  if (bind((*sockfd), (struct sockaddr*) &servaddr, sizeof(servaddr))){
    printf("bind failed\n");
    return 4;
  } 
  
  if (listen((*sockfd), 5)){
    return 5;
  } 
  
  if (addrlenp) {
    *addrlenp = sizeof(servaddr);  /* return size of protocol address */
  }
#endif
  
  return(0);
}

/*
 * Function name: ip_connect
 * Description: Creates the connected socket.
 * Arguments: sockfd - the connected socket
 *            host - the name of the host
 *            serv - the name of the service (port)
 *            type - the type of the socket
 *            lhost - the name of the local host (used for local bind of the socket)
 *            lserv - the name of the local service (port) (used for local bind of the socket)
 * Returns: 0 - success,
 *          !0 - failure.
 */

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

  aflog(LOG_T_INIT, LOG_I_DDEBUG,
      "ip_connect: host=[%s] serv=[%s], type=[%d], lhost=[%s], lserv=[%s]", host, serv, type, lhost, lserv);
  
  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 */
    }
    
    if (type & 0x01) { /* tcp_connect */
      setsockopt((*sockfd), SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
    }

    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;
  
  aflog(LOG_T_INIT, LOG_I_DDEBUG,
      "ip_connect: host=[%s] serv=[%s], type=[%d], lhost=[%s], lserv=[%s]", host, serv, type, lhost, lserv);

  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 (type & 0x01) { /* tcp_connect */
    setsockopt((*sockfd), SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
  }
    
  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);
}

/*
 * Function name: sock_ntop
 * Description: Returns the string representing given socket address.
 * Arguments: sa - pointer to sockaddr structure
 *            salen - size of the sockaddr structure
 *            namebuf - buffer for string representation of the host name
 *            portbuf - buffer for string representation of the host port
 *            type - if the socket address should be resolved to the the DNS name
 * Returns: The string representing given socket address or NULL, if the address
 *          can't be established.
 */

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((void*) &sin->sin_addr, sizeof(struct in_addr), AF_INET))) {
                        strncpy(str, hostname->h_name, 127);
                        str[127] = 0;
                      }
                      else {
                        if (inet_ntop(AF_INET, (void*) &sin->sin_addr, str, sizeof(str)) == NULL) {
                          return NULL;
                        }
                      }
#endif

                    }
                    else {
                      if (inet_ntop(AF_INET, (void*) &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;
}

/*
 * Function name: SSL_writen
 * Description: Writes the given amount of data to the SSL connection.
 * Arguments: fd - the SSL connection
 *            buf - buffer with data to write
 *            amount - the size of the data
 * Returns: The amount of bytes written to the SSL connection.
 */

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

/*
 * Function name: SSL_readn
 * Description: Reads the given amount of data from the SSL connection.
 * Arguments: fd - the SSL connection
 *            buf - buffer for data
 *            amount - the size of the data to read
 * Returns: The amount of bytes read from the SSL connection.
 */

int
SSL_readn(SSL* fd, unsigned char* buf, int amount)
{
  int sent, n;
  sent = 0;
  assert(amount > 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;
} 

/*
 * Function name: writen
 * Description: Writes the given amount of data to the file descriptor.
 * Arguments: fd - the file descriptor
 *            buf - buffer with data to write
 *            amount - the size of the data
 * Returns: The amount of bytes written to the file descriptor
 */

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

/*
 * Function name: readn
 * Description: Reads the given amount of data from the file descriptor.
 * Arguments: fd - the file descriptor
 *            buf - buffer for data
 *            amount - the size of the data to read
 * Returns: The amount of bytes read from the file descriptor.
 */

int
readn(int fd, unsigned char* buf, int amount)
{
  int sent, n;
  sent = 0;
  assert(amount > 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;

}
