/* MIT License
 *
 * Copyright (c) Massachusetts Institute of Technology
 * Copyright (c) The c-ares project and its contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * SPDX-License-Identifier: MIT
 */
#include "ares_setup.h"

#ifdef HAVE_SYS_UIO_H
#  include <sys/uio.h>
#endif
#ifdef HAVE_NETINET_IN_H
#  include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#  include <netinet/tcp.h>
#endif
#ifdef HAVE_NETDB_H
#  include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
#  include <arpa/inet.h>
#endif

#ifdef HAVE_STRINGS_H
#  include <strings.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#  include <sys/ioctl.h>
#endif
#ifdef NETWARE
#  include <sys/filio.h>
#endif

#include <assert.h>
#include <fcntl.h>
#include <limits.h>

#include "ares.h"
#include "ares_private.h"

ares_ssize_t ares__socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
                                   void *data, size_t data_len, int flags,
                                   struct sockaddr *from,
                                   ares_socklen_t  *from_len)
{
  if (channel->sock_funcs && channel->sock_funcs->arecvfrom) {
    return channel->sock_funcs->arecvfrom(s, data, data_len, flags, from,
                                          from_len, channel->sock_func_cb_data);
  }

#ifdef HAVE_RECVFROM
  return (ares_ssize_t)recvfrom(s, data, (RECVFROM_TYPE_ARG3)data_len, flags,
                                from, from_len);
#else
  return sread(s, data, data_len);
#endif
}

ares_ssize_t ares__socket_recv(ares_channel_t *channel, ares_socket_t s,
                               void *data, size_t data_len)
{
  if (channel->sock_funcs && channel->sock_funcs->arecvfrom) {
    return channel->sock_funcs->arecvfrom(s, data, data_len, 0, 0, 0,
                                          channel->sock_func_cb_data);
  }

  /* sread() is a wrapper for read() or recv() depending on the system */
  return sread(s, data, data_len);
}

/*
 * setsocknonblock sets the given socket to either blocking or non-blocking
 * mode based on the 'nonblock' boolean argument. This function is highly
 * portable.
 */
static int setsocknonblock(ares_socket_t sockfd, /* operate on this */
                           int           nonblock /* TRUE or FALSE */)
{
#if defined(USE_BLOCKING_SOCKETS)

  return 0; /* returns success */

#elif defined(HAVE_FCNTL_O_NONBLOCK)

  /* most recent unix versions */
  int flags;
  flags = fcntl(sockfd, F_GETFL, 0);
  if (nonblock) {
    return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
  } else {
    return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); /* LCOV_EXCL_LINE */
  }

#elif defined(HAVE_IOCTL_FIONBIO)

  /* older unix versions */
  int flags = nonblock ? 1 : 0;
  return ioctl(sockfd, FIONBIO, &flags);

#elif defined(HAVE_IOCTLSOCKET_FIONBIO)

#  ifdef WATT32
  char flags = nonblock ? 1 : 0;
#  else
  /* Windows */
  unsigned long flags = nonblock ? 1UL : 0UL;
#  endif
  return ioctlsocket(sockfd, (long)FIONBIO, &flags);

#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO)

  /* Amiga */
  long flags = nonblock ? 1L : 0L;
  return IoctlSocket(sockfd, FIONBIO, flags);

#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK)

  /* BeOS */
  long b = nonblock ? 1L : 0L;
  return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));

#else
#  error "no non-blocking method was found/used/set"
#endif
}

#if defined(IPV6_V6ONLY) && defined(WIN32)
/* It makes support for IPv4-mapped IPv6 addresses.
 * Linux kernel, NetBSD, FreeBSD and Darwin: default is off;
 * Windows Vista and later: default is on;
 * DragonFly BSD: acts like off, and dummy setting;
 * OpenBSD and earlier Windows: unsupported.
 * Linux: controlled by /proc/sys/net/ipv6/bindv6only.
 */
static void set_ipv6_v6only(ares_socket_t sockfd, int on)
{
  (void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on));
}
#else
#  define set_ipv6_v6only(s, v)
#endif

static int configure_socket(ares_socket_t s, struct server_state *server)
{
  union {
    struct sockaddr     sa;
    struct sockaddr_in  sa4;
    struct sockaddr_in6 sa6;
  } local;

  ares_socklen_t  bindlen = 0;
  ares_channel_t *channel = server->channel;

  /* do not set options for user-managed sockets */
  if (channel->sock_funcs && channel->sock_funcs->asocket) {
    return 0;
  }

  (void)setsocknonblock(s, 1);

#if defined(FD_CLOEXEC) && !defined(MSDOS)
  /* Configure the socket fd as close-on-exec. */
  if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
    return -1; /* LCOV_EXCL_LINE */
  }
#endif

  /* Set the socket's send and receive buffer sizes. */
  if ((channel->socket_send_buffer_size > 0) &&
      setsockopt(s, SOL_SOCKET, SO_SNDBUF,
                 (void *)&channel->socket_send_buffer_size,
                 sizeof(channel->socket_send_buffer_size)) == -1) {
    return -1;
  }

  if ((channel->socket_receive_buffer_size > 0) &&
      setsockopt(s, SOL_SOCKET, SO_RCVBUF,
                 (void *)&channel->socket_receive_buffer_size,
                 sizeof(channel->socket_receive_buffer_size)) == -1) {
    return -1;
  }

#ifdef SO_BINDTODEVICE
  if (channel->local_dev_name[0] &&
      setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name,
                 sizeof(channel->local_dev_name))) {
    /* Only root can do this, and usually not fatal if it doesn't work, so */
    /* just continue on. */
  }
#endif

  if (server->addr.family == AF_INET && channel->local_ip4) {
    memset(&local.sa4, 0, sizeof(local.sa4));
    local.sa4.sin_family      = AF_INET;
    local.sa4.sin_addr.s_addr = htonl(channel->local_ip4);
    bindlen                   = sizeof(local.sa4);
  } else if (server->addr.family == AF_INET6 && server->ll_scope == 0 &&
             memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8,
                    sizeof(channel->local_ip6)) != 0) {
    /* Only if not link-local and an ip other than "::" is specified */
    memset(&local.sa6, 0, sizeof(local.sa6));
    local.sa6.sin6_family = AF_INET6;
    memcpy(&local.sa6.sin6_addr, channel->local_ip6,
           sizeof(channel->local_ip6));
    bindlen = sizeof(local.sa6);
  }

  if (bindlen && bind(s, &local.sa, bindlen) < 0) {
    return -1;
  }

  if (server->addr.family == AF_INET6) {
    set_ipv6_v6only(s, 0);
  }

  return 0;
}

ares_status_t ares__open_connection(ares_channel_t      *channel,
                                    struct server_state *server,
                                    ares_bool_t          is_tcp)
{
  ares_socket_t  s;
  int            opt;
  ares_socklen_t salen;

  union {
    struct sockaddr_in  sa4;
    struct sockaddr_in6 sa6;
  } saddr;
  struct sockaddr          *sa;
  struct server_connection *conn;
  ares__llist_node_t       *node;
  int                       type = is_tcp ? SOCK_STREAM : SOCK_DGRAM;
#ifdef __OpenBSD__
  if ((is_tcp && server->tcp_port == 53) ||
      (!is_tcp && server->udp_port == 53)) {
    type |= SOCK_DNS;
  }
#endif

  switch (server->addr.family) {
    case AF_INET:
      sa    = (void *)&saddr.sa4;
      salen = sizeof(saddr.sa4);
      memset(sa, 0, (size_t)salen);
      saddr.sa4.sin_family = AF_INET;
      saddr.sa4.sin_port = htons(is_tcp ? server->tcp_port : server->udp_port);
      memcpy(&saddr.sa4.sin_addr, &server->addr.addr.addr4,
             sizeof(saddr.sa4.sin_addr));
      break;
    case AF_INET6:
      sa    = (void *)&saddr.sa6;
      salen = sizeof(saddr.sa6);
      memset(sa, 0, (size_t)salen);
      saddr.sa6.sin6_family = AF_INET6;
      saddr.sa6.sin6_port = htons(is_tcp ? server->tcp_port : server->udp_port);
      memcpy(&saddr.sa6.sin6_addr, &server->addr.addr.addr6,
             sizeof(saddr.sa6.sin6_addr));
#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
      saddr.sa6.sin6_scope_id = server->ll_scope;
#endif
      break;
    default:
      return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */
  }

  /* Acquire a socket. */
  s = ares__open_socket(channel, server->addr.family, type, 0);
  if (s == ARES_SOCKET_BAD) {
    return ARES_ECONNREFUSED;
  }

  /* Configure it. */
  if (configure_socket(s, server) < 0) {
    ares__close_socket(channel, s);
    return ARES_ECONNREFUSED;
  }

#ifdef TCP_NODELAY
  if (is_tcp) {
    /*
     * Disable the Nagle algorithm (only relevant for TCP sockets, and thus not
     * in configure_socket). In general, in DNS lookups we're pretty much
     * interested in firing off a single request and then waiting for a reply,
     * so batching isn't very interesting.
     */
    opt = 1;
    if ((!channel->sock_funcs || !channel->sock_funcs->asocket) &&
        setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt)) ==
          -1) {
      ares__close_socket(channel, s);
      return ARES_ECONNREFUSED;
    }
  }
#endif

  if (channel->sock_config_cb) {
    int err = channel->sock_config_cb(s, type, channel->sock_config_cb_data);
    if (err < 0) {
      ares__close_socket(channel, s);
      return ARES_ECONNREFUSED;
    }
  }

  /* Connect to the server. */
  if (ares__connect_socket(channel, s, sa, salen) == -1) {
    int err = SOCKERRNO;

    if (err != EINPROGRESS && err != EWOULDBLOCK) {
      ares__close_socket(channel, s);
      return ARES_ECONNREFUSED;
    }
  }

  if (channel->sock_create_cb) {
    int err = channel->sock_create_cb(s, type, channel->sock_create_cb_data);
    if (err < 0) {
      ares__close_socket(channel, s);
      return ARES_ECONNREFUSED;
    }
  }

  conn = ares_malloc(sizeof(*conn));
  if (conn == NULL) {
    ares__close_socket(channel, s);
    return ARES_ENOMEM;
  }
  memset(conn, 0, sizeof(*conn));
  conn->fd              = s;
  conn->server          = server;
  conn->queries_to_conn = ares__llist_create(NULL);
  conn->is_tcp          = is_tcp;
  if (conn->queries_to_conn == NULL) {
    ares__close_socket(channel, s);
    ares_free(conn);
    return ARES_ENOMEM;
  }

  /* TCP connections are thrown to the end as we don't spawn multiple TCP
   * connections. UDP connections are put on front where the newest connection
   * can be quickly pulled */
  if (is_tcp) {
    node = ares__llist_insert_last(server->connections, conn);
  } else {
    node = ares__llist_insert_first(server->connections, conn);
  }
  if (node == NULL) {
    ares__close_socket(channel, s);
    ares__llist_destroy(conn->queries_to_conn);
    ares_free(conn);
    return ARES_ENOMEM;
  }

  /* Register globally to quickly map event on file descriptor to connection
   * node object */
  if (!ares__htable_asvp_insert(channel->connnode_by_socket, s, node)) {
    ares__close_socket(channel, s);
    ares__llist_destroy(conn->queries_to_conn);
    ares__llist_node_claim(node);
    ares_free(conn);
    return ARES_ENOMEM;
  }

  SOCK_STATE_CALLBACK(channel, s, 1, 0);

  if (is_tcp) {
    server->tcp_conn = conn;
  }

  return ARES_SUCCESS;
}

ares_socket_t ares__open_socket(ares_channel_t *channel, int af, int type,
                                int protocol)
{
  if (channel->sock_funcs && channel->sock_funcs->asocket) {
    return channel->sock_funcs->asocket(af, type, protocol,
                                        channel->sock_func_cb_data);
  }

  return socket(af, type, protocol);
}

int ares__connect_socket(ares_channel_t *channel, ares_socket_t sockfd,
                         const struct sockaddr *addr, ares_socklen_t addrlen)
{
  if (channel->sock_funcs && channel->sock_funcs->aconnect) {
    return channel->sock_funcs->aconnect(sockfd, addr, addrlen,
                                         channel->sock_func_cb_data);
  }

  return connect(sockfd, addr, addrlen);
}

void ares__close_socket(ares_channel_t *channel, ares_socket_t s)
{
  if (s == ARES_SOCKET_BAD) {
    return;
  }

  if (channel->sock_funcs && channel->sock_funcs->aclose) {
    channel->sock_funcs->aclose(s, channel->sock_func_cb_data);
  } else {
    sclose(s);
  }
}

#ifndef HAVE_WRITEV
/* Structure for scatter/gather I/O. */
struct iovec {
  void  *iov_base; /* Pointer to data. */
  size_t iov_len;  /* Length of data.  */
};
#endif

ares_ssize_t ares__socket_write(ares_channel_t *channel, ares_socket_t s,
                                const void *data, size_t len)
{
  if (channel->sock_funcs && channel->sock_funcs->asendv) {
    struct iovec vec;
    vec.iov_base = (void *)((size_t)data); /* Cast off const */
    vec.iov_len  = len;
    return channel->sock_funcs->asendv(s, &vec, 1, channel->sock_func_cb_data);
  }
  return swrite(s, data, len);
}

void ares_set_socket_callback(ares_channel_t           *channel,
                              ares_sock_create_callback cb, void *data)
{
  if (channel == NULL) {
    return;
  }
  channel->sock_create_cb      = cb;
  channel->sock_create_cb_data = data;
}

void ares_set_socket_configure_callback(ares_channel_t           *channel,
                                        ares_sock_config_callback cb,
                                        void                     *data)
{
  if (channel == NULL || channel->optmask & ARES_OPT_EVENT_THREAD) {
    return;
  }
  channel->sock_config_cb      = cb;
  channel->sock_config_cb_data = data;
}

void ares_set_socket_functions(ares_channel_t                     *channel,
                               const struct ares_socket_functions *funcs,
                               void                               *data)
{
  if (channel == NULL || channel->optmask & ARES_OPT_EVENT_THREAD) {
    return;
  }
  channel->sock_funcs        = funcs;
  channel->sock_func_cb_data = data;
}
