/* $NetBSD: ssu.c,v 1.9 2024/09/22 00:14:06 christos Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SSUTABLEMAGIC ISC_MAGIC('S', 'S', 'U', 'T') #define VALID_SSUTABLE(table) ISC_MAGIC_VALID(table, SSUTABLEMAGIC) #define SSURULEMAGIC ISC_MAGIC('S', 'S', 'U', 'R') #define VALID_SSURULE(table) ISC_MAGIC_VALID(table, SSURULEMAGIC) struct dns_ssurule { unsigned int magic; bool grant; /*%< is this a grant or a deny? */ dns_ssumatchtype_t matchtype; /*%< which type of pattern match? */ dns_name_t *identity; /*%< the identity to match */ dns_name_t *name; /*%< the name being updated */ unsigned int ntypes; /*%< number of data types covered */ dns_ssuruletype_t *types; /*%< the data types. Can include */ /* ANY. if NULL, defaults to all */ /* types except SIG, SOA, and NS */ ISC_LINK(dns_ssurule_t) link; }; struct dns_ssutable { unsigned int magic; isc_mem_t *mctx; isc_refcount_t references; dns_dlzdb_t *dlzdatabase; ISC_LIST(dns_ssurule_t) rules; }; void dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **tablep) { dns_ssutable_t *table; REQUIRE(tablep != NULL && *tablep == NULL); REQUIRE(mctx != NULL); table = isc_mem_get(mctx, sizeof(dns_ssutable_t)); isc_refcount_init(&table->references, 1); table->mctx = NULL; isc_mem_attach(mctx, &table->mctx); ISC_LIST_INIT(table->rules); table->magic = SSUTABLEMAGIC; *tablep = table; } static void destroy(dns_ssutable_t *table) { isc_mem_t *mctx; REQUIRE(VALID_SSUTABLE(table)); mctx = table->mctx; while (!ISC_LIST_EMPTY(table->rules)) { dns_ssurule_t *rule = ISC_LIST_HEAD(table->rules); if (rule->identity != NULL) { dns_name_free(rule->identity, mctx); isc_mem_put(mctx, rule->identity, sizeof(*rule->identity)); } if (rule->name != NULL) { dns_name_free(rule->name, mctx); isc_mem_put(mctx, rule->name, sizeof(*rule->name)); } if (rule->types != NULL) { isc_mem_put(mctx, rule->types, rule->ntypes * sizeof(*rule->types)); } ISC_LIST_UNLINK(table->rules, rule, link); rule->magic = 0; isc_mem_put(mctx, rule, sizeof(dns_ssurule_t)); } isc_refcount_destroy(&table->references); table->magic = 0; isc_mem_putanddetach(&table->mctx, table, sizeof(dns_ssutable_t)); } void dns_ssutable_attach(dns_ssutable_t *source, dns_ssutable_t **targetp) { REQUIRE(VALID_SSUTABLE(source)); REQUIRE(targetp != NULL && *targetp == NULL); isc_refcount_increment(&source->references); *targetp = source; } void dns_ssutable_detach(dns_ssutable_t **tablep) { dns_ssutable_t *table; REQUIRE(tablep != NULL); table = *tablep; *tablep = NULL; REQUIRE(VALID_SSUTABLE(table)); if (isc_refcount_decrement(&table->references) == 1) { destroy(table); } } void dns_ssutable_addrule(dns_ssutable_t *table, bool grant, const dns_name_t *identity, dns_ssumatchtype_t matchtype, const dns_name_t *name, unsigned int ntypes, dns_ssuruletype_t *types) { dns_ssurule_t *rule; isc_mem_t *mctx; REQUIRE(VALID_SSUTABLE(table)); REQUIRE(dns_name_isabsolute(identity)); REQUIRE(dns_name_isabsolute(name)); REQUIRE(matchtype <= dns_ssumatchtype_max); if (matchtype == dns_ssumatchtype_wildcard) { REQUIRE(dns_name_iswildcard(name)); } if (ntypes > 0) { REQUIRE(types != NULL); } mctx = table->mctx; rule = isc_mem_get(mctx, sizeof(*rule)); rule->identity = NULL; rule->name = NULL; rule->types = NULL; rule->grant = grant; rule->identity = isc_mem_get(mctx, sizeof(*rule->identity)); dns_name_init(rule->identity, NULL); dns_name_dup(identity, mctx, rule->identity); rule->name = isc_mem_get(mctx, sizeof(*rule->name)); dns_name_init(rule->name, NULL); dns_name_dup(name, mctx, rule->name); rule->matchtype = matchtype; rule->ntypes = ntypes; if (ntypes > 0) { rule->types = isc_mem_get(mctx, ntypes * sizeof(*rule->types)); memmove(rule->types, types, ntypes * sizeof(*rule->types)); } else { rule->types = NULL; } rule->magic = SSURULEMAGIC; ISC_LIST_INITANDAPPEND(table->rules, rule, link); } static bool isusertype(dns_rdatatype_t type) { return (type != dns_rdatatype_ns && type != dns_rdatatype_soa && type != dns_rdatatype_rrsig); } static void reverse_from_address(dns_name_t *tcpself, const isc_netaddr_t *tcpaddr) { char buf[16 * 4 + sizeof("IP6.ARPA.")]; isc_result_t result; const unsigned char *ap; isc_buffer_t b; unsigned long l; switch (tcpaddr->family) { case AF_INET: l = ntohl(tcpaddr->type.in.s_addr); result = snprintf(buf, sizeof(buf), "%lu.%lu.%lu.%lu.IN-ADDR.ARPA.", (l >> 0) & 0xff, (l >> 8) & 0xff, (l >> 16) & 0xff, (l >> 24) & 0xff); RUNTIME_CHECK(result < sizeof(buf)); break; case AF_INET6: ap = tcpaddr->type.in6.s6_addr; result = snprintf( buf, sizeof(buf), "%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x." "IP6.ARPA.", ap[15] & 0x0f, (ap[15] >> 4) & 0x0f, ap[14] & 0x0f, (ap[14] >> 4) & 0x0f, ap[13] & 0x0f, (ap[13] >> 4) & 0x0f, ap[12] & 0x0f, (ap[12] >> 4) & 0x0f, ap[11] & 0x0f, (ap[11] >> 4) & 0x0f, ap[10] & 0x0f, (ap[10] >> 4) & 0x0f, ap[9] & 0x0f, (ap[9] >> 4) & 0x0f, ap[8] & 0x0f, (ap[8] >> 4) & 0x0f, ap[7] & 0x0f, (ap[7] >> 4) & 0x0f, ap[6] & 0x0f, (ap[6] >> 4) & 0x0f, ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f, (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f, ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f, (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f); RUNTIME_CHECK(result < sizeof(buf)); break; default: UNREACHABLE(); } isc_buffer_init(&b, buf, strlen(buf)); isc_buffer_add(&b, strlen(buf)); result = dns_name_fromtext(tcpself, &b, dns_rootname, 0, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); } static void stf_from_address(dns_name_t *stfself, const isc_netaddr_t *tcpaddr) { char buf[sizeof("X.X.X.X.Y.Y.Y.Y.2.0.0.2.IP6.ARPA.")]; isc_result_t result; const unsigned char *ap; isc_buffer_t b; unsigned long l; switch (tcpaddr->family) { case AF_INET: l = ntohl(tcpaddr->type.in.s_addr); result = snprintf( buf, sizeof(buf), "%lx.%lx.%lx.%lx.%lx.%lx.%lx.%lx.2.0.0.2.IP6.ARPA.", l & 0xf, (l >> 4) & 0xf, (l >> 8) & 0xf, (l >> 12) & 0xf, (l >> 16) & 0xf, (l >> 20) & 0xf, (l >> 24) & 0xf, (l >> 28) & 0xf); RUNTIME_CHECK(result < sizeof(buf)); break; case AF_INET6: ap = tcpaddr->type.in6.s6_addr; result = snprintf( buf, sizeof(buf), "%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.IP6.ARPA.", ap[5] & 0x0f, (ap[5] >> 4) & 0x0f, ap[4] & 0x0f, (ap[4] >> 4) & 0x0f, ap[3] & 0x0f, (ap[3] >> 4) & 0x0f, ap[2] & 0x0f, (ap[2] >> 4) & 0x0f, ap[1] & 0x0f, (ap[1] >> 4) & 0x0f, ap[0] & 0x0f, (ap[0] >> 4) & 0x0f); RUNTIME_CHECK(result < sizeof(buf)); break; default: UNREACHABLE(); } isc_buffer_init(&b, buf, strlen(buf)); isc_buffer_add(&b, strlen(buf)); result = dns_name_fromtext(stfself, &b, dns_rootname, 0, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); } bool dns_ssutable_checkrules(dns_ssutable_t *table, const dns_name_t *signer, const dns_name_t *name, const isc_netaddr_t *addr, bool tcp, dns_aclenv_t *env, dns_rdatatype_t type, const dns_name_t *target, const dst_key_t *key, const dns_ssurule_t **rulep) { dns_fixedname_t fixed; dns_name_t *stfself; dns_name_t *tcpself; dns_name_t *wildcard; dns_ssurule_t *rule; const dns_name_t *tname; int match; isc_result_t result; unsigned int i; REQUIRE(VALID_SSUTABLE(table)); REQUIRE(signer == NULL || dns_name_isabsolute(signer)); REQUIRE(dns_name_isabsolute(name)); REQUIRE(addr == NULL || env != NULL); if (signer == NULL && addr == NULL) { return (false); } for (rule = ISC_LIST_HEAD(table->rules); rule != NULL; rule = ISC_LIST_NEXT(rule, link)) { switch (rule->matchtype) { case dns_ssumatchtype_local: case dns_ssumatchtype_name: case dns_ssumatchtype_self: case dns_ssumatchtype_selfsub: case dns_ssumatchtype_selfwild: case dns_ssumatchtype_subdomain: case dns_ssumatchtype_wildcard: if (signer == NULL) { continue; } if (dns_name_iswildcard(rule->identity)) { if (!dns_name_matcheswildcard(signer, rule->identity)) { continue; } } else { if (!dns_name_equal(signer, rule->identity)) { continue; } } break; case dns_ssumatchtype_selfkrb5: case dns_ssumatchtype_selfms: case dns_ssumatchtype_selfsubkrb5: case dns_ssumatchtype_selfsubms: case dns_ssumatchtype_subdomainkrb5: case dns_ssumatchtype_subdomainms: case dns_ssumatchtype_subdomainselfkrb5rhs: case dns_ssumatchtype_subdomainselfmsrhs: if (signer == NULL) { continue; } break; case dns_ssumatchtype_tcpself: case dns_ssumatchtype_6to4self: if (!tcp || addr == NULL) { continue; } break; case dns_ssumatchtype_external: case dns_ssumatchtype_dlz: break; } switch (rule->matchtype) { case dns_ssumatchtype_name: if (!dns_name_equal(name, rule->name)) { continue; } break; case dns_ssumatchtype_subdomain: if (!dns_name_issubdomain(name, rule->name)) { continue; } break; case dns_ssumatchtype_local: if (addr == NULL) { continue; } if (!dns_name_issubdomain(name, rule->name)) { continue; } RWLOCK(&env->rwlock, isc_rwlocktype_read); dns_acl_match(addr, NULL, env->localhost, NULL, &match, NULL); RWUNLOCK(&env->rwlock, isc_rwlocktype_read); if (match == 0) { if (signer != NULL) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_SSU, ISC_LOG_WARNING, "update-policy local: " "match on session " "key not from " "localhost"); } continue; } break; case dns_ssumatchtype_wildcard: if (!dns_name_matcheswildcard(name, rule->name)) { continue; } break; case dns_ssumatchtype_self: if (!dns_name_equal(signer, name)) { continue; } break; case dns_ssumatchtype_selfsub: if (!dns_name_issubdomain(name, signer)) { continue; } break; case dns_ssumatchtype_selfwild: wildcard = dns_fixedname_initname(&fixed); result = dns_name_concatenate(dns_wildcardname, signer, wildcard, NULL); if (result != ISC_R_SUCCESS) { continue; } if (!dns_name_matcheswildcard(name, wildcard)) { continue; } break; case dns_ssumatchtype_selfkrb5: if (dst_gssapi_identitymatchesrealmkrb5( signer, name, rule->identity, false)) { break; } continue; case dns_ssumatchtype_selfms: if (dst_gssapi_identitymatchesrealmms( signer, name, rule->identity, false)) { break; } continue; case dns_ssumatchtype_selfsubkrb5: if (dst_gssapi_identitymatchesrealmkrb5( signer, name, rule->identity, true)) { break; } continue; case dns_ssumatchtype_selfsubms: if (dst_gssapi_identitymatchesrealmms( signer, name, rule->identity, true)) { break; } continue; case dns_ssumatchtype_subdomainkrb5: case dns_ssumatchtype_subdomainselfkrb5rhs: if (!dns_name_issubdomain(name, rule->name)) { continue; } tname = NULL; switch (rule->matchtype) { case dns_ssumatchtype_subdomainselfkrb5rhs: if (type == dns_rdatatype_ptr) { tname = target; } if (type == dns_rdatatype_srv) { tname = target; } break; default: break; } if (dst_gssapi_identitymatchesrealmkrb5( signer, tname, rule->identity, false)) { break; } continue; case dns_ssumatchtype_subdomainms: case dns_ssumatchtype_subdomainselfmsrhs: if (!dns_name_issubdomain(name, rule->name)) { continue; } tname = NULL; switch (rule->matchtype) { case dns_ssumatchtype_subdomainselfmsrhs: if (type == dns_rdatatype_ptr) { tname = target; } if (type == dns_rdatatype_srv) { tname = target; } break; default: break; } if (dst_gssapi_identitymatchesrealmms( signer, tname, rule->identity, false)) { break; } continue; case dns_ssumatchtype_tcpself: tcpself = dns_fixedname_initname(&fixed); reverse_from_address(tcpself, addr); if (dns_name_iswildcard(rule->identity)) { if (!dns_name_matcheswildcard(tcpself, rule->identity)) { continue; } } else { if (!dns_name_equal(tcpself, rule->identity)) { continue; } } if (!dns_name_equal(tcpself, name)) { continue; } break; case dns_ssumatchtype_6to4self: stfself = dns_fixedname_initname(&fixed); stf_from_address(stfself, addr); if (dns_name_iswildcard(rule->identity)) { if (!dns_name_matcheswildcard(stfself, rule->identity)) { continue; } } else { if (!dns_name_equal(stfself, rule->identity)) { continue; } } if (!dns_name_equal(stfself, name)) { continue; } break; case dns_ssumatchtype_external: if (!dns_ssu_external_match(rule->identity, signer, name, addr, type, key, table->mctx)) { continue; } break; case dns_ssumatchtype_dlz: if (!dns_dlz_ssumatch(table->dlzdatabase, signer, name, addr, type, key)) { continue; } break; } if (rule->ntypes == 0) { /* * If this is a DLZ rule, then the DLZ ssu * checks will have already checked the type. */ if (rule->matchtype != dns_ssumatchtype_dlz && !isusertype(type)) { continue; } } else { for (i = 0; i < rule->ntypes; i++) { if (rule->types[i].type == dns_rdatatype_any || rule->types[i].type == type) { break; } } if (i == rule->ntypes) { continue; } } if (rule->grant && rulep != NULL) { *rulep = rule; } return (rule->grant); } return (false); } bool dns_ssurule_isgrant(const dns_ssurule_t *rule) { REQUIRE(VALID_SSURULE(rule)); return (rule->grant); } dns_name_t * dns_ssurule_identity(const dns_ssurule_t *rule) { REQUIRE(VALID_SSURULE(rule)); return (rule->identity); } unsigned int dns_ssurule_matchtype(const dns_ssurule_t *rule) { REQUIRE(VALID_SSURULE(rule)); return (rule->matchtype); } dns_name_t * dns_ssurule_name(const dns_ssurule_t *rule) { REQUIRE(VALID_SSURULE(rule)); return (rule->name); } unsigned int dns_ssurule_types(const dns_ssurule_t *rule, dns_ssuruletype_t **types) { REQUIRE(VALID_SSURULE(rule)); REQUIRE(types != NULL && *types != NULL); *types = rule->types; return (rule->ntypes); } unsigned int dns_ssurule_max(const dns_ssurule_t *rule, dns_rdatatype_t type) { unsigned int i; unsigned int max = 0; REQUIRE(VALID_SSURULE(rule)); for (i = 0; i < rule->ntypes; i++) { if (rule->types[i].type == dns_rdatatype_any) { max = rule->types[i].max; } if (rule->types[i].type == type) { return (rule->types[i].max); } } return (max); } isc_result_t dns_ssutable_firstrule(const dns_ssutable_t *table, dns_ssurule_t **rule) { REQUIRE(VALID_SSUTABLE(table)); REQUIRE(rule != NULL && *rule == NULL); *rule = ISC_LIST_HEAD(table->rules); return (*rule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE); } isc_result_t dns_ssutable_nextrule(dns_ssurule_t *rule, dns_ssurule_t **nextrule) { REQUIRE(VALID_SSURULE(rule)); REQUIRE(nextrule != NULL && *nextrule == NULL); *nextrule = ISC_LIST_NEXT(rule, link); return (*nextrule != NULL ? ISC_R_SUCCESS : ISC_R_NOMORE); } /* * Create a specialised SSU table that points at an external DLZ database */ void dns_ssutable_createdlz(isc_mem_t *mctx, dns_ssutable_t **tablep, dns_dlzdb_t *dlzdatabase) { dns_ssurule_t *rule; dns_ssutable_t *table = NULL; REQUIRE(tablep != NULL && *tablep == NULL); dns_ssutable_create(mctx, &table); table->dlzdatabase = dlzdatabase; rule = isc_mem_get(table->mctx, sizeof(dns_ssurule_t)); rule->identity = NULL; rule->name = NULL; rule->grant = true; rule->matchtype = dns_ssumatchtype_dlz; rule->ntypes = 0; rule->types = NULL; rule->magic = SSURULEMAGIC; ISC_LIST_INITANDAPPEND(table->rules, rule, link); *tablep = table; } isc_result_t dns_ssu_mtypefromstring(const char *str, dns_ssumatchtype_t *mtype) { REQUIRE(str != NULL); REQUIRE(mtype != NULL); if (strcasecmp(str, "name") == 0) { *mtype = dns_ssumatchtype_name; } else if (strcasecmp(str, "subdomain") == 0) { *mtype = dns_ssumatchtype_subdomain; } else if (strcasecmp(str, "wildcard") == 0) { *mtype = dns_ssumatchtype_wildcard; } else if (strcasecmp(str, "self") == 0) { *mtype = dns_ssumatchtype_self; } else if (strcasecmp(str, "selfsub") == 0) { *mtype = dns_ssumatchtype_selfsub; } else if (strcasecmp(str, "selfwild") == 0) { *mtype = dns_ssumatchtype_selfwild; } else if (strcasecmp(str, "ms-self") == 0) { *mtype = dns_ssumatchtype_selfms; } else if (strcasecmp(str, "ms-selfsub") == 0) { *mtype = dns_ssumatchtype_selfsubms; } else if (strcasecmp(str, "krb5-self") == 0) { *mtype = dns_ssumatchtype_selfkrb5; } else if (strcasecmp(str, "krb5-selfsub") == 0) { *mtype = dns_ssumatchtype_selfsubkrb5; } else if (strcasecmp(str, "ms-subdomain") == 0) { *mtype = dns_ssumatchtype_subdomainms; } else if (strcasecmp(str, "ms-subdomain-self-rhs") == 0) { *mtype = dns_ssumatchtype_subdomainselfmsrhs; } else if (strcasecmp(str, "krb5-subdomain") == 0) { *mtype = dns_ssumatchtype_subdomainkrb5; } else if (strcasecmp(str, "krb5-subdomain-self-rhs") == 0) { *mtype = dns_ssumatchtype_subdomainselfkrb5rhs; } else if (strcasecmp(str, "tcp-self") == 0) { *mtype = dns_ssumatchtype_tcpself; } else if (strcasecmp(str, "6to4-self") == 0) { *mtype = dns_ssumatchtype_6to4self; } else if (strcasecmp(str, "zonesub") == 0) { *mtype = dns_ssumatchtype_subdomain; } else if (strcasecmp(str, "external") == 0) { *mtype = dns_ssumatchtype_external; } else { return (ISC_R_NOTFOUND); } return (ISC_R_SUCCESS); }