summaryrefslogtreecommitdiff
path: root/usr.sbin/unbound/services
diff options
context:
space:
mode:
authorStuart Henderson <sthen@cvs.openbsd.org>2022-02-23 11:57:28 +0000
committerStuart Henderson <sthen@cvs.openbsd.org>2022-02-23 11:57:28 +0000
commit7f2b33bc0f33b673ae4a555e8a7ece541b1be052 (patch)
treedbe57efaa2ff0add14f1d6e92cbee8fff550d627 /usr.sbin/unbound/services
parent7540d33e7a06e95fc1c12998181ea1b973836e32 (diff)
update to unbound 1.15.0, ok florian@
Diffstat (limited to 'usr.sbin/unbound/services')
-rw-r--r--usr.sbin/unbound/services/outbound_list.h2
-rw-r--r--usr.sbin/unbound/services/rpz.c1755
-rw-r--r--usr.sbin/unbound/services/rpz.h56
3 files changed, 1590 insertions, 223 deletions
diff --git a/usr.sbin/unbound/services/outbound_list.h b/usr.sbin/unbound/services/outbound_list.h
index ad59e42d192..73c137d5099 100644
--- a/usr.sbin/unbound/services/outbound_list.h
+++ b/usr.sbin/unbound/services/outbound_list.h
@@ -79,7 +79,7 @@ void outbound_list_init(struct outbound_list* list);
* Clear the user owner outbound list structure.
* Deletes serviced queries.
* @param list: the list structure. It is cleared, but the list struct itself
- * is callers responsability to delete.
+ * is callers responsibility to delete.
*/
void outbound_list_clear(struct outbound_list* list);
diff --git a/usr.sbin/unbound/services/rpz.c b/usr.sbin/unbound/services/rpz.c
index 3a1ec00d7d3..322e9d1393c 100644
--- a/usr.sbin/unbound/services/rpz.c
+++ b/usr.sbin/unbound/services/rpz.c
@@ -50,45 +50,50 @@
#include "util/data/dname.h"
#include "util/locks.h"
#include "util/regional.h"
+#include "util/data/msgencode.h"
+#include "services/cache/dns.h"
+#include "iterator/iterator.h"
+#include "iterator/iter_delegpt.h"
+#include "daemon/worker.h"
+
+typedef struct resp_addr rpz_aclnode_type;
+
+struct matched_delegation_point {
+ uint8_t* dname;
+ size_t dname_len;
+};
/** string for RPZ action enum */
const char*
rpz_action_to_string(enum rpz_action a)
{
switch(a) {
- case RPZ_NXDOMAIN_ACTION: return "nxdomain";
- case RPZ_NODATA_ACTION: return "nodata";
- case RPZ_PASSTHRU_ACTION: return "passthru";
- case RPZ_DROP_ACTION: return "drop";
- case RPZ_TCP_ONLY_ACTION: return "tcp_only";
- case RPZ_INVALID_ACTION: return "invalid";
- case RPZ_LOCAL_DATA_ACTION: return "local_data";
- case RPZ_DISABLED_ACTION: return "disabled";
- case RPZ_CNAME_OVERRIDE_ACTION: return "cname_override";
- case RPZ_NO_OVERRIDE_ACTION: return "no_override";
+ case RPZ_NXDOMAIN_ACTION: return "rpz-nxdomain";
+ case RPZ_NODATA_ACTION: return "rpz-nodata";
+ case RPZ_PASSTHRU_ACTION: return "rpz-passthru";
+ case RPZ_DROP_ACTION: return "rpz-drop";
+ case RPZ_TCP_ONLY_ACTION: return "rpz-tcp-only";
+ case RPZ_INVALID_ACTION: return "rpz-invalid";
+ case RPZ_LOCAL_DATA_ACTION: return "rpz-local-data";
+ case RPZ_DISABLED_ACTION: return "rpz-disabled";
+ case RPZ_CNAME_OVERRIDE_ACTION: return "rpz-cname-override";
+ case RPZ_NO_OVERRIDE_ACTION: return "rpz-no-override";
+ default: return "rpz-unknown-action";
}
- return "unknown";
}
/** RPZ action enum for config string */
static enum rpz_action
rpz_config_to_action(char* a)
{
- if(strcmp(a, "nxdomain") == 0)
- return RPZ_NXDOMAIN_ACTION;
- else if(strcmp(a, "nodata") == 0)
- return RPZ_NODATA_ACTION;
- else if(strcmp(a, "passthru") == 0)
- return RPZ_PASSTHRU_ACTION;
- else if(strcmp(a, "drop") == 0)
- return RPZ_DROP_ACTION;
- else if(strcmp(a, "tcp_only") == 0)
- return RPZ_TCP_ONLY_ACTION;
- else if(strcmp(a, "cname") == 0)
- return RPZ_CNAME_OVERRIDE_ACTION;
- else if(strcmp(a, "disabled") == 0)
- return RPZ_DISABLED_ACTION;
- return RPZ_INVALID_ACTION;
+ if(strcmp(a, "nxdomain") == 0) return RPZ_NXDOMAIN_ACTION;
+ else if(strcmp(a, "nodata") == 0) return RPZ_NODATA_ACTION;
+ else if(strcmp(a, "passthru") == 0) return RPZ_PASSTHRU_ACTION;
+ else if(strcmp(a, "drop") == 0) return RPZ_DROP_ACTION;
+ else if(strcmp(a, "tcp_only") == 0) return RPZ_TCP_ONLY_ACTION;
+ else if(strcmp(a, "cname") == 0) return RPZ_CNAME_OVERRIDE_ACTION;
+ else if(strcmp(a, "disabled") == 0) return RPZ_DISABLED_ACTION;
+ else return RPZ_INVALID_ACTION;
}
/** string for RPZ trigger enum */
@@ -96,14 +101,14 @@ static const char*
rpz_trigger_to_string(enum rpz_trigger r)
{
switch(r) {
- case RPZ_QNAME_TRIGGER: return "qname";
- case RPZ_CLIENT_IP_TRIGGER: return "client_ip";
- case RPZ_RESPONSE_IP_TRIGGER: return "response_ip";
- case RPZ_NSDNAME_TRIGGER: return "nsdname";
- case RPZ_NSIP_TRIGGER: return "nsip";
- case RPZ_INVALID_TRIGGER: return "invalid";
+ case RPZ_QNAME_TRIGGER: return "rpz-qname";
+ case RPZ_CLIENT_IP_TRIGGER: return "rpz-client-ip";
+ case RPZ_RESPONSE_IP_TRIGGER: return "rpz-response-ip";
+ case RPZ_NSDNAME_TRIGGER: return "rpz-nsdname";
+ case RPZ_NSIP_TRIGGER: return "rpz-nsip";
+ case RPZ_INVALID_TRIGGER: return "rpz-invalid";
+ default: return "rpz-unknown-trigger";
}
- return "unknown";
}
/**
@@ -138,6 +143,31 @@ get_tld_label(uint8_t* dname, size_t maxdnamelen)
}
/**
+ * The RR types that are to be ignored.
+ * DNSSEC RRs at the apex, and SOA and NS are ignored.
+ */
+static int
+rpz_type_ignored(uint16_t rr_type)
+{
+ switch(rr_type) {
+ case LDNS_RR_TYPE_SOA:
+ case LDNS_RR_TYPE_NS:
+ case LDNS_RR_TYPE_DNAME:
+ /* all DNSSEC-related RRs must be ignored */
+ case LDNS_RR_TYPE_DNSKEY:
+ case LDNS_RR_TYPE_DS:
+ case LDNS_RR_TYPE_RRSIG:
+ case LDNS_RR_TYPE_NSEC:
+ case LDNS_RR_TYPE_NSEC3:
+ case LDNS_RR_TYPE_NSEC3PARAM:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/**
* Classify RPZ action for RR type/rdata
* @param rr_type: the RR type
* @param rdatawl: RDATA with 2 bytes length
@@ -208,15 +238,15 @@ static enum localzone_type
rpz_action_to_localzone_type(enum rpz_action a)
{
switch(a) {
- case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain;
- case RPZ_NODATA_ACTION: return local_zone_always_nodata;
- case RPZ_DROP_ACTION: return local_zone_always_deny;
- case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent;
+ case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain;
+ case RPZ_NODATA_ACTION: return local_zone_always_nodata;
+ case RPZ_DROP_ACTION: return local_zone_always_deny;
+ case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent;
case RPZ_LOCAL_DATA_ACTION: /* fallthrough */
case RPZ_CNAME_OVERRIDE_ACTION: return local_zone_redirect;
- case RPZ_INVALID_ACTION: /* fallthrough */
- case RPZ_TCP_ONLY_ACTION: /* fallthrough */
- default: return local_zone_invalid;
+ case RPZ_TCP_ONLY_ACTION: return local_zone_truncate;
+ case RPZ_INVALID_ACTION: /* fallthrough */
+ default: return local_zone_invalid;
}
}
@@ -224,15 +254,15 @@ enum respip_action
rpz_action_to_respip_action(enum rpz_action a)
{
switch(a) {
- case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain;
- case RPZ_NODATA_ACTION: return respip_always_nodata;
- case RPZ_DROP_ACTION: return respip_always_deny;
- case RPZ_PASSTHRU_ACTION: return respip_always_transparent;
- case RPZ_LOCAL_DATA_ACTION: /* fallthrough */
+ case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain;
+ case RPZ_NODATA_ACTION: return respip_always_nodata;
+ case RPZ_DROP_ACTION: return respip_always_deny;
+ case RPZ_PASSTHRU_ACTION: return respip_always_transparent;
+ case RPZ_LOCAL_DATA_ACTION: /* fallthrough */
case RPZ_CNAME_OVERRIDE_ACTION: return respip_redirect;
- case RPZ_INVALID_ACTION: /* fallthrough */
- case RPZ_TCP_ONLY_ACTION: /* fallthrough */
- default: return respip_invalid;
+ case RPZ_TCP_ONLY_ACTION: return respip_truncate;
+ case RPZ_INVALID_ACTION: /* fallthrough */
+ default: return respip_invalid;
}
}
@@ -240,14 +270,14 @@ static enum rpz_action
localzone_type_to_rpz_action(enum localzone_type lzt)
{
switch(lzt) {
- case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION;
- case local_zone_always_nodata: return RPZ_NODATA_ACTION;
- case local_zone_always_deny: return RPZ_DROP_ACTION;
- case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION;
- case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION;
- case local_zone_invalid:
- default:
- return RPZ_INVALID_ACTION;
+ case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION;
+ case local_zone_always_nodata: return RPZ_NODATA_ACTION;
+ case local_zone_always_deny: return RPZ_DROP_ACTION;
+ case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION;
+ case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION;
+ case local_zone_truncate: return RPZ_TCP_ONLY_ACTION;
+ case local_zone_invalid: /* fallthrough */
+ default: return RPZ_INVALID_ACTION;
}
}
@@ -255,14 +285,14 @@ enum rpz_action
respip_action_to_rpz_action(enum respip_action a)
{
switch(a) {
- case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION;
- case respip_always_nodata: return RPZ_NODATA_ACTION;
- case respip_always_deny: return RPZ_DROP_ACTION;
- case respip_always_transparent: return RPZ_PASSTHRU_ACTION;
- case respip_redirect: return RPZ_LOCAL_DATA_ACTION;
- case respip_invalid:
- default:
- return RPZ_INVALID_ACTION;
+ case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION;
+ case respip_always_nodata: return RPZ_NODATA_ACTION;
+ case respip_always_deny: return RPZ_DROP_ACTION;
+ case respip_always_transparent: return RPZ_PASSTHRU_ACTION;
+ case respip_redirect: return RPZ_LOCAL_DATA_ACTION;
+ case respip_truncate: return RPZ_TCP_ONLY_ACTION;
+ case respip_invalid: /* fallthrough */
+ default: return RPZ_INVALID_ACTION;
}
}
@@ -298,12 +328,55 @@ rpz_dname_to_trigger(uint8_t* dname, size_t dname_len)
return RPZ_QNAME_TRIGGER;
}
-void rpz_delete(struct rpz* r)
+static inline struct clientip_synthesized_rrset*
+rpz_clientip_synthesized_set_create(void)
+{
+ struct clientip_synthesized_rrset* set = calloc(1, sizeof(*set));
+ if(set == NULL) {
+ return NULL;
+ }
+ set->region = regional_create();
+ if(set->region == NULL) {
+ free(set);
+ return NULL;
+ }
+ addr_tree_init(&set->entries);
+ lock_rw_init(&set->lock);
+ return set;
+}
+
+static void
+rpz_clientip_synthesized_rr_delete(rbnode_type* n, void* ATTR_UNUSED(arg))
+{
+ struct clientip_synthesized_rr* r = (struct clientip_synthesized_rr*)n->key;
+ lock_rw_destroy(&r->lock);
+#ifdef THREADS_DISABLED
+ (void)r;
+#endif
+}
+
+static inline void
+rpz_clientip_synthesized_set_delete(struct clientip_synthesized_rrset* set)
+{
+ if(set == NULL) {
+ return;
+ }
+ lock_rw_destroy(&set->lock);
+ traverse_postorder(&set->entries, rpz_clientip_synthesized_rr_delete, NULL);
+ regional_destroy(set->region);
+ free(set);
+}
+
+void
+rpz_delete(struct rpz* r)
{
if(!r)
return;
local_zones_delete(r->local_zones);
+ local_zones_delete(r->nsdname_zones);
respip_set_delete(r->respip_set);
+ rpz_clientip_synthesized_set_delete(r->client_set);
+ rpz_clientip_synthesized_set_delete(r->ns_set);
regional_destroy(r->region);
free(r->taglist);
free(r->log_name);
@@ -315,13 +388,31 @@ rpz_clear(struct rpz* r)
{
/* must hold write lock on auth_zone */
local_zones_delete(r->local_zones);
+ r->local_zones = NULL;
+ local_zones_delete(r->nsdname_zones);
+ r->nsdname_zones = NULL;
respip_set_delete(r->respip_set);
+ r->respip_set = NULL;
+ rpz_clientip_synthesized_set_delete(r->client_set);
+ r->client_set = NULL;
+ rpz_clientip_synthesized_set_delete(r->ns_set);
+ r->ns_set = NULL;
if(!(r->local_zones = local_zones_create())){
return 0;
}
+ r->nsdname_zones = local_zones_create();
+ if(r->nsdname_zones == NULL) {
+ return 0;
+ }
if(!(r->respip_set = respip_set_create())) {
return 0;
}
+ if(!(r->client_set = rpz_clientip_synthesized_set_create())) {
+ return 0;
+ }
+ if(!(r->ns_set = rpz_clientip_synthesized_set_create())) {
+ return 0;
+ }
return 1;
}
@@ -331,6 +422,14 @@ rpz_finish_config(struct rpz* r)
lock_rw_wrlock(&r->respip_set->lock);
addr_tree_init_parents(&r->respip_set->ip_tree);
lock_rw_unlock(&r->respip_set->lock);
+
+ lock_rw_wrlock(&r->client_set->lock);
+ addr_tree_init_parents(&r->client_set->entries);
+ lock_rw_unlock(&r->client_set->lock);
+
+ lock_rw_wrlock(&r->ns_set->lock);
+ addr_tree_init_parents(&r->ns_set->entries);
+ lock_rw_unlock(&r->ns_set->lock);
}
/** new rrset containing CNAME override, does not yet contain a dname */
@@ -394,9 +493,26 @@ rpz_create(struct config_auth* p)
if(!(r->local_zones = local_zones_create())){
goto err;
}
+
+ r->nsdname_zones = local_zones_create();
+ if(r->local_zones == NULL){
+ goto err;
+ }
+
if(!(r->respip_set = respip_set_create())) {
goto err;
}
+
+ r->client_set = rpz_clientip_synthesized_set_create();
+ if(r->client_set == NULL) {
+ goto err;
+ }
+
+ r->ns_set = rpz_clientip_synthesized_set_create();
+ if(r->ns_set == NULL) {
+ goto err;
+ }
+
r->taglistlen = p->rpz_taglistlen;
r->taglist = memdup(p->rpz_taglist, r->taglistlen);
if(p->rpz_action_override) {
@@ -426,6 +542,7 @@ rpz_create(struct config_auth* p)
}
}
r->log = p->rpz_log;
+ r->signal_nxdomain_ra = p->rpz_signal_nxdomain_ra;
if(p->rpz_log_name) {
if(!(r->log_name = strdup(p->rpz_log_name))) {
log_err("malloc failure on RPZ log_name strdup");
@@ -437,8 +554,14 @@ err:
if(r) {
if(r->local_zones)
local_zones_delete(r->local_zones);
+ if(r->nsdname_zones)
+ local_zones_delete(r->nsdname_zones);
if(r->respip_set)
respip_set_delete(r->respip_set);
+ if(r->client_set != NULL)
+ rpz_clientip_synthesized_set_delete(r->client_set);
+ if(r->ns_set != NULL)
+ rpz_clientip_synthesized_set_delete(r->ns_set);
if(r->taglist)
free(r->taglist);
if(r->region)
@@ -467,19 +590,17 @@ strip_dname_origin(uint8_t* dname, size_t dnamelen, size_t originlen,
return newdnamelen + 1; /* + 1 for root label */
}
-/** Insert RR into RPZ's local-zone */
static void
-rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
- enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
- uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname,
+ size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass,
+ uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
{
struct local_zone* z;
enum localzone_type tp = local_zone_always_transparent;
int dnamelabs = dname_count_labels(dname);
- char* rrstr;
int newzone = 0;
- if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION) {
+ if(a == RPZ_INVALID_ACTION) {
char str[255+1];
if(rrtype == LDNS_RR_TYPE_SOA || rrtype == LDNS_RR_TYPE_NS ||
rrtype == LDNS_RR_TYPE_DNAME ||
@@ -499,110 +620,394 @@ rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
return;
}
- lock_rw_wrlock(&r->local_zones->lock);
+ lock_rw_wrlock(&lz->lock);
/* exact match */
- z = local_zones_find(r->local_zones, dname, dnamelen, dnamelabs,
- LDNS_RR_CLASS_IN);
- if(z && a != RPZ_LOCAL_DATA_ACTION) {
- rrstr = sldns_wire2str_rr(rr, rr_len);
- if(!rrstr) {
- log_err("malloc error while inserting RPZ qname "
- "trigger");
+ z = local_zones_find(lz, dname, dnamelen, dnamelabs, LDNS_RR_CLASS_IN);
+ if(z != NULL && a != RPZ_LOCAL_DATA_ACTION) {
+ char* rrstr = sldns_wire2str_rr(rr, rr_len);
+ if(rrstr == NULL) {
+ log_err("malloc error while inserting rpz nsdname trigger");
free(dname);
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&lz->lock);
return;
}
- verbose(VERB_ALGO, "RPZ: skipping duplicate record: '%s'",
- rrstr);
+ if(rrstr[0])
+ rrstr[strlen(rrstr)-1]=0; /* remove newline */
+ verbose(VERB_ALGO, "rpz: skipping duplicate record: '%s'", rrstr);
free(rrstr);
free(dname);
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&lz->lock);
return;
}
- if(!z) {
+ if(z == NULL) {
tp = rpz_action_to_localzone_type(a);
- if(!(z = local_zones_add_zone(r->local_zones, dname, dnamelen,
- dnamelabs, rrclass, tp))) {
- log_warn("RPZ create failed");
- lock_rw_unlock(&r->local_zones->lock);
+ z = local_zones_add_zone(lz, dname, dnamelen,
+ dnamelabs, rrclass, tp);
+ if(z == NULL) {
+ log_warn("rpz: create failed");
+ lock_rw_unlock(&lz->lock);
/* dname will be free'd in failed local_zone_create() */
return;
}
newzone = 1;
}
if(a == RPZ_LOCAL_DATA_ACTION) {
- rrstr = sldns_wire2str_rr(rr, rr_len);
- if(!rrstr) {
- log_err("malloc error while inserting RPZ qname "
- "trigger");
+ char* rrstr = sldns_wire2str_rr(rr, rr_len);
+ if(rrstr == NULL) {
+ log_err("malloc error while inserting rpz nsdname trigger");
free(dname);
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&lz->lock);
return;
}
lock_rw_wrlock(&z->lock);
- local_zone_enter_rr(z, dname, dnamelen, dnamelabs,
- rrtype, rrclass, ttl, rdata, rdata_len, rrstr);
+ local_zone_enter_rr(z, dname, dnamelen, dnamelabs, rrtype,
+ rrclass, ttl, rdata, rdata_len, rrstr);
lock_rw_unlock(&z->lock);
free(rrstr);
}
- if(!newzone)
+ if(!newzone) {
free(dname);
- lock_rw_unlock(&r->local_zones->lock);
- return;
+ }
+ lock_rw_unlock(&lz->lock);
+}
+
+static void
+rpz_log_dname(char const* msg, uint8_t* dname, size_t dname_len)
+{
+ char buf[LDNS_MAX_DOMAINLEN+1];
+ (void)dname_len;
+ dname_str(dname, buf);
+ verbose(VERB_ALGO, "rpz: %s: <%s>", msg, buf);
+}
+
+static void
+rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+ enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
+ uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+{
+ if(a == RPZ_INVALID_ACTION) {
+ verbose(VERB_ALGO, "rpz: skipping invalid action");
+ free(dname);
+ return;
+ }
+
+ rpz_insert_local_zones_trigger(r->local_zones, dname, dnamelen, a, rrtype,
+ rrclass, ttl, rdata, rdata_len, rr, rr_len);
}
-/** Insert RR into RPZ's respip_set */
static int
-rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+rpz_strip_nsdname_suffix(uint8_t* dname, size_t maxdnamelen,
+ uint8_t** stripdname, size_t* stripdnamelen)
+{
+ uint8_t* tldstart = get_tld_label(dname, maxdnamelen);
+ uint8_t swap;
+ if(tldstart == NULL) {
+ if(dname == NULL) {
+ *stripdname = NULL;
+ *stripdnamelen = 0;
+ return 0;
+ }
+ *stripdname = memdup(dname, maxdnamelen);
+ if(!*stripdname) {
+ *stripdnamelen = 0;
+ log_err("malloc failure for rpz strip suffix");
+ return 0;
+ }
+ *stripdnamelen = maxdnamelen;
+ return 1;
+ }
+ /* shorten the domain name briefly,
+ * then we allocate a new name with the correct length */
+ swap = *tldstart;
+ *tldstart = 0;
+ (void)dname_count_size_labels(dname, stripdnamelen);
+ *stripdname = memdup(dname, *stripdnamelen);
+ *tldstart = swap;
+ if(!*stripdname) {
+ *stripdnamelen = 0;
+ log_err("malloc failure for rpz strip suffix");
+ return 0;
+ }
+ return 1;
+}
+
+static void
+rpz_insert_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
{
+ uint8_t* dname_stripped = NULL;
+ size_t dnamelen_stripped = 0;
+
+ rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped,
+ &dnamelen_stripped);
+ if(a == RPZ_INVALID_ACTION) {
+ verbose(VERB_ALGO, "rpz: skipping invalid action");
+ free(dname_stripped);
+ return;
+ }
+
+ /* dname_stripped is consumed or freed by the insert routine */
+ rpz_insert_local_zones_trigger(r->nsdname_zones, dname_stripped,
+ dnamelen_stripped, a, rrtype, rrclass, ttl, rdata, rdata_len,
+ rr, rr_len);
+}
+
+static int
+rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage* addr,
+ socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype,
+ uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len,
+ uint8_t* rr, size_t rr_len)
+{
struct resp_addr* node;
- struct sockaddr_storage addr;
- socklen_t addrlen;
- int net, af;
char* rrstr;
enum respip_action respa = rpz_action_to_respip_action(a);
- if(a == RPZ_TCP_ONLY_ACTION || a == RPZ_INVALID_ACTION ||
- respa == respip_invalid) {
- char str[255+1];
- dname_str(dname, str);
- verbose(VERB_ALGO, "RPZ: respip trigger, %s skipping unsupported action: %s",
- str, rpz_action_to_string(a));
+ lock_rw_wrlock(&set->lock);
+ rrstr = sldns_wire2str_rr(rr, rr_len);
+ if(rrstr == NULL) {
+ log_err("malloc error while inserting rpz ipaddr based trigger");
+ lock_rw_unlock(&set->lock);
return 0;
}
- if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af))
+ node = respip_sockaddr_find_or_create(set, addr, addrlen, net, 1, rrstr);
+ if(node == NULL) {
+ lock_rw_unlock(&set->lock);
+ free(rrstr);
return 0;
+ }
- lock_rw_wrlock(&r->respip_set->lock);
- rrstr = sldns_wire2str_rr(rr, rr_len);
- if(!rrstr) {
- log_err("malloc error while inserting RPZ respip trigger");
- lock_rw_unlock(&r->respip_set->lock);
+ lock_rw_wrlock(&node->lock);
+ lock_rw_unlock(&set->lock);
+
+ node->action = respa;
+
+ if(a == RPZ_LOCAL_DATA_ACTION) {
+ respip_enter_rr(set->region, node, rrtype,
+ rrclass, ttl, rdata, rdata_len, rrstr, "");
+ }
+
+ lock_rw_unlock(&node->lock);
+ free(rrstr);
+ return 1;
+}
+
+static inline struct clientip_synthesized_rr*
+rpz_clientip_ensure_entry(struct clientip_synthesized_rrset* set,
+ struct sockaddr_storage* addr, socklen_t addrlen, int net)
+{
+ int insert_ok;
+ struct clientip_synthesized_rr* node =
+ (struct clientip_synthesized_rr*)addr_tree_find(&set->entries,
+ addr, addrlen, net);
+
+ if(node != NULL) { return node; }
+
+ /* node does not yet exist => allocate one */
+ node = regional_alloc_zero(set->region, sizeof(*node));
+ if(node == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+
+ lock_rw_init(&node->lock);
+ node->action = RPZ_INVALID_ACTION;
+ insert_ok = addr_tree_insert(&set->entries, &node->node,
+ addr, addrlen, net);
+ if (!insert_ok) {
+ log_warn("rpz: unexpected: unable to insert clientip address node");
+ /* we can not free the just allocated node.
+ * theoretically a memleak */
+ return NULL;
+ }
+
+ return node;
+}
+
+static void
+rpz_report_rrset_error(const char* msg, uint8_t* rr, size_t rr_len) {
+ char* rrstr = sldns_wire2str_rr(rr, rr_len);
+ if(rrstr == NULL) {
+ log_err("malloc error while inserting rpz clientip based record");
+ return;
+ }
+ log_err("rpz: unexpected: unable to insert %s: %s", msg, rrstr);
+ free(rrstr);
+}
+
+/* from localzone.c; difference is we don't have a dname */
+static struct local_rrset*
+rpz_clientip_new_rrset(struct regional* region,
+ struct clientip_synthesized_rr* raddr, uint16_t rrtype, uint16_t rrclass)
+{
+ struct packed_rrset_data* pd;
+ struct local_rrset* rrset = (struct local_rrset*)
+ regional_alloc_zero(region, sizeof(*rrset));
+ if(rrset == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+ rrset->next = raddr->data;
+ raddr->data = rrset;
+ rrset->rrset = (struct ub_packed_rrset_key*)
+ regional_alloc_zero(region, sizeof(*rrset->rrset));
+ if(rrset->rrset == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+ rrset->rrset->entry.key = rrset->rrset;
+ pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd));
+ if(pd == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+ pd->trust = rrset_trust_prim_noglue;
+ pd->security = sec_status_insecure;
+ rrset->rrset->entry.data = pd;
+ rrset->rrset->rk.type = htons(rrtype);
+ rrset->rrset->rk.rrset_class = htons(rrclass);
+ rrset->rrset->rk.dname = regional_alloc_zero(region, 1);
+ if(rrset->rrset->rk.dname == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+ rrset->rrset->rk.dname_len = 1;
+ return rrset;
+}
+
+static int
+rpz_clientip_enter_rr(struct regional* region, struct clientip_synthesized_rr* raddr,
+ uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata,
+ size_t rdata_len)
+{
+ struct local_rrset* rrset;
+ if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data != NULL) {
+ log_err("CNAME response-ip data can not co-exist with other "
+ "client-ip data");
return 0;
}
- if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen,
- net, 1, rrstr))) {
- lock_rw_unlock(&r->respip_set->lock);
- free(rrstr);
+
+ rrset = rpz_clientip_new_rrset(region, raddr, rrtype, rrclass);
+ if(raddr->data == NULL) {
+ return 0;
+ }
+
+ return rrset_insert_rr(region, rrset->rrset->entry.data, rdata, rdata_len, ttl, "");
+}
+
+static int
+rpz_clientip_insert_trigger_rr(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr,
+ socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype,
+ uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len,
+ uint8_t* rr, size_t rr_len)
+{
+ struct clientip_synthesized_rr* node;
+
+ lock_rw_wrlock(&set->lock);
+
+ node = rpz_clientip_ensure_entry(set, addr, addrlen, net);
+ if(node == NULL) {
+ lock_rw_unlock(&set->lock);
+ rpz_report_rrset_error("client ip address", rr, rr_len);
return 0;
}
lock_rw_wrlock(&node->lock);
- lock_rw_unlock(&r->respip_set->lock);
- node->action = respa;
+ lock_rw_unlock(&set->lock);
+ node->action = a;
if(a == RPZ_LOCAL_DATA_ACTION) {
- respip_enter_rr(r->respip_set->region, node, rrtype,
- rrclass, ttl, rdata, rdata_len, rrstr, "");
+ if(!rpz_clientip_enter_rr(set->region, node, rrtype,
+ rrclass, ttl, rdata, rdata_len)) {
+ verbose(VERB_ALGO, "rpz: unable to insert clientip rr");
+ lock_rw_unlock(&node->lock);
+ return 0;
+ }
+
}
+
lock_rw_unlock(&node->lock);
- free(rrstr);
+
return 1;
}
+static int
+rpz_insert_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+ enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
+ uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int net, af;
+
+ if(a == RPZ_INVALID_ACTION) {
+ return 0;
+ }
+
+ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) {
+ verbose(VERB_ALGO, "rpz: unable to parse client ip");
+ return 0;
+ }
+
+ return rpz_clientip_insert_trigger_rr(r->client_set, &addr, addrlen, net,
+ a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len);
+}
+
+static int
+rpz_insert_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+ enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
+ uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int net, af;
+
+ if(a == RPZ_INVALID_ACTION) {
+ return 0;
+ }
+
+ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) {
+ verbose(VERB_ALGO, "rpz: unable to parse ns ip");
+ return 0;
+ }
+
+ return rpz_clientip_insert_trigger_rr(r->ns_set, &addr, addrlen, net,
+ a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len);
+}
+
+/** Insert RR into RPZ's respip_set */
+static int
+rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
+ enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl,
+ uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int net, af;
+
+ if(a == RPZ_INVALID_ACTION) {
+ return 0;
+ }
+
+ if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) {
+ verbose(VERB_ALGO, "rpz: unable to parse response ip");
+ return 0;
+ }
+
+ if(a == RPZ_INVALID_ACTION ||
+ rpz_action_to_respip_action(a) == respip_invalid) {
+ char str[255+1];
+ dname_str(dname, str);
+ verbose(VERB_ALGO, "RPZ: respip trigger, %s skipping unsupported action: %s",
+ str, rpz_action_to_string(a));
+ return 0;
+ }
+
+ return rpz_insert_ipaddr_based_trigger(r->respip_set, &addr, addrlen, net,
+ a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len);
+}
+
int
rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl,
@@ -614,15 +1019,19 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
enum rpz_action a;
uint8_t* policydname;
+ if(rpz_type_ignored(rr_type)) {
+ /* this rpz action is not valid, eg. this is the SOA or NS RR */
+ return 1;
+ }
if(!dname_subdomain_c(dname, azname)) {
char* dname_str = sldns_wire2str_dname(dname, dnamelen);
char* azname_str = sldns_wire2str_dname(azname, aznamelen);
if(dname_str && azname_str) {
- log_err("RPZ: name of record (%s) to insert into RPZ is not a "
+ log_err("rpz: name of record (%s) to insert into RPZ is not a "
"subdomain of the configured name of the RPZ zone (%s)",
dname_str, azname_str);
} else {
- log_err("RPZ: name of record to insert into RPZ is not a "
+ log_err("rpz: name of record to insert into RPZ is not a "
"subdomain of the configured name of the RPZ zone");
}
free(dname_str);
@@ -645,23 +1054,37 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
t = rpz_dname_to_trigger(policydname, policydnamelen);
if(t == RPZ_INVALID_TRIGGER) {
free(policydname);
- verbose(VERB_ALGO, "RPZ: skipping invalid trigger");
+ verbose(VERB_ALGO, "rpz: skipping invalid trigger");
return 1;
}
if(t == RPZ_QNAME_TRIGGER) {
+ /* policydname will be consumed, no free */
rpz_insert_qname_trigger(r, policydname, policydnamelen,
a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
rr_len);
- }
- else if(t == RPZ_RESPONSE_IP_TRIGGER) {
+ } else if(t == RPZ_RESPONSE_IP_TRIGGER) {
rpz_insert_response_ip_trigger(r, policydname, policydnamelen,
a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
rr_len);
free(policydname);
- }
- else {
+ } else if(t == RPZ_CLIENT_IP_TRIGGER) {
+ rpz_insert_clientip_trigger(r, policydname, policydnamelen,
+ a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
+ rr_len);
+ free(policydname);
+ } else if(t == RPZ_NSIP_TRIGGER) {
+ rpz_insert_nsip_trigger(r, policydname, policydnamelen,
+ a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
+ rr_len);
+ free(policydname);
+ } else if(t == RPZ_NSDNAME_TRIGGER) {
+ rpz_insert_nsdname_trigger(r, policydname, policydnamelen,
+ a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr,
+ rr_len);
free(policydname);
- verbose(VERB_ALGO, "RPZ: skipping unsupported trigger: %s",
+ } else {
+ free(policydname);
+ verbose(VERB_ALGO, "rpz: skipping unsupported trigger: %s",
rpz_trigger_to_string(t));
}
return 1;
@@ -669,18 +1092,18 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname,
/**
* Find RPZ local-zone by qname.
- * @param r: rpz containing local-zone tree
+ * @param zones: local-zone tree
* @param qname: qname
* @param qname_len: length of qname
* @param qclass: qclass
- * @param only_exact: if 1 only excact (non wildcard) matches are returned
+ * @param only_exact: if 1 only exact (non wildcard) matches are returned
* @param wr: get write lock for local-zone if 1, read lock if 0
* @param zones_keep_lock: if set do not release the r->local_zones lock, this
* makes the caller of this function responsible for releasing the lock.
* @return: NULL or local-zone holding rd or wr lock
*/
static struct local_zone*
-rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
+rpz_find_zone(struct local_zones* zones, uint8_t* qname, size_t qname_len, uint16_t qclass,
int only_exact, int wr, int zones_keep_lock)
{
uint8_t* ce;
@@ -689,16 +1112,19 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
uint8_t wc[LDNS_MAX_DOMAINLEN+1];
int exact;
struct local_zone* z = NULL;
+
if(wr) {
- lock_rw_wrlock(&r->local_zones->lock);
+ lock_rw_wrlock(&zones->lock);
} else {
- lock_rw_rdlock(&r->local_zones->lock);
+ lock_rw_rdlock(&zones->lock);
}
- z = local_zones_find_le(r->local_zones, qname, qname_len,
+ z = local_zones_find_le(zones, qname, qname_len,
dname_count_labels(qname),
LDNS_RR_CLASS_IN, &exact);
if(!z || (only_exact && !exact)) {
- lock_rw_unlock(&r->local_zones->lock);
+ if(!zones_keep_lock) {
+ lock_rw_unlock(&zones->lock);
+ }
return NULL;
}
if(wr) {
@@ -707,7 +1133,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
lock_rw_rdlock(&z->lock);
}
if(!zones_keep_lock) {
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&zones->lock);
}
if(exact)
@@ -721,7 +1147,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
if(!ce /* should not happen */) {
lock_rw_unlock(&z->lock);
if(zones_keep_lock) {
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&zones->lock);
}
return NULL;
}
@@ -729,7 +1155,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
if(ce_len+2 > sizeof(wc)) {
lock_rw_unlock(&z->lock);
if(zones_keep_lock) {
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&zones->lock);
}
return NULL;
}
@@ -740,15 +1166,15 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
if(!zones_keep_lock) {
if(wr) {
- lock_rw_wrlock(&r->local_zones->lock);
+ lock_rw_wrlock(&zones->lock);
} else {
- lock_rw_rdlock(&r->local_zones->lock);
+ lock_rw_rdlock(&zones->lock);
}
}
- z = local_zones_find_le(r->local_zones, wc,
+ z = local_zones_find_le(zones, wc,
ce_len+2, ce_labs+1, qclass, &exact);
if(!z || !exact) {
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&zones->lock);
return NULL;
}
if(wr) {
@@ -757,7 +1183,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
lock_rw_rdlock(&z->lock);
}
if(!zones_keep_lock) {
- lock_rw_unlock(&r->local_zones->lock);
+ lock_rw_unlock(&zones->lock);
}
return z;
}
@@ -766,7 +1192,7 @@ rpz_find_zone(struct rpz* r, uint8_t* qname, size_t qname_len, uint16_t qclass,
* Remove RR from RPZ's local-data
* @param z: local-zone for RPZ, holding write lock
* @param policydname: dname of RR to remove
- * @param policydnamelen: lenth of policydname
+ * @param policydnamelen: length of policydname
* @param rr_type: RR type of RR to remove
* @param rdata: rdata of RR to remove
* @param rdatalen: length of rdata
@@ -852,10 +1278,10 @@ rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
{
struct local_zone* z;
int delete_zone = 1;
- z = rpz_find_zone(r, dname, dnamelen, rr_class,
+ z = rpz_find_zone(r->local_zones, dname, dnamelen, rr_class,
1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/);
if(!z) {
- verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, "
+ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, "
"RPZ domain not found");
return;
}
@@ -891,7 +1317,7 @@ rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen,
lock_rw_wrlock(&r->respip_set->lock);
if(!(node = (struct resp_addr*)addr_tree_find(
&r->respip_set->ip_tree, &addr, addrlen, net))) {
- verbose(VERB_ALGO, "RPZ: cannot remove RR from IXFR, "
+ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, "
"RPZ domain not found");
lock_rw_unlock(&r->respip_set->lock);
return;
@@ -944,118 +1370,1009 @@ rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname, size_t dnamelen,
/** print log information for an applied RPZ policy. Based on local-zone's
* lz_inform_print().
+ * The repinfo contains the reply address. If it is NULL, the module
+ * state is used to report the first IP address (if any).
+ * The dname is used, for the applied rpz, if NULL, addrnode is used.
*/
static void
-log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo,
- struct comm_reply* repinfo, char* log_name)
+log_rpz_apply(char* trigger, uint8_t* dname, struct addr_tree_node* addrnode,
+ enum rpz_action a, struct query_info* qinfo,
+ struct comm_reply* repinfo, struct module_qstate* ms, char* log_name)
{
- char ip[128], txt[512];
+ char ip[128], txt[512], portstr[32];
char dnamestr[LDNS_MAX_DOMAINLEN+1];
- uint16_t port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port);
- dname_str(dname, dnamestr);
- addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip));
- if(log_name)
- snprintf(txt, sizeof(txt), "RPZ applied [%s] %s %s %s@%u",
- log_name, dnamestr, rpz_action_to_string(a), ip,
- (unsigned)port);
- else
- snprintf(txt, sizeof(txt), "RPZ applied %s %s %s@%u",
- dnamestr, rpz_action_to_string(a), ip, (unsigned)port);
+ uint16_t port = 0;
+ if(dname) {
+ dname_str(dname, dnamestr);
+ } else if(addrnode) {
+ char addrbuf[128];
+ addr_to_str(&addrnode->addr, addrnode->addrlen, addrbuf, sizeof(addrbuf));
+ snprintf(dnamestr, sizeof(dnamestr), "%s/%d", addrbuf, addrnode->net);
+ } else {
+ dnamestr[0]=0;
+ }
+ if(repinfo) {
+ addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip));
+ port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port);
+ } else if(ms && ms->mesh_info && ms->mesh_info->reply_list) {
+ addr_to_str(&ms->mesh_info->reply_list->query_reply.addr, ms->mesh_info->reply_list->query_reply.addrlen, ip, sizeof(ip));
+ port = ntohs(((struct sockaddr_in*)&ms->mesh_info->reply_list->query_reply.addr)->sin_port);
+ } else {
+ ip[0]=0;
+ port = 0;
+ }
+ snprintf(portstr, sizeof(portstr), "@%u", (unsigned)port);
+ snprintf(txt, sizeof(txt), "rpz: applied %s%s%s%s%s%s %s %s%s",
+ (log_name?"[":""), (log_name?log_name:""), (log_name?"] ":""),
+ (strcmp(trigger,"qname")==0?"":trigger),
+ (strcmp(trigger,"qname")==0?"":" "),
+ dnamestr, rpz_action_to_string(a),
+ (ip[0]?ip:""), (ip[0]?portstr:""));
log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass);
}
-int
-rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env,
- struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf,
- struct regional* temp, struct comm_reply* repinfo,
- uint8_t* taglist, size_t taglen, struct ub_server_stats* stats)
+static struct clientip_synthesized_rr*
+rpz_ipbased_trigger_lookup(struct clientip_synthesized_rrset* set,
+ struct sockaddr_storage* addr, socklen_t addrlen, char* triggername)
{
+ struct clientip_synthesized_rr* raddr = NULL;
+ enum rpz_action action = RPZ_INVALID_ACTION;
+
+ lock_rw_rdlock(&set->lock);
+
+ raddr = (struct clientip_synthesized_rr*)addr_tree_lookup(&set->entries,
+ addr, addrlen);
+ if(raddr != NULL) {
+ lock_rw_rdlock(&raddr->lock);
+ action = raddr->action;
+ if(verbosity >= VERB_ALGO) {
+ char ip[256], net[256];
+ addr_to_str(addr, addrlen, ip, sizeof(ip));
+ addr_to_str(&raddr->node.addr, raddr->node.addrlen,
+ net, sizeof(net));
+ verbose(VERB_ALGO, "rpz: trigger %s %s/%d on %s action=%s",
+ triggername, net, raddr->node.net, ip, rpz_action_to_string(action));
+ }
+ }
+ lock_rw_unlock(&set->lock);
+
+ return raddr;
+}
+
+static inline
+struct clientip_synthesized_rr*
+rpz_resolve_client_action_and_zone(struct auth_zones* az, struct query_info* qinfo,
+ struct comm_reply* repinfo, uint8_t* taglist, size_t taglen,
+ struct ub_server_stats* stats,
+ /* output parameters */
+ struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out)
+{
+ struct clientip_synthesized_rr* node = NULL;
+ struct auth_zone* a = NULL;
struct rpz* r = NULL;
- struct auth_zone* a;
- int ret;
- enum localzone_type lzt;
struct local_zone* z = NULL;
- struct local_data* ld = NULL;
+
lock_rw_rdlock(&az->rpz_lock);
+
for(a = az->rpz_first; a; a = a->rpz_az_next) {
lock_rw_rdlock(&a->lock);
r = a->rpz;
- if(!r->disabled && (!r->taglist || taglist_intersect(r->taglist,
- r->taglistlen, taglist, taglen))) {
- z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len,
- qinfo->qclass, 0, 0, 0);
- if(z && r->action_override == RPZ_DISABLED_ACTION) {
- if(r->log)
- log_rpz_apply(z->name,
- r->action_override,
- qinfo, repinfo, r->log_name);
- /* TODO only register stats when stats_extended?
- * */
- stats->rpz_action[r->action_override]++;
+ if(r->disabled) {
+ lock_rw_unlock(&a->lock);
+ continue;
+ }
+ if(r->taglist && !taglist_intersect(r->taglist,
+ r->taglistlen, taglist, taglen)) {
+ lock_rw_unlock(&a->lock);
+ continue;
+ }
+ z = rpz_find_zone(r->local_zones, qinfo->qname, qinfo->qname_len,
+ qinfo->qclass, 0, 0, 0);
+ node = rpz_ipbased_trigger_lookup(r->client_set, &repinfo->addr, repinfo->addrlen, "clientip");
+ if((z || node) && r->action_override == RPZ_DISABLED_ACTION) {
+ if(r->log)
+ log_rpz_apply((node?"clientip":"qname"),
+ (z?z->name:NULL),
+ (node?&node->node:NULL),
+ r->action_override,
+ qinfo, repinfo, NULL, r->log_name);
+ stats->rpz_action[r->action_override]++;
+ if(z != NULL) {
lock_rw_unlock(&z->lock);
z = NULL;
}
- if(z)
- break;
+ if(node != NULL) {
+ lock_rw_unlock(&node->lock);
+ node = NULL;
+ }
}
- lock_rw_unlock(&a->lock); /* not found in this auth_zone */
+ if(z || node) {
+ break;
+ }
+ /* not found in this auth_zone */
+ lock_rw_unlock(&a->lock);
}
+
lock_rw_unlock(&az->rpz_lock);
- if(!z)
- return 0; /* not holding auth_zone.lock anymore */
- log_assert(r);
- if(r->action_override == RPZ_NO_OVERRIDE_ACTION)
- lzt = z->type;
- else
- lzt = rpz_action_to_localzone_type(r->action_override);
+ *r_out = r;
+ *a_out = a;
+ *z_out = z;
+
+ return node;
+}
+
+static inline int
+rpz_is_udp_query(struct comm_reply* repinfo) {
+ return repinfo != NULL
+ ? (repinfo->c != NULL
+ ? repinfo->c->type == comm_udp
+ : 0)
+ : 0;
+}
+
+/** encode answer consisting of 1 rrset */
+static int
+rpz_local_encode(struct module_env* env, struct query_info* qinfo,
+ struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf,
+ struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec,
+ int rcode, struct ub_packed_rrset_key* soa_rrset)
+{
+ struct reply_info rep;
+ uint16_t udpsize;
+ struct ub_packed_rrset_key* rrsetlist[3];
+
+ memset(&rep, 0, sizeof(rep));
+ rep.flags = (uint16_t)((BIT_QR | BIT_AA | BIT_RA) | rcode);
+ rep.qdcount = 1;
+ rep.rrset_count = ansec;
+ rep.rrsets = rrsetlist;
+ if(ansec > 0) {
+ rep.an_numrrsets = 1;
+ rep.rrsets[0] = rrset;
+ rep.ttl = ((struct packed_rrset_data*)rrset->entry.data)->rr_ttl[0];
+ }
+ if(soa_rrset != NULL) {
+ rep.ar_numrrsets = 1;
+ rep.rrsets[rep.rrset_count] = soa_rrset;
+ rep.rrset_count ++;
+ if(rep.ttl < ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]) {
+ rep.ttl = ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0];
+ }
+ }
+
+ udpsize = edns->udp_size;
+ edns->edns_version = EDNS_ADVERTISED_VERSION;
+ edns->udp_size = EDNS_ADVERTISED_SIZE;
+ edns->ext_rcode = 0;
+ edns->bits &= EDNS_DO;
+ if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns,
+ repinfo, temp, env->now_tv) ||
+ !reply_info_answer_encode(qinfo, &rep,
+ *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2),
+ buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) {
+ error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo,
+ *(uint16_t*)sldns_buffer_begin(buf),
+ sldns_buffer_read_u16_at(buf, 2), edns);
+ }
+
+ return 1;
+}
+
+static struct local_rrset*
+rpz_find_synthesized_rrset(int qtype, struct clientip_synthesized_rr* data) {
+ struct local_rrset* cursor = data->data;
+ while( cursor != NULL) {
+ struct packed_rrset_key* packed_rrset = &cursor->rrset->rk;
+ if(htons(qtype) == packed_rrset->type) {
+ return cursor;
+ }
+ cursor = cursor->next;
+ }
+ return NULL;
+}
+
+/** allocate SOA record ubrrsetkey in region */
+static struct ub_packed_rrset_key*
+make_soa_ubrrset(struct auth_zone* auth_zone, struct auth_rrset* soa,
+ struct regional* temp)
+{
+ struct ub_packed_rrset_key csoa;
+ if(!soa)
+ return NULL;
+ memset(&csoa, 0, sizeof(csoa));
+ csoa.entry.key = &csoa;
+ csoa.rk.rrset_class = htons(LDNS_RR_CLASS_IN);
+ csoa.rk.type = htons(LDNS_RR_TYPE_SOA);
+ csoa.rk.flags |= PACKED_RRSET_FIXEDTTL
+ | PACKED_RRSET_RPZ;
+ csoa.rk.dname = auth_zone->name;
+ csoa.rk.dname_len = auth_zone->namelen;
+ csoa.entry.hash = rrset_key_hash(&csoa.rk);
+ csoa.entry.data = soa->data;
+ return respip_copy_rrset(&csoa, temp);
+}
+
+static void
+rpz_apply_clientip_localdata_action(struct clientip_synthesized_rr* raddr,
+ struct module_env* env, struct query_info* qinfo,
+ struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf,
+ struct regional* temp, struct auth_zone* auth_zone)
+{
+ struct local_rrset* rrset;
+ enum rpz_action action = RPZ_INVALID_ACTION;
+ struct ub_packed_rrset_key* rp = NULL;
+ struct ub_packed_rrset_key* rsoa = NULL;
+ int rcode = LDNS_RCODE_NOERROR|BIT_AA;
+ int rrset_count = 1;
+
+ /* prepare synthesized answer for client */
+ action = raddr->action;
+ if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL ) {
+ verbose(VERB_ALGO, "rpz: bug: local-data action but no local data");
+ return;
+ }
+
+ /* check query type / rr type */
+ rrset = rpz_find_synthesized_rrset(qinfo->qtype, raddr);
+ if(rrset == NULL) {
+ verbose(VERB_ALGO, "rpz: unable to find local-data for query");
+ rrset_count = 0;
+ goto nodata;
+ }
+
+ rp = respip_copy_rrset(rrset->rrset, temp);
+ if(!rp) {
+ verbose(VERB_ALGO, "rpz: local data action: out of memory");
+ return;
+ }
+
+ rp->rk.flags |= PACKED_RRSET_FIXEDTTL | PACKED_RRSET_RPZ;
+ rp->rk.dname = qinfo->qname;
+ rp->rk.dname_len = qinfo->qname_len;
+ rp->entry.hash = rrset_key_hash(&rp->rk);
+nodata:
+ if(auth_zone) {
+ struct auth_rrset* soa = NULL;
+ soa = auth_zone_get_soa_rrset(auth_zone);
+ if(soa) {
+ rsoa = make_soa_ubrrset(auth_zone, soa, temp);
+ if(!rsoa) {
+ verbose(VERB_ALGO, "rpz: local data action soa: out of memory");
+ return;
+ }
+ }
+ }
+
+ rpz_local_encode(env, qinfo, edns, repinfo, buf, temp, rp,
+ rrset_count, rcode, rsoa);
+}
+
+/** add additional section SOA record to the reply.
+ * Since this gets fed into the normal iterator answer creation, it
+ * gets minimal-responses applied to it, that can remove the additional SOA
+ * again. */
+static int
+rpz_add_soa(struct reply_info* rep, struct module_qstate* ms,
+ struct auth_zone* az)
+{
+ struct auth_rrset* soa = NULL;
+ struct ub_packed_rrset_key* rsoa = NULL;
+ struct ub_packed_rrset_key** prevrrsets;
+ if(!az) return 1;
+ soa = auth_zone_get_soa_rrset(az);
+ if(!soa) return 1;
+ if(!rep) return 0;
+ rsoa = make_soa_ubrrset(az, soa, ms->region);
+ if(!rsoa) return 0;
+ prevrrsets = rep->rrsets;
+ rep->rrsets = regional_alloc_zero(ms->region,
+ sizeof(*rep->rrsets)*(rep->rrset_count+1));
+ if(!rep->rrsets)
+ return 0;
+ if(prevrrsets && rep->rrset_count > 0)
+ memcpy(rep->rrsets, prevrrsets, rep->rrset_count*sizeof(*rep->rrsets));
+ rep->rrset_count++;
+ rep->ar_numrrsets++;
+ rep->rrsets[rep->rrset_count-1] = rsoa;
+ return 1;
+}
+
+static inline struct dns_msg*
+rpz_dns_msg_new(struct regional* region)
+{
+ struct dns_msg* msg =
+ (struct dns_msg*)regional_alloc(region,
+ sizeof(struct dns_msg));
+ if(msg == NULL) { return NULL; }
+ memset(msg, 0, sizeof(struct dns_msg));
+
+ return msg;
+}
+static inline struct dns_msg*
+rpz_synthesize_nodata(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms,
+ struct query_info* qinfo, struct auth_zone* az)
+{
+ struct dns_msg* msg = rpz_dns_msg_new(ms->region);
+ if(msg == NULL) { return msg; }
+ msg->qinfo = *qinfo;
+ msg->rep = construct_reply_info_base(ms->region,
+ LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA,
+ 1, /* qd */
+ 0, /* ttl */
+ 0, /* prettl */
+ 0, /* expttl */
+ 0, /* an */
+ 0, /* ns */
+ 0, /* ar */
+ 0, /* total */
+ sec_status_insecure);
+ if(msg->rep)
+ msg->rep->authoritative = 1;
+ if(!rpz_add_soa(msg->rep, ms, az))
+ return NULL;
+ return msg;
+}
+
+static inline struct dns_msg*
+rpz_synthesize_nxdomain(struct rpz* r, struct module_qstate* ms,
+ struct query_info* qinfo, struct auth_zone* az)
+{
+ struct dns_msg* msg = rpz_dns_msg_new(ms->region);
+ uint16_t flags;
+ if(msg == NULL) { return msg; }
+ msg->qinfo = *qinfo;
+ flags = LDNS_RCODE_NXDOMAIN | BIT_QR | BIT_AA | BIT_RA;
+ if(r->signal_nxdomain_ra)
+ flags &= ~BIT_RA;
+ msg->rep = construct_reply_info_base(ms->region,
+ flags,
+ 1, /* qd */
+ 0, /* ttl */
+ 0, /* prettl */
+ 0, /* expttl */
+ 0, /* an */
+ 0, /* ns */
+ 0, /* ar */
+ 0, /* total */
+ sec_status_insecure);
+ if(msg->rep)
+ msg->rep->authoritative = 1;
+ if(!rpz_add_soa(msg->rep, ms, az))
+ return NULL;
+ return msg;
+}
+
+static inline struct dns_msg*
+rpz_synthesize_localdata_from_rrset(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms,
+ struct query_info* qi, struct local_rrset* rrset, struct auth_zone* az)
+{
+ struct dns_msg* msg = NULL;
+ struct reply_info* new_reply_info;
+ struct ub_packed_rrset_key* rp;
+
+
+ msg = rpz_dns_msg_new(ms->region);
+ if(msg == NULL) { return NULL; }
+
+ new_reply_info = construct_reply_info_base(ms->region,
+ LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA,
+ 1, /* qd */
+ 0, /* ttl */
+ 0, /* prettl */
+ 0, /* expttl */
+ 1, /* an */
+ 0, /* ns */
+ 0, /* ar */
+ 1, /* total */
+ sec_status_insecure);
+ if(new_reply_info == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+ new_reply_info->authoritative = 1;
+ rp = respip_copy_rrset(rrset->rrset, ms->region);
+ if(rp == NULL) {
+ log_err("out of memory");
+ return NULL;
+ }
+ rp->rk.dname = qi->qname;
+ rp->rk.dname_len = qi->qname_len;
+ /* this rrset is from the rpz data, or synthesized.
+ * It is not actually from the network, so we flag it with this
+ * flags as a fake RRset. If later the cache is used to look up
+ * rrsets, then the fake ones are not returned (if you look without
+ * the flag). For like CNAME lookups from the iterator or A, AAAA
+ * lookups for nameserver targets, it would use the without flag
+ * actual data. So that the actual network data and fake data
+ * are kept track of separately. */
+ rp->rk.flags |= PACKED_RRSET_RPZ;
+ new_reply_info->rrsets[0] = rp;
+ msg->rep = new_reply_info;
+ if(!rpz_add_soa(msg->rep, ms, az))
+ return NULL;
+ return msg;
+}
+
+static inline struct dns_msg*
+rpz_synthesize_nsip_localdata(struct rpz* r, struct module_qstate* ms,
+ struct clientip_synthesized_rr* data, struct auth_zone* az)
+{
+ struct query_info* qi = &ms->qinfo;
+ struct local_rrset* rrset;
+
+ rrset = rpz_find_synthesized_rrset(qi->qtype, data);
+ if(rrset == NULL) {
+ verbose(VERB_ALGO, "rpz: nsip: no matching local data found");
+ return NULL;
+ }
+
+ return rpz_synthesize_localdata_from_rrset(r, ms, &ms->qinfo, rrset, az);
+}
+
+/* copy'n'paste from localzone.c */
+static struct local_rrset*
+local_data_find_type(struct local_data* data, uint16_t type, int alias_ok)
+{
+ struct local_rrset* p;
+ type = htons(type);
+ for(p = data->rrsets; p; p = p->next) {
+ if(p->rrset->rk.type == type)
+ return p;
+ if(alias_ok && p->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME))
+ return p;
+ }
+ return NULL;
+}
+
+/* based on localzone.c:local_data_answer() */
+static inline struct dns_msg*
+rpz_synthesize_nsdname_localdata(struct rpz* r, struct module_qstate* ms,
+ struct local_zone* z, struct matched_delegation_point const* match,
+ struct auth_zone* az)
+{
+ struct local_data key;
+ struct local_data* ld;
+ struct local_rrset* rrset;
+
+ if(match->dname == NULL) { return NULL; }
+
+ key.node.key = &key;
+ key.name = match->dname;
+ key.namelen = match->dname_len;
+ key.namelabs = dname_count_labels(match->dname);
+
+ rpz_log_dname("nsdname local data", key.name, key.namelen);
+
+ ld = (struct local_data*)rbtree_search(&z->data, &key.node);
+ if(ld == NULL) {
+ verbose(VERB_ALGO, "rpz: nsdname: impossible: qname not found");
+ return NULL;
+ }
+
+ rrset = local_data_find_type(ld, ms->qinfo.qtype, 1);
+ if(rrset == NULL) {
+ verbose(VERB_ALGO, "rpz: nsdname: no matching local data found");
+ return NULL;
+ }
+
+ return rpz_synthesize_localdata_from_rrset(r, ms, &ms->qinfo, rrset, az);
+}
+
+/* like local_data_answer for qname triggers after a cname */
+static struct dns_msg*
+rpz_synthesize_qname_localdata_msg(struct rpz* r, struct module_qstate* ms,
+ struct query_info* qinfo, struct local_zone* z, struct auth_zone* az)
+{
+ struct local_data key;
+ struct local_data* ld;
+ struct local_rrset* rrset;
+ key.node.key = &key;
+ key.name = qinfo->qname;
+ key.namelen = qinfo->qname_len;
+ key.namelabs = dname_count_labels(qinfo->qname);
+ ld = (struct local_data*)rbtree_search(&z->data, &key.node);
+ if(ld == NULL) {
+ verbose(VERB_ALGO, "rpz: qname after cname: name not found");
+ return NULL;
+ }
+ rrset = local_data_find_type(ld, qinfo->qtype, 1);
+ if(rrset == NULL) {
+ verbose(VERB_ALGO, "rpz: qname after cname: type not found");
+ return NULL;
+ }
+ return rpz_synthesize_localdata_from_rrset(r, ms, qinfo, rrset, az);
+}
+
+static int
+rpz_synthesize_qname_localdata(struct module_env* env, struct rpz* r,
+ struct local_zone* z, enum localzone_type lzt, struct query_info* qinfo,
+ struct edns_data* edns, sldns_buffer* buf, struct regional* temp,
+ struct comm_reply* repinfo, struct ub_server_stats* stats)
+{
+ struct local_data* ld = NULL;
+ int ret = 0;
if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) {
- qinfo->local_alias =
- regional_alloc_zero(temp, sizeof(struct local_rrset));
- if(!qinfo->local_alias) {
- lock_rw_unlock(&z->lock);
- lock_rw_unlock(&a->lock);
+ qinfo->local_alias = regional_alloc_zero(temp, sizeof(struct local_rrset));
+ if(qinfo->local_alias == NULL) {
return 0; /* out of memory */
}
- qinfo->local_alias->rrset =
- regional_alloc_init(temp, r->cname_override,
- sizeof(*r->cname_override));
- if(!qinfo->local_alias->rrset) {
- lock_rw_unlock(&z->lock);
- lock_rw_unlock(&a->lock);
+ qinfo->local_alias->rrset = regional_alloc_init(temp, r->cname_override,
+ sizeof(*r->cname_override));
+ if(qinfo->local_alias->rrset == NULL) {
return 0; /* out of memory */
}
qinfo->local_alias->rrset->rk.dname = qinfo->qname;
qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len;
- if(r->log)
- log_rpz_apply(z->name, RPZ_CNAME_OVERRIDE_ACTION,
- qinfo, repinfo, r->log_name);
+ if(r->log) {
+ log_rpz_apply("qname", z->name, NULL, RPZ_CNAME_OVERRIDE_ACTION,
+ qinfo, repinfo, NULL, r->log_name);
+ }
stats->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++;
- lock_rw_unlock(&z->lock);
- lock_rw_unlock(&a->lock);
return 0;
}
if(lzt == local_zone_redirect && local_data_answer(z, env, qinfo,
edns, repinfo, buf, temp, dname_count_labels(qinfo->qname),
&ld, lzt, -1, NULL, 0, NULL, 0)) {
- if(r->log)
- log_rpz_apply(z->name,
+ if(r->log) {
+ log_rpz_apply("qname", z->name, NULL,
localzone_type_to_rpz_action(lzt), qinfo,
- repinfo, r->log_name);
+ repinfo, NULL, r->log_name);
+ }
stats->rpz_action[localzone_type_to_rpz_action(lzt)]++;
- lock_rw_unlock(&z->lock);
- lock_rw_unlock(&a->lock);
return !qinfo->local_alias;
}
ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp,
0 /* no local data used */, lzt);
- if(r->log)
- log_rpz_apply(z->name, localzone_type_to_rpz_action(lzt),
- qinfo, repinfo, r->log_name);
+ if(r->signal_nxdomain_ra && LDNS_RCODE_WIRE(sldns_buffer_begin(buf))
+ == LDNS_RCODE_NXDOMAIN)
+ LDNS_RA_CLR(sldns_buffer_begin(buf));
+ if(r->log) {
+ log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt),
+ qinfo, repinfo, NULL, r->log_name);
+ }
stats->rpz_action[localzone_type_to_rpz_action(lzt)]++;
+ return ret;
+}
+
+static struct clientip_synthesized_rr*
+rpz_delegation_point_ipbased_trigger_lookup(struct rpz* rpz, struct iter_qstate* is)
+{
+ struct delegpt_addr* cursor;
+ struct clientip_synthesized_rr* action = NULL;
+ if(is->dp == NULL) { return NULL; }
+ for(cursor = is->dp->target_list;
+ cursor != NULL;
+ cursor = cursor->next_target) {
+ if(cursor->bogus) { continue; }
+ action = rpz_ipbased_trigger_lookup(rpz->ns_set, &cursor->addr,
+ cursor->addrlen, "nsip");
+ if(action != NULL) { return action; }
+ }
+ return NULL;
+}
+
+static struct dns_msg*
+rpz_apply_nsip_trigger(struct module_qstate* ms, struct rpz* r,
+ struct clientip_synthesized_rr* raddr, struct auth_zone* az)
+{
+ enum rpz_action action = raddr->action;
+ struct dns_msg* ret = NULL;
+
+ if(r->action_override != RPZ_NO_OVERRIDE_ACTION) {
+ verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)",
+ rpz_action_to_string(r->action_override), rpz_action_to_string(action));
+ action = r->action_override;
+ }
+
+ if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL) {
+ verbose(VERB_ALGO, "rpz: bug: nsip local data action but no local data");
+ ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az);
+ goto done;
+ }
+
+ switch(action) {
+ case RPZ_NXDOMAIN_ACTION:
+ ret = rpz_synthesize_nxdomain(r, ms, &ms->qinfo, az);
+ break;
+ case RPZ_NODATA_ACTION:
+ ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az);
+ break;
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+ ms->respip_action_info->action = respip_truncate;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+ ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az);
+ ms->is_drop = 1;
+ break;
+ case RPZ_LOCAL_DATA_ACTION:
+ ret = rpz_synthesize_nsip_localdata(r, ms, raddr, az);
+ if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); }
+ break;
+ case RPZ_PASSTHRU_ACTION:
+ ret = NULL;
+ break;
+ default:
+ verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'",
+ rpz_action_to_string(action));
+ ret = NULL;
+ }
+
+done:
+ if(r->log)
+ log_rpz_apply("nsip", NULL, &raddr->node,
+ action, &ms->qinfo, NULL, ms, r->log_name);
+ if(ms->env->worker)
+ ms->env->worker->stats.rpz_action[action]++;
+ lock_rw_unlock(&raddr->lock);
+ return ret;
+}
+
+static struct dns_msg*
+rpz_apply_nsdname_trigger(struct module_qstate* ms, struct rpz* r,
+ struct local_zone* z, struct matched_delegation_point const* match,
+ struct auth_zone* az)
+{
+ struct dns_msg* ret = NULL;
+ enum rpz_action action = localzone_type_to_rpz_action(z->type);
+
+ if(r->action_override != RPZ_NO_OVERRIDE_ACTION) {
+ verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)",
+ rpz_action_to_string(r->action_override), rpz_action_to_string(action));
+ action = r->action_override;
+ }
+
+ switch(action) {
+ case RPZ_NXDOMAIN_ACTION:
+ ret = rpz_synthesize_nxdomain(r, ms, &ms->qinfo, az);
+ break;
+ case RPZ_NODATA_ACTION:
+ ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az);
+ break;
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+ ms->respip_action_info->action = respip_truncate;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+ ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az);
+ ms->is_drop = 1;
+ break;
+ case RPZ_LOCAL_DATA_ACTION:
+ ret = rpz_synthesize_nsdname_localdata(r, ms, z, match, az);
+ if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &ms->qinfo, az); }
+ break;
+ case RPZ_PASSTHRU_ACTION:
+ ret = NULL;
+ break;
+ default:
+ verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'",
+ rpz_action_to_string(action));
+ ret = NULL;
+ }
+
+ if(r->log)
+ log_rpz_apply("nsdname", match->dname, NULL,
+ action, &ms->qinfo, NULL, ms, r->log_name);
+ if(ms->env->worker)
+ ms->env->worker->stats.rpz_action[action]++;
+ lock_rw_unlock(&z->lock);
+ return ret;
+}
+
+static struct local_zone*
+rpz_delegation_point_zone_lookup(struct delegpt* dp, struct local_zones* zones,
+ uint16_t qclass,
+ /* output parameter */
+ struct matched_delegation_point* match)
+{
+ struct delegpt_ns* nameserver;
+ struct local_zone* z = NULL;
+
+ /* the rpz specs match the nameserver names (NS records), not the
+ * name of the delegation point itself, to the nsdname triggers */
+ for(nameserver = dp->nslist;
+ nameserver != NULL;
+ nameserver = nameserver->next) {
+ z = rpz_find_zone(zones, nameserver->name, nameserver->namelen,
+ qclass, 0, 0, 0);
+ if(z != NULL) {
+ match->dname = nameserver->name;
+ match->dname_len = nameserver->namelen;
+ if(verbosity >= VERB_ALGO) {
+ char nm[255+1], zn[255+1];
+ dname_str(match->dname, nm);
+ dname_str(z->name, zn);
+ if(strcmp(nm, zn) != 0)
+ verbose(VERB_ALGO, "rpz: trigger nsdname %s on %s action=%s",
+ zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type)));
+ else
+ verbose(VERB_ALGO, "rpz: trigger nsdname %s action=%s",
+ nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type)));
+ }
+ break;
+ }
+ }
+
+ return z;
+}
+
+struct dns_msg*
+rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* is)
+{
+ struct auth_zones* az;
+ struct auth_zone* a;
+ struct clientip_synthesized_rr* raddr = NULL;
+ struct rpz* r = NULL;
+ struct local_zone* z = NULL;
+ struct matched_delegation_point match = {0};
+
+ if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; }
+
+ az = ms->env->auth_zones;
+
+ verbose(VERB_ALGO, "rpz: iterator module callback: have_rpz=%d", az->rpz_first != NULL);
+
+ lock_rw_rdlock(&az->rpz_lock);
+
+ /* precedence of RPZ works, loosely, like this:
+ * CNAMEs in order of the CNAME chain. rpzs in the order they are
+ * configured. In an RPZ: first client-IP addr, then QNAME, then
+ * response IP, then NSDNAME, then NSIP. Longest match first. Smallest
+ * one from a set. */
+ /* we use the precedence rules for the topics and triggers that
+ * are pertinent at this stage of the resolve processing */
+ for(a = az->rpz_first; a != NULL; a = a->rpz_az_next) {
+ lock_rw_rdlock(&a->lock);
+ r = a->rpz;
+ if(r->disabled) {
+ lock_rw_unlock(&a->lock);
+ continue;
+ }
+
+ /* the nsdname has precedence over the nsip triggers */
+ z = rpz_delegation_point_zone_lookup(is->dp, r->nsdname_zones,
+ ms->qinfo.qclass, &match);
+ if(z != NULL) {
+ lock_rw_unlock(&a->lock);
+ break;
+ }
+
+ raddr = rpz_delegation_point_ipbased_trigger_lookup(r, is);
+ if(raddr != NULL) {
+ lock_rw_unlock(&a->lock);
+ break;
+ }
+ lock_rw_unlock(&a->lock);
+ }
+
+ lock_rw_unlock(&az->rpz_lock);
+
+ if(raddr == NULL && z == NULL) { return NULL; }
+ else if(raddr != NULL) {
+ if(z) {
+ lock_rw_unlock(&z->lock);
+ }
+ return rpz_apply_nsip_trigger(ms, r, raddr, a);
+ } else if(z != NULL) {
+ if(raddr) {
+ lock_rw_unlock(&raddr->lock);
+ }
+ return rpz_apply_nsdname_trigger(ms, r, z, &match, a);
+ } else { return NULL; }
+}
+
+struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms,
+ struct iter_qstate* is)
+{
+ struct auth_zones* az;
+ struct auth_zone* a = NULL;
+ struct rpz* r = NULL;
+ struct local_zone* z = NULL;
+ enum localzone_type lzt;
+ struct dns_msg* ret = NULL;
+
+ if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; }
+ az = ms->env->auth_zones;
+
+ lock_rw_rdlock(&az->rpz_lock);
+
+ for(a = az->rpz_first; a; a = a->rpz_az_next) {
+ lock_rw_rdlock(&a->lock);
+ r = a->rpz;
+ if(r->disabled) {
+ lock_rw_unlock(&a->lock);
+ continue;
+ }
+ z = rpz_find_zone(r->local_zones, is->qchase.qname,
+ is->qchase.qname_len, is->qchase.qclass, 0, 0, 0);
+ if(z && r->action_override == RPZ_DISABLED_ACTION) {
+ if(r->log)
+ log_rpz_apply("qname", z->name, NULL,
+ r->action_override,
+ &ms->qinfo, NULL, ms, r->log_name);
+ if(ms->env->worker)
+ ms->env->worker->stats.rpz_action[r->action_override]++;
+ lock_rw_unlock(&z->lock);
+ z = NULL;
+ }
+ if(z) {
+ break;
+ }
+ /* not found in this auth_zone */
+ lock_rw_unlock(&a->lock);
+ }
+ lock_rw_unlock(&az->rpz_lock);
+
+ if(z == NULL)
+ return NULL;
+ if(r->action_override == RPZ_NO_OVERRIDE_ACTION) {
+ lzt = z->type;
+ } else {
+ lzt = rpz_action_to_localzone_type(r->action_override);
+ }
+
+ if(verbosity >= VERB_ALGO) {
+ char nm[255+1], zn[255+1];
+ dname_str(is->qchase.qname, nm);
+ dname_str(z->name, zn);
+ if(strcmp(zn, nm) != 0)
+ verbose(VERB_ALGO, "rpz: qname trigger after cname %s on %s, with action=%s",
+ zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ else
+ verbose(VERB_ALGO, "rpz: qname trigger after cname %s, with action=%s",
+ nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ }
+ switch(localzone_type_to_rpz_action(lzt)) {
+ case RPZ_NXDOMAIN_ACTION:
+ ret = rpz_synthesize_nxdomain(r, ms, &is->qchase, a);
+ break;
+ case RPZ_NODATA_ACTION:
+ ret = rpz_synthesize_nodata(r, ms, &is->qchase, a);
+ break;
+ case RPZ_TCP_ONLY_ACTION:
+ /* basically a passthru here but the tcp-only will be
+ * honored before the query gets sent. */
+ ms->respip_action_info->action = respip_truncate;
+ ret = NULL;
+ break;
+ case RPZ_DROP_ACTION:
+ ret = rpz_synthesize_nodata(r, ms, &is->qchase, a);
+ ms->is_drop = 1;
+ break;
+ case RPZ_LOCAL_DATA_ACTION:
+ ret = rpz_synthesize_qname_localdata_msg(r, ms, &is->qchase, z, a);
+ if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); }
+ break;
+ case RPZ_PASSTHRU_ACTION:
+ ret = NULL;
+ break;
+ default:
+ verbose(VERB_ALGO, "rpz: qname trigger after cname: bug: unhandled or invalid action: '%s'",
+ rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ ret = NULL;
+ }
+ lock_rw_unlock(&z->lock);
+ lock_rw_unlock(&a->lock);
+ return ret;
+}
+
+static int
+rpz_apply_maybe_clientip_trigger(struct auth_zones* az, struct module_env* env,
+ struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo,
+ uint8_t* taglist, size_t taglen, struct ub_server_stats* stats,
+ sldns_buffer* buf, struct regional* temp,
+ /* output parameters */
+ struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out)
+{
+ int ret = 0;
+ enum rpz_action client_action;
+ struct clientip_synthesized_rr* node = rpz_resolve_client_action_and_zone(
+ az, qinfo, repinfo, taglist, taglen, stats, z_out, a_out, r_out);
+
+ client_action = ((node == NULL) ? RPZ_INVALID_ACTION : node->action);
+
+ if(*z_out == NULL || (client_action != RPZ_INVALID_ACTION &&
+ client_action != RPZ_PASSTHRU_ACTION)) {
+ if(client_action == RPZ_PASSTHRU_ACTION
+ || client_action == RPZ_INVALID_ACTION
+ || (client_action == RPZ_TCP_ONLY_ACTION
+ && !rpz_is_udp_query(repinfo))) {
+ ret = 0;
+ goto done;
+ }
+ stats->rpz_action[client_action]++;
+ if(client_action == RPZ_LOCAL_DATA_ACTION) {
+ rpz_apply_clientip_localdata_action(node, env, qinfo,
+ edns, repinfo, buf, temp, *a_out);
+ } else {
+ if(*r_out && (*r_out)->log)
+ log_rpz_apply(
+ (node?"clientip":"qname"),
+ ((*z_out)?(*z_out)->name:NULL),
+ (node?&node->node:NULL),
+ client_action, qinfo, repinfo, NULL,
+ (*r_out)->log_name);
+ local_zones_zone_answer(*z_out /*likely NULL, no zone*/, env, qinfo, edns,
+ repinfo, buf, temp, 0 /* no local data used */,
+ rpz_action_to_localzone_type(client_action));
+ if(*r_out && (*r_out)->signal_nxdomain_ra &&
+ LDNS_RCODE_WIRE(sldns_buffer_begin(buf))
+ == LDNS_RCODE_NXDOMAIN)
+ LDNS_RA_CLR(sldns_buffer_begin(buf));
+ }
+ ret = 1;
+ goto done;
+ }
+ ret = -1;
+done:
+ if(node != NULL) {
+ lock_rw_unlock(&node->lock);
+ }
+ return ret;
+}
+
+int
+rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env,
+ struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf,
+ struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist,
+ size_t taglen, struct ub_server_stats* stats)
+{
+ struct rpz* r = NULL;
+ struct auth_zone* a = NULL;
+ struct local_zone* z = NULL;
+ int ret;
+ enum localzone_type lzt;
+
+ int clientip_trigger = rpz_apply_maybe_clientip_trigger(az, env, qinfo,
+ edns, repinfo, taglist, taglen, stats, buf, temp, &z, &a, &r);
+ if(clientip_trigger >= 0) {
+ if(a) {
+ lock_rw_unlock(&a->lock);
+ }
+ if(z) {
+ lock_rw_unlock(&z->lock);
+ }
+ return clientip_trigger;
+ }
+
+ if(z == NULL) {
+ if(a) {
+ lock_rw_unlock(&a->lock);
+ }
+ return 0;
+ }
+
+ log_assert(r);
+
+ if(r->action_override == RPZ_NO_OVERRIDE_ACTION) {
+ lzt = z->type;
+ } else {
+ lzt = rpz_action_to_localzone_type(r->action_override);
+ }
+
+ if(verbosity >= VERB_ALGO) {
+ char nm[255+1], zn[255+1];
+ dname_str(qinfo->qname, nm);
+ dname_str(z->name, zn);
+ if(strcmp(zn, nm) != 0)
+ verbose(VERB_ALGO, "rpz: qname trigger %s on %s with action=%s",
+ zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ else
+ verbose(VERB_ALGO, "rpz: qname trigger %s with action=%s",
+ nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt)));
+ }
+
+ ret = rpz_synthesize_qname_localdata(env, r, z, lzt, qinfo, edns, buf, temp,
+ repinfo, stats);
+
lock_rw_unlock(&z->lock);
lock_rw_unlock(&a->lock);
diff --git a/usr.sbin/unbound/services/rpz.h b/usr.sbin/unbound/services/rpz.h
index d5996a6cfa2..c29d30dff50 100644
--- a/usr.sbin/unbound/services/rpz.h
+++ b/usr.sbin/unbound/services/rpz.h
@@ -50,6 +50,7 @@
#include "sldns/sbuffer.h"
#include "daemon/stats.h"
#include "respip/respip.h"
+struct iter_qstate;
/**
* RPZ triggers, only the QNAME trigger is currently supported in Unbound.
@@ -83,6 +84,27 @@ enum rpz_action {
RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/
};
+struct clientip_synthesized_rrset{
+ struct regional* region;
+ struct rbtree_type entries;
+ lock_rw_type lock; /* lock on the respip tree */
+};
+
+struct clientip_synthesized_rr {
+ /** node in address tree */
+ struct addr_tree_node node;
+ /** lock on the node item */
+ lock_rw_type lock;
+ /** tag bitlist */
+ uint8_t* taglist;
+ /** length of the taglist (in bytes) */
+ size_t taglen;
+ /** action for this address span */
+ enum rpz_action action;
+ /** "local data" for this node */
+ struct local_rrset* data;
+};
+
/**
* RPZ containing policies. Pointed to from corresponding auth-zone. Part of a
* linked list to keep configuration order. Iterating or changing the linked
@@ -92,12 +114,17 @@ enum rpz_action {
struct rpz {
struct local_zones* local_zones;
struct respip_set* respip_set;
+ struct clientip_synthesized_rrset* client_set;
+ struct clientip_synthesized_rrset* ns_set;
+ struct local_zones* nsdname_zones;
uint8_t* taglist;
size_t taglistlen;
enum rpz_action action_override;
struct ub_packed_rrset_key* cname_override;
int log;
char* log_name;
+ /** signal NXDOMAIN blocked with unset RA flag */
+ int signal_nxdomain_ra;
struct regional* region;
int disabled;
};
@@ -147,16 +174,39 @@ void rpz_remove_rr(struct rpz* r, size_t aznamelen, uint8_t* dname,
* @param temp: scratchpad
* @param repinfo: reply info
* @param taglist: taglist to lookup.
- * @param taglen: lenth of taglist.
+ * @param taglen: length of taglist.
* @param stats: worker stats struct
* @return: 1 if client answer is ready, 0 to continue resolving
*/
-int rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env,
+int rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env,
struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf,
struct regional* temp, struct comm_reply* repinfo,
uint8_t* taglist, size_t taglen, struct ub_server_stats* stats);
/**
+ * Callback to process when the iterator module is about to send queries.
+ * Checks for nsip and nsdname triggers.
+ * @param qstate: the query state.
+ * @param iq: iterator module query state.
+ * @return NULL if nothing is done. Or a new message with the contents from
+ * the rpz, based on the delegation point. It is allocated in the
+ * qstate region.
+ */
+struct dns_msg* rpz_callback_from_iterator_module(struct module_qstate* qstate,
+ struct iter_qstate* iq);
+
+/**
+ * Callback to process when the iterator module has followed a cname.
+ * There can be a qname trigger for the new query name.
+ * @param qstate: the query state.
+ * @param iq: iterator module query state.
+ * @return NULL if nothing is done. Or a new message with the contents from
+ * the rpz, based on the iq.qchase. It is allocated in the qstate region.
+ */
+struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* qstate,
+ struct iter_qstate* iq);
+
+/**
* Delete RPZ
* @param r: RPZ struct to delete
*/
@@ -186,7 +236,7 @@ enum rpz_action
respip_action_to_rpz_action(enum respip_action a);
/**
- * Prepare RPZ after procesing feed content.
+ * Prepare RPZ after processing feed content.
* @param r: RPZ to use
*/
void rpz_finish_config(struct rpz* r);