/* 
  Copyright 2018-2023 Thorsten Alteholz <libcontra@alteholz.eu>

  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; version 2 of the License

  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 (see the file COPYING included with this
  distribution); if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*!
*************************************************************************
*
* \file libcontra_connect.c
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief wrapper for connect()
*
* connect a socket
* man 2 connect
*
* \addtogroup libcontra
* The group of the library.
* @{
************************************************************************/

#include <libcontra.h>
#include <libcontra_log.h>
#include <netdb.h>

extern bool	libcontra_logConnect; /*!< this is needed to control the output in the logging function */
extern bool	libcontra_isInitialized; /*!< this is needed to control the output in the logging function */
extern bool	libcontra_configResolve; /*!< this is needed to control the output in the logging function */

/*!
*************************************************************************
*
* \fn int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief wrapper around libc::connect()
*
* @param[in]   sockfd        specifies the file descriptor associated with the socket
* @param[in]   *addr         a pointer to a sockaddr structure that is filled
*                            in with the address of the peer socket
* @param[in]   *addrlen      size (in bytes) ot the structure pointed to by addr
*
* \return On success, return a file descriptor for the accepted socket (>0).
*         On error, return -1, errno is set
*
************************************************************************/
int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen)
{
  int (*libc_connect)(int, const struct sockaddr *, socklen_t);
  int rc;
  int port=0;
  char addrbuf[INET6_ADDRSTRLEN];

  if (libcontra_isInitialized==false) {
    libcontra_init();
  } 

  if (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect start");

  switch (addr->sa_family) {
    case AF_INET:
      /* IPv4 */
      if  (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect with IPv4");
      struct sockaddr_in *ipv4;

      ipv4=(struct sockaddr_in *)addr;
      inet_ntop(ipv4->sin_family, &ipv4->sin_addr, addrbuf,INET6_ADDRSTRLEN);
      port=htons(ipv4->sin_port);

      break;
    case AF_INET6:
      /* IPv6 */
      if  (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect with IPv6");

      struct sockaddr_in6 *ipv6;

      ipv6=(struct sockaddr_in6 *)addr;
      inet_ntop(ipv6->sin6_family, &ipv6->sin6_addr, addrbuf,INET6_ADDRSTRLEN);
      port=htons(ipv6->sin6_port);
      break;
    default:
      /* unknown */
      if  (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect with unknown addr->sa_family");
      break;
  }

  if (libcontra_configResolve==true) {
    if (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect with resolve debugging: %i",port);
    /* DNS will start another request, so do nothing */
    if (port!=53) {
      char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

      memset(hbuf,0,sizeof(hbuf));
      memset(sbuf,0,sizeof(sbuf));

      if (getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NAMEREQD)) {
        /* could not resolv hostname, deny connection */
        errno = EACCES;
        return -1;
      }
      if (libcontra_logConnect) libcontra_log(LOGLEVEL_INFO, "I: connect to %s:%s",hbuf,sbuf);
    }
    else {
      if (libcontra_logConnect) libcontra_log(LOGLEVEL_INFO, "I: DNS connect to %s:%i",addrbuf,port);
    }
  }
  else {
    if (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect without resolve debugging: %i",port);
    if (port!=53) {
      if (libcontra_logConnect) libcontra_log(LOGLEVEL_INFO, "I: connect to %s:%i",addrbuf,port);
    }
    else {
      if (libcontra_logConnect) libcontra_log(LOGLEVEL_INFO, "I: DNS connect to %s:%i",addrbuf,port);
    }
  }

  *(void **)(&libc_connect) = dlsym(RTLD_NEXT, "connect");
  if(dlerror()) {
    errno = EACCES;
    return -1;
  }

  rc = (*libc_connect)(sockfd, addr, addrlen);

  if (libcontra_logConnect) libcontra_log(LOGLEVEL_NULL, "I: connection: %s:%i -> %x",addrbuf, port, rc);

  if (libcontra_logConnect) libcontra_log(LOGLEVEL_DEBUG, "D: connect end");

  return rc;
}

/*! @} */

