#include <sys/capsicum.h>
#include <sys/nv.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libcasper.h>
#include <casper/cap_dns.h>
#include <atf-c.h>
#define GETHOSTBYNAME 0x01
#define GETHOSTBYNAME2_AF_INET 0x02
#define GETHOSTBYNAME2_AF_INET6 0x04
#define GETHOSTBYADDR_AF_INET 0x08
#define GETHOSTBYADDR_AF_INET6 0x10
#define GETADDRINFO_AF_UNSPEC 0x20
#define GETADDRINFO_AF_INET 0x40
#define GETADDRINFO_AF_INET6 0x80
static bool
addrinfo_compare(struct addrinfo *ai0, struct addrinfo *ai1)
{
struct addrinfo *at0, *at1;
if (ai0 == NULL && ai1 == NULL)
return (true);
if (ai0 == NULL || ai1 == NULL)
return (false);
at0 = ai0;
at1 = ai1;
while (true) {
if ((at0->ai_flags == at1->ai_flags) &&
(at0->ai_family == at1->ai_family) &&
(at0->ai_socktype == at1->ai_socktype) &&
(at0->ai_protocol == at1->ai_protocol) &&
(at0->ai_addrlen == at1->ai_addrlen) &&
(memcmp(at0->ai_addr, at1->ai_addr,
at0->ai_addrlen) == 0)) {
if (at0->ai_canonname != NULL &&
at1->ai_canonname != NULL) {
if (strcmp(at0->ai_canonname,
at1->ai_canonname) != 0) {
return (false);
}
}
if (at0->ai_canonname == NULL &&
at1->ai_canonname != NULL) {
return (false);
}
if (at0->ai_canonname != NULL &&
at1->ai_canonname == NULL) {
return (false);
}
if (at0->ai_next == NULL && at1->ai_next == NULL)
return (true);
if (at0->ai_next == NULL || at1->ai_next == NULL)
return (false);
at0 = at0->ai_next;
at1 = at1->ai_next;
} else {
return (false);
}
}
fprintf(stderr, "Dead code reached in addrinfo_compare()\n");
exit(1);
}
static bool
hostent_aliases_compare(char **aliases0, char **aliases1)
{
int i0, i1;
if (aliases0 == NULL && aliases1 == NULL)
return (true);
if (aliases0 == NULL || aliases1 == NULL)
return (false);
for (i0 = 0; aliases0[i0] != NULL; i0++) {
for (i1 = 0; aliases1[i1] != NULL; i1++) {
if (strcmp(aliases0[i0], aliases1[i1]) == 0)
break;
}
if (aliases1[i1] == NULL)
return (false);
}
return (true);
}
static bool
hostent_addr_list_compare(char **addr_list0, char **addr_list1, int length)
{
int i0, i1;
if (addr_list0 == NULL && addr_list1 == NULL)
return (true);
if (addr_list0 == NULL || addr_list1 == NULL)
return (false);
for (i0 = 0; addr_list0[i0] != NULL; i0++) {
for (i1 = 0; addr_list1[i1] != NULL; i1++) {
if (memcmp(addr_list0[i0], addr_list1[i1], length) == 0)
break;
}
if (addr_list1[i1] == NULL)
return (false);
}
return (true);
}
static bool
hostent_compare(const struct hostent *hp0, const struct hostent *hp1)
{
if (hp0 == NULL && hp1 != NULL)
return (true);
if (hp0 == NULL || hp1 == NULL)
return (false);
if (hp0->h_name != NULL || hp1->h_name != NULL) {
if (hp0->h_name == NULL || hp1->h_name == NULL)
return (false);
if (strcmp(hp0->h_name, hp1->h_name) != 0)
return (false);
}
if (!hostent_aliases_compare(hp0->h_aliases, hp1->h_aliases))
return (false);
if (!hostent_aliases_compare(hp1->h_aliases, hp0->h_aliases))
return (false);
if (hp0->h_addrtype != hp1->h_addrtype)
return (false);
if (hp0->h_length != hp1->h_length)
return (false);
if (!hostent_addr_list_compare(hp0->h_addr_list, hp1->h_addr_list,
hp0->h_length)) {
return (false);
}
if (!hostent_addr_list_compare(hp1->h_addr_list, hp0->h_addr_list,
hp0->h_length)) {
return (false);
}
return (true);
}
static void
runtest(cap_channel_t *capdns, unsigned int expected)
{
unsigned int result;
struct addrinfo *ais, *aic, hints, *hintsp;
struct hostent *hps, *hpc;
struct in_addr ip4;
struct in6_addr ip6;
int caperr, syserr;
result = 0;
hps = gethostbyname("example.com");
if (hps == NULL) {
fprintf(stderr, "Unable to resolve %s IPv4.\n", "example.com");
} else {
hpc = cap_gethostbyname(capdns, "example.com");
if (hostent_compare(hps, hpc))
result |= GETHOSTBYNAME;
}
hps = gethostbyname2("example.com", AF_INET);
if (hps == NULL) {
fprintf(stderr, "Unable to resolve %s IPv4.\n", "example.com");
} else {
hpc = cap_gethostbyname2(capdns, "example.com", AF_INET);
if (hostent_compare(hps, hpc))
result |= GETHOSTBYNAME2_AF_INET;
}
hps = gethostbyname2("example.com", AF_INET6);
if (hps == NULL) {
fprintf(stderr, "Unable to resolve %s IPv6.\n", "example.com");
} else {
hpc = cap_gethostbyname2(capdns, "example.com", AF_INET6);
if (hostent_compare(hps, hpc))
result |= GETHOSTBYNAME2_AF_INET6;
}
hints.ai_flags = 0;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = 0;
hints.ai_protocol = 0;
hints.ai_addrlen = 0;
hints.ai_addr = NULL;
hints.ai_canonname = NULL;
hints.ai_next = NULL;
hintsp = &hints;
syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais);
if (syserr != 0) {
fprintf(stderr,
"Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n",
gai_strerror(syserr));
} else {
caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp,
&aic);
if (caperr == 0) {
if (addrinfo_compare(ais, aic))
result |= GETADDRINFO_AF_UNSPEC;
freeaddrinfo(ais);
freeaddrinfo(aic);
}
}
hints.ai_family = AF_INET;
syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais);
if (syserr != 0) {
fprintf(stderr,
"Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n",
gai_strerror(syserr));
} else {
caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp,
&aic);
if (caperr == 0) {
if (addrinfo_compare(ais, aic))
result |= GETADDRINFO_AF_INET;
freeaddrinfo(ais);
freeaddrinfo(aic);
}
}
hints.ai_family = AF_INET6;
syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais);
if (syserr != 0) {
fprintf(stderr,
"Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n",
gai_strerror(syserr));
} else {
caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp,
&aic);
if (caperr == 0) {
if (addrinfo_compare(ais, aic))
result |= GETADDRINFO_AF_INET6;
freeaddrinfo(ais);
freeaddrinfo(aic);
}
}
#define GOOGLE_DNS_IPV4 "8.8.8.8"
#define GOOGLE_DNS_IPV6 "2001:4860:4860::8888"
inet_pton(AF_INET, GOOGLE_DNS_IPV4, &ip4);
hps = gethostbyaddr(&ip4, sizeof(ip4), AF_INET);
if (hps == NULL) {
fprintf(stderr, "Unable to resolve %s.\n", GOOGLE_DNS_IPV4);
} else {
hpc = cap_gethostbyaddr(capdns, &ip4, sizeof(ip4), AF_INET);
if (hostent_compare(hps, hpc))
result |= GETHOSTBYADDR_AF_INET;
}
inet_pton(AF_INET6, GOOGLE_DNS_IPV6, &ip6);
hps = gethostbyaddr(&ip6, sizeof(ip6), AF_INET6);
if (hps == NULL) {
fprintf(stderr, "Unable to resolve %s.\n", GOOGLE_DNS_IPV6);
} else {
hpc = cap_gethostbyaddr(capdns, &ip6, sizeof(ip6), AF_INET6);
if (hostent_compare(hps, hpc)) {
caperr = h_errno;
result |= GETHOSTBYADDR_AF_INET6;
}
}
ATF_REQUIRE_MSG(result == expected,
"expected 0x%x, got 0x%x", expected, result);
}
static cap_channel_t *
cap_dns_init(void)
{
cap_channel_t *capcas, *capdns;
capcas = cap_init();
ATF_REQUIRE(capcas != NULL);
capdns = cap_service_open(capcas, "system.dns");
ATF_REQUIRE(capdns != NULL);
cap_close(capcas);
return (capdns);
}
ATF_TC(dns_no_limits);
ATF_TC_HEAD(dns_no_limits, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_no_limits, tc)
{
cap_channel_t *capdns;
capdns = cap_dns_init();
runtest(capdns,
(GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 |
GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6 |
GETADDRINFO_AF_UNSPEC | GETADDRINFO_AF_INET |
GETADDRINFO_AF_INET6));
cap_close(capdns);
}
ATF_TC(dns_all_limits);
ATF_TC_HEAD(dns_all_limits, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_all_limits, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET;
families[1] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, NULL, 0) == -1);
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, NULL, 0) == -1);
runtest(capdns,
(GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 |
GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6 |
GETADDRINFO_AF_INET | GETADDRINFO_AF_INET6));
cap_close(capdns);
}
ATF_TC(dns_name_limit);
ATF_TC_HEAD(dns_name_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_name_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
types[1] = "ADDR2NAME";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 2) == -1);
types[0] = "ADDR2NAME";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 1) == -1);
families[0] = AF_INET;
families[1] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
runtest(capdns,
(GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 |
GETADDRINFO_AF_INET | GETADDRINFO_AF_INET6));
cap_close(capdns);
}
ATF_TC(dns_addr_limit);
ATF_TC_HEAD(dns_addr_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_addr_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
types[1] = "NAME2ADDR";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 2) == -1);
types[0] = "NAME2ADDR";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 1) == -1);
families[0] = AF_INET;
families[1] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
runtest(capdns,
(GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6));
cap_close(capdns);
}
ATF_TC(dns_inet_limit);
ATF_TC_HEAD(dns_inet_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_inet_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
families[1] = AF_INET6;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 2) == -1);
families[0] = AF_INET6;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 1) == -1);
runtest(capdns,
(GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYADDR_AF_INET |
GETADDRINFO_AF_INET));
cap_close(capdns);
}
ATF_TC(dns_inet6_limit);
ATF_TC_HEAD(dns_inet6_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_inet6_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
families[1] = AF_INET;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 2) == -1);
families[0] = AF_INET;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 1) == -1);
runtest(capdns,
(GETHOSTBYNAME2_AF_INET6 | GETHOSTBYADDR_AF_INET6 |
GETADDRINFO_AF_INET6));
cap_close(capdns);
}
ATF_TC(dns_name_inet_limit);
ATF_TC_HEAD(dns_name_inet_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_name_inet_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET;
families[1] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
types[0] = "NAME2ADDR";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
types[1] = "ADDR2NAME";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 2) == -1);
types[0] = "ADDR2NAME";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 1) == -1);
families[0] = AF_INET;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
families[1] = AF_INET6;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 2) == -1);
families[0] = AF_INET6;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 1) == -1);
runtest(capdns,
(GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETADDRINFO_AF_INET));
cap_close(capdns);
}
ATF_TC(dns_name_inet6_limit);
ATF_TC_HEAD(dns_name_inet6_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_name_inet6_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET6;
families[1] = AF_INET;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
types[0] = "NAME2ADDR";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
types[1] = "ADDR2NAME";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 2) == -1);
types[0] = "ADDR2NAME";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 1) == -1);
families[0] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
families[1] = AF_INET;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 2) == -1);
families[0] = AF_INET;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 1) == -1);
runtest(capdns,
(GETHOSTBYNAME2_AF_INET6 | GETADDRINFO_AF_INET6));
cap_close(capdns);
}
ATF_TC(dns_addr_inet_limit);
ATF_TC_HEAD(dns_addr_inet_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_addr_inet_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET;
families[1] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
types[0] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
types[1] = "NAME2ADDR";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 2) == -1);
types[0] = "NAME2ADDR";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 1) == -1);
families[0] = AF_INET;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
families[1] = AF_INET6;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 2) == -1);
families[0] = AF_INET6;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 1) == -1);
runtest(capdns, GETHOSTBYADDR_AF_INET);
cap_close(capdns);
}
ATF_TC(dns_addr_inet6_limit);
ATF_TC_HEAD(dns_addr_inet6_limit, tc)
{
atf_tc_set_md_var(tc, "require.config", "allow_network_access");
}
ATF_TC_BODY(dns_addr_inet6_limit, tc)
{
cap_channel_t *capdns;
const char *types[2];
int families[2];
capdns = cap_dns_init();
types[0] = "NAME2ADDR";
types[1] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
families[0] = AF_INET6;
families[1] = AF_INET;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
types[0] = "ADDR2NAME";
ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
types[1] = "NAME2ADDR";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 2) == -1);
types[0] = "NAME2ADDR";
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_type_limit(capdns, types, 1) == -1);
families[0] = AF_INET6;
ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
families[1] = AF_INET;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 2) == -1);
families[0] = AF_INET;
ATF_REQUIRE_ERRNO(ENOTCAPABLE,
cap_dns_family_limit(capdns, families, 1) == -1);
runtest(capdns, GETHOSTBYADDR_AF_INET6);
cap_close(capdns);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, dns_no_limits);
ATF_TP_ADD_TC(tp, dns_all_limits);
ATF_TP_ADD_TC(tp, dns_name_limit);
ATF_TP_ADD_TC(tp, dns_addr_limit);
ATF_TP_ADD_TC(tp, dns_inet_limit);
ATF_TP_ADD_TC(tp, dns_inet6_limit);
ATF_TP_ADD_TC(tp, dns_name_inet_limit);
ATF_TP_ADD_TC(tp, dns_name_inet6_limit);
ATF_TP_ADD_TC(tp, dns_addr_inet_limit);
ATF_TP_ADD_TC(tp, dns_addr_inet6_limit);
return atf_no_error();
}