xref: /freebsd/lib/libcasper/services/cap_dns/cap_dns.c (revision 47dd1d1b619cc035b82b49a91a25544309ff95ae)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2012-2013 The FreeBSD Foundation
5  * All rights reserved.
6  *
7  * This software was developed by Pawel Jakub Dawidek under sponsorship from
8  * the FreeBSD Foundation.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34 
35 #include <sys/dnv.h>
36 #include <sys/nv.h>
37 #include <netinet/in.h>
38 
39 #include <assert.h>
40 #include <errno.h>
41 #include <netdb.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #include <libcasper.h>
47 #include <libcasper_service.h>
48 
49 #include "cap_dns.h"
50 
51 static struct hostent hent;
52 
53 static void
54 hostent_free(struct hostent *hp)
55 {
56 	unsigned int ii;
57 
58 	free(hp->h_name);
59 	hp->h_name = NULL;
60 	if (hp->h_aliases != NULL) {
61 		for (ii = 0; hp->h_aliases[ii] != NULL; ii++)
62 			free(hp->h_aliases[ii]);
63 		free(hp->h_aliases);
64 		hp->h_aliases = NULL;
65 	}
66 	if (hp->h_addr_list != NULL) {
67 		for (ii = 0; hp->h_addr_list[ii] != NULL; ii++)
68 			free(hp->h_addr_list[ii]);
69 		free(hp->h_addr_list);
70 		hp->h_addr_list = NULL;
71 	}
72 }
73 
74 static struct hostent *
75 hostent_unpack(const nvlist_t *nvl, struct hostent *hp)
76 {
77 	unsigned int ii, nitems;
78 	char nvlname[64];
79 	int n;
80 
81 	hostent_free(hp);
82 
83 	hp->h_name = strdup(nvlist_get_string(nvl, "name"));
84 	if (hp->h_name == NULL)
85 		goto fail;
86 	hp->h_addrtype = (int)nvlist_get_number(nvl, "addrtype");
87 	hp->h_length = (int)nvlist_get_number(nvl, "length");
88 
89 	nitems = (unsigned int)nvlist_get_number(nvl, "naliases");
90 	hp->h_aliases = calloc(sizeof(hp->h_aliases[0]), nitems + 1);
91 	if (hp->h_aliases == NULL)
92 		goto fail;
93 	for (ii = 0; ii < nitems; ii++) {
94 		n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);
95 		assert(n > 0 && n < (int)sizeof(nvlname));
96 		hp->h_aliases[ii] =
97 		    strdup(nvlist_get_string(nvl, nvlname));
98 		if (hp->h_aliases[ii] == NULL)
99 			goto fail;
100 	}
101 	hp->h_aliases[ii] = NULL;
102 
103 	nitems = (unsigned int)nvlist_get_number(nvl, "naddrs");
104 	hp->h_addr_list = calloc(sizeof(hp->h_addr_list[0]), nitems + 1);
105 	if (hp->h_addr_list == NULL)
106 		goto fail;
107 	for (ii = 0; ii < nitems; ii++) {
108 		hp->h_addr_list[ii] = malloc(hp->h_length);
109 		if (hp->h_addr_list[ii] == NULL)
110 			goto fail;
111 		n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);
112 		assert(n > 0 && n < (int)sizeof(nvlname));
113 		bcopy(nvlist_get_binary(nvl, nvlname, NULL),
114 		    hp->h_addr_list[ii], hp->h_length);
115 	}
116 	hp->h_addr_list[ii] = NULL;
117 
118 	return (hp);
119 fail:
120 	hostent_free(hp);
121 	h_errno = NO_RECOVERY;
122 	return (NULL);
123 }
124 
125 struct hostent *
126 cap_gethostbyname(cap_channel_t *chan, const char *name)
127 {
128 
129 	return (cap_gethostbyname2(chan, name, AF_INET));
130 }
131 
132 struct hostent *
133 cap_gethostbyname2(cap_channel_t *chan, const char *name, int type)
134 {
135 	struct hostent *hp;
136 	nvlist_t *nvl;
137 
138 	nvl = nvlist_create(0);
139 	nvlist_add_string(nvl, "cmd", "gethostbyname");
140 	nvlist_add_number(nvl, "family", (uint64_t)type);
141 	nvlist_add_string(nvl, "name", name);
142 	nvl = cap_xfer_nvlist(chan, nvl);
143 	if (nvl == NULL) {
144 		h_errno = NO_RECOVERY;
145 		return (NULL);
146 	}
147 	if (nvlist_get_number(nvl, "error") != 0) {
148 		h_errno = (int)nvlist_get_number(nvl, "error");
149 		nvlist_destroy(nvl);
150 		return (NULL);
151 	}
152 
153 	hp = hostent_unpack(nvl, &hent);
154 	nvlist_destroy(nvl);
155 	return (hp);
156 }
157 
158 struct hostent *
159 cap_gethostbyaddr(cap_channel_t *chan, const void *addr, socklen_t len,
160     int type)
161 {
162 	struct hostent *hp;
163 	nvlist_t *nvl;
164 
165 	nvl = nvlist_create(0);
166 	nvlist_add_string(nvl, "cmd", "gethostbyaddr");
167 	nvlist_add_binary(nvl, "addr", addr, (size_t)len);
168 	nvlist_add_number(nvl, "family", (uint64_t)type);
169 	nvl = cap_xfer_nvlist(chan, nvl);
170 	if (nvl == NULL) {
171 		h_errno = NO_RECOVERY;
172 		return (NULL);
173 	}
174 	if (nvlist_get_number(nvl, "error") != 0) {
175 		h_errno = (int)nvlist_get_number(nvl, "error");
176 		nvlist_destroy(nvl);
177 		return (NULL);
178 	}
179 	hp = hostent_unpack(nvl, &hent);
180 	nvlist_destroy(nvl);
181 	return (hp);
182 }
183 
184 static struct addrinfo *
185 addrinfo_unpack(const nvlist_t *nvl)
186 {
187 	struct addrinfo *ai;
188 	const void *addr;
189 	size_t addrlen;
190 	const char *canonname;
191 
192 	addr = nvlist_get_binary(nvl, "ai_addr", &addrlen);
193 	ai = malloc(sizeof(*ai) + addrlen);
194 	if (ai == NULL)
195 		return (NULL);
196 	ai->ai_flags = (int)nvlist_get_number(nvl, "ai_flags");
197 	ai->ai_family = (int)nvlist_get_number(nvl, "ai_family");
198 	ai->ai_socktype = (int)nvlist_get_number(nvl, "ai_socktype");
199 	ai->ai_protocol = (int)nvlist_get_number(nvl, "ai_protocol");
200 	ai->ai_addrlen = (socklen_t)addrlen;
201 	canonname = dnvlist_get_string(nvl, "ai_canonname", NULL);
202 	if (canonname != NULL) {
203 		ai->ai_canonname = strdup(canonname);
204 		if (ai->ai_canonname == NULL) {
205 			free(ai);
206 			return (NULL);
207 		}
208 	} else {
209 		ai->ai_canonname = NULL;
210 	}
211 	ai->ai_addr = (void *)(ai + 1);
212 	bcopy(addr, ai->ai_addr, addrlen);
213 	ai->ai_next = NULL;
214 
215 	return (ai);
216 }
217 
218 int
219 cap_getaddrinfo(cap_channel_t *chan, const char *hostname, const char *servname,
220     const struct addrinfo *hints, struct addrinfo **res)
221 {
222 	struct addrinfo *firstai, *prevai, *curai;
223 	unsigned int ii;
224 	const nvlist_t *nvlai;
225 	char nvlname[64];
226 	nvlist_t *nvl;
227 	int error, n;
228 
229 	nvl = nvlist_create(0);
230 	nvlist_add_string(nvl, "cmd", "getaddrinfo");
231 	if (hostname != NULL)
232 		nvlist_add_string(nvl, "hostname", hostname);
233 	if (servname != NULL)
234 		nvlist_add_string(nvl, "servname", servname);
235 	if (hints != NULL) {
236 		nvlist_add_number(nvl, "hints.ai_flags",
237 		    (uint64_t)hints->ai_flags);
238 		nvlist_add_number(nvl, "hints.ai_family",
239 		    (uint64_t)hints->ai_family);
240 		nvlist_add_number(nvl, "hints.ai_socktype",
241 		    (uint64_t)hints->ai_socktype);
242 		nvlist_add_number(nvl, "hints.ai_protocol",
243 		    (uint64_t)hints->ai_protocol);
244 	}
245 	nvl = cap_xfer_nvlist(chan, nvl);
246 	if (nvl == NULL)
247 		return (EAI_MEMORY);
248 	if (nvlist_get_number(nvl, "error") != 0) {
249 		error = (int)nvlist_get_number(nvl, "error");
250 		nvlist_destroy(nvl);
251 		return (error);
252 	}
253 
254 	nvlai = NULL;
255 	firstai = prevai = curai = NULL;
256 	for (ii = 0; ; ii++) {
257 		n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
258 		assert(n > 0 && n < (int)sizeof(nvlname));
259 		if (!nvlist_exists_nvlist(nvl, nvlname))
260 			break;
261 		nvlai = nvlist_get_nvlist(nvl, nvlname);
262 		curai = addrinfo_unpack(nvlai);
263 		if (curai == NULL)
264 			break;
265 		if (prevai != NULL)
266 			prevai->ai_next = curai;
267 		else if (firstai == NULL)
268 			firstai = curai;
269 		prevai = curai;
270 	}
271 	nvlist_destroy(nvl);
272 	if (curai == NULL && nvlai != NULL) {
273 		if (firstai == NULL)
274 			freeaddrinfo(firstai);
275 		return (EAI_MEMORY);
276 	}
277 
278 	*res = firstai;
279 	return (0);
280 }
281 
282 int
283 cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, socklen_t salen,
284     char *host, size_t hostlen, char *serv, size_t servlen, int flags)
285 {
286 	nvlist_t *nvl;
287 	int error;
288 
289 	nvl = nvlist_create(0);
290 	nvlist_add_string(nvl, "cmd", "getnameinfo");
291 	nvlist_add_number(nvl, "hostlen", (uint64_t)hostlen);
292 	nvlist_add_number(nvl, "servlen", (uint64_t)servlen);
293 	nvlist_add_binary(nvl, "sa", sa, (size_t)salen);
294 	nvlist_add_number(nvl, "flags", (uint64_t)flags);
295 	nvl = cap_xfer_nvlist(chan, nvl);
296 	if (nvl == NULL)
297 		return (EAI_MEMORY);
298 	if (nvlist_get_number(nvl, "error") != 0) {
299 		error = (int)nvlist_get_number(nvl, "error");
300 		nvlist_destroy(nvl);
301 		return (error);
302 	}
303 
304 	if (host != NULL && nvlist_exists_string(nvl, "host"))
305 		strlcpy(host, nvlist_get_string(nvl, "host"), hostlen + 1);
306 	if (serv != NULL && nvlist_exists_string(nvl, "serv"))
307 		strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen + 1);
308 	nvlist_destroy(nvl);
309 	return (0);
310 }
311 
312 static void
313 limit_remove(nvlist_t *limits, const char *prefix)
314 {
315 	const char *name;
316 	size_t prefixlen;
317 	void *cookie;
318 
319 	prefixlen = strlen(prefix);
320 again:
321 	cookie = NULL;
322 	while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
323 		if (strncmp(name, prefix, prefixlen) == 0) {
324 			nvlist_free(limits, name);
325 			goto again;
326 		}
327 	}
328 }
329 
330 int
331 cap_dns_type_limit(cap_channel_t *chan, const char * const *types,
332     size_t ntypes)
333 {
334 	nvlist_t *limits;
335 	unsigned int i;
336 	char nvlname[64];
337 	int n;
338 
339 	if (cap_limit_get(chan, &limits) < 0)
340 		return (-1);
341 	if (limits == NULL)
342 		limits = nvlist_create(0);
343 	else
344 		limit_remove(limits, "type");
345 	for (i = 0; i < ntypes; i++) {
346 		n = snprintf(nvlname, sizeof(nvlname), "type%u", i);
347 		assert(n > 0 && n < (int)sizeof(nvlname));
348 		nvlist_add_string(limits, nvlname, types[i]);
349 	}
350 	return (cap_limit_set(chan, limits));
351 }
352 
353 int
354 cap_dns_family_limit(cap_channel_t *chan, const int *families,
355     size_t nfamilies)
356 {
357 	nvlist_t *limits;
358 	unsigned int i;
359 	char nvlname[64];
360 	int n;
361 
362 	if (cap_limit_get(chan, &limits) < 0)
363 		return (-1);
364 	if (limits == NULL)
365 		limits = nvlist_create(0);
366 	else
367 		limit_remove(limits, "family");
368 	for (i = 0; i < nfamilies; i++) {
369 		n = snprintf(nvlname, sizeof(nvlname), "family%u", i);
370 		assert(n > 0 && n < (int)sizeof(nvlname));
371 		nvlist_add_number(limits, nvlname, (uint64_t)families[i]);
372 	}
373 	return (cap_limit_set(chan, limits));
374 }
375 
376 /*
377  * Service functions.
378  */
379 static bool
380 dns_allowed_type(const nvlist_t *limits, const char *type)
381 {
382 	const char *name;
383 	bool notypes;
384 	void *cookie;
385 
386 	if (limits == NULL)
387 		return (true);
388 
389 	notypes = true;
390 	cookie = NULL;
391 	while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
392 		if (strncmp(name, "type", sizeof("type") - 1) != 0)
393 			continue;
394 		notypes = false;
395 		if (strcmp(nvlist_get_string(limits, name), type) == 0)
396 			return (true);
397 	}
398 
399 	/* If there are no types at all, allow any type. */
400 	if (notypes)
401 		return (true);
402 
403 	return (false);
404 }
405 
406 static bool
407 dns_allowed_family(const nvlist_t *limits, int family)
408 {
409 	const char *name;
410 	bool nofamilies;
411 	void *cookie;
412 
413 	if (limits == NULL)
414 		return (true);
415 
416 	nofamilies = true;
417 	cookie = NULL;
418 	while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
419 		if (strncmp(name, "family", sizeof("family") - 1) != 0)
420 			continue;
421 		nofamilies = false;
422 		if (family == AF_UNSPEC)
423 			continue;
424 		if (nvlist_get_number(limits, name) == (uint64_t)family)
425 			return (true);
426 	}
427 
428 	/* If there are no families at all, allow any family. */
429 	if (nofamilies)
430 		return (true);
431 
432 	return (false);
433 }
434 
435 static void
436 hostent_pack(const struct hostent *hp, nvlist_t *nvl)
437 {
438 	unsigned int ii;
439 	char nvlname[64];
440 	int n;
441 
442 	nvlist_add_string(nvl, "name", hp->h_name);
443 	nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype);
444 	nvlist_add_number(nvl, "length", (uint64_t)hp->h_length);
445 
446 	if (hp->h_aliases == NULL) {
447 		nvlist_add_number(nvl, "naliases", 0);
448 	} else {
449 		for (ii = 0; hp->h_aliases[ii] != NULL; ii++) {
450 			n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);
451 			assert(n > 0 && n < (int)sizeof(nvlname));
452 			nvlist_add_string(nvl, nvlname, hp->h_aliases[ii]);
453 		}
454 		nvlist_add_number(nvl, "naliases", (uint64_t)ii);
455 	}
456 
457 	if (hp->h_addr_list == NULL) {
458 		nvlist_add_number(nvl, "naddrs", 0);
459 	} else {
460 		for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) {
461 			n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);
462 			assert(n > 0 && n < (int)sizeof(nvlname));
463 			nvlist_add_binary(nvl, nvlname, hp->h_addr_list[ii],
464 			    (size_t)hp->h_length);
465 		}
466 		nvlist_add_number(nvl, "naddrs", (uint64_t)ii);
467 	}
468 }
469 
470 static int
471 dns_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin,
472     nvlist_t *nvlout)
473 {
474 	struct hostent *hp;
475 	int family;
476 
477 	if (!dns_allowed_type(limits, "NAME"))
478 		return (NO_RECOVERY);
479 
480 	family = (int)nvlist_get_number(nvlin, "family");
481 
482 	if (!dns_allowed_family(limits, family))
483 		return (NO_RECOVERY);
484 
485 	hp = gethostbyname2(nvlist_get_string(nvlin, "name"), family);
486 	if (hp == NULL)
487 		return (h_errno);
488 	hostent_pack(hp, nvlout);
489 	return (0);
490 }
491 
492 static int
493 dns_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin,
494     nvlist_t *nvlout)
495 {
496 	struct hostent *hp;
497 	const void *addr;
498 	size_t addrsize;
499 	int family;
500 
501 	if (!dns_allowed_type(limits, "ADDR"))
502 		return (NO_RECOVERY);
503 
504 	family = (int)nvlist_get_number(nvlin, "family");
505 
506 	if (!dns_allowed_family(limits, family))
507 		return (NO_RECOVERY);
508 
509 	addr = nvlist_get_binary(nvlin, "addr", &addrsize);
510 	hp = gethostbyaddr(addr, (socklen_t)addrsize, family);
511 	if (hp == NULL)
512 		return (h_errno);
513 	hostent_pack(hp, nvlout);
514 	return (0);
515 }
516 
517 static int
518 dns_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
519 {
520 	struct sockaddr_storage sast;
521 	const void *sabin;
522 	char *host, *serv;
523 	size_t sabinsize, hostlen, servlen;
524 	socklen_t salen;
525 	int error, flags;
526 
527 	if (!dns_allowed_type(limits, "NAME"))
528 		return (NO_RECOVERY);
529 
530 	error = 0;
531 	host = serv = NULL;
532 	memset(&sast, 0, sizeof(sast));
533 
534 	hostlen = (size_t)nvlist_get_number(nvlin, "hostlen");
535 	servlen = (size_t)nvlist_get_number(nvlin, "servlen");
536 
537 	if (hostlen > 0) {
538 		host = calloc(1, hostlen + 1);
539 		if (host == NULL) {
540 			error = EAI_MEMORY;
541 			goto out;
542 		}
543 	}
544 	if (servlen > 0) {
545 		serv = calloc(1, servlen + 1);
546 		if (serv == NULL) {
547 			error = EAI_MEMORY;
548 			goto out;
549 		}
550 	}
551 
552 	sabin = nvlist_get_binary(nvlin, "sa", &sabinsize);
553 	if (sabinsize > sizeof(sast)) {
554 		error = EAI_FAIL;
555 		goto out;
556 	}
557 
558 	memcpy(&sast, sabin, sabinsize);
559 	salen = (socklen_t)sabinsize;
560 
561 	if ((sast.ss_family != AF_INET ||
562 	     salen != sizeof(struct sockaddr_in)) &&
563 	    (sast.ss_family != AF_INET6 ||
564 	     salen != sizeof(struct sockaddr_in6))) {
565 		error = EAI_FAIL;
566 		goto out;
567 	}
568 
569 	if (!dns_allowed_family(limits, (int)sast.ss_family)) {
570 		error = NO_RECOVERY;
571 		goto out;
572 	}
573 
574 	flags = (int)nvlist_get_number(nvlin, "flags");
575 
576 	error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen,
577 	    serv, servlen, flags);
578 	if (error != 0)
579 		goto out;
580 
581 	if (host != NULL)
582 		nvlist_move_string(nvlout, "host", host);
583 	if (serv != NULL)
584 		nvlist_move_string(nvlout, "serv", serv);
585 out:
586 	if (error != 0) {
587 		free(host);
588 		free(serv);
589 	}
590 	return (error);
591 }
592 
593 static nvlist_t *
594 addrinfo_pack(const struct addrinfo *ai)
595 {
596 	nvlist_t *nvl;
597 
598 	nvl = nvlist_create(0);
599 	nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags);
600 	nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family);
601 	nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype);
602 	nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol);
603 	nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen);
604 	if (ai->ai_canonname != NULL)
605 		nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname);
606 
607 	return (nvl);
608 }
609 
610 static int
611 dns_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
612 {
613 	struct addrinfo hints, *hintsp, *res, *cur;
614 	const char *hostname, *servname;
615 	char nvlname[64];
616 	nvlist_t *elem;
617 	unsigned int ii;
618 	int error, family, n;
619 
620 	if (!dns_allowed_type(limits, "ADDR"))
621 		return (NO_RECOVERY);
622 
623 	hostname = dnvlist_get_string(nvlin, "hostname", NULL);
624 	servname = dnvlist_get_string(nvlin, "servname", NULL);
625 	if (nvlist_exists_number(nvlin, "hints.ai_flags")) {
626 		hints.ai_flags = (int)nvlist_get_number(nvlin,
627 		    "hints.ai_flags");
628 		hints.ai_family = (int)nvlist_get_number(nvlin,
629 		    "hints.ai_family");
630 		hints.ai_socktype = (int)nvlist_get_number(nvlin,
631 		    "hints.ai_socktype");
632 		hints.ai_protocol = (int)nvlist_get_number(nvlin,
633 		    "hints.ai_protocol");
634 		hints.ai_addrlen = 0;
635 		hints.ai_addr = NULL;
636 		hints.ai_canonname = NULL;
637 		hints.ai_next = NULL;
638 		hintsp = &hints;
639 		family = hints.ai_family;
640 	} else {
641 		hintsp = NULL;
642 		family = AF_UNSPEC;
643 	}
644 
645 	if (!dns_allowed_family(limits, family))
646 		return (NO_RECOVERY);
647 
648 	error = getaddrinfo(hostname, servname, hintsp, &res);
649 	if (error != 0)
650 		goto out;
651 
652 	for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) {
653 		elem = addrinfo_pack(cur);
654 		n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
655 		assert(n > 0 && n < (int)sizeof(nvlname));
656 		nvlist_move_nvlist(nvlout, nvlname, elem);
657 	}
658 
659 	freeaddrinfo(res);
660 	error = 0;
661 out:
662 	return (error);
663 }
664 
665 static bool
666 limit_has_entry(const nvlist_t *limits, const char *prefix)
667 {
668 	const char *name;
669 	size_t prefixlen;
670 	void *cookie;
671 
672 	if (limits == NULL)
673 		return (false);
674 
675 	prefixlen = strlen(prefix);
676 
677 	cookie = NULL;
678 	while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
679 		if (strncmp(name, prefix, prefixlen) == 0)
680 			return (true);
681 	}
682 
683 	return (false);
684 }
685 
686 static int
687 dns_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
688 {
689 	const char *name;
690 	void *cookie;
691 	int nvtype;
692 	bool hastype, hasfamily;
693 
694 	hastype = false;
695 	hasfamily = false;
696 
697 	cookie = NULL;
698 	while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) {
699 		if (nvtype == NV_TYPE_STRING) {
700 			const char *type;
701 
702 			if (strncmp(name, "type", sizeof("type") - 1) != 0)
703 				return (EINVAL);
704 			type = nvlist_get_string(newlimits, name);
705 			if (strcmp(type, "ADDR") != 0 &&
706 			    strcmp(type, "NAME") != 0) {
707 				return (EINVAL);
708 			}
709 			if (!dns_allowed_type(oldlimits, type))
710 				return (ENOTCAPABLE);
711 			hastype = true;
712 		} else if (nvtype == NV_TYPE_NUMBER) {
713 			int family;
714 
715 			if (strncmp(name, "family", sizeof("family") - 1) != 0)
716 				return (EINVAL);
717 			family = (int)nvlist_get_number(newlimits, name);
718 			if (!dns_allowed_family(oldlimits, family))
719 				return (ENOTCAPABLE);
720 			hasfamily = true;
721 		} else {
722 			return (EINVAL);
723 		}
724 	}
725 
726 	/*
727 	 * If the new limit doesn't mention type or family we have to
728 	 * check if the current limit does have those. Missing type or
729 	 * family in the limit means that all types or families are
730 	 * allowed.
731 	 */
732 	if (!hastype) {
733 		if (limit_has_entry(oldlimits, "type"))
734 			return (ENOTCAPABLE);
735 	}
736 	if (!hasfamily) {
737 		if (limit_has_entry(oldlimits, "family"))
738 			return (ENOTCAPABLE);
739 	}
740 
741 	return (0);
742 }
743 
744 static int
745 dns_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
746     nvlist_t *nvlout)
747 {
748 	int error;
749 
750 	if (strcmp(cmd, "gethostbyname") == 0)
751 		error = dns_gethostbyname(limits, nvlin, nvlout);
752 	else if (strcmp(cmd, "gethostbyaddr") == 0)
753 		error = dns_gethostbyaddr(limits, nvlin, nvlout);
754 	else if (strcmp(cmd, "getnameinfo") == 0)
755 		error = dns_getnameinfo(limits, nvlin, nvlout);
756 	else if (strcmp(cmd, "getaddrinfo") == 0)
757 		error = dns_getaddrinfo(limits, nvlin, nvlout);
758 	else
759 		error = NO_RECOVERY;
760 
761 	return (error);
762 }
763 
764 CREATE_SERVICE("system.dns", dns_limit, dns_command, 0);
765