/* * 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. */ /* * dns_common.c */ #include "dns_common.h" #pragma weak dn_expand #pragma weak res_ninit #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); } /* * __res_ndestroy is a simplified version of the non-public function * res_ndestroy in libresolv.so.2. Before res_ndestroy can be made * public, __res_ndestroy will be used to make sure the memory pointed * by statp->_u._ext.ext is freed after res_nclose() is called. */ static void __res_ndestroy(res_state statp) { res_nclose(statp); if (statp->_u._ext.ext != NULL) free(statp->_u._ext.ext); } /* * 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); } /* * 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. * * ***Currently the first version of this API only performs simple * single res_nsearch lookups for with T_A or T_AAAA results. * Other searches are deferred to the generic API w/t ttls. * * This function is not a generic res_* operation. It only performs * a single T_A or T_AAAA lookups*** * * 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; 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); } if (ipnode) { /* initially only handle the simple cases */ if (arg.key.ipnode.flags != 0) { __res_ndestroy(statp); return (NSS_ERROR); } name = arg.key.ipnode.name; if (arg.key.ipnode.af_family == AF_INET6) qtype = T_AAAA; else qtype = T_A; } else { name = arg.key.name; qtype = T_A; } ret = res_nsearch(statp, name, C_IN, qtype, resbuf.buf, NS_MAXMSG); if (ret == -1) { if (statp->res_h_errno == HOST_NOT_FOUND) { pbuf->p_herrno = HOST_NOT_FOUND; pbuf->p_status = NSS_NOTFOUND; pbuf->data_len = 0; __res_ndestroy(statp); return (NSS_NOTFOUND); } /* else lookup error - handle in general code */ __res_ndestroy(statp); return (NSS_ERROR); } 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++; } /* 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); } 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); }