/****************************************************************************
 * libs/libc/netdb/lib_dnsaddserver.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/net/ip.h>
#include <nuttx/net/dns.h>

#include "netdb/lib_dns.h"

#ifdef CONFIG_NETDB_DNSCLIENT

/****************************************************************************
 * Public Data
 ****************************************************************************/

#ifndef CONFIG_NETDB_RESOLVCONF
/* The DNS server addresses */

union dns_addr_u g_dns_servers[CONFIG_NETDB_DNSSERVER_NAMESERVERS] =
  {
#if defined(CONFIG_NETDB_DNSSERVER_IPv4)
    {
      .ipv4.sin_family      = AF_INET,
      .ipv4.sin_port        = HTONS(DNS_DEFAULT_PORT),
      .ipv4.sin_addr.s_addr = HTONL(CONFIG_NETDB_DNSSERVER_IPv4ADDR),
    }
#elif defined(CONFIG_NETDB_DNSSERVER_IPv6)
    {
      .ipv6.sin6_family               = AF_INET6,
      .ipv6.sin6_port                 = HTONS(DNS_DEFAULT_PORT),
      .ipv6.sin6_addr.in6_u.u6_addr16 =
        {
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_1),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_2),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_3),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_4),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_5),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_6),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_7),
          HTONS(CONFIG_NETDB_DNSSERVER_IPv6ADDR_8)
        }
    }
#endif
  };

/* Number of currently configured nameservers */

#if defined(CONFIG_NETDB_DNSSERVER_IPv4) || defined(CONFIG_NETDB_DNSSERVER_IPv6)
uint8_t g_dns_nservers = 1;
#else
uint8_t g_dns_nservers;
#endif

#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int dns_check_nameserver(FAR void *arg, FAR struct sockaddr *addr,
                                socklen_t addrlen)
{
  FAR struct sockaddr *newaddr = (FAR struct sockaddr *)arg;

  if (addr->sa_family == newaddr->sa_family)
    {
      if (memcmp(addr, newaddr, addrlen) == 0)
        {
          return -EEXIST;
        }
    }

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: dns_add_nameserver
 *
 * Description:
 *   Configure which DNS server to use for queries
 *
 ****************************************************************************/

#ifdef CONFIG_NETDB_RESOLVCONF
int dns_add_nameserver(FAR const struct sockaddr *addr, socklen_t addrlen)
{
  FAR FILE *stream;
  char addrstr[DNS_MAX_ADDRSTR];
  union dns_addr_u dns_addr;
  FAR uint16_t *pport;
  size_t copylen;
#if CONFIG_NETDB_DNSSERVER_NAMESERVERS > 1
  char prev_addr[CONFIG_NETDB_DNSSERVER_NAMESERVERS - 1][DNS_MAX_LINE];
  int prev_cnt;
  int i;
#endif
  int ret;

  stream = fopen(CONFIG_NETDB_RESOLVCONF_PATH, "a+");
  if (stream == NULL)
    {
      ret = -get_errno();
      nerr("ERROR: Failed to open %s: %d\n",
           CONFIG_NETDB_RESOLVCONF_PATH, ret);
      DEBUGASSERT(ret < 0);
      return ret;
    }

  dns_lock();

#ifdef CONFIG_NET_IPv4
  /* Check for an IPv4 address */

  if (addr->sa_family == AF_INET)
    {
      if (addrlen < sizeof(struct sockaddr_in))
        {
          ret = -EINVAL;
          goto errout;
        }
      else
        {
          FAR struct sockaddr_in *in4 = (FAR struct sockaddr_in *)addr;

          copylen = sizeof(struct sockaddr_in);
          pport   = &dns_addr.ipv4.sin_port;
          if (inet_ntop(AF_INET, &in4->sin_addr, addrstr,
                        DNS_MAX_ADDRSTR) == NULL)
            {
              ret = -get_errno();
              nerr("ERROR: inet_ntop failed: %d\n", ret);
              DEBUGASSERT(ret < 0);
              goto errout;
            }
        }
    }
  else
#endif

#ifdef CONFIG_NET_IPv6
  /* Check for an IPv6 address */

  if (addr->sa_family == AF_INET6)
    {
      if (addrlen < sizeof(struct sockaddr_in6))
        {
          ret = -EINVAL;
          goto errout;
        }
      else
        {
          FAR struct sockaddr_in6 *in6 = (FAR struct sockaddr_in6 *)addr;

          copylen = sizeof(struct sockaddr_in6);
          pport   = &dns_addr.ipv6.sin6_port;
          if (inet_ntop(AF_INET6, &in6->sin6_addr, addrstr,
                        DNS_MAX_ADDRSTR) == NULL)
            {
              ret = -get_errno();
              nerr("ERROR: inet_ntop failed: %d\n", ret);
              DEBUGASSERT(ret < 0);
              goto errout;
            }
        }
    }
  else
#endif
    {
      nerr("ERROR: Unsupported family: %d\n", addr->sa_family);
      ret = -ENOSYS;
      goto errout;
    }

  memcpy(&dns_addr.addr, addr, copylen);

  /* A port number of zero means to use the default DNS server port number */

  if (*pport == 0)
    {
      *pport = HTONS(DNS_DEFAULT_PORT);
    }

  ret = dns_foreach_nameserver(dns_check_nameserver, &dns_addr.addr);
  if (ret < 0)
    {
      /* If the nameserver is existing, don't need to write it into the
       * resolv.conf file.
       */

      if (ret == -EEXIST)
        {
          ret = OK;
        }

      goto errout;
    }

#if CONFIG_NETDB_DNSSERVER_NAMESERVERS > 1
  fseek(stream, 0, SEEK_SET);
  for (prev_cnt = 0; prev_cnt < CONFIG_NETDB_DNSSERVER_NAMESERVERS - 1;
       prev_cnt++)
    {
      if (fgets(prev_addr[prev_cnt], DNS_MAX_LINE, stream) == NULL)
        {
          break;
        }
    }
#endif

  fclose(stream);
  stream = fopen(CONFIG_NETDB_RESOLVCONF_PATH, "w");

  /* Write the new record to the head of the resolv.conf file. */

#ifdef CONFIG_NETDB_RESOLVCONF_NONSTDPORT
  /* The OpenBSD version supports a [host]:port syntax.  When a non-standard
   * port is specified the host address must be enclosed in square brackets.
   * For example:
   *
   *   nameserver [10.0.0.1]:5353
   *   nameserver [::1]:5353
   */

  ret = fprintf(stream, "%s [%s]:%u\n",
                NETDB_DNS_KEYWORD, addrstr, NTOHS(*pport));
#else
  ret = fprintf(stream, "%s %s\n", NETDB_DNS_KEYWORD, addrstr);
#endif

  if (ret < 0)
    {
      ret = -get_errno();
      nerr("ERROR: fprintf failed: %d\n", ret);
      DEBUGASSERT(ret < 0);
      goto errout;
    }

#if CONFIG_NETDB_DNSSERVER_NAMESERVERS > 1
  for (i = 0; i < prev_cnt; i++)
    {
      ret = fputs(prev_addr[i], stream);
      if (ret < 0)
        {
          ret = -get_errno();
          nerr("ERROR: fprintf failed: %d\n", ret);
          DEBUGASSERT(ret < 0);
          goto errout;
        }
    }
#endif

  ret = OK;

errout:
  dns_unlock();
  fclose(stream);

  if (ret == OK)
    {
      dns_notify_nameserver(&dns_addr.addr, addrlen);
    }

  return ret;
}

#else /* CONFIG_NETDB_RESOLVCONF */

int dns_add_nameserver(FAR const struct sockaddr *addr, socklen_t addrlen)
{
  union dns_addr_u dns_addr;
  FAR uint16_t *pport;
  size_t copylen;
  int nservers;
  int idx;
  int ret;

  DEBUGASSERT(addr != NULL);

  /* Get the index of the next free nameserver slot. */

  dns_lock();
  if (g_dns_nservers == CONFIG_NETDB_DNSSERVER_NAMESERVERS)
    {
      idx = 0;
      nservers = g_dns_nservers;
    }
  else
    {
      idx = g_dns_nservers;
      nservers = idx + 1;
    }

#ifdef CONFIG_NET_IPv4
  /* Check for an IPv4 address */

  if (addr->sa_family == AF_INET)
    {
      /* Set up for the IPv4 address copy */

      copylen = sizeof(struct sockaddr_in);
      pport   = &dns_addr.ipv4.sin_port;
    }
  else
#endif

#ifdef CONFIG_NET_IPv6
  /* Check for an IPv6 address */

  if (addr->sa_family == AF_INET6)
    {
      /* Set up for the IPv6 address copy */

      copylen = sizeof(struct sockaddr_in6);
      pport   = &dns_addr.ipv6.sin6_port;
    }
  else
#endif
    {
      nerr("ERROR: Unsupported family: %d\n", addr->sa_family);
      dns_unlock();
      return -ENOSYS;
    }

  /* Copy the IP address */

  if (addrlen < copylen)
    {
      nerr("ERROR: Invalid addrlen %ld for family %d\n",
            (long)addrlen, addr->sa_family);
      dns_unlock();
      return -EINVAL;
    }

  memcpy(&dns_addr.addr, addr, copylen);

  /* A port number of zero means to use the default DNS server port number */

  if (*pport == 0)
    {
      *pport = HTONS(DNS_DEFAULT_PORT);
    }

  ret = dns_foreach_nameserver(dns_check_nameserver, &dns_addr.addr);
  if (ret < 0)
    {
      /* If the nameserver is existing, we simply return the Ok. */

      if (ret == -EEXIST)
        {
          ret = OK;
        }

      dns_unlock();
      return ret;
    }

  memcpy(&g_dns_servers[idx].addr, &dns_addr.addr, copylen);

  /* We now have a valid DNS address */

  g_dns_nservers = nservers;
  dns_unlock();
#if CONFIG_NETDB_DNSCLIENT_ENTRIES > 0
  dns_clear_answer();
#endif
  dns_notify_nameserver(&dns_addr.addr, addrlen);
  return OK;
}

#endif /* CONFIG_NETDB_RESOLVCONF */
#endif /* CONFIG_NETDB_DNSCLIENT */
