/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ /* * dns_common.c */ #include "dns_common.h" #include #include #include #include #pragma weak dn_expand #pragma weak res_ninit #pragma weak res_ndestroy #pragma weak res_nsearch #pragma weak res_nclose #pragma weak ns_get16 #pragma weak ns_get32 #pragma weak __ns_get16 #pragma weak __ns_get32 #define DNS_ALIASES 0 #define DNS_ADDRLIST 1 #define DNS_MAPDLIST 2 #ifndef tolower #define tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) | 0x20 : (c)) #endif static int dns_netdb_aliases(from_list, to_list, aliaspp, type, count, af_type) char **from_list, **to_list, **aliaspp; int type, *count, af_type; { char *fstr; int cnt = 0; size_t len; *count = 0; if ((char *)to_list >= *aliaspp) return (NSS_STR_PARSE_ERANGE); for (fstr = from_list[cnt]; fstr != NULL; fstr = from_list[cnt]) { if (type == DNS_ALIASES) len = strlen(fstr) + 1; else len = (af_type == AF_INET) ? sizeof (struct in_addr) : sizeof (struct in6_addr); *aliaspp -= len; to_list[cnt] = *aliaspp; if (*aliaspp <= (char *)&to_list[cnt+1]) return (NSS_STR_PARSE_ERANGE); if (type == DNS_MAPDLIST) { /* LINTED: E_BAD_PTR_CAST_ALIGN */ struct in6_addr *addr6p = (struct in6_addr *)*aliaspp; (void) memset(addr6p, '\0', sizeof (struct in6_addr)); (void) memcpy(&addr6p->s6_addr[12], fstr, sizeof (struct in_addr)); addr6p->s6_addr[10] = 0xffU; addr6p->s6_addr[11] = 0xffU; ++cnt; } else { (void) memcpy (*aliaspp, fstr, len); ++cnt; } } to_list[cnt] = NULL; *count = cnt; if (cnt == 0) return (NSS_STR_PARSE_PARSE); return (NSS_STR_PARSE_SUCCESS); } int ent2result(he, argp, af_type) struct hostent *he; nss_XbyY_args_t *argp; int af_type; { char *buffer, *limit; int buflen = argp->buf.buflen; int ret, count; size_t len; struct hostent *host; struct in_addr *addrp; struct in6_addr *addrp6; limit = argp->buf.buffer + buflen; host = (struct hostent *)argp->buf.result; buffer = argp->buf.buffer; /* h_addrtype and h_length */ host->h_addrtype = af_type; host->h_length = (af_type == AF_INET) ? sizeof (struct in_addr) : sizeof (struct in6_addr); /* h_name */ len = strlen(he->h_name) + 1; host->h_name = buffer; if (host->h_name + len >= limit) return (NSS_STR_PARSE_ERANGE); (void) memcpy(host->h_name, he->h_name, len); buffer += len; /* h_addr_list */ if (af_type == AF_INET) { addrp = (struct in_addr *)ROUND_DOWN(limit, sizeof (*addrp)); host->h_addr_list = (char **) ROUND_UP(buffer, sizeof (char **)); ret = dns_netdb_aliases(he->h_addr_list, host->h_addr_list, (char **)&addrp, DNS_ADDRLIST, &count, af_type); if (ret != NSS_STR_PARSE_SUCCESS) return (ret); /* h_aliases */ host->h_aliases = host->h_addr_list + count + 1; ret = dns_netdb_aliases(he->h_aliases, host->h_aliases, (char **)&addrp, DNS_ALIASES, &count, af_type); } else { addrp6 = (struct in6_addr *) ROUND_DOWN(limit, sizeof (*addrp6)); host->h_addr_list = (char **) ROUND_UP(buffer, sizeof (char **)); if (he->h_addrtype == AF_INET && af_type == AF_INET6) { ret = dns_netdb_aliases(he->h_addr_list, host->h_addr_list, (char **)&addrp6, DNS_MAPDLIST, &count, af_type); } else { ret = dns_netdb_aliases(he->h_addr_list, host->h_addr_list, (char **)&addrp6, DNS_ADDRLIST, &count, af_type); } if (ret != NSS_STR_PARSE_SUCCESS) return (ret); /* h_aliases */ host->h_aliases = host->h_addr_list + count + 1; ret = dns_netdb_aliases(he->h_aliases, host->h_aliases, (char **)&addrp6, DNS_ALIASES, &count, af_type); } if (ret == NSS_STR_PARSE_PARSE) ret = NSS_STR_PARSE_SUCCESS; return (ret); } /* * Convert the hostent structure into string in the following * format: * * IP-address official-host-name nicknames ... * * If more than one IP-addresses matches the official-host-name, * the above line will be followed by: * IP-address-1 official-host-name * IP-address-2 official-host-name * ... * * This is so that the str2hostent function in libnsl * can convert the string back to the original hostent * data. */ int ent2str( struct hostent *hp, nss_XbyY_args_t *ap, int af_type) { char **p; char obuf[INET6_ADDRSTRLEN]; void *addr; struct in_addr in4; int af; int n; const char *res; char **q; int l = ap->buf.buflen; char *s = ap->buf.buffer; /* * for "hosts" lookup, we only want address type of * AF_INET. For "ipnodes", we can have both AF_INET * and AF_INET6. */ if (af_type == AF_INET && hp->h_addrtype != AF_INET) return (NSS_STR_PARSE_PARSE); for (p = hp->h_addr_list; *p != 0; p++) { if (p != hp->h_addr_list) { *s = '\n'; s++; l--; } if (hp->h_addrtype == AF_INET6) { /* LINTED: E_BAD_PTR_CAST_ALIGN */ if (IN6_IS_ADDR_V4MAPPED((struct in6_addr *)*p)) { /* LINTED: E_BAD_PTR_CAST_ALIGN */ IN6_V4MAPPED_TO_INADDR((struct in6_addr *)*p, &in4); af = AF_INET; addr = &in4; } else { af = AF_INET6; addr = *p; } } else { af = AF_INET; addr = *p; } res = inet_ntop(af, addr, obuf, sizeof (obuf)); if (res == NULL) return (NSS_STR_PARSE_PARSE); if ((n = snprintf(s, l, "%s", res)) >= l) return (NSS_STR_PARSE_ERANGE); l -= n; s += n; if (hp->h_name != NULL && *hp->h_name != '\0') { if ((n = snprintf(s, l, " %s", hp->h_name)) >= l) return (NSS_STR_PARSE_ERANGE); l -= n; s += n; } if (p == hp->h_addr_list) { for (q = hp->h_aliases; q && *q; q++) { if ((n = snprintf(s, l, " %s", *q)) >= l) return (NSS_STR_PARSE_ERANGE); l -= n; s += n; } } } ap->returnlen = s - ap->buf.buffer; return (NSS_STR_PARSE_SUCCESS); } nss_backend_t * _nss_dns_constr(dns_backend_op_t ops[], int n_ops) { dns_backend_ptr_t be; if ((be = (dns_backend_ptr_t)malloc(sizeof (*be))) == 0) return (0); be->ops = ops; be->n_ops = n_ops; return ((nss_backend_t *)be); } /* * name_is_alias(aliases_ptr, name_ptr) * Verify name matches an alias in the provided aliases list. * * Within DNS there should be only one canonical name, aliases should * all refer to the one canonical. However alias chains do occur and * pre BIND 9 servers may also respond with multiple CNAMEs. This * routine checks if a given name has been provided as a CNAME in the * response. This assumes that the chains have been sent in-order. * * INPUT: * aliases_ptr: space separated list of alias names. * name_ptr: name to look for in aliases_ptr list. * RETURNS: NSS_SUCCESS or NSS_NOTFOUND * NSS_SUCCESS indicates that the name is listed in the collected aliases. */ static nss_status_t name_is_alias(char *aliases_ptr, char *name_ptr) { char *host_ptr; /* Loop through alias string and compare it against host string. */ while (*aliases_ptr != '\0') { host_ptr = name_ptr; /* Compare name with alias. */ while (tolower(*host_ptr) == tolower(*aliases_ptr) && *host_ptr != '\0') { host_ptr++; aliases_ptr++; } /* * If name was exhausted and the next character in the * alias is either the end-of-string or space * character then we have a match. */ if (*host_ptr == '\0' && (*aliases_ptr == '\0' || *aliases_ptr == ' ')) { return (NSS_SUCCESS); } /* Alias did not match, step over remainder of alias. */ while (*aliases_ptr != ' ' && *aliases_ptr != '\0') aliases_ptr++; /* Step over separator character. */ while (*aliases_ptr == ' ') aliases_ptr++; } return (NSS_NOTFOUND); } static int _nss_has_interfaces(boolean_t *v4, boolean_t *v6) { struct ifaddrs *ifp, *i; struct in_addr in4; struct in6_addr in6; const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; *v4 = *v6 = B_FALSE; if (getifaddrs(&ifp) != 0) return (-1); for (i = ifp; i != NULL; i = i->ifa_next) { if (i->ifa_flags & IFF_LOOPBACK) continue; if ((i->ifa_flags & IFF_UP) == 0) continue; if (i->ifa_addr->sa_family == AF_INET) { if (*v4 != B_FALSE) continue; if (((struct sockaddr_in *)i->ifa_addr)-> sin_addr.s_addr == INADDR_ANY) continue; *v4 = B_TRUE; } if (i->ifa_addr->sa_family == AF_INET6) { if (*v6 != B_FALSE) continue; if (memcmp(&in6addr_any, &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr, sizeof (struct in6_addr)) == 0) continue; *v6 = B_TRUE; } } freeifaddrs(ifp); return (0); } /* * nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode) * nss2 get hosts/ipnodes with ttl backend DNS search engine. * * This API is given a pointer to a packed buffer, and the buffer size * It's job is to perform the appropriate res_nsearch, extract the * results and build a unmarshalled hosts/ipnodes result buffer. * Additionally in the extended results a nssuint_t ttl is placed. * This ttl is the lessor of the ttl's extracted from the result. * * RETURNS: NSS_SUCCESS or NSS_ERROR * If an NSS_ERROR result is returned, nscd is expected * to resubmit the gethosts request using the old style * nsswitch lookup format. */ nss_status_t _nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode) { /* nss buffer variables */ nss_pheader_t *pbuf = (nss_pheader_t *)buffer; nss_XbyY_args_t arg; char *dbname; int dbop; nss_status_t sret; size_t bsize, blen; char *bptr; /* resolver query variables */ struct __res_state stat, *statp; /* dns state block */ union msg { uchar_t buf[NS_MAXMSG]; /* max legal DNS answer size */ HEADER h; } resbuf; char aliases[NS_MAXMSG]; /* set of aliases */ const char *name; int qtype; /* answer parsing variables */ HEADER *hp; uchar_t *cp; /* current location in message */ uchar_t *bom; /* start of message */ uchar_t *eom; /* end of message */ uchar_t *eor; /* end of record */ int ancount, qdcount; int type, class; nssuint_t nttl, ttl, *pttl; /* The purpose of this API */ int n, ret; const char *np; /* temporary buffers */ char nbuf[INET6_ADDRSTRLEN]; /* address parser */ char host[MAXHOSTNAMELEN]; /* result host name */ char ans[MAXHOSTNAMELEN]; /* record name */ char aname[MAXHOSTNAMELEN]; /* alias result (C_NAME) */ /* misc variables */ int af; char *ap, *apc; int hlen = 0, alen, iplen, len, isans; boolean_t has_v4 = B_FALSE, has_v6 = B_FALSE; int flags, family, pass2 = 0; statp = &stat; (void) memset(statp, '\0', sizeof (struct __res_state)); if (res_ninit(statp) == -1) { return (NSS_ERROR); } ap = apc = (char *)aliases; alen = 0; ttl = (nssuint_t)0xFFFFFFF; /* start w/max, find smaller */ /* save space for ttl otherwise, why bother... */ bsize = pbuf->data_len - sizeof (nssuint_t); bptr = (char *)buffer + pbuf->data_off; blen = 0; sret = nss_packed_getkey(buffer, bufsize, &dbname, &dbop, &arg); if (sret != NSS_SUCCESS) { res_ndestroy(statp); return (NSS_ERROR); } /* * There may be flags set when we are handling ipnode. There are three * different values for flags: * * o AI_V4MAPPED * o AI_ALL * o AI_ADDRCONFIG * * The first two only have a meaning when af_family is ipv6. The latter * means something in both cases. These flags are documented in * getipnodebyname(3SOCKET), though the combinations leave a little * something to be desired. It would be great if we could actually use * getipnodebyname directly here since it already knows how to handle * this kind of logic; however, we're not quite so lucky. Ideally we * would add such an interface to libresolv.so.2 to handle this kind of * thing, but that's rather painful as well. We'll summarize what has to * happen below: * * AI_ALL is only meaningful when AI_V4MAPPED is also specified. Both * are ignored if the family is not AF_INET6 * * family == AF_INET, flags | AI_ADDRCONFIG * - lookup A records iff we have v4 plumbed * family == AF_INET, !(flags | AI_ADDRCONFIG) * - lookup A records * family == AF_INET6, flags == 0 || flags == AI_ALL * - lookup AAAA records * family == AF_INET6, flags | AI_V4MAPPED * - lookup AAAA, if none, lookup A * family == AF_INET6, flags | AI_ADDRCONFIG * - lookup AAAA records if ipv6 * family == AF_INET6, flags | AI_V4MAPPED && flags | AI_ALL * - lookup AAAA records, lookup A records * family == AF_INET6, flags | AI_V4MAPPED && flags | AI_ADDRCONFIG * - lookup AAAA records if ipv6 * - If no AAAA && ipv4 exists, lookup A * family == AF_INET6, flags | AI_V4MAPPED && flags | AI_ADDRCONFIG && * flags | AI_ALL * - lookup AAAA records if ipv6 * - loookup A records if ipv4 */ if (ipnode) { /* initially only handle the simple cases */ name = arg.key.ipnode.name; flags = arg.key.ipnode.flags; family = arg.key.ipnode.af_family; if (flags != 0) { /* * Figure out our first pass. We'll determine if we need * to do a second pass afterwards once we successfully * finish our first pass. */ if ((flags & AI_ADDRCONFIG) != 0) { if (_nss_has_interfaces(&has_v4, &has_v6) != 0) { res_ndestroy(statp); return (NSS_ERROR); } /* Impossible situations... */ if (family == AF_INET && has_v4 == B_FALSE) { res_ndestroy(statp); return (NSS_NOTFOUND); } if (family == AF_INET6 && has_v6 == B_FALSE && !(flags & AI_V4MAPPED)) { res_ndestroy(statp); return (NSS_NOTFOUND); } if (family == AF_INET6 && has_v6) qtype = T_AAAA; if (family == AF_INET || (family == AF_INET6 && has_v6 == B_FALSE && flags & AI_V4MAPPED)) qtype = T_A; } else { has_v4 = has_v6 = B_TRUE; if (family == AF_INET6) qtype = T_AAAA; else qtype = T_A; } } else { if (family == AF_INET6) qtype = T_AAAA; else qtype = T_A; } } else { name = arg.key.name; qtype = T_A; } searchagain: ret = res_nsearch(statp, name, C_IN, qtype, resbuf.buf, NS_MAXMSG); if (ret == -1) { /* * We want to continue on unless we got NO_RECOVERY. Otherwise, * HOST_NOT_FOUND, TRY_AGAIN, and NO_DATA all suggest to me that * we should keep going. */ if (statp->res_h_errno == NO_RECOVERY) { /* else lookup error - handle in general code */ res_ndestroy(statp); return (NSS_ERROR); } /* * We found something on our first pass. Make sure that we do * not clobber this information. This ultimately means that we * were successful. */ if (pass2 == 2) goto out; /* * If we're on the second pass (eg. we need to check both for A * and AAAA records), or we were only ever doing a search for * one type of record and are not supposed to do a second pass, * then we need to return that we couldn't find anything to the * user. */ if (pass2 == 1 || flags == 0 || family == AF_INET || (family == AF_INET6 && !(flags & AI_V4MAPPED))) { pbuf->p_herrno = HOST_NOT_FOUND; pbuf->p_status = NSS_NOTFOUND; pbuf->data_len = 0; res_ndestroy(statp); return (NSS_NOTFOUND); } /* * If we were only requested to search for flags on an IPv6 * interface or we have no IPv4 interface, we stick to only * doing a single pass and bail now. */ if ((flags & AI_ADDRCONFIG) && !(flags & AI_ALL) && has_v4 == B_FALSE) { pbuf->p_herrno = HOST_NOT_FOUND; pbuf->p_status = NSS_NOTFOUND; pbuf->data_len = 0; res_ndestroy(statp); return (NSS_NOTFOUND); } qtype = T_A; flags = 0; pass2 = 1; goto searchagain; } cp = resbuf.buf; hp = (HEADER *)&resbuf.h; bom = cp; eom = cp + ret; ancount = ntohs(hp->ancount); qdcount = ntohs(hp->qdcount); cp += HFIXEDSZ; if (qdcount != 1) { res_ndestroy(statp); return (NSS_ERROR); } n = dn_expand(bom, eom, cp, host, MAXHOSTNAMELEN); if (n < 0) { res_ndestroy(statp); return (NSS_ERROR); } else hlen = strlen(host); /* no host name is an error, return */ if (hlen <= 0) { res_ndestroy(statp); return (NSS_ERROR); } cp += n + QFIXEDSZ; if (cp > eom) { res_ndestroy(statp); return (NSS_ERROR); } while (ancount-- > 0 && cp < eom && blen < bsize) { n = dn_expand(bom, eom, cp, ans, MAXHOSTNAMELEN); if (n > 0) { /* * Check that the expanded name is either the * name we asked for or a learned alias. */ if ((isans = strncasecmp(host, ans, hlen)) != 0 && (alen == 0 || name_is_alias(aliases, ans) == NSS_NOTFOUND)) { res_ndestroy(statp); return (NSS_ERROR); /* spoof? */ } } cp += n; /* bounds check */ type = ns_get16(cp); /* type */ cp += INT16SZ; class = ns_get16(cp); /* class */ cp += INT16SZ; nttl = (nssuint_t)ns_get32(cp); /* ttl in sec */ if (nttl < ttl) ttl = nttl; cp += INT32SZ; n = ns_get16(cp); /* len */ cp += INT16SZ; if (class != C_IN) { cp += n; continue; } eor = cp + n; if (type == T_CNAME) { /* * The name looked up is really an alias and the * canonical name should be in the RDATA. * A canonical name may have several aliases but an * alias should only have one canonical name. * However multiple CNAMEs and CNAME chains do exist! * * Just error out on attempted buffer overflow exploit, * generic code will syslog. * */ n = dn_expand(bom, eor, cp, aname, MAXHOSTNAMELEN); if (n > 0 && (len = strlen(aname)) > 0) { if (isans == 0) { /* host matched ans. */ /* * Append host to alias list. */ if (alen + hlen + 2 > NS_MAXMSG) { res_ndestroy(statp); return (NSS_ERROR); } *apc++ = ' '; alen++; (void) strlcpy(apc, host, NS_MAXMSG - alen); alen += hlen; apc += hlen; } /* * Overwrite host with canonical name. */ if (strlcpy(host, aname, MAXHOSTNAMELEN) >= MAXHOSTNAMELEN) { res_ndestroy(statp); return (NSS_ERROR); } hlen = len; } cp += n; continue; } if (type != qtype) { cp += n; continue; } /* check data size */ if ((type == T_A && n != INADDRSZ) || (type == T_AAAA && n != IN6ADDRSZ)) { cp += n; continue; } af = (type == T_A ? AF_INET : AF_INET6); np = inet_ntop(af, (void *)cp, nbuf, INET6_ADDRSTRLEN); if (np == NULL) { res_ndestroy(statp); return (NSS_ERROR); } cp += n; /* append IP host aliases to results */ iplen = strlen(np); /* ip hostname [][aliases] */ len = iplen + 2 + hlen + alen; if (alen > 0) len++; if (blen + len > bsize) { res_ndestroy(statp); return (NSS_ERROR); } (void) strlcpy(bptr, np, bsize - blen); blen += iplen; bptr += iplen; *bptr++ = ' '; blen++; (void) strlcpy(bptr, host, bsize - blen); blen += hlen; bptr += hlen; if (alen > 0) { *bptr++ = ' '; blen++; (void) strlcpy(bptr, ap, bsize - blen); blen += alen; bptr += alen; } *bptr++ = '\n'; blen++; } /* Depending on our flags we may need to go back another time. */ if (qtype == T_AAAA && family == AF_INET6 && ((flags & AI_V4MAPPED) != 0) && ((flags & AI_ALL) != 0) && has_v4 == B_TRUE) { qtype = T_A; pass2 = 2; /* Indicate that we found data this pass */ goto searchagain; } /* Presumably the buffer is now filled. */ len = ROUND_UP(blen, sizeof (nssuint_t)); /* still room? */ if (len + sizeof (nssuint_t) > pbuf->data_len) { /* sigh, no, what happened? */ res_ndestroy(statp); return (NSS_ERROR); } out: pbuf->ext_off = pbuf->data_off + len; pbuf->ext_len = sizeof (nssuint_t); pbuf->data_len = blen; pttl = (nssuint_t *)((void *)((char *)pbuf + pbuf->ext_off)); *pttl = ttl; res_ndestroy(statp); return (NSS_SUCCESS); }