/*
 * "$Id: http-addrlist.c 11627 2014-02-20 16:15:09Z msweet $"
 *
 * HTTP address list routines for CUPS.
 *
 * Copyright 2007-2014 by Apple Inc.
 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
 *
 * These coded instructions, statements, and computer programs are the
 * property of Apple Inc. and are protected by Federal copyright
 * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
 * which should have been included with this file.  If this file is
 * file is missing or damaged, see the license at "http://www.cups.org/".
 *
 * This file is subject to the Apple OS-Developed Software exception.
 */

/*
 * Include necessary headers...
 */

#include "cups-private.h"
#ifdef HAVE_RESOLV_H
#  include <resolv.h>
#endif /* HAVE_RESOLV_H */
#ifdef HAVE_POLL
#  include <poll.h>
#endif /* HAVE_POLL */
#ifndef WIN32
#  include <fcntl.h>
#endif /* WIN32 */


/*
 * 'httpAddrConnect()' - Connect to any of the addresses in the list.
 *
 * @since CUPS 1.2/OS X 10.5@
 */

http_addrlist_t *			/* O - Connected address or NULL on failure */
httpAddrConnect(
    http_addrlist_t *addrlist,		/* I - List of potential addresses */
    int             *sock)		/* O - Socket */
{
  DEBUG_printf(("httpAddrConnect(addrlist=%p, sock=%p)", addrlist, sock));

  return (httpAddrConnect2(addrlist, sock, 30000, NULL));
}


/*
 * 'httpAddrConnect2()' - Connect to any of the addresses in the list with a
 *                        timeout and optional cancel.
 *
 * @since CUPS 1.7/OS X 10.9@
 */

http_addrlist_t *			/* O - Connected address or NULL on failure */
httpAddrConnect2(
    http_addrlist_t *addrlist,		/* I - List of potential addresses */
    int             *sock,		/* O - Socket */
    int             msec,		/* I - Timeout in milliseconds */
    int             *cancel)		/* I - Pointer to "cancel" variable */
{
  int			val;		/* Socket option value */
#ifdef O_NONBLOCK
  socklen_t		len;		/* Length of value */
  http_addr_t		peer;		/* Peer address */
  int			flags,		/* Socket flags */
			remaining;	/* Remaining timeout */
#  ifdef HAVE_POLL
  struct pollfd		pfd;		/* Polled file descriptor */
#  else
  fd_set		input_set,	/* select() input set */
			output_set;	/* select() output set */
  struct timeval	timeout;	/* Timeout */
#  endif /* HAVE_POLL */
  int			nfds;		/* Result from select()/poll() */
#endif /* O_NONBLOCK */
#ifdef DEBUG
  char			temp[256];	/* Temporary address string */
#endif /* DEBUG */


  DEBUG_printf(("httpAddrConnect2(addrlist=%p, sock=%p, msec=%d, cancel=%p)",
                addrlist, sock, msec, cancel));

  if (!sock)
  {
    errno = EINVAL;
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
    return (NULL);
  }

  if (cancel && *cancel)
    return (NULL);

  if (msec <= 0 || getenv("CUPS_DISABLE_ASYNC_CONNECT"))
    msec = INT_MAX;

 /*
  * Loop through each address until we connect or run out of addresses...
  */

  while (addrlist)
  {
    if (cancel && *cancel)
      return (NULL);

   /*
    * Create the socket...
    */

    DEBUG_printf(("2httpAddrConnect2: Trying %s:%d...",
		  httpAddrString(&(addrlist->addr), temp, sizeof(temp)),
		  httpAddrPort(&(addrlist->addr))));

    if ((*sock = (int)socket(httpAddrFamily(&(addrlist->addr)), SOCK_STREAM,
                             0)) < 0)
    {
     /*
      * Don't abort yet, as this could just be an issue with the local
      * system not being configured with IPv4/IPv6/domain socket enabled...
      */

      addrlist = addrlist->next;
      continue;
    }

   /*
    * Set options...
    */

    val = 1;
    setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, CUPS_SOCAST &val, sizeof(val));

#ifdef SO_REUSEPORT
    val = 1;
    setsockopt(*sock, SOL_SOCKET, SO_REUSEPORT, CUPS_SOCAST &val, sizeof(val));
#endif /* SO_REUSEPORT */

#ifdef SO_NOSIGPIPE
    val = 1;
    setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, CUPS_SOCAST &val, sizeof(val));
#endif /* SO_NOSIGPIPE */

   /*
    * Using TCP_NODELAY improves responsiveness, especially on systems
    * with a slow loopback interface...
    */

    val = 1;
    setsockopt(*sock, IPPROTO_TCP, TCP_NODELAY, CUPS_SOCAST &val, sizeof(val));

#ifdef FD_CLOEXEC
   /*
    * Close this socket when starting another process...
    */

    fcntl(*sock, F_SETFD, FD_CLOEXEC);
#endif /* FD_CLOEXEC */

#ifdef O_NONBLOCK
   /*
    * Do an asynchronous connect by setting the socket non-blocking...
    */

    DEBUG_printf(("httpAddrConnect2: Setting non-blocking connect()"));

    flags = fcntl(*sock, F_GETFL, 0);
    if (msec != INT_MAX)
    {
      DEBUG_puts("httpAddrConnect2: Setting non-blocking connect()");

      fcntl(*sock, F_SETFL, flags | O_NONBLOCK);
    }
#endif /* O_NONBLOCK */

   /*
    * Then connect...
    */

    if (!connect(*sock, &(addrlist->addr.addr), (socklen_t)httpAddrLength(&(addrlist->addr))))
    {
      DEBUG_printf(("1httpAddrConnect2: Connected to %s:%d...",
		    httpAddrString(&(addrlist->addr), temp, sizeof(temp)),
		    httpAddrPort(&(addrlist->addr))));

#ifdef O_NONBLOCK
      fcntl(*sock, F_SETFL, flags);
#endif /* O_NONBLOCK */

      return (addrlist);
    }

#ifdef O_NONBLOCK
#  ifdef WIN32
    if (WSAGetLastError() == WSAEINPROGRESS ||
        WSAGetLastError() == WSAEWOULDBLOCK)
#  else
    if (errno == EINPROGRESS || errno == EWOULDBLOCK)
#  endif /* WIN32 */
    {
      DEBUG_puts("1httpAddrConnect2: Finishing async connect()");

      fcntl(*sock, F_SETFL, flags);

      for (remaining = msec; remaining > 0; remaining -= 250)
      {
	do
        {
          if (cancel && *cancel)
          {
	   /*
	    * Close this socket and return...
	    */

            DEBUG_puts("1httpAddrConnect2: Canceled connect()");

            httpAddrClose(NULL, *sock);

	    *sock = -1;

	    return (NULL);
          }

#  ifdef HAVE_POLL
	  pfd.fd     = *sock;
	  pfd.events = POLLIN | POLLOUT;

          nfds = poll(&pfd, 1, remaining > 250 ? 250 : remaining);

	  DEBUG_printf(("1httpAddrConnect2: poll() returned %d (%d)", nfds,
	                errno));

#  else
	  FD_ZERO(&input_set);
	  FD_SET(*sock, &input_set);
	  output_set = input_set;

	  timeout.tv_sec  = 0;
	  timeout.tv_usec = (remaining > 250 ? 250 : remaining) * 1000;

	  nfds = select(*sock + 1, &input_set, &output_set, NULL, &timeout);

	  DEBUG_printf(("1httpAddrConnect2: select() returned %d (%d)", nfds,
	                errno));
#  endif /* HAVE_POLL */
	}
#  ifdef WIN32
	while (nfds < 0 && (WSAGetLastError() == WSAEINTR ||
			    WSAGetLastError() == WSAEWOULDBLOCK));
#  else
	while (nfds < 0 && (errno == EINTR || errno == EAGAIN));
#  endif /* WIN32 */

        if (nfds > 0)
        {
          len = sizeof(peer);
          if (!getpeername(*sock, (struct sockaddr *)&peer, &len))
          {
	    DEBUG_printf(("1httpAddrConnect2: Connected to %s:%d...",
			  httpAddrString(&peer, temp, sizeof(temp)),
			  httpAddrPort(&peer)));

	    return (addrlist);
	  }

          break;
        }
      }
    }
#endif /* O_NONBLOCK */

    DEBUG_printf(("1httpAddrConnect2: Unable to connect to %s:%d: %s",
		  httpAddrString(&(addrlist->addr), temp, sizeof(temp)),
		  httpAddrPort(&(addrlist->addr)), strerror(errno)));

#ifndef WIN32
    if (errno == EINPROGRESS)
      errno = ETIMEDOUT;
#endif /* !WIN32 */

   /*
    * Close this socket and move to the next address...
    */

    httpAddrClose(NULL, *sock);

    *sock    = -1;
    addrlist = addrlist->next;
  }

  if (!addrlist)
#ifdef WIN32
    _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, "Connection failed", 0);
#else
    _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, strerror(errno), 0);
#endif /* WIN32 */

  return (addrlist);
}



/*
 * 'httpAddrCopyList()' - Copy an address list.
 *
 * @since CUPS 1.7/OS X 10.9@
 */

http_addrlist_t	*			/* O - New address list or @code NULL@ on error */
httpAddrCopyList(
    http_addrlist_t *src)		/* I - Source address list */
{
  http_addrlist_t	*dst = NULL,	/* First list entry */
			*prev = NULL,	/* Previous list entry */
			*current = NULL;/* Current list entry */


  while (src)
  {
    if ((current = malloc(sizeof(http_addrlist_t))) == NULL)
    {
      current = dst;

      while (current)
      {
        prev    = current;
        current = current->next;

        free(prev);
      }

      return (NULL);
    }

    memcpy(current, src, sizeof(http_addrlist_t));

    current->next = NULL;

    if (prev)
      prev->next = current;
    else
      dst = current;

    prev = current;
    src  = src->next;
  }

  return (dst);
}


/*
 * 'httpAddrFreeList()' - Free an address list.
 *
 * @since CUPS 1.2/OS X 10.5@
 */

void
httpAddrFreeList(
    http_addrlist_t *addrlist)		/* I - Address list to free */
{
  http_addrlist_t	*next;		/* Next address in list */


 /*
  * Free each address in the list...
  */

  while (addrlist)
  {
    next = addrlist->next;

    free(addrlist);

    addrlist = next;
  }
}


/*
 * 'httpAddrGetList()' - Get a list of addresses for a hostname.
 *
 * @since CUPS 1.2/OS X 10.5@
 */

http_addrlist_t	*			/* O - List of addresses or NULL */
httpAddrGetList(const char *hostname,	/* I - Hostname, IP address, or NULL for passive listen address */
                int        family,	/* I - Address family or AF_UNSPEC */
		const char *service)	/* I - Service name or port number */
{
  http_addrlist_t	*first,		/* First address in list */
			*addr,		/* Current address in list */
			*temp;		/* New address */
  _cups_globals_t	*cg = _cupsGlobals();
					/* Global data */


#ifdef DEBUG
  _cups_debug_printf("httpAddrGetList(hostname=\"%s\", family=AF_%s, "
                     "service=\"%s\")\n",
		     hostname ? hostname : "(nil)",
		     family == AF_UNSPEC ? "UNSPEC" :
#  ifdef AF_LOCAL
	                 family == AF_LOCAL ? "LOCAL" :
#  endif /* AF_LOCAL */
#  ifdef AF_INET6
	                 family == AF_INET6 ? "INET6" :
#  endif /* AF_INET6 */
	                 family == AF_INET ? "INET" : "???", service);
#endif /* DEBUG */

#ifdef HAVE_RES_INIT
 /*
  * STR #2920: Initialize resolver after failure in cups-polld
  *
  * If the previous lookup failed, re-initialize the resolver to prevent
  * temporary network errors from persisting.  This *should* be handled by
  * the resolver libraries, but apparently the glibc folks do not agree.
  *
  * We set a flag at the end of this function if we encounter an error that
  * requires reinitialization of the resolver functions.  We then call
  * res_init() if the flag is set on the next call here or in httpAddrLookup().
  */

  if (cg->need_res_init)
  {
    res_init();

    cg->need_res_init = 0;
  }
#endif /* HAVE_RES_INIT */

 /*
  * Lookup the address the best way we can...
  */

  first = addr = NULL;

#ifdef AF_LOCAL
  if (hostname && hostname[0] == '/')
  {
   /*
    * Domain socket address...
    */

    if ((first = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t))) != NULL)
    {
      addr = first;
      first->addr.un.sun_family = AF_LOCAL;
      strlcpy(first->addr.un.sun_path, hostname, sizeof(first->addr.un.sun_path));
    }
  }
  else
#endif /* AF_LOCAL */
  if (!hostname || _cups_strcasecmp(hostname, "localhost"))
  {
#ifdef HAVE_GETADDRINFO
    struct addrinfo	hints,		/* Address lookup hints */
			*results,	/* Address lookup results */
			*current;	/* Current result */
    char		ipv6[64],	/* IPv6 address */
			*ipv6zone;	/* Pointer to zone separator */
    int			ipv6len;	/* Length of IPv6 address */
    int			error;		/* getaddrinfo() error */


   /*
    * Lookup the address as needed...
    */

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = family;
    hints.ai_flags    = hostname ? 0 : AI_PASSIVE;
    hints.ai_socktype = SOCK_STREAM;

    if (hostname && *hostname == '[')
    {
     /*
      * Remove brackets from numeric IPv6 address...
      */

      if (!strncmp(hostname, "[v1.", 4))
      {
       /*
        * Copy the newer address format which supports link-local addresses...
	*/

	strlcpy(ipv6, hostname + 4, sizeof(ipv6));
	if ((ipv6len = (int)strlen(ipv6) - 1) >= 0 && ipv6[ipv6len] == ']')
	{
          ipv6[ipv6len] = '\0';
	  hostname      = ipv6;

         /*
	  * Convert "+zone" in address to "%zone"...
	  */

          if ((ipv6zone = strrchr(ipv6, '+')) != NULL)
	    *ipv6zone = '%';
	}
      }
      else
      {
       /*
        * Copy the regular non-link-local IPv6 address...
	*/

	strlcpy(ipv6, hostname + 1, sizeof(ipv6));
	if ((ipv6len = (int)strlen(ipv6) - 1) >= 0 && ipv6[ipv6len] == ']')
	{
          ipv6[ipv6len] = '\0';
	  hostname      = ipv6;
	}
      }
    }

    if ((error = getaddrinfo(hostname, service, &hints, &results)) == 0)
    {
     /*
      * Copy the results to our own address list structure...
      */

      for (current = results; current; current = current->ai_next)
        if (current->ai_family == AF_INET || current->ai_family == AF_INET6)
	{
	 /*
          * Copy the address over...
	  */

	  temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	  if (!temp)
	  {
	    httpAddrFreeList(first);
	    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
	    return (NULL);
	  }

          if (current->ai_family == AF_INET6)
	    memcpy(&(temp->addr.ipv6), current->ai_addr,
	           sizeof(temp->addr.ipv6));
	  else
	    memcpy(&(temp->addr.ipv4), current->ai_addr,
	           sizeof(temp->addr.ipv4));

         /*
	  * Append the address to the list...
	  */

	  if (!first)
	    first = temp;

	  if (addr)
	    addr->next = temp;

	  addr = temp;
	}

     /*
      * Free the results from getaddrinfo()...
      */

      freeaddrinfo(results);
    }
    else
    {
      if (error == EAI_FAIL)
        cg->need_res_init = 1;

      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gai_strerror(error), 0);
    }

#else
    if (hostname)
    {
      int		i;		/* Looping vars */
      unsigned		ip[4];		/* IPv4 address components */
      const char	*ptr;		/* Pointer into hostname */
      struct hostent	*host;		/* Result of lookup */
      struct servent	*port;		/* Port number for service */
      int		portnum;	/* Port number */


     /*
      * Lookup the service...
      */

      if (!service)
	portnum = 0;
      else if (isdigit(*service & 255))
	portnum = atoi(service);
      else if ((port = getservbyname(service, NULL)) != NULL)
	portnum = ntohs(port->s_port);
      else if (!strcmp(service, "http"))
        portnum = 80;
      else if (!strcmp(service, "https"))
        portnum = 443;
      else if (!strcmp(service, "ipp") || !strcmp(service, "ipps"))
        portnum = 631;
      else if (!strcmp(service, "lpd"))
        portnum = 515;
      else if (!strcmp(service, "socket"))
        portnum = 9100;
      else
	return (NULL);

     /*
      * This code is needed because some operating systems have a
      * buggy implementation of gethostbyname() that does not support
      * IPv4 addresses.  If the hostname string is an IPv4 address, then
      * sscanf() is used to extract the IPv4 components.  We then pack
      * the components into an IPv4 address manually, since the
      * inet_aton() function is deprecated.  We use the htonl() macro
      * to get the right byte order for the address.
      */

      for (ptr = hostname; isdigit(*ptr & 255) || *ptr == '.'; ptr ++);

      if (!*ptr)
      {
       /*
	* We have an IPv4 address; break it up and create an IPv4 address...
	*/

	if (sscanf(hostname, "%u.%u.%u.%u", ip, ip + 1, ip + 2, ip + 3) == 4 &&
            ip[0] <= 255 && ip[1] <= 255 && ip[2] <= 255 && ip[3] <= 255)
	{
	  first = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	  if (!first)
	    return (NULL);

          first->addr.ipv4.sin_family = AF_INET;
          first->addr.ipv4.sin_addr.s_addr = htonl((((((((unsigned)ip[0] << 8) |
	                                               (unsigned)ip[1]) << 8) |
						     (unsigned)ip[2]) << 8) |
						   (unsigned)ip[3]));
          first->addr.ipv4.sin_port = htons(portnum);
	}
      }
      else if ((host = gethostbyname(hostname)) != NULL &&
#  ifdef AF_INET6
               (host->h_addrtype == AF_INET || host->h_addrtype == AF_INET6))
#  else
               host->h_addrtype == AF_INET)
#  endif /* AF_INET6 */
      {
	for (i = 0; host->h_addr_list[i]; i ++)
	{
	 /*
          * Copy the address over...
	  */

	  temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	  if (!temp)
	  {
	    httpAddrFreeList(first);
	    return (NULL);
	  }

#  ifdef AF_INET6
          if (host->h_addrtype == AF_INET6)
	  {
            temp->addr.ipv6.sin6_family = AF_INET6;
	    memcpy(&(temp->addr.ipv6.sin6_addr), host->h_addr_list[i],
	           sizeof(temp->addr.ipv6));
            temp->addr.ipv6.sin6_port = htons(portnum);
	  }
	  else
#  endif /* AF_INET6 */
	  {
            temp->addr.ipv4.sin_family = AF_INET;
	    memcpy(&(temp->addr.ipv4.sin_addr), host->h_addr_list[i],
	           sizeof(temp->addr.ipv4));
            temp->addr.ipv4.sin_port = htons(portnum);
          }

	 /*
	  * Append the address to the list...
	  */

	  if (!first)
	    first = temp;

	  if (addr)
	    addr->next = temp;

	  addr = temp;
	}
      }
      else
      {
        if (h_errno == NO_RECOVERY)
          cg->need_res_init = 1;

	_cupsSetError(IPP_STATUS_ERROR_INTERNAL, hstrerror(h_errno), 0);
      }
    }
#endif /* HAVE_GETADDRINFO */
  }

 /*
  * Detect some common errors and handle them sanely...
  */

  if (!addr && (!hostname || !_cups_strcasecmp(hostname, "localhost")))
  {
    struct servent	*port;		/* Port number for service */
    int			portnum;	/* Port number */


   /*
    * Lookup the service...
    */

    if (!service)
      portnum = 0;
    else if (isdigit(*service & 255))
      portnum = atoi(service);
    else if ((port = getservbyname(service, NULL)) != NULL)
      portnum = ntohs(port->s_port);
    else if (!strcmp(service, "http"))
      portnum = 80;
    else if (!strcmp(service, "https"))
      portnum = 443;
    else if (!strcmp(service, "ipp") || !strcmp(service, "ipps"))
      portnum = 631;
    else if (!strcmp(service, "lpd"))
      portnum = 515;
    else if (!strcmp(service, "socket"))
      portnum = 9100;
    else
    {
      httpAddrFreeList(first);

      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown service name."), 1);
      return (NULL);
    }

    if (hostname && !_cups_strcasecmp(hostname, "localhost"))
    {
     /*
      * Unfortunately, some users ignore all of the warnings in the
      * /etc/hosts file and delete "localhost" from it. If we get here
      * then we were unable to resolve the name, so use the IPv6 and/or
      * IPv4 loopback interface addresses...
      */

#ifdef AF_INET6
      if (family != AF_INET)
      {
       /*
        * Add [::1] to the address list...
	*/

	temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	if (!temp)
	{
	  _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
	  httpAddrFreeList(first);
	  return (NULL);
	}

        temp->addr.ipv6.sin6_family            = AF_INET6;
	temp->addr.ipv6.sin6_port              = htons(portnum);
#  ifdef WIN32
	temp->addr.ipv6.sin6_addr.u.Byte[15]   = 1;
#  else
	temp->addr.ipv6.sin6_addr.s6_addr32[3] = htonl(1);
#  endif /* WIN32 */

        if (!first)
          first = temp;

        addr = temp;
      }

      if (family != AF_INET6)
#endif /* AF_INET6 */
      {
       /*
        * Add 127.0.0.1 to the address list...
	*/

	temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	if (!temp)
	{
	  _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
	  httpAddrFreeList(first);
	  return (NULL);
	}

        temp->addr.ipv4.sin_family      = AF_INET;
	temp->addr.ipv4.sin_port        = htons(portnum);
	temp->addr.ipv4.sin_addr.s_addr = htonl(0x7f000001);

        if (!first)
          first = temp;

        if (addr)
	  addr->next = temp;
      }
    }
    else if (!hostname)
    {
     /*
      * Provide one or more passive listening addresses...
      */

#ifdef AF_INET6
      if (family != AF_INET)
      {
       /*
        * Add [::] to the address list...
	*/

	temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	if (!temp)
	{
	  _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
	  httpAddrFreeList(first);
	  return (NULL);
	}

        temp->addr.ipv6.sin6_family = AF_INET6;
	temp->addr.ipv6.sin6_port   = htons(portnum);

        if (!first)
          first = temp;

        addr = temp;
      }

      if (family != AF_INET6)
#endif /* AF_INET6 */
      {
       /*
        * Add 0.0.0.0 to the address list...
	*/

	temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
	if (!temp)
	{
	  _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
	  httpAddrFreeList(first);
	  return (NULL);
	}

        temp->addr.ipv4.sin_family = AF_INET;
	temp->addr.ipv4.sin_port   = htons(portnum);

        if (!first)
          first = temp;

        if (addr)
	  addr->next = temp;
      }
    }
  }

 /*
  * Return the address list...
  */

  return (first);
}


/*
 * End of "$Id: http-addrlist.c 11627 2014-02-20 16:15:09Z msweet $".
 */
