/*
 * Network interface functions for CUPS.
 *
 * Copyright © 2007-2018 by Apple Inc.
 * Copyright © 1997-2006 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers.
 */

#include "getifaddrs-internal.h"


#ifndef HAVE_GETIFADDRS
/*
 * '_cups_getifaddrs()' - Get a list of network interfaces on the system.
 */

int					/* O - 0 on success, -1 on error */
_cups_getifaddrs(struct ifaddrs **addrs)/* O - List of interfaces */
{
  int			sock;		/* Socket */
  char			buffer[65536],	/* Buffer for address info */
			*bufptr,	/* Pointer into buffer */
			*bufend;	/* End of buffer */
  struct ifconf		conf;		/* Interface configurations */
  struct sockaddr	addr;		/* Address data */
  struct ifreq		*ifp;		/* Interface data */
  int			ifpsize;	/* Size of interface data */
  struct ifaddrs	*temp;		/* Pointer to current interface */
  struct ifreq		request;	/* Interface request */


 /*
  * Start with an empty list...
  */

  if (addrs == NULL)
    return (-1);

  *addrs = NULL;

 /*
  * Create a UDP socket to get the interface data...
  */

  memset (&addr, 0, sizeof(addr));
  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    return (-1);

 /*
  * Try to get the list of interfaces...
  */

  conf.ifc_len = sizeof(buffer);
  conf.ifc_buf = buffer;

  if (ioctl(sock, SIOCGIFCONF, &conf) < 0)
  {
   /*
    * Couldn't get the list of interfaces...
    */

    close(sock);
    return (-1);
  }

 /*
  * OK, got the list of interfaces, now lets step through the
  * buffer to pull them out...
  */

#  ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
#    define sockaddr_len(a)	((a)->sa_len)
#  else
#    define sockaddr_len(a)	(sizeof(struct sockaddr))
#  endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */

  for (bufptr = buffer, bufend = buffer + conf.ifc_len;
       bufptr < bufend;
       bufptr += ifpsize)
  {
   /*
    * Get the current interface information...
    */

    ifp     = (struct ifreq *)bufptr;
    ifpsize = sizeof(ifp->ifr_name) + sockaddr_len(&(ifp->ifr_addr));

    if (ifpsize < sizeof(struct ifreq))
      ifpsize = sizeof(struct ifreq);

    memset(&request, 0, sizeof(request));
    memcpy(request.ifr_name, ifp->ifr_name, sizeof(ifp->ifr_name));

   /*
    * Check the status of the interface...
    */

    if (ioctl(sock, SIOCGIFFLAGS, &request) < 0)
      continue;

   /*
    * Allocate memory for a single interface record...
    */

    if ((temp = calloc(1, sizeof(struct ifaddrs))) == NULL)
    {
     /*
      * Unable to allocate memory...
      */

      close(sock);
      return (-1);
    }

   /*
    * Add this record to the front of the list and copy the name, flags,
    * and network address...
    */

    temp->ifa_next  = *addrs;
    *addrs          = temp;
    temp->ifa_name  = strdup(ifp->ifr_name);
    temp->ifa_flags = request.ifr_flags;
    if ((temp->ifa_addr = calloc(1, sockaddr_len(&(ifp->ifr_addr)))) != NULL)
      memcpy(temp->ifa_addr, &(ifp->ifr_addr), sockaddr_len(&(ifp->ifr_addr)));

   /*
    * Try to get the netmask for the interface...
    */

    if (!ioctl(sock, SIOCGIFNETMASK, &request))
    {
     /*
      * Got it, make a copy...
      */

      if ((temp->ifa_netmask = calloc(1, sizeof(request.ifr_netmask))) != NULL)
	memcpy(temp->ifa_netmask, &(request.ifr_netmask),
	       sizeof(request.ifr_netmask));
    }

   /*
    * Then get the broadcast or point-to-point (destination) address,
    * if applicable...
    */

    if (temp->ifa_flags & IFF_BROADCAST)
    {
     /*
      * Have a broadcast address, so get it!
      */

      if (!ioctl(sock, SIOCGIFBRDADDR, &request))
      {
       /*
	* Got it, make a copy...
	*/

	if ((temp->ifa_broadaddr =
	         calloc(1, sizeof(request.ifr_broadaddr))) != NULL)
	  memcpy(temp->ifa_broadaddr, &(request.ifr_broadaddr),
		 sizeof(request.ifr_broadaddr));
      }
    }
    else if (temp->ifa_flags & IFF_POINTOPOINT)
    {
     /*
      * Point-to-point interface; grab the remote address...
      */

      if (!ioctl(sock, SIOCGIFDSTADDR, &request))
      {
	temp->ifa_dstaddr = malloc(sizeof(request.ifr_dstaddr));
	memcpy(temp->ifa_dstaddr, &(request.ifr_dstaddr),
	       sizeof(request.ifr_dstaddr));
      }
    }
  }

 /*
  * OK, we're done with the socket, close it and return 0...
  */

  close(sock);

  return (0);
}


/*
 * '_cups_freeifaddrs()' - Free an interface list...
 */

void
_cups_freeifaddrs(struct ifaddrs *addrs)/* I - Interface list to free */
{
  struct ifaddrs	*next;		/* Next interface in list */


  while (addrs != NULL)
  {
   /*
    * Make a copy of the next interface pointer...
    */

    next = addrs->ifa_next;

   /*
    * Free data values as needed...
    */

    if (addrs->ifa_name)
    {
      free(addrs->ifa_name);
      addrs->ifa_name = NULL;
    }

    if (addrs->ifa_addr)
    {
      free(addrs->ifa_addr);
      addrs->ifa_addr = NULL;
    }

    if (addrs->ifa_netmask)
    {
      free(addrs->ifa_netmask);
      addrs->ifa_netmask = NULL;
    }

    if (addrs->ifa_dstaddr)
    {
      free(addrs->ifa_dstaddr);
      addrs->ifa_dstaddr = NULL;
    }

   /*
    * Free this node and continue to the next...
    */

    free(addrs);

    addrs = next;
  }
}
#endif /* !HAVE_GETIFADDRS */
