/*
     This file is part of GNUnet.
     Copyright (C) 2004, 2005, 2006, 2015 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later

 */
/**
 * @file util/os_network.c
 * @brief function to determine available network interfaces
 * @author Nils Durner
 * @author Heikki Lindholm
 * @author Jake Dust
 * @author LRN
 * @author Christian Grothoff
 */
#include "platform.h"
#include "gnunet_util_lib.h"


#define LOG(kind, ...) GNUNET_log_from (kind, "util-os-network", __VA_ARGS__)
#define LOG_STRERROR_FILE(kind, syscall, \
                          filename) GNUNET_log_from_strerror_file (kind, \
                                                                   "util-os-network", \
                                                                   syscall, \
                                                                   filename)


#if ! (HAVE_GETIFADDRS && HAVE_FREEIFADDRS)
/**
 * Try to enumerate all network interfaces using 'ifconfig'.
 *
 * @param proc the callback function
 * @param proc_cls closure for @a proc
 * @return #GNUNET_OK if it worked
 */
static int
try_ifconfig (GNUNET_OS_NetworkInterfaceProcessor proc,
              void *proc_cls)
{
  int i;
  char line[1024];
  char *replace;
  const char *start;
  char ifc[12];
  char addrstr[128];
  char bcstr[128];
  char netmaskstr[128];
  FILE *f;
  int have_ifc;
  struct sockaddr_in a4;
  struct sockaddr_in6 a6;
  struct in_addr v4;
  struct in6_addr v6;
  struct sockaddr_in bcaddr;
  struct sockaddr_in netmask;
  struct sockaddr_in6 netmask6;
  struct sockaddr *pass_bcaddr;
  struct sockaddr *pass_netmask;
  int prefixlen;
  static char *pcall;

  if (NULL == pcall)
  {
    const char *sbin_ifconfig;

#ifdef IFCONFIG
    if (0 == access (IFCONFIG, X_OK))
      sbin_ifconfig = IFCONFIG;
    else
#endif
    if (0 == access ("/sbin/ifconfig", X_OK))
      sbin_ifconfig = "/sbin/ifconfig";
    else if (0 == access ("/usr/sbin/ifconfig", X_OK))
      sbin_ifconfig = "/usr/sbin/ifconfig";
    else
      sbin_ifconfig = "ifconfig";
    GNUNET_asprintf (&pcall,
                     "%s -a 2> /dev/null",
                     sbin_ifconfig);
  }
  f = popen (pcall, "r");
  if (NULL == f)
  {
    LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
                       "popen",
                       "ifconfig");

    return GNUNET_SYSERR;
  }

  have_ifc = GNUNET_NO;
  ifc[11] = '\0';
  while (NULL != fgets (line, sizeof(line), f))
  {
    if (strlen (line) == 0)
    {
      have_ifc = GNUNET_NO;
      continue;
    }
    if (! isspace (line[0]))
    {
      have_ifc = (1 == sscanf (line, "%11s", ifc)) ? GNUNET_YES : GNUNET_NO;
      /* would end with ':' on OSX, fix it! */
      if (ifc[strlen (ifc) - 1] == ':')
        ifc[strlen (ifc) - 1] = '\0';
      continue;
    }
    if (! have_ifc)
      continue;                 /* strange input, hope for the best */

    /* make parsing of ipv6 addresses easier */
    for (replace = line; *replace != '\0'; replace++)
    {
      if (*replace == '/')
        *replace = ' ';
    }
    prefixlen = -1;

    start = line;
    while (('\0' != *start) && (isspace (*start)))
      start++;

    /* Zero-out stack allocated values */
    memset (addrstr, 0, 128);
    memset (netmaskstr, 0, 128);
    memset (bcstr, 0, 128);
    prefixlen = 0;

    if (   /* Linux */
      (3 == sscanf (start, "inet addr:%127s Bcast:%127s Mask:%127s", addrstr,
                    bcstr, netmaskstr)) ||
      (2 == sscanf (start, "inet addr:%127s Mask:%127s", addrstr,
                    netmaskstr)) ||
      (2 == sscanf (start, "inet6 addr:%127s %d", addrstr, &prefixlen)) ||
      /* Solaris, OS X */
      (1 == sscanf (start, "inet %127s", addrstr)) ||
      (1 == sscanf (start, "inet6 %127s", addrstr)))
    {
      /* IPv4 */
      if (1 == inet_pton (AF_INET, addrstr, &v4))
      {
        memset (&a4, 0, sizeof(a4));
        a4.sin_family = AF_INET;
#if HAVE_SOCKADDR_IN_SIN_LEN
        a4.sin_len = (u_char) sizeof(struct sockaddr_in);
#endif
        a4.sin_addr = v4;

        pass_bcaddr = NULL;
        pass_netmask = NULL;
        if (1 == inet_pton (AF_INET, bcstr, &v4))
        {
          memset (&bcaddr, 0, sizeof(bcaddr));
          bcaddr.sin_family = AF_INET;
#if HAVE_SOCKADDR_IN_SIN_LEN
          bcaddr.sin_len = (u_char) sizeof(struct sockaddr_in);
#endif
          bcaddr.sin_addr = v4;
          pass_bcaddr = (struct sockaddr *) &bcaddr;
        }
        if (1 == inet_pton (AF_INET, netmaskstr, &v4))
        {
          memset (&netmask, 0, sizeof(netmask));
          netmask.sin_family = AF_INET;
#if HAVE_SOCKADDR_IN_SIN_LEN
          netmask.sin_len = (u_char) sizeof(struct sockaddr_in);
#endif
          netmask.sin_addr = v4;
          pass_netmask = (struct sockaddr *) &netmask;
        }


        if (GNUNET_OK !=
            proc (proc_cls, ifc,(0 == strcmp (ifc, GNUNET_DEFAULT_INTERFACE)),
                  (const struct sockaddr *) &a4,
                  pass_bcaddr, pass_netmask, sizeof(a4)))
          break;
        continue;
      }
      /* IPv6 */
      if (1 == inet_pton (AF_INET6, addrstr, &v6))
      {
        memset (&a6, 0, sizeof(a6));
        a6.sin6_family = AF_INET6;
#if HAVE_SOCKADDR_IN_SIN_LEN
        a6.sin6_len = (u_char) sizeof(struct sockaddr_in6);
#endif
        a6.sin6_addr = v6;

        pass_netmask = NULL;
        if (prefixlen != -1)
        {
          memset (v6.s6_addr, 0, sizeof(v6.s6_addr));
          for (i = 0; i < prefixlen; i++)
          {
            v6.s6_addr[i >> 3] |= 1 << (i & 7);
          }
          memset (&netmask6, 0, sizeof(netmask6));
          netmask6.sin6_family = AF_INET6;
#if HAVE_SOCKADDR_IN_SIN_LEN
          netmask6.sin6_len = (u_char) sizeof(struct sockaddr_in6);
#endif
          netmask6.sin6_addr = v6;

          pass_netmask = (struct sockaddr *) &netmask6;
        }

        if (GNUNET_OK !=
            proc (proc_cls, ifc,(0 == strcmp (ifc, GNUNET_DEFAULT_INTERFACE)),
                  (const struct sockaddr *) &a6,
                  NULL, pass_netmask, sizeof(a6)))
          break;
        continue;
      }
    }
  }
  pclose (f);
  return GNUNET_OK;
}


/**
 * Try to enumerate all network interfaces using 'ip'.
 *
 * @param proc the callback function
 * @param proc_cls closure for @a proc
 * @return #GNUNET_OK if it worked
 */
static int
try_ip (GNUNET_OS_NetworkInterfaceProcessor proc,
        void *proc_cls)
{
  char line[1024];
  char *replace;
  char ifname[64];
  char afstr[6];
  char addrstr[128];
  FILE *f;
  struct sockaddr_in a4;
  struct sockaddr_in6 a6;
  struct in_addr v4;
  struct in6_addr v6;
  struct sockaddr_in netmask;
  struct sockaddr_in6 netmask6;
  unsigned int i;
  unsigned int prefixlen;
  static char *pcall;

  if (NULL == pcall)
  {
    const char *sbin_ip;

#ifdef IFCONFIG
    if (0 == access (PATH_TO_IP, X_OK))
      sbin_ip = PATH_TO_IP;
    else
#endif
    if (0 == access ("/sbin/ip", X_OK))
      sbin_ip = "/sbin/ip";
    else if (0 == access ("/usr/sbin/ip", X_OK))
      sbin_ip = "/usr/sbin/ip";
    else
      sbin_ip = "if";
    GNUNET_asprintf (&pcall,
                     "%s -o add 2> /dev/null",
                     sbin_ip);
  }
  f = popen (pcall, "r");
  if (! f)
  {
    LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
                       "popen",
                       "ip");
    return GNUNET_SYSERR;
  }

  while (NULL != fgets (line, sizeof(line), f))
  {
    /* make parsing easier */
    for (replace = line; *replace != '\0'; replace++)
    {
      if (*replace == '/')
        *replace = ' ';
    }
    /* Zero-out stack allocated values */
    memset (ifname, 0, 64);
    memset (afstr, 0, 6);
    memset (addrstr, 0, 128);
    if (4 != sscanf (line,
                     "%*u: %63s %5s %127s %6u",
                     ifname,
                     afstr,
                     addrstr,
                     &prefixlen))
      continue;
    /* IPv4 */
    if ((0 == strcasecmp ("inet",
                          afstr)) &&
        (1 == inet_pton (AF_INET,
                         addrstr,
                         &v4)))
    {
      memset (&a4, 0, sizeof(a4));
      a4.sin_family = AF_INET;
#if HAVE_SOCKADDR_IN_SIN_LEN
      a4.sin_len = (u_char) sizeof(struct sockaddr_in);
#endif
      a4.sin_addr = v4;

      memset (&v4.s_addr, 0, sizeof(v4.s_addr));
      for (i = 0; i < prefixlen; i++)
        v4.s_addr |= 1 << (i & 7);
      memset (&netmask, 0, sizeof(netmask));
      netmask.sin_family = AF_INET;
#if HAVE_SOCKADDR_IN_SIN_LEN
      netmask.sin_len = (u_char) sizeof(struct sockaddr_in);
#endif
      netmask.sin_addr = v4;

      if (GNUNET_OK !=
          proc (proc_cls,
                ifname,
                (0 == strcmp (ifname,
                              GNUNET_DEFAULT_INTERFACE)),
                (const struct sockaddr *) &a4,
                NULL,
                (const struct sockaddr *) &netmask,
                sizeof(a4)))
        break;
    }
    /* IPv6 */
    if ((0 == strcasecmp ("inet6",
                          afstr)) &&
        (1 == inet_pton (AF_INET6,
                         addrstr,
                         &v6)))
    {
      memset (&a6, 0, sizeof(a6));
      a6.sin6_family = AF_INET6;
#if HAVE_SOCKADDR_IN_SIN_LEN
      a6.sin6_len = (u_char) sizeof(struct sockaddr_in6);
#endif
      a6.sin6_addr = v6;

      memset (v6.s6_addr, 0, sizeof(v6.s6_addr));
      for (i = 0; i < prefixlen; i++)
        v6.s6_addr[i >> 3] |= 1 << (i & 7);
      memset (&netmask6, 0, sizeof(netmask6));
      netmask6.sin6_family = AF_INET6;
#if HAVE_SOCKADDR_IN_SIN_LEN
      netmask6.sin6_len = (u_char) sizeof(struct sockaddr_in6);
#endif
      netmask6.sin6_addr = v6;

      if (GNUNET_OK !=
          proc (proc_cls,
                ifname,
                (0 == strcmp (ifname,
                              GNUNET_DEFAULT_INTERFACE)),
                (const struct sockaddr *) &a6,
                NULL,
                (const struct sockaddr *) &netmask6,
                sizeof(a6)))
        break;
    }
  }
  pclose (f);
  return GNUNET_OK;
}


#endif


/**
 * @brief Enumerate all network interfaces
 *
 * @param proc the callback function
 * @param proc_cls closure for @a proc
 */
void
GNUNET_OS_network_interfaces_list (GNUNET_OS_NetworkInterfaceProcessor proc,
                                   void *proc_cls)
{
#if HAVE_GETIFADDRS && HAVE_FREEIFADDRS
  struct ifaddrs *ifa_first;
  struct ifaddrs *ifa_ptr;
  socklen_t alen;

  if (getifaddrs (&ifa_first) == 0)
  {
    for (ifa_ptr = ifa_first; ifa_ptr != NULL; ifa_ptr = ifa_ptr->ifa_next)
    {
      if ((ifa_ptr->ifa_name != NULL) && (ifa_ptr->ifa_addr != NULL) &&
          ( (ifa_ptr->ifa_flags & IFF_UP) != 0) )
      {
        if ((ifa_ptr->ifa_addr->sa_family != AF_INET) &&
            (ifa_ptr->ifa_addr->sa_family != AF_INET6))
          continue;
        if (ifa_ptr->ifa_addr->sa_family == AF_INET)
          alen = sizeof(struct sockaddr_in);
        else
          alen = sizeof(struct sockaddr_in6);
        if (GNUNET_OK !=
            proc (proc_cls, ifa_ptr->ifa_name,
                  (0 == strcmp (ifa_ptr->ifa_name, GNUNET_DEFAULT_INTERFACE)),
                  ifa_ptr->ifa_addr, ifa_ptr->ifa_broadaddr,
                  ifa_ptr->ifa_netmask, alen))
          break;
      }
    }
    freeifaddrs (ifa_first);
  }
#else
  if (GNUNET_OK ==
      try_ip (proc,
              proc_cls))
    return;
  if (GNUNET_OK ==
      try_ifconfig (proc,
                    proc_cls))
    return;
  LOG (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
       "Failed to enumerate network interfaces\n");
#endif
}


/* end of os_network.c */
