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