#include "cups-private.h"
#include "debug-internal.h"
#ifdef HAVE_RESOLV_H
# include <resolv.h>
#endif
#ifdef HAVE_POLL
# include <poll.h>
#endif
#ifndef _WIN32
# include <fcntl.h>
#endif
http_addrlist_t *
httpAddrConnect(
http_addrlist_t *addrlist,
int *sock)
{
DEBUG_printf(("httpAddrConnect(addrlist=%p, sock=%p)", (void *)addrlist, (void *)sock));
return (httpAddrConnect2(addrlist, sock, 30000, NULL));
}
http_addrlist_t *
httpAddrConnect2(
http_addrlist_t *addrlist,
int *sock,
int msec,
int *cancel)
{
int val;
#ifndef _WIN32
int i, j,
flags,
result;
#endif
int remaining;
int nfds,
fds[100];
http_addrlist_t *addrs[100];
#ifndef HAVE_POLL
int max_fd = -1;
#endif
#ifdef O_NONBLOCK
# ifdef HAVE_POLL
struct pollfd pfds[100];
# else
fd_set input_set,
output_set,
error_set;
struct timeval timeout;
# endif
#endif
#ifdef DEBUG
# ifndef _WIN32
socklen_t len;
http_addr_t peer;
# endif
char temp[256];
#endif
DEBUG_printf(("httpAddrConnect2(addrlist=%p, sock=%p, msec=%d, cancel=%p)", (void *)addrlist, (void *)sock, msec, (void *)cancel));
if (!sock)
{
errno = EINVAL;
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
return (NULL);
}
if (cancel && *cancel)
return (NULL);
if (msec <= 0)
msec = INT_MAX;
nfds = 0;
remaining = msec;
while (remaining > 0)
{
if (cancel && *cancel)
{
while (nfds > 0)
{
nfds --;
httpAddrClose(NULL, fds[nfds]);
}
return (NULL);
}
if (addrlist && nfds < (int)(sizeof(fds) / sizeof(fds[0])))
{
DEBUG_printf(("2httpAddrConnect2: Trying %s:%d...", httpAddrString(&(addrlist->addr), temp, sizeof(temp)), httpAddrPort(&(addrlist->addr))));
if ((fds[nfds] = (int)socket(httpAddrFamily(&(addrlist->addr)), SOCK_STREAM, 0)) < 0)
{
addrlist = addrlist->next;
continue;
}
val = 1;
setsockopt(fds[nfds], SOL_SOCKET, SO_REUSEADDR, CUPS_SOCAST &val, sizeof(val));
#ifdef SO_REUSEPORT
val = 1;
setsockopt(fds[nfds], SOL_SOCKET, SO_REUSEPORT, CUPS_SOCAST &val, sizeof(val));
#endif
#ifdef SO_NOSIGPIPE
val = 1;
setsockopt(fds[nfds], SOL_SOCKET, SO_NOSIGPIPE, CUPS_SOCAST &val, sizeof(val));
#endif
val = 1;
setsockopt(fds[nfds], IPPROTO_TCP, TCP_NODELAY, CUPS_SOCAST &val, sizeof(val));
#ifdef FD_CLOEXEC
fcntl(fds[nfds], F_SETFD, FD_CLOEXEC);
#endif
#ifdef O_NONBLOCK
DEBUG_printf(("httpAddrConnect2: Setting non-blocking connect()"));
flags = fcntl(fds[nfds], F_GETFL, 0);
fcntl(fds[nfds], F_SETFL, flags | O_NONBLOCK);
#endif
if (!connect(fds[nfds], &(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(fds[nfds], F_SETFL, flags);
#endif
*sock = fds[nfds];
while (nfds > 0)
{
nfds --;
httpAddrClose(NULL, fds[nfds]);
}
return (addrlist);
}
#ifdef _WIN32
if (WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK)
#else
if (errno != EINPROGRESS && errno != EWOULDBLOCK)
#endif
{
DEBUG_printf(("1httpAddrConnect2: Unable to connect to %s:%d: %s", httpAddrString(&(addrlist->addr), temp, sizeof(temp)), httpAddrPort(&(addrlist->addr)), strerror(errno)));
httpAddrClose(NULL, fds[nfds]);
addrlist = addrlist->next;
continue;
}
#ifndef _WIN32
fcntl(fds[nfds], F_SETFL, flags);
#endif
#ifndef HAVE_POLL
if (fds[nfds] > max_fd)
max_fd = fds[nfds];
#endif
addrs[nfds] = addrlist;
nfds ++;
addrlist = addrlist->next;
}
if (!addrlist && nfds == 0)
break;
#ifdef O_NONBLOCK
DEBUG_puts("1httpAddrConnect2: Finishing async connect()");
do
{
if (cancel && *cancel)
{
DEBUG_puts("1httpAddrConnect2: Canceled connect()");
while (nfds > 0)
{
nfds --;
httpAddrClose(NULL, fds[nfds]);
}
*sock = -1;
return (NULL);
}
# ifdef HAVE_POLL
for (i = 0; i < nfds; i ++)
{
pfds[i].fd = fds[i];
pfds[i].events = POLLIN | POLLOUT;
}
result = poll(pfds, (nfds_t)nfds, addrlist ? 100 : remaining > 250 ? 250 : remaining);
DEBUG_printf(("1httpAddrConnect2: poll() returned %d (%d)", result, errno));
# else
FD_ZERO(&input_set);
for (i = 0; i < nfds; i ++)
FD_SET(fds[i], &input_set);
output_set = input_set;
error_set = input_set;
timeout.tv_sec = 0;
timeout.tv_usec = (addrlist ? 100 : remaining > 250 ? 250 : remaining) * 1000;
result = select(max_fd + 1, &input_set, &output_set, &error_set, &timeout);
DEBUG_printf(("1httpAddrConnect2: select() returned %d (%d)", result, errno));
# endif
}
# ifdef _WIN32
while (result < 0 && (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEWOULDBLOCK));
# else
while (result < 0 && (errno == EINTR || errno == EAGAIN));
# endif
if (result > 0)
{
http_addrlist_t *connaddr = NULL;
for (i = 0; i < nfds; i ++)
{
# ifdef HAVE_POLL
DEBUG_printf(("pfds[%d].revents=%x\n", i, pfds[i].revents));
if (pfds[i].revents && !(pfds[i].revents & (POLLERR | POLLHUP)))
# else
if (FD_ISSET(fds[i], &input_set) && !FD_ISSET(fds[i], &error_set))
# endif
{
*sock = fds[i];
connaddr = addrs[i];
# ifdef DEBUG
len = sizeof(peer);
if (!getpeername(fds[i], (struct sockaddr *)&peer, &len))
DEBUG_printf(("1httpAddrConnect2: Connected to %s:%d...", httpAddrString(&peer, temp, sizeof(temp)), httpAddrPort(&peer)));
# endif
break;
}
# ifdef HAVE_POLL
else if (pfds[i].revents & (POLLERR | POLLHUP))
# else
else if (FD_ISSET(fds[i], &error_set))
# endif
{
httpAddrClose(NULL, fds[i]);
nfds --;
if (i < nfds)
{
memmove(fds + i, fds + i + 1, (size_t)(nfds - i) * (sizeof(fds[0])));
memmove(addrs + i, addrs + i + 1, (size_t)(nfds - i) * (sizeof(addrs[0])));
}
i --;
}
}
if (connaddr)
{
for (j = 0; j < i; j ++)
httpAddrClose(NULL, fds[j]);
for (j ++; j < nfds; j ++)
httpAddrClose(NULL, fds[j]);
return (connaddr);
}
}
#endif
if (addrlist)
remaining -= 100;
else
remaining -= 250;
}
while (nfds > 0)
{
nfds --;
httpAddrClose(NULL, fds[nfds]);
}
#ifdef _WIN32
_cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, "Connection failed", 0);
#else
_cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, strerror(errno), 0);
#endif
return (NULL);
}
http_addrlist_t *
httpAddrCopyList(
http_addrlist_t *src)
{
http_addrlist_t *dst = NULL,
*prev = NULL,
*current = NULL;
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);
}
void
httpAddrFreeList(
http_addrlist_t *addrlist)
{
http_addrlist_t *next;
while (addrlist)
{
next = addrlist->next;
free(addrlist);
addrlist = next;
}
}
http_addrlist_t *
httpAddrGetList(const char *hostname,
int family,
const char *service)
{
http_addrlist_t *first,
*addr,
*temp;
_cups_globals_t *cg = _cupsGlobals();
#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
# ifdef AF_INET6
family == AF_INET6 ? "INET6" :
# endif
family == AF_INET ? "INET" : "???", service);
#endif
#ifdef HAVE_RES_INIT
if (cg->need_res_init)
{
res_init();
cg->need_res_init = 0;
}
#endif
first = addr = NULL;
#ifdef AF_LOCAL
if (hostname && hostname[0] == '/')
{
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
if (!hostname || _cups_strcasecmp(hostname, "localhost"))
{
#ifdef HAVE_GETADDRINFO
struct addrinfo hints,
*results,
*current;
char ipv6[64],
*ipv6zone;
int ipv6len;
int error;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_flags = hostname ? 0 : AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
if (hostname && *hostname == '[')
{
if (!strncmp(hostname, "[v1.", 4))
{
strlcpy(ipv6, hostname + 4, sizeof(ipv6));
if ((ipv6len = (int)strlen(ipv6) - 1) >= 0 && ipv6[ipv6len] == ']')
{
ipv6[ipv6len] = '\0';
hostname = ipv6;
if ((ipv6zone = strrchr(ipv6, '+')) != NULL)
*ipv6zone = '%';
}
}
else
{
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)
{
for (current = results; current; current = current->ai_next)
if (current->ai_family == AF_INET || current->ai_family == AF_INET6)
{
temp = (http_addrlist_t *)calloc(1, sizeof(http_addrlist_t));
if (!temp)
{
httpAddrFreeList(first);
freeaddrinfo(results);
_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));
if (!first)
first = temp;
if (addr)
addr->next = temp;
addr = temp;
}
freeaddrinfo(results);
}
else
{
if (error == EAI_FAIL)
cg->need_res_init = 1;
# ifdef _WIN32
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, gai_strerrorA(error), 0);
# else
_cupsSetError(IPP_STATUS_ERROR_INTERNAL, gai_strerror(error), 0);
# endif
}
#else
if (hostname)
{
int i;
unsigned ip[4];
const char *ptr;
struct hostent *host;
struct servent *port;
int portnum;
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);
for (ptr = hostname; isdigit(*ptr & 255) || *ptr == '.'; ptr ++);
if (!*ptr)
{
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
{
for (i = 0; host->h_addr_list[i]; i ++)
{
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
{
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);
}
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
}
if (!addr && (!hostname || !_cups_strcasecmp(hostname, "localhost")))
{
struct servent *port;
int portnum;
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"))
{
#ifdef AF_INET6
if (family != AF_INET)
{
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
if (!first)
first = temp;
addr = temp;
}
if (family != AF_INET6)
#endif
{
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)
{
#ifdef AF_INET6
if (family != AF_INET)
{
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
{
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 (first);
}