/* * "$Id: http-addrlist.c 11642 2014-02-27 15:57:59Z msweet $" * * HTTP address list routines for CUPS. * * Copyright 2007-2013 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 #endif /* HAVE_RESOLV_H */ #ifdef HAVE_POLL # include #endif /* HAVE_POLL */ #ifndef WIN32 # include #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), 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()"); # ifdef WIN32 closesocket(*sock); # else close(*sock); # endif /* WIN32 */ *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... */ #ifdef WIN32 closesocket(*sock); #else close(*sock); #endif /* WIN32 */ *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 11642 2014-02-27 15:57:59Z msweet $". */