Path: blob/main/lib/libcasper/services/cap_net/cap_net.c
48260 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2020 Mariusz Zaborski <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <sys/cdefs.h>28#include <sys/cnv.h>29#include <sys/dnv.h>30#include <sys/nv.h>31#include <sys/socket.h>32#include <netinet/in.h>3334#include <assert.h>35#include <errno.h>36#include <netdb.h>37#include <stdio.h>38#include <string.h>39#include <unistd.h>4041#include <libcasper.h>42#include <libcasper_service.h>4344#include "cap_net.h"4546#define CAPNET_MASK (CAPNET_ADDR2NAME | CAPNET_NAME2ADDR \47CAPNET_DEPRECATED_ADDR2NAME | CAPNET_DEPRECATED_NAME2ADDR | \48CAPNET_CONNECT | CAPNET_BIND | CAPNET_CONNECTDNS)4950/*51* Defines for the names of the limits.52* XXX: we should convert all string constats to this to avoid typos.53*/54#define LIMIT_NV_BIND "bind"55#define LIMIT_NV_CONNECT "connect"56#define LIMIT_NV_ADDR2NAME "addr2name"57#define LIMIT_NV_NAME2ADDR "name2addr"5859struct cap_net_limit {60cap_channel_t *cnl_chan;61uint64_t cnl_mode;62nvlist_t *cnl_addr2name;63nvlist_t *cnl_name2addr;64nvlist_t *cnl_connect;65nvlist_t *cnl_bind;66};6768static struct hostent hent;6970static void71hostent_free(struct hostent *hp)72{73unsigned int ii;7475free(hp->h_name);76hp->h_name = NULL;77if (hp->h_aliases != NULL) {78for (ii = 0; hp->h_aliases[ii] != NULL; ii++)79free(hp->h_aliases[ii]);80free(hp->h_aliases);81hp->h_aliases = NULL;82}83if (hp->h_addr_list != NULL) {84for (ii = 0; hp->h_addr_list[ii] != NULL; ii++)85free(hp->h_addr_list[ii]);86free(hp->h_addr_list);87hp->h_addr_list = NULL;88}89}9091static struct hostent *92hostent_unpack(const nvlist_t *nvl, struct hostent *hp)93{94unsigned int ii, nitems;95char nvlname[64];96int n;9798hostent_free(hp);99100hp->h_name = strdup(nvlist_get_string(nvl, "name"));101if (hp->h_name == NULL)102goto fail;103hp->h_addrtype = (int)nvlist_get_number(nvl, "addrtype");104hp->h_length = (int)nvlist_get_number(nvl, "length");105106nitems = (unsigned int)nvlist_get_number(nvl, "naliases");107hp->h_aliases = calloc(nitems + 1, sizeof(hp->h_aliases[0]));108if (hp->h_aliases == NULL)109goto fail;110for (ii = 0; ii < nitems; ii++) {111n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);112assert(n > 0 && n < (int)sizeof(nvlname));113hp->h_aliases[ii] =114strdup(nvlist_get_string(nvl, nvlname));115if (hp->h_aliases[ii] == NULL)116goto fail;117}118hp->h_aliases[ii] = NULL;119120nitems = (unsigned int)nvlist_get_number(nvl, "naddrs");121hp->h_addr_list = calloc(nitems + 1, sizeof(hp->h_addr_list[0]));122if (hp->h_addr_list == NULL)123goto fail;124for (ii = 0; ii < nitems; ii++) {125hp->h_addr_list[ii] = malloc(hp->h_length);126if (hp->h_addr_list[ii] == NULL)127goto fail;128n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);129assert(n > 0 && n < (int)sizeof(nvlname));130bcopy(nvlist_get_binary(nvl, nvlname, NULL),131hp->h_addr_list[ii], hp->h_length);132}133hp->h_addr_list[ii] = NULL;134135return (hp);136fail:137hostent_free(hp);138h_errno = NO_RECOVERY;139return (NULL);140}141142static int143request_cb(cap_channel_t *chan, const char *name, int s,144const struct sockaddr *saddr, socklen_t len)145{146nvlist_t *nvl;147int serrno;148149nvl = nvlist_create(0);150nvlist_add_string(nvl, "cmd", name);151nvlist_add_descriptor(nvl, "s", s);152nvlist_add_binary(nvl, "saddr", saddr, len);153154nvl = cap_xfer_nvlist(chan, nvl);155if (nvl == NULL)156return (-1);157158if (nvlist_get_number(nvl, "error") != 0) {159serrno = (int)nvlist_get_number(nvl, "error");160nvlist_destroy(nvl);161errno = serrno;162return (-1);163}164165s = dup2(s, nvlist_get_descriptor(nvl, "s"));166nvlist_destroy(nvl);167168return (s == -1 ? -1 : 0);169}170171int172cap_bind(cap_channel_t *chan, int s, const struct sockaddr *addr,173socklen_t addrlen)174{175176return (request_cb(chan, LIMIT_NV_BIND, s, addr, addrlen));177}178179int180cap_connect(cap_channel_t *chan, int s, const struct sockaddr *name,181socklen_t namelen)182{183184return (request_cb(chan, LIMIT_NV_CONNECT, s, name, namelen));185}186187188struct hostent *189cap_gethostbyname(cap_channel_t *chan, const char *name)190{191192return (cap_gethostbyname2(chan, name, AF_INET));193}194195struct hostent *196cap_gethostbyname2(cap_channel_t *chan, const char *name, int af)197{198struct hostent *hp;199nvlist_t *nvl;200201nvl = nvlist_create(0);202nvlist_add_string(nvl, "cmd", "gethostbyname");203nvlist_add_number(nvl, "family", (uint64_t)af);204nvlist_add_string(nvl, "name", name);205nvl = cap_xfer_nvlist(chan, nvl);206if (nvl == NULL) {207h_errno = NO_RECOVERY;208return (NULL);209}210if (nvlist_get_number(nvl, "error") != 0) {211h_errno = (int)nvlist_get_number(nvl, "error");212nvlist_destroy(nvl);213return (NULL);214}215216hp = hostent_unpack(nvl, &hent);217nvlist_destroy(nvl);218return (hp);219}220221struct hostent *222cap_gethostbyaddr(cap_channel_t *chan, const void *addr, socklen_t len,223int af)224{225struct hostent *hp;226nvlist_t *nvl;227228nvl = nvlist_create(0);229nvlist_add_string(nvl, "cmd", "gethostbyaddr");230nvlist_add_binary(nvl, "addr", addr, (size_t)len);231nvlist_add_number(nvl, "family", (uint64_t)af);232nvl = cap_xfer_nvlist(chan, nvl);233if (nvl == NULL) {234h_errno = NO_RECOVERY;235return (NULL);236}237if (nvlist_get_number(nvl, "error") != 0) {238h_errno = (int)nvlist_get_number(nvl, "error");239nvlist_destroy(nvl);240return (NULL);241}242hp = hostent_unpack(nvl, &hent);243nvlist_destroy(nvl);244return (hp);245}246247static struct addrinfo *248addrinfo_unpack(const nvlist_t *nvl)249{250struct addrinfo *ai;251const void *addr;252size_t addrlen;253const char *canonname;254255addr = nvlist_get_binary(nvl, "ai_addr", &addrlen);256ai = malloc(sizeof(*ai) + addrlen);257if (ai == NULL)258return (NULL);259ai->ai_flags = (int)nvlist_get_number(nvl, "ai_flags");260ai->ai_family = (int)nvlist_get_number(nvl, "ai_family");261ai->ai_socktype = (int)nvlist_get_number(nvl, "ai_socktype");262ai->ai_protocol = (int)nvlist_get_number(nvl, "ai_protocol");263ai->ai_addrlen = (socklen_t)addrlen;264canonname = dnvlist_get_string(nvl, "ai_canonname", NULL);265if (canonname != NULL) {266ai->ai_canonname = strdup(canonname);267if (ai->ai_canonname == NULL) {268free(ai);269return (NULL);270}271} else {272ai->ai_canonname = NULL;273}274ai->ai_addr = (void *)(ai + 1);275bcopy(addr, ai->ai_addr, addrlen);276ai->ai_next = NULL;277278return (ai);279}280281int282cap_getaddrinfo(cap_channel_t *chan, const char *hostname, const char *servname,283const struct addrinfo *hints, struct addrinfo **res)284{285struct addrinfo *firstai, *prevai, *curai;286unsigned int ii;287const nvlist_t *nvlai;288char nvlname[64];289nvlist_t *nvl;290int error, serrno, n;291292nvl = nvlist_create(0);293nvlist_add_string(nvl, "cmd", "getaddrinfo");294if (hostname != NULL)295nvlist_add_string(nvl, "hostname", hostname);296if (servname != NULL)297nvlist_add_string(nvl, "servname", servname);298if (hints != NULL) {299nvlist_add_number(nvl, "hints.ai_flags",300(uint64_t)hints->ai_flags);301nvlist_add_number(nvl, "hints.ai_family",302(uint64_t)hints->ai_family);303nvlist_add_number(nvl, "hints.ai_socktype",304(uint64_t)hints->ai_socktype);305nvlist_add_number(nvl, "hints.ai_protocol",306(uint64_t)hints->ai_protocol);307}308nvl = cap_xfer_nvlist(chan, nvl);309if (nvl == NULL)310return (EAI_MEMORY);311if (nvlist_get_number(nvl, "error") != 0) {312error = (int)nvlist_get_number(nvl, "error");313serrno = dnvlist_get_number(nvl, "errno", 0);314nvlist_destroy(nvl);315errno = (error == EAI_SYSTEM) ? serrno : 0;316return (error);317}318319nvlai = NULL;320firstai = prevai = curai = NULL;321for (ii = 0; ; ii++) {322n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);323assert(n > 0 && n < (int)sizeof(nvlname));324if (!nvlist_exists_nvlist(nvl, nvlname))325break;326nvlai = nvlist_get_nvlist(nvl, nvlname);327curai = addrinfo_unpack(nvlai);328if (curai == NULL) {329nvlist_destroy(nvl);330return (EAI_MEMORY);331}332if (prevai != NULL)333prevai->ai_next = curai;334else335firstai = curai;336prevai = curai;337}338nvlist_destroy(nvl);339if (curai == NULL && nvlai != NULL) {340if (firstai == NULL)341freeaddrinfo(firstai);342return (EAI_MEMORY);343}344345*res = firstai;346return (0);347}348349int350cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, socklen_t salen,351char *host, size_t hostlen, char *serv, size_t servlen, int flags)352{353nvlist_t *nvl;354int error, serrno;355356nvl = nvlist_create(0);357nvlist_add_string(nvl, "cmd", "getnameinfo");358nvlist_add_number(nvl, "hostlen", (uint64_t)hostlen);359nvlist_add_number(nvl, "servlen", (uint64_t)servlen);360nvlist_add_binary(nvl, "sa", sa, (size_t)salen);361nvlist_add_number(nvl, "flags", (uint64_t)flags);362nvl = cap_xfer_nvlist(chan, nvl);363if (nvl == NULL)364return (EAI_MEMORY);365if (nvlist_get_number(nvl, "error") != 0) {366error = (int)nvlist_get_number(nvl, "error");367serrno = dnvlist_get_number(nvl, "errno", 0);368nvlist_destroy(nvl);369errno = (error == EAI_SYSTEM) ? serrno : 0;370return (error);371}372373if (host != NULL && nvlist_exists_string(nvl, "host"))374strlcpy(host, nvlist_get_string(nvl, "host"), hostlen);375if (serv != NULL && nvlist_exists_string(nvl, "serv"))376strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen);377nvlist_destroy(nvl);378return (0);379}380381cap_net_limit_t *382cap_net_limit_init(cap_channel_t *chan, uint64_t mode)383{384cap_net_limit_t *limit;385386limit = calloc(1, sizeof(*limit));387if (limit != NULL) {388limit->cnl_mode = mode;389limit->cnl_chan = chan;390limit->cnl_addr2name = nvlist_create(0);391limit->cnl_name2addr = nvlist_create(0);392limit->cnl_connect = nvlist_create(0);393limit->cnl_bind = nvlist_create(0);394}395396return (limit);397}398399static void400pack_limit(nvlist_t *lnvl, const char *name, nvlist_t *limit)401{402403if (!nvlist_empty(limit)) {404nvlist_move_nvlist(lnvl, name, limit);405} else {406nvlist_destroy(limit);407}408}409410int411cap_net_limit(cap_net_limit_t *limit)412{413nvlist_t *lnvl;414cap_channel_t *chan;415416lnvl = nvlist_create(0);417nvlist_add_number(lnvl, "mode", limit->cnl_mode);418419pack_limit(lnvl, LIMIT_NV_ADDR2NAME, limit->cnl_addr2name);420pack_limit(lnvl, LIMIT_NV_NAME2ADDR, limit->cnl_name2addr);421pack_limit(lnvl, LIMIT_NV_CONNECT, limit->cnl_connect);422pack_limit(lnvl, LIMIT_NV_BIND, limit->cnl_bind);423424chan = limit->cnl_chan;425free(limit);426427return (cap_limit_set(chan, lnvl));428}429430void431cap_net_free(cap_net_limit_t *limit)432{433434if (limit == NULL)435return;436437nvlist_destroy(limit->cnl_addr2name);438nvlist_destroy(limit->cnl_name2addr);439nvlist_destroy(limit->cnl_connect);440nvlist_destroy(limit->cnl_bind);441442free(limit);443}444445static void446pack_family(nvlist_t *nvl, int *family, size_t size)447{448size_t i;449450i = 0;451if (!nvlist_exists_number_array(nvl, "family")) {452uint64_t val;453454val = family[0];455nvlist_add_number_array(nvl, "family", &val, 1);456i += 1;457}458459for (; i < size; i++) {460nvlist_append_number_array(nvl, "family", family[i]);461}462}463464static void465pack_sockaddr(nvlist_t *res, const struct sockaddr *sa, socklen_t salen)466{467nvlist_t *nvl;468469if (!nvlist_exists_nvlist(res, "sockaddr")) {470nvl = nvlist_create(NV_FLAG_NO_UNIQUE);471} else {472nvl = nvlist_take_nvlist(res, "sockaddr");473}474475nvlist_add_binary(nvl, "", sa, salen);476nvlist_move_nvlist(res, "sockaddr", nvl);477}478479cap_net_limit_t *480cap_net_limit_addr2name_family(cap_net_limit_t *limit, int *family, size_t size)481{482483pack_family(limit->cnl_addr2name, family, size);484return (limit);485}486487cap_net_limit_t *488cap_net_limit_name2addr_family(cap_net_limit_t *limit, int *family, size_t size)489{490491pack_family(limit->cnl_name2addr, family, size);492return (limit);493}494495cap_net_limit_t *496cap_net_limit_name2addr(cap_net_limit_t *limit, const char *host,497const char *serv)498{499nvlist_t *nvl;500501if (!nvlist_exists_nvlist(limit->cnl_name2addr, "hosts")) {502nvl = nvlist_create(NV_FLAG_NO_UNIQUE);503} else {504nvl = nvlist_take_nvlist(limit->cnl_name2addr, "hosts");505}506507nvlist_add_string(nvl,508host != NULL ? host : "",509serv != NULL ? serv : "");510511nvlist_move_nvlist(limit->cnl_name2addr, "hosts", nvl);512return (limit);513}514515cap_net_limit_t *516cap_net_limit_addr2name(cap_net_limit_t *limit, const struct sockaddr *sa,517socklen_t salen)518{519520pack_sockaddr(limit->cnl_addr2name, sa, salen);521return (limit);522}523524525cap_net_limit_t *526cap_net_limit_connect(cap_net_limit_t *limit, const struct sockaddr *sa,527socklen_t salen)528{529530pack_sockaddr(limit->cnl_connect, sa, salen);531return (limit);532}533534cap_net_limit_t *535cap_net_limit_bind(cap_net_limit_t *limit, const struct sockaddr *sa,536socklen_t salen)537{538539pack_sockaddr(limit->cnl_bind, sa, salen);540return (limit);541}542543/*544* Service functions.545*/546547static nvlist_t *capdnscache;548549static void550net_add_sockaddr_to_cache(struct sockaddr *sa, socklen_t salen, bool deprecated)551{552void *cookie;553554if (capdnscache == NULL) {555capdnscache = nvlist_create(NV_FLAG_NO_UNIQUE);556} else {557/* Lets keep it clean. Look for dups. */558cookie = NULL;559while (nvlist_next(capdnscache, NULL, &cookie) != NULL) {560const void *data;561size_t size;562563assert(cnvlist_type(cookie) == NV_TYPE_BINARY);564565data = cnvlist_get_binary(cookie, &size);566if (salen != size)567continue;568if (memcmp(data, sa, size) == 0)569return;570}571}572573nvlist_add_binary(capdnscache, deprecated ? "d" : "", sa, salen);574}575576static void577net_add_hostent_to_cache(const char *address, size_t asize, int family)578{579580if (family != AF_INET && family != AF_INET6)581return;582583if (family == AF_INET6) {584struct sockaddr_in6 connaddr;585586memset(&connaddr, 0, sizeof(connaddr));587connaddr.sin6_family = AF_INET6;588memcpy((char *)&connaddr.sin6_addr, address, asize);589connaddr.sin6_port = 0;590591net_add_sockaddr_to_cache((struct sockaddr *)&connaddr,592sizeof(connaddr), true);593} else {594struct sockaddr_in connaddr;595596memset(&connaddr, 0, sizeof(connaddr));597connaddr.sin_family = AF_INET;598memcpy((char *)&connaddr.sin_addr.s_addr, address, asize);599connaddr.sin_port = 0;600601net_add_sockaddr_to_cache((struct sockaddr *)&connaddr,602sizeof(connaddr), true);603}604}605606static bool607net_allowed_mode(const nvlist_t *limits, uint64_t mode)608{609610if (limits == NULL)611return (true);612613return ((nvlist_get_number(limits, "mode") & mode) == mode);614}615616static bool617net_allowed_family(const nvlist_t *limits, int family)618{619const uint64_t *allowedfamily;620size_t i, allsize;621622if (limits == NULL)623return (true);624625/* If there are no familes at all, allow any mode. */626if (!nvlist_exists_number_array(limits, "family"))627return (true);628629allowedfamily = nvlist_get_number_array(limits, "family", &allsize);630for (i = 0; i < allsize; i++) {631/* XXX: what with AF_UNSPEC? */632if (allowedfamily[i] == (uint64_t)family) {633return (true);634}635}636637return (false);638}639640static bool641net_allowed_bsaddr_impl(const nvlist_t *salimits, const void *saddr,642size_t saddrsize)643{644void *cookie;645const void *limit;646size_t limitsize;647648cookie = NULL;649while (nvlist_next(salimits, NULL, &cookie) != NULL) {650limit = cnvlist_get_binary(cookie, &limitsize);651652if (limitsize != saddrsize) {653continue;654}655if (memcmp(limit, saddr, limitsize) == 0) {656return (true);657}658659/*660* In case of deprecated version (gethostbyname) we have to661* ignore port, because there is no such info in the hostent.662* Suporting only AF_INET and AF_INET6.663*/664if (strcmp(cnvlist_name(cookie), "d") != 0 ||665(saddrsize != sizeof(struct sockaddr_in) &&666saddrsize != sizeof(struct sockaddr_in6))) {667continue;668}669if (saddrsize == sizeof(struct sockaddr_in)) {670const struct sockaddr_in *saddrptr;671struct sockaddr_in sockaddr;672673saddrptr = (const struct sockaddr_in *)saddr;674memcpy(&sockaddr, limit, sizeof(sockaddr));675sockaddr.sin_port = saddrptr->sin_port;676677if (memcmp(&sockaddr, saddr, saddrsize) == 0) {678return (true);679}680} else if (saddrsize == sizeof(struct sockaddr_in6)) {681const struct sockaddr_in6 *saddrptr;682struct sockaddr_in6 sockaddr;683684saddrptr = (const struct sockaddr_in6 *)saddr;685memcpy(&sockaddr, limit, sizeof(sockaddr));686sockaddr.sin6_port = saddrptr->sin6_port;687688if (memcmp(&sockaddr, saddr, saddrsize) == 0) {689return (true);690}691}692}693694return (false);695}696697static bool698net_allowed_bsaddr(const nvlist_t *limits, const void *saddr, size_t saddrsize)699{700701if (limits == NULL)702return (true);703704if (!nvlist_exists_nvlist(limits, "sockaddr"))705return (true);706707return (net_allowed_bsaddr_impl(nvlist_get_nvlist(limits, "sockaddr"),708saddr, saddrsize));709}710711static bool712net_allowed_hosts(const nvlist_t *limits, const char *name, const char *srvname)713{714void *cookie;715const nvlist_t *hlimits;716const char *testname, *testsrvname;717718if (limits == NULL) {719return (true);720}721722/* If there are no hosts at all, allow any. */723if (!nvlist_exists_nvlist(limits, "hosts")) {724return (true);725}726727cookie = NULL;728testname = (name == NULL ? "" : name);729testsrvname = (srvname == NULL ? "" : srvname);730hlimits = nvlist_get_nvlist(limits, "hosts");731while (nvlist_next(hlimits, NULL, &cookie) != NULL) {732if (strcmp(cnvlist_name(cookie), "") != 0 &&733strcmp(cnvlist_name(cookie), testname) != 0) {734continue;735}736737if (strcmp(cnvlist_get_string(cookie), "") != 0 &&738strcmp(cnvlist_get_string(cookie), testsrvname) != 0) {739continue;740}741742return (true);743}744745return (false);746}747748static void749hostent_pack(const struct hostent *hp, nvlist_t *nvl, bool addtocache)750{751unsigned int ii;752char nvlname[64];753int n;754755nvlist_add_string(nvl, "name", hp->h_name);756nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype);757nvlist_add_number(nvl, "length", (uint64_t)hp->h_length);758759if (hp->h_aliases == NULL) {760nvlist_add_number(nvl, "naliases", 0);761} else {762for (ii = 0; hp->h_aliases[ii] != NULL; ii++) {763n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);764assert(n > 0 && n < (int)sizeof(nvlname));765nvlist_add_string(nvl, nvlname, hp->h_aliases[ii]);766}767nvlist_add_number(nvl, "naliases", (uint64_t)ii);768}769770if (hp->h_addr_list == NULL) {771nvlist_add_number(nvl, "naddrs", 0);772} else {773for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) {774n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);775assert(n > 0 && n < (int)sizeof(nvlname));776nvlist_add_binary(nvl, nvlname, hp->h_addr_list[ii],777(size_t)hp->h_length);778if (addtocache) {779net_add_hostent_to_cache(hp->h_addr_list[ii],780hp->h_length, hp->h_addrtype);781}782}783nvlist_add_number(nvl, "naddrs", (uint64_t)ii);784}785}786787static int788net_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin,789nvlist_t *nvlout)790{791struct hostent *hp;792int family;793const nvlist_t *funclimit;794const char *name;795bool dnscache;796797if (!net_allowed_mode(limits, CAPNET_DEPRECATED_NAME2ADDR))798return (ENOTCAPABLE);799800dnscache = net_allowed_mode(limits, CAPNET_CONNECTDNS);801funclimit = NULL;802if (limits != NULL) {803funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_NAME2ADDR,804NULL);805}806807family = (int)nvlist_get_number(nvlin, "family");808if (!net_allowed_family(funclimit, family))809return (ENOTCAPABLE);810811name = nvlist_get_string(nvlin, "name");812if (!net_allowed_hosts(funclimit, name, ""))813return (ENOTCAPABLE);814815hp = gethostbyname2(name, family);816if (hp == NULL)817return (h_errno);818hostent_pack(hp, nvlout, dnscache);819return (0);820}821822static int823net_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin,824nvlist_t *nvlout)825{826struct hostent *hp;827const void *addr;828size_t addrsize;829int family;830const nvlist_t *funclimit;831832if (!net_allowed_mode(limits, CAPNET_DEPRECATED_ADDR2NAME))833return (ENOTCAPABLE);834835funclimit = NULL;836if (limits != NULL) {837funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_ADDR2NAME,838NULL);839}840841family = (int)nvlist_get_number(nvlin, "family");842if (!net_allowed_family(funclimit, family))843return (ENOTCAPABLE);844845addr = nvlist_get_binary(nvlin, "addr", &addrsize);846if (!net_allowed_bsaddr(funclimit, addr, addrsize))847return (ENOTCAPABLE);848849hp = gethostbyaddr(addr, (socklen_t)addrsize, family);850if (hp == NULL)851return (h_errno);852hostent_pack(hp, nvlout, false);853return (0);854}855856static int857net_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)858{859struct sockaddr_storage sast;860const void *sabin;861char *host, *serv;862size_t sabinsize, hostlen, servlen;863socklen_t salen;864int error, serrno, flags;865const nvlist_t *funclimit;866867host = serv = NULL;868if (!net_allowed_mode(limits, CAPNET_ADDR2NAME)) {869serrno = ENOTCAPABLE;870error = EAI_SYSTEM;871goto out;872}873funclimit = NULL;874if (limits != NULL) {875funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_ADDR2NAME,876NULL);877}878error = 0;879memset(&sast, 0, sizeof(sast));880881hostlen = (size_t)nvlist_get_number(nvlin, "hostlen");882servlen = (size_t)nvlist_get_number(nvlin, "servlen");883884if (hostlen > 0) {885host = calloc(1, hostlen);886if (host == NULL) {887error = EAI_MEMORY;888goto out;889}890}891if (servlen > 0) {892serv = calloc(1, servlen);893if (serv == NULL) {894error = EAI_MEMORY;895goto out;896}897}898899sabin = nvlist_get_binary(nvlin, "sa", &sabinsize);900if (sabinsize > sizeof(sast)) {901error = EAI_FAIL;902goto out;903}904if (!net_allowed_bsaddr(funclimit, sabin, sabinsize)) {905serrno = ENOTCAPABLE;906error = EAI_SYSTEM;907goto out;908}909910memcpy(&sast, sabin, sabinsize);911salen = (socklen_t)sabinsize;912913if ((sast.ss_family != AF_INET ||914salen != sizeof(struct sockaddr_in)) &&915(sast.ss_family != AF_INET6 ||916salen != sizeof(struct sockaddr_in6))) {917error = EAI_FAIL;918goto out;919}920921if (!net_allowed_family(funclimit, (int)sast.ss_family)) {922serrno = ENOTCAPABLE;923error = EAI_SYSTEM;924goto out;925}926927flags = (int)nvlist_get_number(nvlin, "flags");928929error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen,930serv, servlen, flags);931serrno = errno;932if (error != 0)933goto out;934935if (host != NULL)936nvlist_move_string(nvlout, "host", host);937if (serv != NULL)938nvlist_move_string(nvlout, "serv", serv);939out:940if (error != 0) {941free(host);942free(serv);943if (error == EAI_SYSTEM)944nvlist_add_number(nvlout, "errno", serrno);945}946return (error);947}948949static nvlist_t *950addrinfo_pack(const struct addrinfo *ai)951{952nvlist_t *nvl;953954nvl = nvlist_create(0);955nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags);956nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family);957nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype);958nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol);959nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen);960if (ai->ai_canonname != NULL)961nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname);962963return (nvl);964}965966static int967net_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)968{969struct addrinfo hints, *hintsp, *res, *cur;970const char *hostname, *servname;971char nvlname[64];972nvlist_t *elem;973unsigned int ii;974int error, serrno, family, n;975const nvlist_t *funclimit;976bool dnscache;977978if (!net_allowed_mode(limits, CAPNET_NAME2ADDR)) {979serrno = ENOTCAPABLE;980error = EAI_SYSTEM;981goto out;982}983dnscache = net_allowed_mode(limits, CAPNET_CONNECTDNS);984funclimit = NULL;985if (limits != NULL) {986funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_NAME2ADDR,987NULL);988}989990hostname = dnvlist_get_string(nvlin, "hostname", NULL);991servname = dnvlist_get_string(nvlin, "servname", NULL);992if (nvlist_exists_number(nvlin, "hints.ai_flags")) {993hints.ai_flags = (int)nvlist_get_number(nvlin,994"hints.ai_flags");995hints.ai_family = (int)nvlist_get_number(nvlin,996"hints.ai_family");997hints.ai_socktype = (int)nvlist_get_number(nvlin,998"hints.ai_socktype");999hints.ai_protocol = (int)nvlist_get_number(nvlin,1000"hints.ai_protocol");1001hints.ai_addrlen = 0;1002hints.ai_addr = NULL;1003hints.ai_canonname = NULL;1004hints.ai_next = NULL;1005hintsp = &hints;1006family = hints.ai_family;1007} else {1008hintsp = NULL;1009family = AF_UNSPEC;1010}10111012if (!net_allowed_family(funclimit, family)) {1013errno = ENOTCAPABLE;1014error = EAI_SYSTEM;1015goto out;1016}1017if (!net_allowed_hosts(funclimit, hostname, servname)) {1018errno = ENOTCAPABLE;1019error = EAI_SYSTEM;1020goto out;1021}1022error = getaddrinfo(hostname, servname, hintsp, &res);1023serrno = errno;1024if (error != 0) {1025goto out;1026}10271028for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) {1029elem = addrinfo_pack(cur);1030n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);1031assert(n > 0 && n < (int)sizeof(nvlname));1032nvlist_move_nvlist(nvlout, nvlname, elem);1033if (dnscache) {1034net_add_sockaddr_to_cache(cur->ai_addr,1035cur->ai_addrlen, false);1036}1037}10381039freeaddrinfo(res);1040error = 0;1041out:1042if (error == EAI_SYSTEM)1043nvlist_add_number(nvlout, "errno", serrno);1044return (error);1045}10461047static int1048net_bind(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout)1049{1050int socket, serrno;1051const void *saddr;1052size_t len;1053const nvlist_t *funclimit;10541055if (!net_allowed_mode(limits, CAPNET_BIND))1056return (ENOTCAPABLE);1057funclimit = NULL;1058if (limits != NULL)1059funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_BIND, NULL);10601061saddr = nvlist_get_binary(nvlin, "saddr", &len);10621063if (!net_allowed_bsaddr(funclimit, saddr, len))1064return (ENOTCAPABLE);10651066socket = nvlist_take_descriptor(nvlin, "s");1067if (bind(socket, saddr, len) < 0) {1068serrno = errno;1069close(socket);1070return (serrno);1071}10721073nvlist_move_descriptor(nvlout, "s", socket);10741075return (0);1076}10771078static int1079net_connect(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout)1080{1081int socket, serrno;1082const void *saddr;1083const nvlist_t *funclimit;1084size_t len;1085bool conn, conndns, allowed;10861087conn = net_allowed_mode(limits, CAPNET_CONNECT);1088conndns = net_allowed_mode(limits, CAPNET_CONNECTDNS);10891090if (!conn && !conndns)1091return (ENOTCAPABLE);10921093funclimit = NULL;1094if (limits != NULL)1095funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_CONNECT, NULL);10961097saddr = nvlist_get_binary(nvlin, "saddr", &len);1098allowed = false;10991100if (conn && net_allowed_bsaddr(funclimit, saddr, len)) {1101allowed = true;1102}1103if (conndns && capdnscache != NULL &&1104net_allowed_bsaddr_impl(capdnscache, saddr, len)) {1105allowed = true;1106}11071108if (allowed == false) {1109return (ENOTCAPABLE);1110}11111112socket = dup(nvlist_get_descriptor(nvlin, "s"));1113if (connect(socket, saddr, len) < 0) {1114serrno = errno;1115close(socket);1116return (serrno);1117}11181119nvlist_move_descriptor(nvlout, "s", socket);11201121return (0);1122}11231124static bool1125verify_only_sa_newlimts(const nvlist_t *oldfunclimits,1126const nvlist_t *newfunclimit)1127{1128void *cookie;11291130cookie = NULL;1131while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) {1132void *sacookie;11331134if (strcmp(cnvlist_name(cookie), "sockaddr") != 0)1135return (false);11361137if (cnvlist_type(cookie) != NV_TYPE_NVLIST)1138return (false);11391140sacookie = NULL;1141while (nvlist_next(cnvlist_get_nvlist(cookie), NULL,1142&sacookie) != NULL) {1143const void *sa;1144size_t sasize;11451146if (cnvlist_type(sacookie) != NV_TYPE_BINARY)1147return (false);11481149sa = cnvlist_get_binary(sacookie, &sasize);1150if (!net_allowed_bsaddr(oldfunclimits, sa, sasize))1151return (false);1152}1153}11541155return (true);1156}11571158static bool1159verify_bind_newlimts(const nvlist_t *oldlimits,1160const nvlist_t *newfunclimit)1161{1162const nvlist_t *oldfunclimits;11631164oldfunclimits = NULL;1165if (oldlimits != NULL) {1166oldfunclimits = dnvlist_get_nvlist(oldlimits, LIMIT_NV_BIND,1167NULL);1168}11691170return (verify_only_sa_newlimts(oldfunclimits, newfunclimit));1171}117211731174static bool1175verify_connect_newlimits(const nvlist_t *oldlimits,1176const nvlist_t *newfunclimit)1177{1178const nvlist_t *oldfunclimits;11791180oldfunclimits = NULL;1181if (oldlimits != NULL) {1182oldfunclimits = dnvlist_get_nvlist(oldlimits, LIMIT_NV_CONNECT,1183NULL);1184}11851186return (verify_only_sa_newlimts(oldfunclimits, newfunclimit));1187}11881189static bool1190verify_addr2name_newlimits(const nvlist_t *oldlimits,1191const nvlist_t *newfunclimit)1192{1193void *cookie;1194const nvlist_t *oldfunclimits;11951196oldfunclimits = NULL;1197if (oldlimits != NULL) {1198oldfunclimits = dnvlist_get_nvlist(oldlimits,1199LIMIT_NV_ADDR2NAME, NULL);1200}12011202cookie = NULL;1203while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) {1204if (strcmp(cnvlist_name(cookie), "sockaddr") == 0) {1205void *sacookie;12061207if (cnvlist_type(cookie) != NV_TYPE_NVLIST)1208return (false);12091210sacookie = NULL;1211while (nvlist_next(cnvlist_get_nvlist(cookie), NULL,1212&sacookie) != NULL) {1213const void *sa;1214size_t sasize;12151216if (cnvlist_type(sacookie) != NV_TYPE_BINARY)1217return (false);12181219sa = cnvlist_get_binary(sacookie, &sasize);1220if (!net_allowed_bsaddr(oldfunclimits, sa,1221sasize)) {1222return (false);1223}1224}1225} else if (strcmp(cnvlist_name(cookie), "family") == 0) {1226size_t i, sfamilies;1227const uint64_t *families;12281229if (cnvlist_type(cookie) != NV_TYPE_NUMBER_ARRAY)1230return (false);12311232families = cnvlist_get_number_array(cookie, &sfamilies);1233for (i = 0; i < sfamilies; i++) {1234if (!net_allowed_family(oldfunclimits,1235families[i])) {1236return (false);1237}1238}1239} else {1240return (false);1241}1242}12431244return (true);1245}12461247static bool1248verify_name2addr_newlimits(const nvlist_t *oldlimits,1249const nvlist_t *newfunclimit)1250{1251void *cookie;1252const nvlist_t *oldfunclimits;12531254oldfunclimits = NULL;1255if (oldlimits != NULL) {1256oldfunclimits = dnvlist_get_nvlist(oldlimits,1257LIMIT_NV_NAME2ADDR, NULL);1258}12591260cookie = NULL;1261while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) {1262if (strcmp(cnvlist_name(cookie), "hosts") == 0) {1263void *hostcookie;12641265if (cnvlist_type(cookie) != NV_TYPE_NVLIST)1266return (false);12671268hostcookie = NULL;1269while (nvlist_next(cnvlist_get_nvlist(cookie), NULL,1270&hostcookie) != NULL) {1271if (cnvlist_type(hostcookie) != NV_TYPE_STRING)1272return (false);12731274if (!net_allowed_hosts(oldfunclimits,1275cnvlist_name(hostcookie),1276cnvlist_get_string(hostcookie))) {1277return (false);1278}1279}1280} else if (strcmp(cnvlist_name(cookie), "family") == 0) {1281size_t i, sfamilies;1282const uint64_t *families;12831284if (cnvlist_type(cookie) != NV_TYPE_NUMBER_ARRAY)1285return (false);12861287families = cnvlist_get_number_array(cookie, &sfamilies);1288for (i = 0; i < sfamilies; i++) {1289if (!net_allowed_family(oldfunclimits,1290families[i])) {1291return (false);1292}1293}1294} else {1295return (false);1296}1297}12981299return (true);1300}13011302static int1303net_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)1304{1305const char *name;1306void *cookie;1307bool hasmode, hasconnect, hasbind, hasaddr2name, hasname2addr;13081309/*1310* Modes:1311* ADDR2NAME:1312* getnameinfo1313* DEPRECATED_ADDR2NAME:1314* gethostbyaddr1315*1316* NAME2ADDR:1317* getaddrinfo1318* DEPRECATED_NAME2ADDR:1319* gethostbyname1320*1321* Limit scheme:1322* mode : NV_TYPE_NUMBER1323* connect : NV_TYPE_NVLIST1324* sockaddr : NV_TYPE_NVLIST1325* "" : NV_TYPE_BINARY1326* ... : NV_TYPE_BINARY1327* bind : NV_TYPE_NVLIST1328* sockaddr : NV_TYPE_NVLIST1329* "" : NV_TYPE_BINARY1330* ... : NV_TYPE_BINARY1331* addr2name : NV_TYPE_NVLIST1332* family : NV_TYPE_NUMBER_ARRAY1333* sockaddr : NV_TYPE_NVLIST1334* "" : NV_TYPE_BINARY1335* ... : NV_TYPE_BINARY1336* name2addr : NV_TYPE_NVLIST1337* family : NV_TYPE_NUMBER1338* hosts : NV_TYPE_NVLIST1339* host : servname : NV_TYPE_STRING1340*/13411342hasmode = false;1343hasconnect = false;1344hasbind = false;1345hasaddr2name = false;1346hasname2addr = false;13471348cookie = NULL;1349while ((name = nvlist_next(newlimits, NULL, &cookie)) != NULL) {1350if (strcmp(name, "mode") == 0) {1351if (cnvlist_type(cookie) != NV_TYPE_NUMBER) {1352return (NO_RECOVERY);1353}1354if (!net_allowed_mode(oldlimits,1355cnvlist_get_number(cookie))) {1356return (ENOTCAPABLE);1357}1358hasmode = true;1359continue;1360}13611362if (cnvlist_type(cookie) != NV_TYPE_NVLIST) {1363return (NO_RECOVERY);1364}13651366if (strcmp(name, LIMIT_NV_BIND) == 0) {1367hasbind = true;1368if (!verify_bind_newlimts(oldlimits,1369cnvlist_get_nvlist(cookie))) {1370return (ENOTCAPABLE);1371}1372} else if (strcmp(name, LIMIT_NV_CONNECT) == 0) {1373hasconnect = true;1374if (!verify_connect_newlimits(oldlimits,1375cnvlist_get_nvlist(cookie))) {1376return (ENOTCAPABLE);1377}1378} else if (strcmp(name, LIMIT_NV_ADDR2NAME) == 0) {1379hasaddr2name = true;1380if (!verify_addr2name_newlimits(oldlimits,1381cnvlist_get_nvlist(cookie))) {1382return (ENOTCAPABLE);1383}1384} else if (strcmp(name, LIMIT_NV_NAME2ADDR) == 0) {1385hasname2addr = true;1386if (!verify_name2addr_newlimits(oldlimits,1387cnvlist_get_nvlist(cookie))) {1388return (ENOTCAPABLE);1389}1390}1391}13921393/* Mode is required. */1394if (!hasmode)1395return (ENOTCAPABLE);13961397/*1398* If the new limit doesn't mention mode or family we have to1399* check if the current limit does have those. Missing mode or1400* family in the limit means that all modes or families are1401* allowed.1402*/1403if (oldlimits == NULL)1404return (0);1405if (!hasbind && nvlist_exists(oldlimits, LIMIT_NV_BIND))1406return (ENOTCAPABLE);1407if (!hasconnect && nvlist_exists(oldlimits, LIMIT_NV_CONNECT))1408return (ENOTCAPABLE);1409if (!hasaddr2name && nvlist_exists(oldlimits, LIMIT_NV_ADDR2NAME))1410return (ENOTCAPABLE);1411if (!hasname2addr && nvlist_exists(oldlimits, LIMIT_NV_NAME2ADDR))1412return (ENOTCAPABLE);1413return (0);1414}14151416static int1417net_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,1418nvlist_t *nvlout)1419{14201421if (strcmp(cmd, "bind") == 0)1422return (net_bind(limits, nvlin, nvlout));1423else if (strcmp(cmd, "connect") == 0)1424return (net_connect(limits, nvlin, nvlout));1425else if (strcmp(cmd, "gethostbyname") == 0)1426return (net_gethostbyname(limits, nvlin, nvlout));1427else if (strcmp(cmd, "gethostbyaddr") == 0)1428return (net_gethostbyaddr(limits, nvlin, nvlout));1429else if (strcmp(cmd, "getnameinfo") == 0)1430return (net_getnameinfo(limits, nvlin, nvlout));1431else if (strcmp(cmd, "getaddrinfo") == 0)1432return (net_getaddrinfo(limits, nvlin, nvlout));14331434return (EINVAL);1435}14361437CREATE_SERVICE("system.net", net_limit, net_command, 0);143814391440