xref: /freebsd/lib/libcasper/services/cap_dns/cap_dns.c (revision 9f44a47fd07924afc035991af15d84e6585dea4f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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);
306 	if (serv != NULL && nvlist_exists_string(nvl, "serv"))
307 		strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen);
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, "NAME2ADDR") &&
478 	    !dns_allowed_type(limits, "NAME"))
479 		return (NO_RECOVERY);
480 
481 	family = (int)nvlist_get_number(nvlin, "family");
482 
483 	if (!dns_allowed_family(limits, family))
484 		return (NO_RECOVERY);
485 
486 	hp = gethostbyname2(nvlist_get_string(nvlin, "name"), family);
487 	if (hp == NULL)
488 		return (h_errno);
489 	hostent_pack(hp, nvlout);
490 	return (0);
491 }
492 
493 static int
494 dns_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin,
495     nvlist_t *nvlout)
496 {
497 	struct hostent *hp;
498 	const void *addr;
499 	size_t addrsize;
500 	int family;
501 
502 	if (!dns_allowed_type(limits, "ADDR2NAME") &&
503 	    !dns_allowed_type(limits, "ADDR"))
504 		return (NO_RECOVERY);
505 
506 	family = (int)nvlist_get_number(nvlin, "family");
507 
508 	if (!dns_allowed_family(limits, family))
509 		return (NO_RECOVERY);
510 
511 	addr = nvlist_get_binary(nvlin, "addr", &addrsize);
512 	hp = gethostbyaddr(addr, (socklen_t)addrsize, family);
513 	if (hp == NULL)
514 		return (h_errno);
515 	hostent_pack(hp, nvlout);
516 	return (0);
517 }
518 
519 static int
520 dns_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
521 {
522 	struct sockaddr_storage sast;
523 	const void *sabin;
524 	char *host, *serv;
525 	size_t sabinsize, hostlen, servlen;
526 	socklen_t salen;
527 	int error, flags;
528 
529 	if (!dns_allowed_type(limits, "ADDR2NAME") &&
530 	    !dns_allowed_type(limits, "ADDR"))
531 		return (NO_RECOVERY);
532 
533 	error = 0;
534 	host = serv = NULL;
535 	memset(&sast, 0, sizeof(sast));
536 
537 	hostlen = (size_t)nvlist_get_number(nvlin, "hostlen");
538 	servlen = (size_t)nvlist_get_number(nvlin, "servlen");
539 
540 	if (hostlen > 0) {
541 		host = calloc(1, hostlen);
542 		if (host == NULL) {
543 			error = EAI_MEMORY;
544 			goto out;
545 		}
546 	}
547 	if (servlen > 0) {
548 		serv = calloc(1, servlen);
549 		if (serv == NULL) {
550 			error = EAI_MEMORY;
551 			goto out;
552 		}
553 	}
554 
555 	sabin = nvlist_get_binary(nvlin, "sa", &sabinsize);
556 	if (sabinsize > sizeof(sast)) {
557 		error = EAI_FAIL;
558 		goto out;
559 	}
560 
561 	memcpy(&sast, sabin, sabinsize);
562 	salen = (socklen_t)sabinsize;
563 
564 	if ((sast.ss_family != AF_INET ||
565 	     salen != sizeof(struct sockaddr_in)) &&
566 	    (sast.ss_family != AF_INET6 ||
567 	     salen != sizeof(struct sockaddr_in6))) {
568 		error = EAI_FAIL;
569 		goto out;
570 	}
571 
572 	if (!dns_allowed_family(limits, (int)sast.ss_family)) {
573 		error = NO_RECOVERY;
574 		goto out;
575 	}
576 
577 	flags = (int)nvlist_get_number(nvlin, "flags");
578 
579 	error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen,
580 	    serv, servlen, flags);
581 	if (error != 0)
582 		goto out;
583 
584 	if (host != NULL)
585 		nvlist_move_string(nvlout, "host", host);
586 	if (serv != NULL)
587 		nvlist_move_string(nvlout, "serv", serv);
588 out:
589 	if (error != 0) {
590 		free(host);
591 		free(serv);
592 	}
593 	return (error);
594 }
595 
596 static nvlist_t *
597 addrinfo_pack(const struct addrinfo *ai)
598 {
599 	nvlist_t *nvl;
600 
601 	nvl = nvlist_create(0);
602 	nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags);
603 	nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family);
604 	nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype);
605 	nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol);
606 	nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen);
607 	if (ai->ai_canonname != NULL)
608 		nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname);
609 
610 	return (nvl);
611 }
612 
613 static int
614 dns_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
615 {
616 	struct addrinfo hints, *hintsp, *res, *cur;
617 	const char *hostname, *servname;
618 	char nvlname[64];
619 	nvlist_t *elem;
620 	unsigned int ii;
621 	int error, family, n;
622 
623 	if (!dns_allowed_type(limits, "NAME2ADDR") &&
624 	    !dns_allowed_type(limits, "NAME"))
625 		return (NO_RECOVERY);
626 
627 	hostname = dnvlist_get_string(nvlin, "hostname", NULL);
628 	servname = dnvlist_get_string(nvlin, "servname", NULL);
629 	if (nvlist_exists_number(nvlin, "hints.ai_flags")) {
630 		hints.ai_flags = (int)nvlist_get_number(nvlin,
631 		    "hints.ai_flags");
632 		hints.ai_family = (int)nvlist_get_number(nvlin,
633 		    "hints.ai_family");
634 		hints.ai_socktype = (int)nvlist_get_number(nvlin,
635 		    "hints.ai_socktype");
636 		hints.ai_protocol = (int)nvlist_get_number(nvlin,
637 		    "hints.ai_protocol");
638 		hints.ai_addrlen = 0;
639 		hints.ai_addr = NULL;
640 		hints.ai_canonname = NULL;
641 		hints.ai_next = NULL;
642 		hintsp = &hints;
643 		family = hints.ai_family;
644 	} else {
645 		hintsp = NULL;
646 		family = AF_UNSPEC;
647 	}
648 
649 	if (!dns_allowed_family(limits, family))
650 		return (NO_RECOVERY);
651 
652 	error = getaddrinfo(hostname, servname, hintsp, &res);
653 	if (error != 0)
654 		goto out;
655 
656 	for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) {
657 		elem = addrinfo_pack(cur);
658 		n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
659 		assert(n > 0 && n < (int)sizeof(nvlname));
660 		nvlist_move_nvlist(nvlout, nvlname, elem);
661 	}
662 
663 	freeaddrinfo(res);
664 	error = 0;
665 out:
666 	return (error);
667 }
668 
669 static bool
670 limit_has_entry(const nvlist_t *limits, const char *prefix)
671 {
672 	const char *name;
673 	size_t prefixlen;
674 	void *cookie;
675 
676 	if (limits == NULL)
677 		return (false);
678 
679 	prefixlen = strlen(prefix);
680 
681 	cookie = NULL;
682 	while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
683 		if (strncmp(name, prefix, prefixlen) == 0)
684 			return (true);
685 	}
686 
687 	return (false);
688 }
689 
690 static int
691 dns_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
692 {
693 	const char *name;
694 	void *cookie;
695 	int nvtype;
696 	bool hastype, hasfamily;
697 
698 	hastype = false;
699 	hasfamily = false;
700 
701 	cookie = NULL;
702 	while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) {
703 		if (nvtype == NV_TYPE_STRING) {
704 			const char *type;
705 
706 			if (strncmp(name, "type", sizeof("type") - 1) != 0)
707 				return (EINVAL);
708 			type = nvlist_get_string(newlimits, name);
709 			if (strcmp(type, "ADDR2NAME") != 0 &&
710 			    strcmp(type, "NAME2ADDR") != 0 &&
711 			    strcmp(type, "ADDR") != 0 &&
712 			    strcmp(type, "NAME") != 0) {
713 				return (EINVAL);
714 			}
715 			if (!dns_allowed_type(oldlimits, type))
716 				return (ENOTCAPABLE);
717 			hastype = true;
718 		} else if (nvtype == NV_TYPE_NUMBER) {
719 			int family;
720 
721 			if (strncmp(name, "family", sizeof("family") - 1) != 0)
722 				return (EINVAL);
723 			family = (int)nvlist_get_number(newlimits, name);
724 			if (!dns_allowed_family(oldlimits, family))
725 				return (ENOTCAPABLE);
726 			hasfamily = true;
727 		} else {
728 			return (EINVAL);
729 		}
730 	}
731 
732 	/*
733 	 * If the new limit doesn't mention type or family we have to
734 	 * check if the current limit does have those. Missing type or
735 	 * family in the limit means that all types or families are
736 	 * allowed.
737 	 */
738 	if (!hastype) {
739 		if (limit_has_entry(oldlimits, "type"))
740 			return (ENOTCAPABLE);
741 	}
742 	if (!hasfamily) {
743 		if (limit_has_entry(oldlimits, "family"))
744 			return (ENOTCAPABLE);
745 	}
746 
747 	return (0);
748 }
749 
750 static int
751 dns_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
752     nvlist_t *nvlout)
753 {
754 	int error;
755 
756 	if (strcmp(cmd, "gethostbyname") == 0)
757 		error = dns_gethostbyname(limits, nvlin, nvlout);
758 	else if (strcmp(cmd, "gethostbyaddr") == 0)
759 		error = dns_gethostbyaddr(limits, nvlin, nvlout);
760 	else if (strcmp(cmd, "getnameinfo") == 0)
761 		error = dns_getnameinfo(limits, nvlin, nvlout);
762 	else if (strcmp(cmd, "getaddrinfo") == 0)
763 		error = dns_getaddrinfo(limits, nvlin, nvlout);
764 	else
765 		error = NO_RECOVERY;
766 
767 	return (error);
768 }
769 
770 CREATE_SERVICE("system.dns", dns_limit, dns_command, 0);
771