/* $NetBSD: nss_mdnsd.c,v 1.4 2014/03/31 02:03:38 christos Exp $ */ /*- * Copyright (c) 2009 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Tyler C. Sarna * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Multicast DNS ("Bonjour") hosts name service switch * * Documentation links: * * http://developer.apple.com/bonjour/ * http://www.multicastdns.org/ * http://www.dns-sd.org/ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hostent.h" /* * Pool of mdnsd connections */ static SLIST_HEAD(, svc_ref) conn_list = LIST_HEAD_INITIALIZER(&conn_list); static unsigned int conn_count = 0; static pid_t my_pid; struct timespec last_config; typedef struct svc_ref { SLIST_ENTRY(svc_ref) entries; DNSServiceRef sdRef; unsigned int uses; } svc_ref; /* * There is a large class of programs that do a few lookups at startup * and then never again (ping, telnet, etc). Keeping a persistent connection * for these would be a waste, so there is a kind of slow start mechanism. * The first SLOWSTART_LOOKUPS times, dispose of the connection after use. * After that we assume the program is a serious consumer of host lookup * services and start keeping connections. */ #define SLOWSTART_LOOKUPS 5 static unsigned int svc_puts = 0; /* * Age out connections. Free connection instead of putting on the list * if used more than REUSE_TIMES and there are others on the list. */ #define REUSE_TIMES 32 /* protects above data */ static pthread_mutex_t conn_list_lock = PTHREAD_MUTEX_INITIALIZER; extern int __isthreaded; /* libc private -- wish there was a better way */ #define LOCK(x) do { if (__isthreaded) pthread_mutex_lock(x); } while (0) #define UNLOCK(x) do { if (__isthreaded) pthread_mutex_unlock(x); } while (0) #ifndef lint #define UNUSED(a) (void)&a #else #define UNUSED(a) a = a #endif #define MAXALIASES 35 #define MAXADDRS 35 typedef struct callback_ctx { bool done; } callback_ctx; typedef struct hostent_ctx { callback_ctx cb_ctx; /* must come first */ struct hostent host; char *h_addr_ptrs[MAXADDRS + 1]; char *host_aliases[MAXALIASES]; char addrs[MAXADDRS * 16]; char buf[8192], *next; int naliases, naddrs; } hostent_ctx; typedef struct addrinfo_ctx { callback_ctx cb_ctx; /* must come first */ struct addrinfo start, *last; } addrinfo_ctx; #define HCTX_BUFLEFT(c) (sizeof((c)->buf) - ((c)->next - (c)->buf)) typedef struct res_conf { unsigned int refcount; char **search_domains; char **no_search; short ndots; short timeout; } res_conf; static res_conf *cur_res_conf; /* protects above data */ static pthread_mutex_t res_conf_lock = PTHREAD_MUTEX_INITIALIZER; typedef struct search_iter { res_conf *conf; const char *name; char **next_search; size_t baselen; bool abs_first; bool abs_last; char buf[MAXHOSTNAMELEN]; } search_iter; static DNSServiceFlags svc_flags = 0; ns_mtab *nss_module_register(const char *, u_int *, nss_module_unregister_fn *); static int load_config(res_state); static int _mdns_getaddrinfo(void *, void *, va_list); static int _mdns_gethtbyaddr(void *, void *, va_list); static int _mdns_gethtbyname(void *, void *, va_list); static int _mdns_getaddrinfo_abs(const char *, DNSServiceProtocol, svc_ref **, addrinfo_ctx *, short); static void _mdns_addrinfo_init(addrinfo_ctx *, const struct addrinfo *); static void _mdns_addrinfo_add_ai(addrinfo_ctx *, struct addrinfo *); static struct addrinfo *_mdns_addrinfo_done(addrinfo_ctx *); static int _mdns_gethtbyname_abs(struct getnamaddr *, struct hostent_ctx *, const char *, int, svc_ref **, short); static void _mdns_hostent_init(hostent_ctx *, int, int); static void _mdns_hostent_add_host(hostent_ctx *, const char *); static void _mdns_hostent_add_addr(hostent_ctx *, const void *, uint16_t); static int _mdns_hostent_done(struct getnamaddr *, hostent_ctx *); static void _mdns_addrinfo_cb(DNSServiceRef, DNSServiceFlags, uint32_t, DNSServiceErrorType, const char *, const struct sockaddr *, uint32_t, void *); static void _mdns_hostent_cb(DNSServiceRef, DNSServiceFlags, uint32_t, DNSServiceErrorType, const char *, uint16_t, uint16_t, uint16_t, const void *, uint32_t, void *); static void _mdns_eventloop(svc_ref *, callback_ctx *, short); static char *_mdns_rdata2name(const unsigned char *, uint16_t, char *, size_t); static int search_init(search_iter *, const char *, const char **); static void search_done(search_iter *); static const char *search_next(search_iter *); static bool searchable_domain(char *); static void destroy_svc_ref(svc_ref *); static svc_ref *get_svc_ref(void); static void put_svc_ref(svc_ref *); static bool retry_query(svc_ref **, DNSServiceErrorType); static void decref_res_conf(res_conf *); static res_conf *get_res_conf(void); static void put_res_conf(res_conf *); static res_conf *new_res_conf(res_state); static short get_timeout(void); static ns_mtab mtab[] = { { NSDB_HOSTS, "getaddrinfo", _mdns_getaddrinfo, NULL }, { NSDB_HOSTS, "gethostbyaddr", _mdns_gethtbyaddr, NULL }, { NSDB_HOSTS, "gethostbyname", _mdns_gethtbyname, NULL }, }; ns_mtab * nss_module_register(const char *source, u_int *nelems, nss_module_unregister_fn *unreg) { *nelems = sizeof(mtab) / sizeof(mtab[0]); *unreg = NULL; my_pid = getpid(); if (!strcmp(source, "multicast_dns")) { svc_flags = kDNSServiceFlagsForceMulticast; } return mtab; } static int _mdns_getaddrinfo(void *cbrv, void *cbdata, va_list ap) { const struct addrinfo *pai; const char *name, *sname; DNSServiceProtocol proto; DNSServiceRef sdRef; addrinfo_ctx ctx; search_iter iter; res_conf *rc; svc_ref *sr; int err; UNUSED(cbdata); name = va_arg(ap, char *); pai = va_arg(ap, struct addrinfo *); switch (pai->ai_family) { case AF_UNSPEC: proto = kDNSServiceProtocol_IPv6 | kDNSServiceProtocol_IPv4; break; case AF_INET6: proto = kDNSServiceProtocol_IPv6; break; case AF_INET: proto = kDNSServiceProtocol_IPv4; break; default: h_errno = NO_RECOVERY; return NS_UNAVAIL; } sr = get_svc_ref(); if (!sr) { h_errno = NETDB_INTERNAL; return NS_UNAVAIL; } if ((err = search_init(&iter, name, &sname)) != NS_SUCCESS) { put_svc_ref(sr); return err; } _mdns_addrinfo_init(&ctx, pai); err = NS_NOTFOUND; while (sr && sname && (err != NS_SUCCESS)) { err = _mdns_getaddrinfo_abs(sname, proto, &sr, &ctx, iter.conf->timeout); if (err != NS_SUCCESS) { sname = search_next(&iter); } }; search_done(&iter); put_svc_ref(sr); if (err == NS_SUCCESS) { *(struct addrinfo **)cbrv = _mdns_addrinfo_done(&ctx); } return err; } static int _mdns_getaddrinfo_abs(const char *name, DNSServiceProtocol proto, svc_ref **sr, addrinfo_ctx *ctx, short timeout) { DNSServiceErrorType err = kDNSServiceErr_ServiceNotRunning; DNSServiceRef sdRef; bool retry = true; while (*sr && retry) { /* We must always use a copy of the ref when using a shared connection, per kDNSServiceFlagsShareConnection docs */ sdRef = (*sr)->sdRef; err = DNSServiceGetAddrInfo( &sdRef, svc_flags | kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates, kDNSServiceInterfaceIndexAny, proto, name, _mdns_addrinfo_cb, ctx ); retry = retry_query(sr, err); } if (err) { h_errno = NETDB_INTERNAL; return NS_UNAVAIL; } _mdns_eventloop(*sr, (void *)ctx, timeout); DNSServiceRefDeallocate(sdRef); if (ctx->start.ai_next) { return NS_SUCCESS; } else { h_errno = HOST_NOT_FOUND; return NS_NOTFOUND; } } static int _mdns_gethtbyaddr(void *cbrv, void *cbdata, va_list ap) { const unsigned char *addr; int addrlen, af; char qbuf[NS_MAXDNAME + 1], *qp, *ep; int advance, n; DNSServiceErrorType err; DNSServiceRef sdRef; svc_ref *sr; bool retry = true; struct getnamaddr *info = cbrv; UNUSED(cbdata); addr = va_arg(ap, unsigned char *); addrlen = va_arg(ap, int); af = va_arg(ap, int); switch (af) { case AF_INET: /* if mcast-only don't bother for non-LinkLocal addrs) */ if (svc_flags & kDNSServiceFlagsForceMulticast) { if ((addr[0] != 169) || (addr[1] != 254)) { *info->he = HOST_NOT_FOUND; return NS_NOTFOUND; } } (void)snprintf(qbuf, sizeof(qbuf), "%u.%u.%u.%u.in-addr.arpa", (addr[3] & 0xff), (addr[2] & 0xff), (addr[1] & 0xff), (addr[0] & 0xff)); break; case AF_INET6: /* if mcast-only don't bother for non-LinkLocal addrs) */ if (svc_flags & kDNSServiceFlagsForceMulticast) { if ((addr[0] != 0xfe) || ((addr[1] & 0xc0) != 0x80)) { *info->he = HOST_NOT_FOUND; return NS_NOTFOUND; } } qp = qbuf; ep = qbuf + sizeof(qbuf) - 1; for (n = IN6ADDRSZ - 1; n >= 0; n--) { advance = snprintf(qp, (size_t)(ep - qp), "%x.%x.", addr[n] & 0xf, ((unsigned int)addr[n] >> 4) & 0xf); if (advance > 0 && qp + advance < ep) qp += advance; else { *info->he = NETDB_INTERNAL; return NS_NOTFOUND; } } if (strlcat(qbuf, "ip6.arpa", sizeof(qbuf)) >= sizeof(qbuf)) { *info->he = NETDB_INTERNAL; return NS_NOTFOUND; } break; default: *info->he = NO_RECOVERY; return NS_UNAVAIL; } hostent_ctx h_ctx; _mdns_hostent_init(&h_ctx, af, addrlen); _mdns_hostent_add_addr(&h_ctx, addr, addrlen); sr = get_svc_ref(); if (!sr) { *info->he = NETDB_INTERNAL; return NS_UNAVAIL; } while (sr && retry) { /* We must always use a copy of the ref when using a shared connection, per kDNSServiceFlagsShareConnection docs */ sdRef = sr->sdRef; err = DNSServiceQueryRecord( &sdRef, svc_flags | kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates, kDNSServiceInterfaceIndexAny, qbuf, kDNSServiceType_PTR, kDNSServiceClass_IN, _mdns_hostent_cb, &h_ctx ); retry = retry_query(&sr, err); } if (err) { put_svc_ref(sr); *info->he = NETDB_INTERNAL; return NS_UNAVAIL; } _mdns_eventloop(sr, (void *)&h_ctx, get_timeout()); DNSServiceRefDeallocate(sdRef); put_svc_ref(sr); if (h_ctx.naliases) { return _mdns_hostent_done(info, &h_ctx); } else { *info->he = HOST_NOT_FOUND; return NS_NOTFOUND; } } static int _mdns_gethtbyname(void *cbrv, void *cbdata, va_list ap) { int namelen, af, addrlen, rrtype, err; const char *name, *sname; DNSServiceRef sdRef; search_iter iter; svc_ref *sr; struct getnamaddr *info = cbrv; UNUSED(cbdata); name = va_arg(ap, char *); namelen = va_arg(ap, int); af = va_arg(ap, int); UNUSED(namelen); switch (af) { case AF_INET: rrtype = kDNSServiceType_A; addrlen = 4; break; case AF_INET6: rrtype = kDNSServiceType_AAAA; addrlen = 16; break; default: *info->he = NO_RECOVERY; return NS_UNAVAIL; } sr = get_svc_ref(); if (!sr) { *info->he = NETDB_INTERNAL; return NS_UNAVAIL; } if ((err = search_init(&iter, name, &sname)) != NS_SUCCESS) { put_svc_ref(sr); return err; } hostent_ctx h_ctx; _mdns_hostent_init(&h_ctx, af, addrlen); err = NS_NOTFOUND; while (sr && sname && (err != NS_SUCCESS)) { err = _mdns_gethtbyname_abs(info, &h_ctx, sname, rrtype, &sr, iter.conf->timeout); if (err != NS_SUCCESS) { sname = search_next(&iter); } }; search_done(&iter); put_svc_ref(sr); if (err != NS_SUCCESS) return err; _mdns_hostent_add_host(&h_ctx, sname); _mdns_hostent_add_host(&h_ctx, name); return _mdns_hostent_done(info, &h_ctx); } static int _mdns_gethtbyname_abs(struct getnamaddr *info, struct hostent_ctx *ctx, const char *name, int rrtype, svc_ref **sr, short timeout) { DNSServiceErrorType err = kDNSServiceErr_ServiceNotRunning; DNSServiceRef sdRef; bool retry = true; while (*sr && retry) { /* We must always use a copy of the ref when using a shared connection, per kDNSServiceFlagsShareConnection docs */ sdRef = (*sr)->sdRef; err = DNSServiceQueryRecord( &sdRef, svc_flags | kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates, kDNSServiceInterfaceIndexAny, name, rrtype, kDNSServiceClass_IN, _mdns_hostent_cb, ctx ); retry = retry_query(sr, err); } if (err) { *info->he = NETDB_INTERNAL; return NS_UNAVAIL; } _mdns_eventloop(*sr, (void *)ctx, timeout); DNSServiceRefDeallocate(sdRef); if (ctx->naddrs) { return NS_SUCCESS; } else { *info->he = HOST_NOT_FOUND; return NS_NOTFOUND; } } static void _mdns_addrinfo_init(addrinfo_ctx *ctx, const struct addrinfo *ai) { ctx->cb_ctx.done = false; ctx->start = *ai; ctx->start.ai_next = NULL; ctx->start.ai_canonname = NULL; ctx->last = &(ctx->start); } static void _mdns_addrinfo_add_ai(addrinfo_ctx *ctx, struct addrinfo *ai) { ctx->last->ai_next = ai; while (ctx->last->ai_next) ctx->last = ctx->last->ai_next; } static struct addrinfo * _mdns_addrinfo_done(addrinfo_ctx *ctx) { struct addrinfo head, *t, *p; /* sort v6 up */ t = &head; p = ctx->start.ai_next; while (p->ai_next) { if (p->ai_next->ai_family == AF_INET6) { t->ai_next = p->ai_next; t = t->ai_next; p->ai_next = p->ai_next->ai_next; } else { p = p->ai_next; } } /* add rest of list and reset start to the new list */ t->ai_next = ctx->start.ai_next; ctx->start.ai_next = head.ai_next; return ctx->start.ai_next; } static void _mdns_hostent_init(hostent_ctx *ctx, int af, int addrlen) { int i; ctx->cb_ctx.done = false; ctx->naliases = ctx->naddrs = 0; ctx->next = ctx->buf; ctx->host.h_aliases = ctx->host_aliases; ctx->host.h_addr_list = ctx->h_addr_ptrs; ctx->host.h_name = ctx->host.h_aliases[0] = NULL; ctx->host.h_addrtype = af; ctx->host.h_length = addrlen; for (i = 0; i < MAXADDRS; i++) { ctx->host.h_addr_list[i] = &(ctx->addrs[i * 16]); } } static void _mdns_hostent_add_host(hostent_ctx *ctx, const char *name) { size_t len; int i; if (name && (len = strlen(name)) && (HCTX_BUFLEFT(ctx) > len) && (ctx->naliases < MAXALIASES)) { if (len && (name[len - 1] == '.')) { len--; } /* skip dupe names */ if ((ctx->host.h_name) && !strncmp(ctx->host.h_name, name, len) && (strlen(ctx->host.h_name) == len)) { return; } for (i = 0; i < ctx->naliases - 1; i++) { if (!strncmp(ctx->host.h_aliases[i], name, len) && (strlen(ctx->host.h_aliases[i]) == len)) { return; } } strncpy(ctx->next, name, len); ctx->next[len] = 0; if (ctx->naliases == 0) { ctx->host.h_name = ctx->next; } else { ctx->host.h_aliases[ctx->naliases - 1] = ctx->next; } ctx->next += (len + 1); ctx->naliases++; } /* else silently ignore */ } static void _mdns_hostent_add_addr(hostent_ctx *ctx, const void *addr, uint16_t len) { if ((len == ctx->host.h_length) && (ctx->naddrs < MAXADDRS)) { memcpy(ctx->host.h_addr_list[ctx->naddrs++], addr, (size_t)len); } /* else wrong address type or out of room... silently skip */ } static int _mdns_hostent_done(struct getnamaddr *info, hostent_ctx *ctx) { int i; char *ptr = info->buf; size_t len = info->buflen; struct hostent *hp = info->hp; struct hostent *chp = &ctx->host; hp->h_length = ctx->host.h_length; hp->h_addrtype = ctx->host.h_addrtype; HENT_ARRAY(hp->h_addr_list, ctx->naddrs, ptr, len); HENT_ARRAY(hp->h_aliases, ctx->naliases - 1, ptr, len); for (i = 0; i < ctx->naddrs; i++) HENT_COPY(hp->h_addr_list[i], chp->h_addr_list[i], hp->h_length, ptr, len); hp->h_addr_list[ctx->naddrs] = NULL; HENT_SCOPY(hp->h_name, chp->h_name, ptr, len); for (i = 0; i < ctx->naliases - 1; i++) HENT_SCOPY(hp->h_aliases[i], chp->h_aliases[i], ptr, len); hp->h_aliases[ctx->naliases - 1] = NULL; *info->he = 0; return NS_SUCCESS; nospc: *info->he = NETDB_INTERNAL; errno = ENOSPC; return NS_UNAVAIL; } static void _mdns_addrinfo_cb( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *hostname, const struct sockaddr *address, uint32_t ttl, void *context ) { addrinfo_ctx *ctx = context; struct addrinfo *ai; UNUSED(sdRef); UNUSED(interfaceIndex); UNUSED(ttl); if (errorCode == kDNSServiceErr_NoError) { if (! (flags & kDNSServiceFlagsMoreComing)) { ctx->cb_ctx.done = true; } ai = allocaddrinfo((socklen_t)(address->sa_len)); if (ai) { ai->ai_flags = ctx->start.ai_flags; ai->ai_family = address->sa_family; ai->ai_socktype = ctx->start.ai_socktype; ai->ai_protocol = ctx->start.ai_protocol; memcpy(ai->ai_addr, address, (size_t)(address->sa_len)); if ((ctx->start.ai_flags & AI_CANONNAME) && hostname) { ai->ai_canonname = strdup(hostname); if (ai->ai_canonname[strlen(ai->ai_canonname) - 1] == '.') { ai->ai_canonname[strlen(ai->ai_canonname) - 1] = '\0'; } } _mdns_addrinfo_add_ai(ctx, ai); } } } static void _mdns_hostent_cb( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context ) { hostent_ctx *ctx = (hostent_ctx *)context; char buf[NS_MAXDNAME+1]; UNUSED(sdRef); UNUSED(interfaceIndex); UNUSED(rrclass); UNUSED(ttl); if (! (flags & kDNSServiceFlagsMoreComing)) { ctx->cb_ctx.done = true; } if (errorCode == kDNSServiceErr_NoError) { switch (rrtype) { case kDNSServiceType_PTR: if (!_mdns_rdata2name(rdata, rdlen, buf, sizeof(buf))) { /* corrupt response -- skip */ return; } _mdns_hostent_add_host(ctx, buf); break; case kDNSServiceType_A: if (ctx->host.h_addrtype == AF_INET) { _mdns_hostent_add_host(ctx, fullname); _mdns_hostent_add_addr(ctx, rdata, rdlen); } break; case kDNSServiceType_AAAA: if (ctx->host.h_addrtype == AF_INET6) { _mdns_hostent_add_host(ctx, fullname); _mdns_hostent_add_addr(ctx, rdata, rdlen); } break; } } else if (errorCode == kDNSServiceErr_NoSuchRecord) { ctx->cb_ctx.done = true; } } static void _mdns_eventloop(svc_ref *sr, callback_ctx *ctx, short timeout) { struct pollfd fds; int fd, ret; fd = DNSServiceRefSockFD(sr->sdRef); fds.fd = fd; fds.events = POLLRDNORM; while (!ctx->done) { ret = poll(&fds, 1, timeout * 1000); if (ret > 0) { DNSServiceProcessResult(sr->sdRef); } else { break; } } } static char * _mdns_rdata2name(const unsigned char *rdata, uint16_t rdlen, char *buf, size_t buflen) { unsigned char l; char *r = buf; /* illegal 0-size answer or not enough room for even "." */ if ((!rdlen) || (rdlen < 2)) { return NULL; } buflen--; /* reserve space for terminating NUL now */ /* special case empty as "." */ if ((rdlen == 1) && (!*rdata)) { strcpy(buf, "."); return r; } while (rdlen && *rdata) { /* label length byte */ l = *rdata++; rdlen--; if (l > 63) { /* compression or bitstrings -- shouldn't happen */ return NULL; } else if (l > buflen) { /* not enough space */ return NULL; } else if (l > rdlen) { /* label shouldn't be longer than remaining rdata */ return NULL; } else if (!l) { /* empty label -- should be done */ if (rdlen) { /* but more left!? */ return NULL; } else { break; } } memcpy(buf, rdata, (size_t)l); rdata += l; buf += l; rdlen -= l; buflen -= l; /* Another label to come? add a separator */ if (rdlen && *rdata) { if (!buflen) { return NULL; } *buf++ = '.'; buflen--; } } /* we reserved space above, so we know we have space to add this termination */ *buf = '\0'; return r; } int search_init(search_iter *iter, const char *name, const char **first) { const char *c = name, *cmp; int dots = 0, enddot = 0; size_t len, cl; iter->conf = get_res_conf(); if (!iter->conf) { h_errno = NETDB_INTERNAL; return NS_UNAVAIL; } iter->name = name; iter->baselen = 0; iter->abs_first = iter->abs_last = false; iter->next_search = iter->conf->search_domains; while (*c) { if (*c == '.') { dots++; enddot = 1; } else { enddot = 0; } c++; } if (svc_flags & kDNSServiceFlagsForceMulticast) { if (dots) { iter->next_search = iter->conf->no_search; if ((dots - enddot) == 1) { len = strlen(iter->name); cl = strlen(".local") + enddot; if (len > cl) { cmp = enddot ? ".local." : ".local"; c = iter->name + len - cl; if (!strcasecmp(c, cmp)) { iter->abs_first = true; } } } } } else { if (dots >= iter->conf->ndots) { iter->abs_first = true; } else { iter->abs_last = true; } if (enddot) { /* absolute; don't search */ iter->next_search = iter->conf->no_search; } } *first = search_next(iter); if (!first) { search_done(iter); h_errno = HOST_NOT_FOUND; return NS_NOTFOUND; } return NS_SUCCESS; } const char * search_next(search_iter *iter) { const char *a = NULL; res_state res; size_t len; if (iter->abs_first) { iter->abs_first = false; return iter->name; } while (*(iter->next_search)) { if (!iter->baselen) { iter->baselen = strlcpy(iter->buf, iter->name, sizeof(iter->buf)); if (iter->baselen >= sizeof(iter->buf) - 1) { /* original is too long, don't try any search domains */ iter->next_search = iter->conf->no_search; break; } iter->buf[iter->baselen++] = '.'; } len = strlcpy(&(iter->buf[iter->baselen]), *(iter->next_search), sizeof(iter->buf) - iter->baselen); iter->next_search++; if (len >= sizeof(iter->buf) - iter->baselen) { /* result was too long */ continue; } return iter->buf; } if (iter->abs_last) { iter->abs_last = false; return iter->name; } return NULL; } void search_done(search_iter *iter) { if (iter->conf) { put_res_conf(iter->conf); iter->conf = NULL; } } /* * Is domain appropriate to be in the domain search list? * For mdnsd, take everything. For multicast_dns, only "local" * if present. */ bool searchable_domain(char *d) { if (!(svc_flags & kDNSServiceFlagsForceMulticast)) { return true; } if (!strcasecmp(d, "local") || !strcasecmp(d, "local.")) { return true; } return false; } static void destroy_svc_ref(svc_ref *sr) { /* assumes not on conn list */ if (sr) { DNSServiceRefDeallocate(sr->sdRef); free(sr); } } static svc_ref * get_svc_ref(void) { svc_ref *sr; LOCK(&conn_list_lock); if (getpid() != my_pid) { my_pid = getpid(); /* * We forked and kept running. We don't want to share * connections with the parent or we'll garble each others * comms, so throw away the parent's list and start over */ while ((sr = SLIST_FIRST(&conn_list))) { SLIST_REMOVE_HEAD(&conn_list, entries); destroy_svc_ref(sr); } sr = NULL; } else { /* try to recycle a connection */ sr = SLIST_FIRST(&conn_list); if (sr) { SLIST_REMOVE_HEAD(&conn_list, entries); conn_count--; } } UNLOCK(&conn_list_lock); if (!sr) { /* none available, we need a new one */ sr = calloc(sizeof(svc_ref), 1); if (sr) { if (DNSServiceCreateConnection(&(sr->sdRef))) { free(sr); return NULL; } if (fcntl(DNSServiceRefSockFD(sr->sdRef), F_SETFD, FD_CLOEXEC) < 0) { destroy_svc_ref(sr); sr = NULL; } } } return sr; } static void put_svc_ref(svc_ref *sr) { if (sr) { LOCK(&conn_list_lock); sr->uses++; /* if slow start or aged out, destroy */ if ((svc_puts++ < SLOWSTART_LOOKUPS) || (conn_count && (sr->uses > REUSE_TIMES))) { UNLOCK(&conn_list_lock); destroy_svc_ref(sr); return; } conn_count++; SLIST_INSERT_HEAD(&conn_list, sr, entries); UNLOCK(&conn_list_lock); } } /* * determine if this is a call we should retry with a fresh * connection, for example if mdnsd went away and came back. */ static bool retry_query(svc_ref **sr, DNSServiceErrorType err) { if ((err == kDNSServiceErr_Unknown) || (err == kDNSServiceErr_ServiceNotRunning)) { /* these errors might indicate a stale socket */ if ((*sr)->uses) { /* this was an old socket, so kill it and get another */ destroy_svc_ref(*sr); *sr = get_svc_ref(); if (*sr) { /* we can retry */ return true; } } } return false; } static void decref_res_conf(res_conf *rc) { rc->refcount--; if (rc->refcount < 1) { if ((rc->no_search = rc->search_domains)) { for (; *(rc->no_search); rc->no_search++) { free(*(rc->no_search)); } free(rc->search_domains); } free(rc); } } res_conf * get_res_conf(void) { struct timespec last_change; res_state res; res_conf *rc; LOCK(&res_conf_lock); /* check if resolver config changed */ res = __res_get_state(); if (res) { res_check(res, &last_change); if (timespeccmp(&last_config, &last_change, <)) { if (cur_res_conf) { decref_res_conf(cur_res_conf); } cur_res_conf = new_res_conf(res); if (cur_res_conf) { last_config = last_change; } } __res_put_state(res); } rc = cur_res_conf; if (rc) { rc->refcount++; } UNLOCK(&res_conf_lock); return rc; } static void put_res_conf(res_conf *rc) { LOCK(&res_conf_lock); decref_res_conf(rc); UNLOCK(&res_conf_lock); } static res_conf * new_res_conf(res_state res) { res_conf *rc; int count = 0; char **sd, *p; rc = calloc(sizeof(res_conf), 1); if (rc) { rc->refcount = 1; sd = res->dnsrch; while (*sd) { if (searchable_domain(*sd)) { count++; } sd++; } rc->search_domains = calloc(sizeof(char *), count + 1); if (!(rc->search_domains)) { decref_res_conf(rc); return NULL; } sd = res->dnsrch; rc->no_search = rc->search_domains; while (*sd) { if (searchable_domain(*sd)) { *(rc->no_search) = p = strdup(*sd); if (!p) { decref_res_conf(rc); return NULL; } rc->no_search++; } sd++; } rc->timeout = res->retrans; if (svc_flags & kDNSServiceFlagsForceMulticast) { rc->ndots = 1; if (rc->timeout > 2) { rc->timeout = 2; } } else { rc->ndots = res->ndots; } } return rc; } static short get_timeout(void) { short timeout = 5; res_conf *rc; rc = get_res_conf(); if (rc) { timeout = rc->timeout; put_res_conf(rc); } return timeout; }