diff --git a/bin/tests/system/Makefile.am b/bin/tests/system/Makefile.am index 8ee01e1753..c4ba3a0266 100644 --- a/bin/tests/system/Makefile.am +++ b/bin/tests/system/Makefile.am @@ -11,11 +11,17 @@ dist-hook: SUBDIRS = dyndb/driver dlzexternal/driver hooks/driver +if DNSRPS +SUBDIRS += rpz/testlib +endif + AM_CPPFLAGS += \ - $(LIBISC_CFLAGS) + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) LDADD += \ - $(LIBISC_LIBS) + $(LIBISC_LIBS) \ + $(LIBDNS_LIBS) if HAVE_PERL @@ -48,11 +54,13 @@ pipelined_pipequeries_LDADD = \ rpz_dnsrps_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(LIBDNS_CFLAGS) + $(LIBDNS_CFLAGS) \ + -DLIBRPZ_LIB_OPEN=\"$(abs_builddir)/rpz/testlib/.libs/libdummyrpz.so\" rpz_dnsrps_LDADD = \ $(LDADD) \ - $(LIBDNS_LIBS) + $(LIBDNS_LIBS) \ + -ldl TESTS = diff --git a/bin/tests/system/rpz/dnsrps.c b/bin/tests/system/rpz/dnsrps.c index 7bd7448f56..664af22512 100644 --- a/bin/tests/system/rpz/dnsrps.c +++ b/bin/tests/system/rpz/dnsrps.c @@ -36,7 +36,6 @@ #include #ifdef USE_DNSRPS -#define LIBRPZ_LIB_OPEN DNSRPS_LIB_OPEN #include librpz_t *librpz; diff --git a/bin/tests/system/rpz/testlib/Makefile.am b/bin/tests/system/rpz/testlib/Makefile.am new file mode 100644 index 0000000000..ac66f688fa --- /dev/null +++ b/bin/tests/system/rpz/testlib/Makefile.am @@ -0,0 +1,12 @@ +include $(top_srcdir)/Makefile.top + +AM_CPPFLAGS += \ + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) + +AM_CFLAGS += -Wall -pedantic + +check_LTLIBRARIES = libdummyrpz.la +libdummyrpz_la_SOURCES= dummylib.c test-data.c trpz.h test-data.h +libdummyrpz_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -rpath $(abs_builddir) +LDADD += -lpthread -ldl diff --git a/bin/tests/system/rpz/testlib/dummylib.c b/bin/tests/system/rpz/testlib/dummylib.c new file mode 100644 index 0000000000..2645c289fa --- /dev/null +++ b/bin/tests/system/rpz/testlib/dummylib.c @@ -0,0 +1,2181 @@ +/* + * 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. + */ + +/* + * Limited implementation of the DNSRPS API for testing purposes. + * + * Copyright (c) 2016-2017 Farsight Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "test-data.h" +#include "trpz.h" + +librpz_log_fnc_t *g_log_fnc = NULL; +const char *g_prog_nm = NULL; +bool g_scan_data_file_for_errors = true; + +typedef struct { + void *mutex_ctx; + void *log_ctx; + librpz_mutex_t *mutex_lock_fn; + librpz_mutex_t *mutex_unlock_fn; + librpz_mutex_t *mutex_destroy_fn; +} trpz_clist_t; + +typedef struct { + char *cstr; + bool uses_expired; + trpz_clist_t *pclist; + ssize_t *base_zones; + size_t nbase_zones; +} trpz_client_t; + +typedef struct { + size_t idx; /* value only used for node iteration */ + trpz_client_t *client; + bool have_rd; + char zone[256]; + char domain[256]; + size_t zidx; + trpz_result_t rstack[LIBRPZ_RSP_STACK_DEPTH]; + size_t stack_idx; + trpz_zone_t *all_zones; + trpz_result_t *all_nodes; + size_t num_zones, num_nodes; + ssize_t last_zone; +} trpz_rsp_t; + +librpz_log_level_t g_log_level = LIBRPZ_LOG_TRACE2; +FILE *g_log_outf = NULL; + +static int +apply_all_updates(trpz_rsp_t *trsp); +static void +clear_all_updates(trpz_rsp_t *trsp); + +static int +domain_ntop(const u_char *src, char *dst, size_t dstsiz); +static int +domain_pton2(const char *src, u_char *dst, size_t dstsiz, size_t *dstlen, + bool lower); + +void +trpz_set_log(librpz_log_fnc_t *new_log, const char *prog_nm); +void +trpz_vlog(librpz_log_level_t level, void *ctx, const char *p, va_list args); +void +trpz_log(librpz_log_level_t level, void *ctx, const char *p, ...); +librpz_log_level_t +trpz_log_level_val(librpz_log_level_t level); +void +trpz_vpemsg(librpz_emsg_t *emsg, const char *p, va_list args); +void +trpz_pemsg(librpz_emsg_t *emsg, const char *fmt, ...); +librpz_clist_t * +trpz_clist_create(librpz_emsg_t *emsg, librpz_mutex_t *lock, + librpz_mutex_t *unlock, librpz_mutex_t *mutex_destroy, + void *mutex_ctx, void *log_ctx); +void +trpz_clist_detach(librpz_clist_t **clistp); +bool +trpz_connect(librpz_emsg_t *emsg, librpz_client_t *client, bool optional); +librpz_client_t * +trpz_client_create(librpz_emsg_t *emsg, librpz_clist_t *clist, const char *cstr, + bool use_expired); +void +trpz_client_detach(librpz_client_t **clientp); +bool +trpz_rsp_create(librpz_emsg_t *emsg, librpz_rsp_t **rspp, int *min_ns_dotsp, + librpz_client_t *client, bool have_rd, bool have_do); +void +trpz_rsp_detach(librpz_rsp_t **rspp); +bool +trpz_rsp_push(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +bool +trpz_rsp_pop(librpz_emsg_t *emsg, librpz_result_t *result, librpz_rsp_t *rsp); +bool +trpz_rsp_pop_discard(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +bool +trpz_rsp_domain(librpz_emsg_t *emsg, librpz_domain_buf_t *owner, + librpz_rsp_t *rsp); +bool +trpz_rsp_result(librpz_emsg_t *emsg, librpz_result_t *result, bool recursed, + const librpz_rsp_t *rsp); +bool +trpz_rsp_soa(librpz_emsg_t *emsg, uint32_t *ttlp, librpz_rr_t **rrp, + librpz_domain_buf_t *origin, librpz_result_t *result, + librpz_rsp_t *rsp); +bool +trpz_rsp_rr(librpz_emsg_t *emsg, uint16_t *typep, uint16_t *classp, + uint32_t *ttlp, librpz_rr_t **rrp, librpz_result_t *result, + const uint8_t *qname, size_t qname_size, librpz_rsp_t *rsp); +bool +trpz_ck_domain(librpz_emsg_t *emsg, const uint8_t *domain, size_t domain_size, + librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed, + librpz_rsp_t *rsp); +bool +trpz_ck_ip(librpz_emsg_t *emsg, const void *addr, uint family, + librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed, + librpz_rsp_t *rsp); +bool +trpz_rsp_clientip_prefix(librpz_emsg_t *emsg, librpz_prefix_t *prefix, + librpz_rsp_t *rsp); +bool +trpz_have_trig(librpz_trig_t trig, bool ipv6, const librpz_rsp_t *rsp); +bool +trpz_rsp_forget_zone(librpz_emsg_t *emsg, librpz_cznum_t znum, + librpz_rsp_t *rsp); +char * +trpz_vers_stats(librpz_emsg_t *emsg, librpz_rsp_t *rsp); +bool +trpz_soa_serial(librpz_emsg_t *emsg, uint32_t *serialp, const char *domain_nm, + librpz_rsp_t *rsp); +const char * +trpz_policy2str(librpz_policy_t policy, char *buf, size_t buf_size); + +#define BASE_ZONE_ANY -1 +#define BASE_ZONE_INVALID -2 + +librpz_0_t LIBRPZ_DEF = { + .dnsrpzd_path = "test-only", + .version = "0.0", + .log_level_val = trpz_log_level_val, + .set_log = trpz_set_log, + .vpemsg = trpz_vpemsg, + .pemsg = trpz_pemsg, + .vlog = trpz_vlog, + .log = trpz_log, + .clist_create = trpz_clist_create, + .clist_detach = trpz_clist_detach, + .client_create = trpz_client_create, + .connect = trpz_connect, + .client_detach = trpz_client_detach, + .rsp_create = trpz_rsp_create, + .rsp_detach = trpz_rsp_detach, + .rsp_result = trpz_rsp_result, + .have_trig = trpz_have_trig, + .rsp_domain = trpz_rsp_domain, + .rsp_rr = trpz_rsp_rr, + .rsp_soa = trpz_rsp_soa, + .soa_serial = trpz_soa_serial, + .rsp_push = trpz_rsp_push, + .rsp_pop = trpz_rsp_pop, + .rsp_pop_discard = trpz_rsp_pop_discard, + .rsp_forget_zone = trpz_rsp_forget_zone, + .ck_ip = trpz_ck_ip, + .ck_domain = trpz_ck_domain, + .policy2str = trpz_policy2str, +}; + +/* + * Returns whether or not searching in the specified zone, by index, is + * permitted. A client/RSP state can support a variable number of configured + * zones. + */ +static bool +has_base_zone(trpz_client_t *cli, ssize_t zone) { + size_t n; + + if (cli == NULL || cli->base_zones == NULL || cli->nbase_zones == 0) { + return (false); + } + + for (n = 0; n < cli->nbase_zones; n++) { + if (cli->base_zones[n] == BASE_ZONE_ANY || + cli->base_zones[n] == zone) + { + return (true); + } + } + + return (false); +} + +static bool +pack_soa_record(unsigned char *rdatap, size_t rbufsz, size_t *rdlenp, + const rpz_soa_t *psoa) { + uint32_t *uptr = NULL; + size_t needed = (sizeof(uint32_t) * 5) + strlen(psoa->mname) + 2 + + strlen(psoa->rname) + 2; + size_t mlen = 0, rlen = 0, used = 0; + + if (needed > rbufsz) { + return (false); + } + + if (!domain_pton2(psoa->mname, rdatap, rbufsz, &rlen, true)) { + return (false); + } + + if (!domain_pton2(psoa->rname, rdatap + rlen, rbufsz - rlen, &mlen, + true)) + { + return (false); + } + + used = rlen + mlen; + + uptr = (uint32_t *)(rdatap + rlen + mlen); + *uptr++ = htonl(psoa->serial); + *uptr++ = htonl(psoa->refresh); + *uptr++ = htonl(psoa->retry); + *uptr++ = htonl(psoa->expire); + *uptr++ = htonl(psoa->minimum); + + used += (sizeof(uint32_t) * 5); + + if (rdlenp) { + *rdlenp = used; + } + + return (true); +} + +static void +do_log(librpz_log_level_t level, void *ctx, const char *fmt, va_list args) { + if (level > g_log_level) { + return; + } + + if (g_log_fnc != NULL) { + char lbuf[8192] = { 0 }; + + vsnprintf(lbuf, sizeof(lbuf) - 1, fmt, args); + g_log_fnc(level, ctx, lbuf); + return; + } + + if (g_log_outf == NULL) { + return; + } + + vfprintf(g_log_outf, fmt, args); + fprintf(g_log_outf, "\n"); + return; +} + +void +trpz_vlog(librpz_log_level_t level, void *ctx, const char *p, va_list args) { + do_log(level, ctx, p, args); + return; +} + +void +trpz_log(librpz_log_level_t level, void *ctx, const char *p, ...) { + va_list ap; + + va_start(ap, p); + trpz_vlog(level, ctx, p, ap); + va_end(ap); + + return; +} + +void +trpz_set_log(librpz_log_fnc_t *new_log, const char *prog_nm) { + assert(new_log != NULL || prog_nm != NULL); + + if (new_log != NULL) { + g_log_fnc = new_log; + } + + if (prog_nm != NULL) { + g_prog_nm = prog_nm; + } +} + +librpz_log_level_t +trpz_log_level_val(librpz_log_level_t level) { + if (level >= LIBRPZ_LOG_INVALID) { + return (g_log_level); + } + + g_log_level = (level < LIBRPZ_LOG_FATAL) ? LIBRPZ_LOG_FATAL : level; + + return (g_log_level); +} + +void +trpz_vpemsg(librpz_emsg_t *emsg, const char *p, va_list args) { + if (emsg == NULL) { + return; + } + + vsnprintf(emsg->c, sizeof(emsg->c), p, args); + emsg->c[sizeof(emsg->c) - 1] = 0; + return; +} + +void +trpz_pemsg(librpz_emsg_t *emsg, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + trpz_vpemsg(emsg, fmt, ap); + va_end(ap); + + return; +} + +/* + * Scan the data file for errors and log anything found that's critically + * wrong. + */ +static void +scan_data_file_for_errors(void *lctx) { + char *updfile = NULL, *fname = NULL, *last = NULL; + + updfile = getenv("DNSRPS_TEST_UPDATE_FILE"); + if (updfile == NULL) { + return; + } + + fname = strtok_r(updfile, ":", &last); + + while (fname) { + char *errp = NULL; + int ret; + + ret = sanity_check_data_file(fname, &errp); + + if ((ret < 0) && errp) { + trpz_log(LIBRPZ_LOG_ERROR, lctx, errp); + free(errp); + } + + fname = strtok_r(NULL, ":", &last); + } + + return; +} + +librpz_clist_t * +trpz_clist_create(librpz_emsg_t *emsg, librpz_mutex_t *lock, + librpz_mutex_t *unlock, librpz_mutex_t *mutex_destroy, + void *mutex_ctx, void *log_ctx) { + trpz_clist_t *result = NULL; + + result = calloc(1, sizeof(*result)); + if (result == NULL) { + trpz_pemsg(emsg, "calloc: %s", strerror(errno)); + return (NULL); + } + + result->mutex_ctx = mutex_ctx; + result->log_ctx = log_ctx; + result->mutex_lock_fn = lock; + result->mutex_unlock_fn = unlock; + result->mutex_destroy_fn = mutex_destroy; + + if (g_scan_data_file_for_errors) { + scan_data_file_for_errors(log_ctx); + } + + return ((librpz_clist_t *)result); +} + +void +trpz_clist_detach(librpz_clist_t **clistp) { + if (clistp != NULL && *clistp != NULL) { + librpz_clist_t *clist = *clistp; + *clistp = NULL; + free(clist); + } + + return; +} + +bool +trpz_connect(librpz_emsg_t *emsg, librpz_client_t *client, bool optional) { + UNUSED(optional); + + if (client == NULL) { + trpz_pemsg(emsg, "Can't connect to null client"); + return (false); + } + + return (true); +} + +const char * +trpz_policy2str(librpz_policy_t policy, char *buf, size_t buf_size) { + const char *pname = NULL; + + if (buf == NULL || buf_size == 0) { + return (NULL); + } + + switch (policy) { + case LIBRPZ_POLICY_UNDEFINED: + pname = "UNDEFINED"; + break; + case LIBRPZ_POLICY_DELETED: + pname = "DELETED"; + break; + case LIBRPZ_POLICY_PASSTHRU: + pname = "PASSTHRU"; + break; + case LIBRPZ_POLICY_DROP: + pname = "DROP"; + break; + case LIBRPZ_POLICY_TCP_ONLY: + pname = "TCP-ONLY"; + break; + case LIBRPZ_POLICY_NXDOMAIN: + pname = "NXDOMAIN"; + break; + case LIBRPZ_POLICY_NODATA: + pname = "NODATA"; + break; + case LIBRPZ_POLICY_RECORD: + pname = "RECORD"; + break; + case LIBRPZ_POLICY_GIVEN: + pname = "GIVEN"; + break; + case LIBRPZ_POLICY_DISABLED: + pname = "DISABLED"; + break; + case LIBRPZ_POLICY_CNAME: + pname = "CNAME"; + break; + default: + pname = "UNKNOWN"; + break; + } + + strncpy(buf, pname, buf_size); + buf[buf_size - 1] = 0; + return (buf); +} + +/* + * Get the entire set of zones configured by the config string, + * and bind then to the specified RSP state. + * + * The array of active zone indices is returned by the function on success, + * or NULL on failure. The total number of zones is stored in pnzones. + */ +static ssize_t * +get_cstr_zones(const char *cstr, trpz_rsp_t *trsp, size_t *pnzones) { + char tmpc[8192] = { 0 }; + char *tptr = tmpc, *tok = NULL; + size_t nzones = 0, cur_idx = 0; + ssize_t *result = NULL; + unsigned long zflags = 0; + + result = calloc(trsp->num_zones + 1, sizeof(*result)); + if (result == NULL) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + if (cstr == NULL) { + result[0] = BASE_ZONE_ANY; + *pnzones = 1; + return (result); + } + + strncpy(tmpc, cstr, sizeof(tmpc) - 1); + *pnzones = 0; + + while (tptr != NULL && *tptr != '\0') { + tok = strsep(&tptr, ";\n"); + + while (isspace(*tok)) { + tok++; + } + + if (strncasecmp(tok, "zone ", 5) == 0) { + char zcmd[1024] = { 0 }; + char *qend = NULL; + size_t zind = 0, old_zct = trsp->num_zones; + unsigned long zopts = 0; + + tok += 5; + + while (isspace(*tok)) { + tok++; + } + + if (*tok == '"') { + qend = strchr(++tok, '"'); + if (qend == NULL) { + fprintf(stderr, "Error parsing cstr " + "contents!\n"); + free(result); + return (NULL); + } + + *qend++ = 0; + + if (tok[strlen(tok) - 1] == '.') { + tok[strlen(tok) - 1] = 0; + } + + } else { + qend = tok; + } + + while (*qend != '\0' && !isspace(*qend)) { + qend++; + } + + if (*qend != '\0') { + *qend++ = '\0'; + zopts = parse_zone_options(qend); + } + + snprintf(zcmd, sizeof(zcmd) - 1, "zone %s 1", tok); + + if (apply_update(zcmd, &(trsp->all_nodes), + &(trsp->num_nodes), &(trsp->all_zones), + &(trsp->num_zones), 0, zopts, + NULL) < 0) + { + fprintf(stderr, "Internal error {%s}!\n", zcmd); + free(result); + return (NULL); + } + + if (trsp->num_zones > old_zct) { + result = realloc(result, + ((trsp->num_zones + 1) * + sizeof(*result))); + if (result == NULL) { + perror("realloc"); + exit(EXIT_FAILURE); + } + } + + for (zind = 0; zind < trsp->num_zones; zind++) { + if (!strcmp(trsp->all_zones[zind].name, tok)) { + break; + } + } + + if (zind == trsp->num_zones) { + free(result); + return (NULL); + } + + result[cur_idx++] = zind; + *pnzones = cur_idx; + nzones++; + } else { + unsigned long flags; + + flags = parse_zone_options(tok); + zflags |= (flags & (ZOPT_QNAME_AS_NS | ZOPT_IP_AS_NS | + ZOPT_RECURSIVE_ONLY | + ZOPT_NOT_RECURSIVE_ONLY | + ZOPT_NO_QNAME_WAIT_RECURSE | + ZOPT_NO_NSIP_WAIT_RECURSE)); + } + + tok = NULL; + } + + if (nzones == 0) { + free(result); + return (NULL); + } + + if (zflags != 0) { + size_t n; + + for (n = 0; n < trsp->num_zones; n++) { + if (zflags & ZOPT_QNAME_AS_NS) { + trsp->all_zones[n].qname_as_ns = true; + } + + if (zflags & ZOPT_IP_AS_NS) { + trsp->all_zones[n].ip_as_ns = true; + } + + if (zflags & ZOPT_RECURSIVE_ONLY) { + trsp->all_zones[n].not_recursive_only = false; + } else if (zflags & ZOPT_NOT_RECURSIVE_ONLY) { + trsp->all_zones[n].not_recursive_only = true; + } + + if (zflags & ZOPT_NO_QNAME_WAIT_RECURSE) { + trsp->all_zones[n].no_qname_wait_recurse = true; + } + + if (zflags & ZOPT_NO_NSIP_WAIT_RECURSE) { + trsp->all_zones[n].no_nsip_wait_recurse = true; + } + } + } + + return (result); +} + +librpz_client_t * +trpz_client_create(librpz_emsg_t *emsg, librpz_clist_t *clist, const char *cstr, + bool use_expired) { + trpz_client_t *result = NULL; + + if (clist == NULL) { + trpz_pemsg(emsg, "clist was NULL\n"); + return (NULL); + } + + result = calloc(1, sizeof(*result)); + if (result == NULL) { + trpz_pemsg(emsg, "calloc: %s", strerror(errno)); + return (NULL); + } + + result->cstr = strdup(cstr); + if (result->cstr == NULL) { + trpz_pemsg(emsg, "strdup: %s", strerror(errno)); + free(result); + return (NULL); + } + + result->uses_expired = use_expired; + result->pclist = (trpz_clist_t *)clist; + + return ((librpz_client_t *)result); +} + +void +trpz_client_detach(librpz_client_t **clientp) { + if (clientp != NULL && *clientp != NULL) { + librpz_client_t *client = *clientp; + *clientp = NULL; + free(client); + } + + return; +} + +/* + * If the DNSRPS_TEST_UPDATE_FILE env variable is set, + * load the current list of test nodes from the specified data file. + * + * Any existing nodes are first destroyed. + */ +static int +apply_all_updates(trpz_rsp_t *trsp) { + char *updfile = NULL, *fname = NULL, *last = NULL; + + updfile = getenv("DNSRPS_TEST_UPDATE_FILE"); + if (updfile == NULL) { + return (0); + } + + fname = strtok_r(updfile, ":", &last); + while (fname != NULL) { + char *errp = NULL; + int ret; + + ret = load_all_updates(fname, &trsp->all_nodes, + &trsp->num_nodes, &trsp->all_zones, + &trsp->num_zones, &errp); + + if (errp) { + fprintf(stderr, "Error loading updates: %s\n", errp); + free(errp); + } + + if (ret < 0) { + return (-1); + } + + fname = strtok_r(NULL, ":", &last); + } + + return (0); +} + +static void +clear_all_updates(trpz_rsp_t *trsp) { + if (trsp == NULL) { + return; + } + + if (trsp->all_zones != NULL) { + free(trsp->all_zones); + } + + trsp->all_zones = NULL; + trsp->num_zones = 0; + + if (trsp->all_nodes != NULL) { + size_t n; + + for (n = 0; n < trsp->num_nodes; n++) { + if (trsp->all_nodes[n].rrs) { + size_t m; + + for (m = 0; m < trsp->all_nodes[n].nrrs; m++) { + if (trsp->all_nodes[n].rrs[m].rdata) { + free(trsp->all_nodes[n] + .rrs[m] + .rdata); + } + } + + free(trsp->all_nodes[n].rrs); + } + } + + free(trsp->all_nodes); + } + + trsp->all_nodes = NULL; + trsp->num_nodes = 0; + + return; +} + +/* + * Start a set of RPZ queries for a single DNS response. + */ +bool +trpz_rsp_create(librpz_emsg_t *emsg, librpz_rsp_t **rspp, int *min_ns_dotsp, + librpz_client_t *client, bool have_rd, bool have_do) { + trpz_client_t *cli = (trpz_client_t *)client; + trpz_rsp_t *result = NULL; + + UNUSED(min_ns_dotsp); + UNUSED(have_do); + + if (client == NULL) { + trpz_pemsg(emsg, "client was NULL"); + return (false); + } else if (rspp == NULL) { + trpz_pemsg(emsg, "rspp was NULL"); + return (false); + } else if (cli->cstr == NULL) { + trpz_pemsg(emsg, "no valid policy zone specified"); + return (false); + } + + result = calloc(1, sizeof(*result)); + if (result == NULL) { + trpz_pemsg(emsg, "calloc: %s", strerror(errno)); + return (false); + } + + result->idx = 0; + result->client = cli; + result->have_rd = have_rd; + result->stack_idx = 1; + result->last_zone = -1; + + *rspp = (librpz_rsp_t *)result; + + clear_all_updates(result); + cli->base_zones = get_cstr_zones(cli->cstr, result, + &(cli->nbase_zones)); + + if (cli->base_zones == NULL) { + trpz_pemsg(emsg, "no valid policy zone specified"); + free(*rspp); + *rspp = NULL; + return (false); + } + + if (apply_all_updates(result) < 0) { + trpz_pemsg(emsg, "internal error loading test data 1"); + return (false); + } + + return (true); +} + +bool +trpz_rsp_push(librpz_emsg_t *emsg, librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + + UNUSED(emsg); + + if (trsp->stack_idx == 0) { + memset(&(trsp->rstack[0]), 0, sizeof(trsp->rstack[0]) * 2); + trsp->stack_idx++; + return (true); + } else if (trsp->stack_idx >= LIBRPZ_RSP_STACK_DEPTH) { + return (false); + } + + memmove(&(trsp->rstack[1]), &(trsp->rstack[0]), + (trsp->stack_idx * sizeof(trsp->rstack[0]))); + trsp->stack_idx++; + + return (true); +} + +bool +trpz_rsp_pop(librpz_emsg_t *emsg, librpz_result_t *result, librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + + UNUSED(emsg); + + if (trsp->stack_idx <= 1) { + return (false); + } + + memmove(&(trsp->rstack[0]), &(trsp->rstack[1]), + ((trsp->stack_idx - 1) * sizeof(trsp->rstack[0]))); + memmove(result, &(trsp->rstack[0].result), sizeof(*result)); + trsp->stack_idx--; + + return (true); +} + +bool +trpz_rsp_pop_discard(librpz_emsg_t *emsg, librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + + UNUSED(emsg); + + if (trsp->stack_idx == 0) { + return (false); + } else if (trsp->stack_idx == 1) { + return (true); + } + + if (trsp->stack_idx > 1) { + memmove(&(trsp->rstack[1]), &(trsp->rstack[2]), + ((trsp->stack_idx - 2) * sizeof(trsp->rstack[0]))); + } + + trsp->stack_idx--; + + return (true); +} + +void +trpz_rsp_detach(librpz_rsp_t **rspp) { + if (rspp && *rspp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)*rspp; + + clear_all_updates(trsp); + + free(*rspp); + *rspp = NULL; + } + + return; +} + +bool +trpz_rsp_domain(librpz_emsg_t *emsg, librpz_domain_buf_t *owner, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + const char *tstr = ""; + char tmpname[256] = { 0 }; + size_t osz = 0; + uint32_t n; + + if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL"); + return (false); + } else if (trsp->stack_idx == 0) { + trpz_pemsg(emsg, "domain not found [1]"); + return (false); + } else if (trsp->rstack[0].result.policy == LIBRPZ_POLICY_UNDEFINED) { + trpz_pemsg(emsg, "domain not found [2]"); + return (false); + } + + if (trsp->all_zones[trsp->rstack[0].result.dznum].forgotten) { + trpz_pemsg(emsg, "domain not found [3]"); + memset(owner, 0, sizeof(*owner)); + return (true); + } + + switch (trsp->rstack[0].result.trig) { + case LIBRPZ_TRIG_CLIENT_IP: + tstr = "rpz-client-ip."; + break; + case LIBRPZ_TRIG_IP: + tstr = "rpz-ip."; + break; + case LIBRPZ_TRIG_NSDNAME: + tstr = "rpz-nsdname."; + break; + case LIBRPZ_TRIG_NSIP: + tstr = "rpz-nsip."; + break; + default: + break; + } + + n = snprintf(tmpname, sizeof(tmpname), "%s.%s%s", trsp->rstack[0].dname, + tstr, trsp->all_zones[trsp->rstack[0].result.dznum].name); + if (n > sizeof(tmpname)) { + trpz_pemsg(emsg, "%s truncated", tmpname); + return (false); + } + + if (!domain_pton2(tmpname, owner->d, sizeof(owner->d), &osz, true)) { + trpz_pemsg(emsg, "unable to read hostname from rsp!"); + return (false); + } + + owner->size = osz; + return (true); +} + +bool +trpz_rsp_result(librpz_emsg_t *emsg, librpz_result_t *result, bool recursed, + const librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + + UNUSED(recursed); + + if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL!"); + return (false); + } else if (result == NULL) { + trpz_pemsg(emsg, "result was NULL"); + return (false); + } + + if (trsp->stack_idx == 0) { + memset(result, 0, sizeof(*result)); + result->policy = LIBRPZ_POLICY_UNDEFINED; + } else { + if (trsp->rstack[0].result.policy && trsp->rstack[0].nrrs && + (trsp->rstack[0].result.policy != LIBRPZ_POLICY_DISABLED)) + { + trsp->rstack[0].result.next_rr = + trsp->rstack[0].rrs[0].rrn; + } + + trsp->rstack[0].rridx = 0; + + memmove(result, &(trsp->rstack[0].result), sizeof(*result)); + + if (result->policy && trsp->rstack[0].poverride) { + result->policy = trsp->rstack[0].poverride; + } + } + + return (true); +} + +bool +trpz_rsp_soa(librpz_emsg_t *emsg, uint32_t *ttlp, librpz_rr_t **rrp, + librpz_domain_buf_t *origin, librpz_result_t *result, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + librpz_rr_t *rres = NULL; + rpz_soa_t tmpsoa; + unsigned char *rdbuf = NULL; + char tmp_rname[1024] = { 0 }; + size_t rdlen = 0; + + UNUSED(ttlp); + + if (result == NULL) { + trpz_pemsg(emsg, "result was NULL!"); + return (false); + } else if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL!"); + return (false); + } + + if (trsp->zidx >= trsp->num_zones) { + trpz_pemsg(emsg, "bad zone"); + return (false); + } + + rdbuf = calloc(1024, 1); + if (rdbuf == NULL) { + trpz_pemsg(emsg, "calloc: %s", strerror(errno)); + return (false); + } + + rres = (librpz_rr_t *)rdbuf; + + rres->type = htons(6); + rres->class = htons(1); + rres->ttl = htonl(60); + + memmove(&tmpsoa, &g_soa_record, sizeof(tmpsoa)); + tmpsoa.serial = trsp->all_zones[result->dznum].serial; + tmpsoa.mname = trsp->all_zones[result->dznum].name; + + snprintf(tmp_rname, sizeof(tmp_rname) - 1, "hostmaster.ns.%s", + tmpsoa.mname); + tmpsoa.rname = tmp_rname; + + if (tmpsoa.serial == 0) { + tmpsoa.serial = time(NULL); + } + + if (!pack_soa_record(rres->rdata, 1024 - sizeof(*rres), &rdlen, + &tmpsoa)) + { + trpz_pemsg(emsg, "Error packing SOA reply"); + free(rdbuf); + return (false); + } + + rres->rdlength = htons(rdlen); + + if (rrp) { + *rrp = rres; + } + + if (origin) { + uint8_t *buf = NULL; + int nbytes; + + if ((nbytes = wdns_str_to_name(tmpsoa.mname, &buf, 1)) < 0) { + trpz_pemsg(emsg, "Error packing domain"); + free(rdbuf); + return (false); + } + + memset(origin, 0, sizeof(*origin)); + memmove(origin->d, buf, nbytes); + origin->size = nbytes; + free(buf); + } + + return (true); +} + +/* + * Compare a query domain against a record, allowing for the possibility of + * a wildcard match. + */ +static int +domain_cmp(const char *query, const char *record, bool *wildp) { + const char *end = NULL; + size_t cmplen; + + end = record + strlen(record); + + cmplen = end - record; + + *wildp = false; + + if (record != NULL && *record == '*' && record[1] == '.') { + const char *rptr = record + 2; + + if ((cmplen - 2) < strlen(query)) { + const char *qptr = NULL; + + qptr = query + strlen(query) - (cmplen - 2); + + if (strncmp(qptr, rptr, (cmplen - 2)) == 0) { + *wildp = true; + return (0); + } + } + } + + if (strlen(query) > cmplen) { + return (1); + } else if (strlen(query) < cmplen) { + return (-1); + } + + return ((strncmp(record, query, cmplen))); +} + +/* + * Count the number of labels in the given domain name. + */ +static size_t +count_labels(const char *domain) { + const char *dptr = NULL; + size_t result = 1; + + if (domain == NULL || *domain == '\0') { + return (0); + } + + dptr = domain + strlen(domain); + + while (1) { + while ((dptr >= domain) && (*dptr != '.')) { + dptr--; + } + + if (dptr <= domain) { + break; + } + + result++; + dptr--; + } + + return (result); +} + +/* + * Does the newly found result supercede the old result in precedence? + * This function is used to determine whether a match on the result stack + * should be overwritten by another match of higher precedence - or whether + * the old match should remain, as-is. + * + * 1. "CNAME or DNAME Chain Position" Precedence Rule + * 2. "RPZ Ordering" Precedence Rule [zone order] + * 3. "Domain Name Matching" Precedence Rule [QNAME/NSDNAME - label count] + * 4. "Trigger Type" Precedence Rule + * 5. "Name Order" Precedence Rule [NSDNAME] + * 6. "Prefix Length" Precedence Rule + * 7. "IP Address Order" Precedence Rule + */ +static bool +result_supercedes(const trpz_result_t *new, const trpz_result_t *old) { + size_t nsz, osz; + + if (old == NULL || old->result.policy == 0 || old->dname == NULL || + old->dname[0] == '\0') + { + return (true); + } + + if (new->result.dznum < old->result.dznum) { + return (true); + } else if (new->result.dznum > old->result.dznum) { + return (false); + } + + nsz = count_labels(new->dname); + osz = count_labels(old->dname); + + /* More matching labels is better. */ + if (nsz > osz) { + return (true); + } else if (nsz < osz) { + return (false); + } + + if (new->result.trig < old->result.trig) { + return (true); + } else if (new->result.trig > old->result.trig) { + return (false); + } + + return (true); +} + +static bool +result_supercedes_address(const trpz_result_t *new, const trpz_result_t *old) { + if (old == NULL || old->result.policy == 0 || old->dname == NULL || + old->dname[0] == '\0') + { + return (true); + } + + if (new->result.dznum < old->result.dznum) { + return (true); + } else if (new->result.dznum > old->result.dznum) { + return (false); + } + + if (new->result.trig < old->result.trig) { + return (true); + } else if (new->result.trig > old->result.trig) { + return (false); + } + + if ((new->flags &NODE_FLAG_IPV6_ADDRESS) && + !(old->flags & NODE_FLAG_IPV6_ADDRESS)) + { + return (true); + } + + /* + * XXX: this is broken. Needs proper address comparison. For + * example, by most specific prefix match. + */ + if (strcmp(old->dname, new->dname) < 0) { + return (false); + } + + return (true); +} + +bool +trpz_ck_domain(librpz_emsg_t *emsg, const uint8_t *domain, size_t domain_size, + librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + char dname[256] = { 0 }; + librpz_trig_t toverride = LIBRPZ_TRIG_BAD; + ssize_t fidx = -1, nfidx = -1; + bool wild; + size_t n; + + if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL!"); + return (false); + } else if (domain == NULL || domain_size == 0) { + trpz_pemsg(emsg, "domain was empty"); + return (false); + } else if (trig != LIBRPZ_TRIG_QNAME && trig != LIBRPZ_TRIG_NSDNAME) { + trpz_pemsg(emsg, "invalid trigger type"); + return (false); + } else if (!domain_ntop(domain, dname, sizeof(dname))) { + trpz_pemsg(emsg, "domain was invalid"); + return (false); + } + + if (trsp->stack_idx == 0) { + trsp->stack_idx = 1; + } + + for (n = 0; n < trsp->num_nodes; n++) { + int pos = 0; + + if (!trsp->have_rd && + !trsp->all_zones[trsp->all_nodes[n].result.dznum] + .not_recursive_only) + { + continue; + } + + if (pos || + ((trig == trsp->all_nodes[n].result.trig) && + (!domain_cmp(dname, trsp->all_nodes[n].dname, &wild)))) + { + if ((trsp->all_zones[trsp->all_nodes[n].result.dznum] + .no_qname_wait_recurse || + trsp->all_zones[trsp->all_nodes[n].result.dznum] + .not_recursive_only || + recursed) && + has_base_zone(trsp->client, + trsp->all_nodes[n].result.dznum) && + !trsp->all_zones[trsp->all_nodes[n].result.dznum] + .forgotten) + { + if (fidx < 0) { + fidx = n; + toverride = LIBRPZ_TRIG_BAD; + } + } + if (!wild) { + break; + } + + /* + * Some inelegant special handling for qname_as_ns + * feature. + */ + } else if (trsp->all_zones[trsp->all_nodes[n].result.dznum] + .qname_as_ns && + (((trig == LIBRPZ_TRIG_QNAME) && + (trsp->all_nodes[n].result.trig == + LIBRPZ_TRIG_NSDNAME)) || + ((trig == LIBRPZ_TRIG_NSDNAME) && + (trsp->all_nodes[n].result.trig == + LIBRPZ_TRIG_QNAME)))) + { + if (!domain_cmp(dname, trsp->all_nodes[n].dname, &wild)) + { + if (recursed && + has_base_zone( + trsp->client, + trsp->all_nodes[n].result.dznum) && + !trsp->all_zones[trsp->all_nodes[n] + .result.dznum] + .forgotten) + { + if (fidx < 0) { + fidx = n; + toverride = trig; + } + } + if (!wild) { + break; + } + } + } + } + + if (!trsp->have_rd && (fidx < 0) && (nfidx < 0)) { + goto out; + } + + if (recursed && (fidx < 0) && trsp->rstack[0].hidden_policy) { + if (trsp->rstack[0].result.trig <= trig) { + trsp->rstack[0].result.policy = + trsp->rstack[0].hidden_policy; + trsp->rstack[0].result.zpolicy = + trsp->rstack[0].hidden_policy; + trsp->rstack[0].hidden_policy = LIBRPZ_POLICY_UNDEFINED; + return (true); + } + } + + if (fidx >= 0) { + bool disabled = false; + + if (!result_supercedes(&(trsp->all_nodes[fidx]), + &(trsp->rstack[0]))) + { + return (true); + } + + strncpy(trsp->domain, dname, sizeof(trsp->domain)); + memmove(&(trsp->rstack[0]), &(trsp->all_nodes[fidx]), + sizeof(trsp->rstack[0])); + trsp->rstack[0].result.hit_id = hit_id; + trsp->last_zone = trsp->rstack[0].result.dznum; + + if (trsp->all_zones[trsp->rstack[0].result.dznum].flags) { + unsigned long flags = + trsp->all_zones[trsp->rstack[0].result.dznum] + .flags; + librpz_policy_t force_policy = 0; + + if (flags & ZOPT_POLICY_NODATA) { + force_policy = LIBRPZ_POLICY_NODATA; + } else if (flags & ZOPT_POLICY_PASSTHRU) { + force_policy = LIBRPZ_POLICY_PASSTHRU; + } else if (flags & ZOPT_POLICY_NXDOMAIN) { + force_policy = LIBRPZ_POLICY_NXDOMAIN; + } else if (flags & ZOPT_POLICY_DROP) { + force_policy = LIBRPZ_POLICY_DROP; + } else if (flags & ZOPT_POLICY_TCP_ONLY) { + force_policy = LIBRPZ_POLICY_TCP_ONLY; + } else if (flags & ZOPT_POLICY_DISABLED) { + disabled = true; + } + + if (force_policy) { + trsp->rstack[0].result.policy = force_policy; + trsp->rstack[0].result.zpolicy = force_policy; + trsp->rstack[0].poverride = force_policy; + } + } + + if (toverride) { + trsp->rstack[0].result.trig = toverride; + } + + if (disabled) { + trsp->rstack[0].poverride = LIBRPZ_POLICY_DISABLED; + } else if (!recursed) { + ssize_t m; + + /* + * If recursed is not set, then an earlier zone of + * higher precedence may not contain a rule of + * trigger types rpz-ip, rpz-nsip, or rpz-nsdname - + * which are by nature post-recursion rules. + */ + for (m = trsp->all_nodes[fidx].result.dznum - 1; m >= 0; + m--) + { + if (trsp->all_zones[m] + .has_triggers[0][LIBRPZ_TRIG_IP] || + trsp->all_zones[m] + .has_triggers[1][LIBRPZ_TRIG_IP] || + trsp->all_zones[m] + .has_triggers[0][LIBRPZ_TRIG_NSIP] || + trsp->all_zones[m] + .has_triggers[1][LIBRPZ_TRIG_NSIP] || + trsp->all_zones[m] + .has_triggers[0] + [LIBRPZ_TRIG_NSDNAME]) + { + trsp->rstack[0].result.policy = + LIBRPZ_POLICY_UNDEFINED; + break; + } + } + } + + return (true); + } else if (nfidx >= 0) { + strncpy(trsp->domain, dname, sizeof(trsp->domain)); + memmove(&(trsp->rstack[0]), &(trsp->all_nodes[nfidx]), + sizeof(trsp->rstack[0])); + trsp->last_zone = trsp->all_nodes[nfidx].result.dznum; + trsp->rstack[0].hidden_policy = trsp->rstack[0].result.policy; + trsp->rstack[0].result.hit_id = hit_id; + trsp->rstack[0].result.policy = LIBRPZ_POLICY_UNDEFINED; + trsp->rstack[0].result.zpolicy = LIBRPZ_POLICY_UNDEFINED; + return (true); + } + +out: + if (trsp->rstack[0].result.policy == LIBRPZ_POLICY_UNDEFINED) { + memset(&(trsp->rstack[0]), 0, sizeof(trsp->rstack[0])); + } + + return (true); +} + +static void +rpzify_ipv6_str(char *buf) { + char tmpb[512] = { 0 }, *tptr = NULL; + + strncpy(tmpb, buf, sizeof(tmpb) - 1); + memset(buf, 0, strlen(buf)); + tptr = tmpb + strlen(tmpb); + + strcat(buf, "128."); + + while (tptr > tmpb) { + if (*tptr == ':') { + strcat(buf, tptr + 1); + strcat(buf, "."); + } + + tptr--; + + if ((*tptr == ':') && (tptr[0] == tptr[1])) { + strcat(buf, "zz"); + } + + if (tptr[1] == ':') { + tptr[1] = 0; + } + } + + strcat(buf, tptr); + + return; +} + +static uint32_t +get_mask(unsigned char prefix) { + uint32_t result = 0; + unsigned char n; + + if (prefix == 0) { + return (0); + } else if (prefix >= 32) { + return (~(0)); + } + + for (n = 1; n < prefix; n++) { + result |= (1 << n); + } + + return (result); +} + +/* XXX: this is broken for handling subnet masks in IPv6. */ +static int +address_cmp(const char *addrstr, const void *addr, uint family, + unsigned int *pmask) { + char abuf[256] = { 0 }; + int ipstr[8] = { 0 }; + unsigned int nmask = 32; + + if (family == AF_INET6) { + if (!inet_ntop(AF_INET6, addr, abuf, sizeof(abuf))) { + return (-1); + } + + rpzify_ipv6_str(abuf); + } else if (family == AF_INET) { + char newstr[32] = { 0 }; + in_addr_t a1, a2; + + if (sscanf(addrstr, "%d.%d.%d.%d.%d", &ipstr[0], &ipstr[1], + &ipstr[2], &ipstr[3], &ipstr[4]) == 5) + { + nmask = ipstr[0]; + if (nmask > 32) { + return (-1); + } + } else if (sscanf(addrstr, "%d.%d.%d.%d", &ipstr[1], &ipstr[2], + &ipstr[3], &ipstr[4]) != 4) + { + perror("bad address format"); + return (-1); + } + + if (ipstr[1] > 255 || ipstr[2] > 255 || ipstr[3] > 255 || + ipstr[4] > 255) { + perror("bad address format"); + return (-1); + } + + sprintf(newstr, "%u.%u.%u.%u", ipstr[4], ipstr[3], ipstr[2], + ipstr[1]); + + a1 = inet_addr(newstr); + if (a1 == INADDR_NONE) { + perror("inet_addr"); + return (-1); + } else { + uint32_t m; + + memmove(&a2, addr, sizeof(uint32_t)); + m = get_mask(nmask); + + if (pmask) { + *pmask = nmask; + } + + return (((a1 & m) == (a2 & m)) ? 0 : 1); + } + + } else { + return (-1); + } + + if (strcmp(addrstr, abuf) == 0) { + if (pmask) { + *pmask = nmask; + } + + return (0); + } + + return (1); +} + +bool +trpz_ck_ip(librpz_emsg_t *emsg, const void *addr, uint family, + librpz_trig_t trig, librpz_result_id_t hit_id, bool recursed, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + size_t n, last_mask = 0; + ssize_t fidx = -1, nfidx = -1; + + if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL!"); + return (false); + } else if (addr == NULL) { + trpz_pemsg(emsg, "addr was empty"); + return (false); + } else if (trig != LIBRPZ_TRIG_IP && trig != LIBRPZ_TRIG_CLIENT_IP && + trig != LIBRPZ_TRIG_NSIP) + { + trpz_pemsg(emsg, "trigger type not supported for IP"); + return (false); + } + + if (trsp->stack_idx == 0) { + trsp->stack_idx = 1; + } + + /* The final match is the most specifically-matching netmask. */ + for (n = 0; n < trsp->num_nodes; n++) { + unsigned int mask = 0; + bool amatch = false; + + if (!trsp->have_rd && + !trsp->all_zones[trsp->all_nodes[n].result.dznum] + .not_recursive_only) + { + continue; + } + + if (trsp->all_zones[trsp->all_nodes[n].result.dznum].ip_as_ns && + (((trig == LIBRPZ_TRIG_IP) && + (trsp->all_nodes[n].result.trig == LIBRPZ_TRIG_NSIP)) || + ((trig == LIBRPZ_TRIG_NSIP) && + (trsp->all_nodes[n].result.trig == LIBRPZ_TRIG_IP))) && + !address_cmp(trsp->all_nodes[n].dname, addr, family, &mask)) + { + amatch = true; + } else if (trsp->all_nodes[n].match_trig != trig) { + continue; + } + + if (amatch || + !address_cmp(trsp->all_nodes[n].dname, addr, family, &mask)) + { + if ((trsp->all_zones[trsp->all_nodes[n].result.dznum] + .no_qname_wait_recurse || + trsp->all_zones[trsp->all_nodes[n].result.dznum] + .not_recursive_only || + recursed) && + has_base_zone(trsp->client, + trsp->all_nodes[n].result.dznum) && + !trsp->all_zones[trsp->all_nodes[n].result.dznum] + .forgotten) + { + if (mask > last_mask) { + last_mask = mask; + fidx = n; + } + + } else if ((nfidx < 0) && !recursed && + has_base_zone( + trsp->client, + trsp->all_nodes[n].result.dznum) && + !trsp->all_zones[trsp->all_nodes[n] + .result.dznum] + .forgotten) + { + nfidx = n; + } + } + } + + if (!trsp->have_rd && (fidx < 0) && (nfidx < 0)) { + goto out; + } + + if (recursed && (fidx < 0) && trsp->rstack[0].hidden_policy) { + if (trsp->rstack[0].result.trig <= trig) { + trsp->rstack[0].result.policy = + trsp->rstack[0].hidden_policy; + trsp->rstack[0].result.zpolicy = + trsp->rstack[0].hidden_policy; + trsp->rstack[0].hidden_policy = LIBRPZ_POLICY_UNDEFINED; + return (true); + } + } + + if (fidx >= 0) { + bool disabled = false; + + if (!result_supercedes_address(&(trsp->all_nodes[fidx]), + &(trsp->rstack[0]))) + { + return (true); + } + + memmove(&(trsp->rstack[0]), &(trsp->all_nodes[fidx]), + sizeof(trsp->rstack[0])); + trsp->rstack[0].result.hit_id = hit_id; + trsp->last_zone = trsp->rstack[0].result.dznum; + + if (trsp->all_zones[trsp->rstack[0].result.dznum].flags) { + unsigned long flags = + trsp->all_zones[trsp->rstack[0].result.dznum] + .flags; + librpz_policy_t force_policy = 0; + + if (flags & ZOPT_POLICY_NODATA) { + force_policy = LIBRPZ_POLICY_NODATA; + } else if (flags & ZOPT_POLICY_PASSTHRU) { + force_policy = LIBRPZ_POLICY_PASSTHRU; + } else if (flags & ZOPT_POLICY_NXDOMAIN) { + force_policy = LIBRPZ_POLICY_NXDOMAIN; + } else if (flags & ZOPT_POLICY_DROP) { + force_policy = LIBRPZ_POLICY_DROP; + } else if (flags & ZOPT_POLICY_TCP_ONLY) { + force_policy = LIBRPZ_POLICY_TCP_ONLY; + } else if (flags & ZOPT_POLICY_DISABLED) { + disabled = true; + } + + if (force_policy) { + trsp->rstack[0].result.policy = force_policy; + trsp->rstack[0].result.zpolicy = force_policy; + trsp->rstack[0].poverride = force_policy; + } + } + + if (disabled) { + trsp->rstack[0].poverride = LIBRPZ_POLICY_DISABLED; + } + + return (true); + } else if (nfidx >= 0) { + memmove(&(trsp->rstack[0]), &(trsp->all_nodes[nfidx]), + sizeof(trsp->rstack[0])); + trsp->last_zone = trsp->rstack[0].result.dznum; + trsp->rstack[0].result.hit_id = hit_id; + trsp->rstack[0].hidden_policy = trsp->rstack[0].result.policy; + trsp->rstack[0].result.policy = LIBRPZ_POLICY_UNDEFINED; + trsp->rstack[0].result.zpolicy = LIBRPZ_POLICY_UNDEFINED; + return (true); + } + +out: + if (trig == LIBRPZ_TRIG_NSIP) { + bool needs_wait = false; + + for (n = 0; n < trsp->num_zones; n++) { + if (trsp->all_zones[n].no_nsip_wait_recurse) { + needs_wait = true; + break; + } + } + + if (!needs_wait) { + usleep(100); + } + } + + if (trsp->rstack[0].result.policy == LIBRPZ_POLICY_UNDEFINED) { + memset(&(trsp->rstack[0]), 0, sizeof(trsp->rstack[0])); + trsp->rstack[0].result.trig = trig; + } + + return (true); +} + +bool +trpz_soa_serial(librpz_emsg_t *emsg, uint32_t *serialp, const char *domain_nm, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + size_t n, dlen; + + if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL"); + return (false); + } else if (domain_nm == NULL) { + trpz_pemsg(emsg, "domain_nm was NULL"); + return (false); + } else if (serialp == NULL) { + trpz_pemsg(emsg, "serialp was NULL"); + return (false); + } + + dlen = strlen(domain_nm); + + if (dlen > 0 && domain_nm[dlen - 1] == '.') { + dlen--; + } + + for (n = 0; n < trsp->num_zones; n++) { + if (dlen != strlen(trsp->all_zones[n].name)) { + continue; + } else if (!has_base_zone(trsp->client, n)) { + continue; + } + + if ((strlen(trsp->all_zones[n].name) == dlen) && + (!strncmp(trsp->all_zones[n].name, domain_nm, dlen))) + { + if (!trsp->all_zones[n].serial) { + trsp->all_zones[n].serial = time(NULL); + } + + *serialp = trsp->all_zones[n].serial; + return (true); + } + } + + trpz_pemsg(emsg, "zone not found"); + return (false); +} + +static int +domain_ntop(const u_char *src, char *dst, size_t dstsiz) { + const unsigned char *sptr = src; + char *dptr = dst, *dend = dst + dstsiz; + + if (dst == NULL || dstsiz == 0) { + return (0); + } + + memset(dst, 0, dstsiz); + + while (*sptr) { + if (((dptr + *sptr) > dend)) { + return (0); + } + + if (sptr != src) { + *dptr++ = '.'; + } + + memmove(dptr, sptr + 1, *sptr); + dptr += *sptr; + sptr += *sptr; + sptr++; + } + + return (1); +} + +static int +domain_pton2(const char *src, u_char *dst, size_t dstsiz, size_t *dstlen, + bool lower) { + unsigned char *dptr = dst; + const unsigned char *dend = dst + dstsiz; + char *tmps = NULL, *tok = NULL, *tptr = NULL; + + UNUSED(lower); + + if (src == NULL || dst == NULL || dstsiz == 0) { + return (false); + } + + memset(dst, 0, dstsiz); + + tmps = strdup(src); + if (tmps == NULL) { + perror("strdup"); + return (0); + } + + tptr = tmps; + + if (dstlen) { + *dstlen = 0; + } + + while (tptr && *tptr) { + tok = strsep(&tptr, "."); + + if (((dptr + strlen(tok) + 1) > dend)) { + return (0); + } + + *dptr++ = strlen(tok); + memmove(dptr, tok, strlen(tok)); + dptr += strlen(tok); + + if (dstlen) { + (*dstlen) += (1 + strlen(tok)); + } + } + + if (dptr >= dend) { + return (0); + } + + *dptr = 0; + + if (dstlen) { + (*dstlen)++; + } + + free(tmps); + + return (1); +} + +/* XXX: needs IPv6 support. */ +bool +trpz_rsp_clientip_prefix(librpz_emsg_t *emsg, librpz_prefix_t *prefix, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + unsigned int cbytes[5] = { 0 }; + uint8_t *aptr = NULL; + + if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL"); + return (false); + } else if (prefix == NULL) { + trpz_pemsg(emsg, "prefix was NULL"); + return (false); + } + + memset(prefix, 0, sizeof(*prefix)); + + if (trsp->rstack[0].result.trig != LIBRPZ_TRIG_CLIENT_IP) { + return (true); + } + + if (sscanf(trsp->rstack[0].dname, "%u.%u.%u.%u.%u", &cbytes[0], + &cbytes[1], &cbytes[2], &cbytes[3], &cbytes[4]) != 5) + { + char abuf[64] = { 0 }; + int family = 0; + + if (sscanf(trsp->rstack[0].dname, "%u.", &cbytes[0]) != 1) { + return (true); + } + + if (get_address_info(trsp->rstack[0].dname, &family, abuf, NULL, + NULL) < 0) + { + return (true); + } else if (family != AF_INET6) { + return (true); + } + + aptr = (uint8_t *)&(prefix->addr.in6); + memset(aptr, 0, sizeof(prefix->addr.in6)); + + if (inet_pton(AF_INET6, abuf, aptr) != 1) { + return (true); + } + + prefix->family = AF_INET6; + prefix->len = cbytes[0]; + + return (true); + } + + prefix->family = AF_INET; + prefix->len = cbytes[0]; + + if (prefix->len <= 24) { + cbytes[1] = 0; + } + + if (prefix->len <= 16) { + cbytes[2] = 0; + } + + if (prefix->len == 86) { + cbytes[3] = 0; + } + + aptr = (uint8_t *)&(prefix->addr.in); + *aptr++ = cbytes[4]; + *aptr++ = cbytes[3]; + *aptr++ = cbytes[2]; + *aptr++ = cbytes[1]; + + return (true); +} + +bool +trpz_have_trig(librpz_trig_t trig, bool ipv6, const librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + size_t ind = ipv6 ? 1 : 0; + + if (rsp == NULL) { + return (false); + } + + /* No hit, so look in all zones for trigger. */ + if (trsp->stack_idx == 0 || trsp->rstack[0].result.policy == 0) { + ssize_t max_z = (trsp->last_zone >= 0) + ? trsp->last_zone + : (ssize_t)trsp->num_zones - 0; + + for (ssize_t n = 0; n < max_z; n++) { + if (!trsp->have_rd && + !trsp->all_zones[n].not_recursive_only) + { + continue; + } else if (trsp->all_zones[n].has_triggers[ind][trig]) { + return (true); + } else if (trsp->all_zones[n].ip_as_ns && + (((trig == LIBRPZ_TRIG_IP) && + trsp->all_zones[n] + .has_triggers[ind] + [LIBRPZ_TRIG_NSIP]) || + ((trig == LIBRPZ_TRIG_NSIP) && + trsp->all_zones[n] + .has_triggers[ind] + [LIBRPZ_TRIG_IP]))) + { + return (true); + } else if (trsp->all_zones[n].qname_as_ns && + (((trig == LIBRPZ_TRIG_QNAME) && + trsp->all_zones[n].has_triggers + [ind][LIBRPZ_TRIG_NSDNAME]) || + ((trig == LIBRPZ_TRIG_NSDNAME) && + trsp->all_zones[n] + .has_triggers[ind] + [LIBRPZ_TRIG_QNAME]))) + { + return (true); + } + } + + return (false); + } + + /* Special case of first base zone. */ + if (trsp->rstack[0].result.dznum == 0 && + (trig > trsp->rstack[0].result.trig)) + { + return (false); + } + + /* Otherwise check lower zones (of higher precedence). */ + for (size_t n = 0; n <= (trsp->rstack[0].result.dznum); n++) { + if (!trsp->have_rd && !trsp->all_zones[n].not_recursive_only) { + continue; + } else if (trsp->all_zones[n].has_triggers[ind][trig]) { + return (true); + } else if (trsp->all_zones[n].ip_as_ns && + (((trig == LIBRPZ_TRIG_IP) && + trsp->all_zones[n] + .has_triggers[ind][LIBRPZ_TRIG_NSIP]) || + ((trig == LIBRPZ_TRIG_NSIP) && + trsp->all_zones[n] + .has_triggers[ind][LIBRPZ_TRIG_IP]))) + { + return (true); + } else if (trsp->all_zones[n].qname_as_ns && + (((trig == LIBRPZ_TRIG_QNAME) && + trsp->all_zones[n] + .has_triggers[ind][LIBRPZ_TRIG_NSDNAME]) || + ((trig == LIBRPZ_TRIG_NSDNAME) && + trsp->all_zones[n] + .has_triggers[ind][LIBRPZ_TRIG_QNAME]))) + { + return (true); + } + } + + return (false); +} + +bool +trpz_rsp_rr(librpz_emsg_t *emsg, uint16_t *typep, uint16_t *classp, + uint32_t *ttlp, librpz_rr_t **rrp, librpz_result_t *result, + const uint8_t *qname, size_t qname_size, librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + trpz_result_t *last_result = NULL; + + if (result == NULL) { + trpz_pemsg(emsg, "result was NULL"); + return (false); + } else if (rsp == NULL) { + trpz_pemsg(emsg, "rsp was NULL"); + return (false); + } + + last_result = &(trsp->rstack[0]); + + if (last_result->rridx < last_result->nrrs) { + trpz_rr_t *this_rr = &(last_result->rrs[last_result->rridx]); + + if (classp != NULL) { + *classp = this_rr->class; + } + + if (ttlp != NULL) { + *ttlp = 3600; + } + + if (typep != NULL) { + *typep = this_rr->type; + } + + if (rrp != NULL) { + uint8_t *copy_src = NULL, *nrdata = NULL; + size_t to_copy, needed; + + copy_src = this_rr->rdata; + to_copy = this_rr->rdlength; + + /* If there's CNAME wild card expansion */ + if (qname != NULL && qname_size != 0 && + (this_rr->type == ns_t_cname) && + (this_rr->rdlength > 2)) + { + if (this_rr->rdata[0] == 1 && + this_rr->rdata[1] == '*') + { + char tmpexp[256] = { 0 }, + tmpexp2[256] = { 0 }, tmpexp3[256]; + + wdns_domain_to_str(this_rr->rdata, + this_rr->rdlength, + tmpexp); + wdns_domain_to_str(qname, qname_size, + tmpexp2); + + if (tmpexp2[strlen(tmpexp2) - 1] == '.') + { + tmpexp2[strlen(tmpexp2) - 1] = + 0; + } + + if (strncmp(tmpexp, "*.", 2) == 0) { + size_t nrd; + uint32_t n = snprintf( + tmpexp3, + sizeof(tmpexp3), + "%s.%s", tmpexp2, + &tmpexp[2]); + if (n > sizeof(tmpexp3)) { + trpz_pemsg( + emsg, + "%s truncated", + tmpexp3); + return (false); + } + nrd = wdns_str_to_name( + tmpexp3, &nrdata, 1); + to_copy = nrd; + copy_src = nrdata; + } + } + } + + needed = sizeof(**rrp) + to_copy; + + *rrp = calloc(1, needed); + if (*rrp == NULL) { + trpz_pemsg(emsg, "calloc: %s", strerror(errno)); + return (false); + } + + (*rrp)->type = htons(this_rr->type); + (*rrp)->class = htons(this_rr->class); + (*rrp)->ttl = htonl(this_rr->ttl); + (*rrp)->rdlength = htons(to_copy); + memmove((*rrp)->rdata, copy_src, to_copy); + } + + result->next_rr = this_rr->rrn; + trsp->rstack[0].result.next_rr = this_rr->rrn; + last_result->rridx++; + } else { + if (typep != NULL) { + *typep = ns_t_invalid; + } + + if (rrp != NULL) { + *rrp = NULL; + } + + result->next_rr = 0; + trsp->rstack[0].result.next_rr = 0; + } + + return (true); +} + +bool +trpz_rsp_forget_zone(librpz_emsg_t *emsg, librpz_cznum_t znum, + librpz_rsp_t *rsp) { + trpz_rsp_t *trsp = (trpz_rsp_t *)rsp; + + if (znum >= trsp->num_zones) { + trpz_pemsg(emsg, "invalid zone number"); + return (false); + } else if (trsp->all_zones[znum].forgotten) { + trpz_pemsg(emsg, "zone already forgotten"); + return (false); + } + + trsp->all_zones[znum].forgotten = true; + + return (true); +} diff --git a/bin/tests/system/rpz/testlib/test-data.c b/bin/tests/system/rpz/testlib/test-data.c new file mode 100644 index 0000000000..5461d3ef94 --- /dev/null +++ b/bin/tests/system/rpz/testlib/test-data.c @@ -0,0 +1,1484 @@ +/* + * 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. + */ + +/* + * Limited implementation of the DNSRPS API for testing purposes. + * + * Copyright (c) 2016-2017 Farsight Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "test-data.h" + +const rpz_soa_t g_soa_record = { "a.rpz-ns.dns-nod.net", + "nod-admin.fsi.io", + 12345, + 3600, + 1200, + 604800, + 60 }; + +int +wdns_str_to_name(const char *str, uint8_t **pbuf, bool downcase); + +static char * +str_printf(const char *fmt, ...) { + va_list ap; + char tbuf[8192], *result = NULL; + + va_start(ap, fmt); + vsnprintf(tbuf, sizeof(tbuf) - 1, fmt, ap); + tbuf[sizeof(tbuf) - 1] = 0; + va_end(ap); + + result = strdup(tbuf); + if (result == NULL) { + perror("strdup"); + } + + return (result); +} + +/* + * Given a config-ready RPZ IP address, determine its family and normal + * canonical representation. + */ +int +get_address_info(const char *astr, int *pfamily, char *pbuf, + const char *optname, char **errp) { + char tmpc[512] = { 0 }; + char *tok = NULL, *tptr = tmpc, *last_tok = NULL; + size_t lcount = 0, bcount = 0; + unsigned int prefix = 0, values[16] = { 0 }, hex_values[16] = { 0 }; + bool is_ipv6 = false; + + if (!astr || !pfamily || !pbuf) { + return (-1); + } + + strncpy(tmpc, astr, sizeof(tmpc) - 1); + + while ((tok = strsep(&tptr, "."))) { + char *eptr = NULL; + unsigned long val; + + lcount++; + last_tok = tok; + + errno = 0; + val = strtoul(tok, &eptr, 10); + + if (errno != 0 || *eptr != '\0') { + bool bad = false; + + bcount++; + errno = 0; + eptr = NULL; + val = strtoul(tok, &eptr, 16); + + if (errno || *eptr != '\0') { + if (strcmp(tok, "zz") == 0) { + val = ~0; + is_ipv6 = true; + } else { + bad = true; + } + } + + if (!bad && (lcount > 1)) { + hex_values[lcount - 2] = val; + } + } else { + if (val > 255) { + bcount++; + } + + if (lcount == 1) { + prefix = val; + } else if (lcount > 1) { + unsigned int hexval; + values[lcount - 2] = val; + + /* + * All integer strings are valid hex + * strings, but decimal values are longer, + * so we have to check for overflow when + * reading as hex. + */ + errno = 0; + hexval = strtoul(tok, &eptr, 16); + if (errno != 0) { + return (-1); + } + hex_values[lcount - 2] = hexval; + } + } + } + + if (last_tok && (strncmp(last_tok, "rpz-", 4) == 0)) { + lcount--; + bcount--; + } + + /* Not acceptable for either address family. */ + if (lcount > 9) { + return (-1); + } + + *pfamily = (!is_ipv6 && (lcount == 5)) ? AF_INET : AF_INET6; + + /* + * For AF_INET we expect exactly 4 "good" (0<->255) octets and the + * subnet mask. + */ + if (*pfamily == AF_INET) { + if (prefix > 32) { + if (errp) { + *errp = str_printf( + "invalid rpz IP address \"%s\"; " + "invalid prefix length of %u", + (optname ? optname : astr), prefix); + } + + return (-1); + } else if (bcount > 0) { + return (-1); + } + + sprintf(pbuf, "%u.%u.%u.%u", values[3], values[2], values[1], + values[0]); + } else { + size_t n; + + if (prefix > 128) { + if (errp) { + *errp = str_printf( + "invalid rpz IP address \"%s\"; " + "invalid prefix length of %u", + (optname ? optname : astr), prefix); + } + + return (-1); + } + + *pbuf = 0; + + /* + * Walk the values backward. Account for :: and discard + * chunks > 2 octets. + */ + for (n = lcount - 1; n > 0; n--) { + if (hex_values[n - 1] == ~0U) { + strcat(pbuf, ":"); + } else { + if (hex_values[n - 1] > 0xffff) { + return (-1); + } else if (n > 1) { + sprintf(&pbuf[strlen(pbuf)], + "%x:", hex_values[n - 1]); + } else { + sprintf(&pbuf[strlen(pbuf)], "%x", + hex_values[n - 1]); + } + } + } + } + + return (0); +} + +rpz_soa_t * +parse_serial(unsigned char *rdata, size_t rdlen) { + rpz_soa_t *result = NULL; + char dname[WDNS_PRESLEN_NAME]; + size_t mlen, rlen; + uint32_t *uptr = NULL; + + result = calloc(1, sizeof(*result)); + if (result == NULL) { + perror("calloc"); + return (NULL); + } + + mlen = wdns_domain_to_str(rdata, rdlen, dname); + result->mname = strdup(dname); + rlen = wdns_domain_to_str(rdata + mlen, rdlen - mlen, dname); + result->rname = strdup(dname); + uptr = (uint32_t *)(rdata + mlen + rlen); + result->serial = ntohl(*uptr); + uptr++; + result->refresh = ntohl(*uptr); + uptr++; + result->retry = ntohl(*uptr); + uptr++; + result->expire = ntohl(*uptr); + uptr++; + result->minimum = ntohl(*uptr); + + return (result); +} + +size_t +wdns_domain_to_str(const uint8_t *src, size_t src_len, char *dst) { + size_t bytes_read = 0; + size_t bytes_remaining = src_len; + uint8_t oclen; + + if (!src) { + return (0); + } + + oclen = *src; + while (bytes_remaining > 0 && oclen != 0) { + src++; + bytes_remaining--; + + bytes_read += oclen + 1 /* length octet */; + + while (oclen-- && bytes_remaining > 0) { + uint8_t c = *src++; + bytes_remaining--; + + if (c == '.' || c == '\\') { + *dst++ = '\\'; + *dst++ = c; + } else if (c >= '!' && c <= '~') { + *dst++ = c; + } else { + snprintf(dst, 5, "\\%.3d", c); + dst += 4; + } + } + *dst++ = '.'; + oclen = *src; + } + if (bytes_read == 0) { + *dst++ = '.'; + } + bytes_read++; + + *dst = '\0'; + return ((bytes_read)); +} + +/* Add parsed update specification to maintained list of nodes. */ +static trpz_result_t * +apply_update_to_set(trpz_result_t **results, size_t *pnresults, + trpz_zone_t **pzones, const char *node, size_t zidx, + uint32_t ttl, librpz_trig_t trigger, librpz_policy_t policy, + int *modified, unsigned long flags, char **errp) { + size_t n; + int family = 0; + + UNUSED(flags); + + *modified = 0; + + switch (trigger) { + case LIBRPZ_TRIG_QNAME: + case LIBRPZ_TRIG_NSDNAME: + (*pzones)[zidx].has_triggers[0][trigger] = 1; + break; + case LIBRPZ_TRIG_CLIENT_IP: + case LIBRPZ_TRIG_IP: + case LIBRPZ_TRIG_NSIP: { + char abuf[128]; + + if (get_address_info(node, &family, abuf, NULL, errp) < 0) { + fprintf(stderr, + "Error in determining IP address type: %s\n", + node); + return (NULL); + } else if (family == AF_INET) { + (*pzones)[zidx].has_triggers[0][trigger] = 1; + } else { + (*pzones)[zidx].has_triggers[1][trigger] = 1; + } + + } break; + default: + break; + } + + for (n = 0; n < *pnresults; n++) { + trpz_result_t *rptr = &((*results)[n]); + + if (rptr->result.cznum != zidx) { + continue; + } + + if (!strcmp(rptr->dname, node)) { + if (rptr->result.trig == trigger && + rptr->result.policy == policy && rptr->ttl == ttl) + { + return (rptr); + } + + rptr->result.trig = trigger; + rptr->result.policy = policy; + rptr->result.zpolicy = policy; + rptr->ttl = ttl; + *modified = 1; + return (rptr); + } + } + + /* No match. Instead, append. */ + (*pnresults)++; + + *results = realloc(*results, (*pnresults * sizeof(**results))); + if (*results == NULL) { + perror("realloc"); + return (NULL); + } + + memset(&((*results)[*pnresults - 1]), 0, sizeof(**results)); + (*results)[*pnresults - 1].dname = strdup(node); + (*results)[*pnresults - 1].ttl = ttl; + (*results)[*pnresults - 1].result.trig = trigger; + (*results)[*pnresults - 1].result.policy = policy; + (*results)[*pnresults - 1].result.zpolicy = policy; + (*results)[*pnresults - 1].result.cznum = zidx; + (*results)[*pnresults - 1].result.dznum = zidx; + (*results)[*pnresults - 1].result.log = 1; + (*results)[*pnresults - 1].poverride = policy; + (*results)[*pnresults - 1].match_trig = trigger; + + if (family == AF_INET6) { + (*results)[*pnresults - 1].flags |= NODE_FLAG_IPV6_ADDRESS; + } + + *modified = 1; + return ((&((*results)[*pnresults - 1]))); +} + +/* + * Add a parsed RR value that is maintained in conjunction with record policy + * items. + */ +static int +add_other_rr(trpz_result_t *node, const char *rrtype, const char *val, + uint32_t ttl, int *modified) { + trpz_rr_t nrec = { 0 }; + size_t n; + static unsigned int rrn = 1; + + *modified = 0; + + nrec.class = ns_c_in; + nrec.ttl = ttl; + nrec.rrn = rrn++; + + if (!strcasecmp(rrtype, "A")) { + uint32_t addr; + + if (inet_pton(AF_INET, val, &addr) != 1) { + fprintf(stderr, + "Error determining policy record IPv4 address: " + "%s\n", + val); + return (-1); + } + + nrec.type = ns_t_a; + nrec.rdlength = sizeof(uint32_t); + + nrec.rdata = malloc(nrec.rdlength); + if (nrec.rdata == NULL) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + memmove(nrec.rdata, &addr, nrec.rdlength); + } else if (!strcasecmp(rrtype, "AAAA")) { + char addr[16] = { 0 }; + + if (inet_pton(AF_INET6, val, addr) != 1) { + fprintf(stderr, + "Error determining policy record IPv6 address: " + "%s\n", + val); + return (-1); + } + + nrec.type = ns_t_aaaa; + nrec.rdlength = sizeof(addr); + + nrec.rdata = malloc(nrec.rdlength); + if (nrec.rdata == NULL) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + memmove(nrec.rdata, addr, nrec.rdlength); + } else if (!strcasecmp(rrtype, "TXT")) { + nrec.type = ns_t_txt; + nrec.rdlength = 1 + strlen(val); + + nrec.rdata = calloc(nrec.rdlength, 1); + if (nrec.rdata == NULL) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + nrec.rdata[0] = nrec.rdlength - 1; + memmove(&(nrec.rdata[1]), val, nrec.rdlength - 1); + } else if (!strcasecmp(rrtype, "CNAME")) { + int ret; + + nrec.type = ns_t_cname; + ret = wdns_str_to_name(val, &(nrec.rdata), 1); + + if (ret <= 0) { + fprintf(stderr, + "Error processing CNAME policy record data " + "(%d)!\n", + ret); + return (-1); + } + + nrec.rdlength = ret; + } else if (!strcasecmp(rrtype, "DNAME")) { + int ret; + + nrec.type = ns_t_dname; + ret = wdns_str_to_name(val, &(nrec.rdata), 1); + + if (ret <= 0) { + fprintf(stderr, + "Error processing DNAME policy record data " + "(%d)!\n", + ret); + return (-1); + } + + nrec.rdlength = ret; + } else { + fprintf(stderr, + "Error: unsupported policy record type: \"%s\"\n", + rrtype); + return (-1); + } + + for (n = 0; n < node->nrrs; n++) { + trpz_rr_t *rptr = &(node->rrs[n]); + + /* Same thing. Don't replace. */ + if (rptr->type == nrec.type && rptr->class == nrec.class && + rptr->ttl == nrec.ttl && rptr->rdlength == nrec.rdlength && + !memcmp(rptr->rdata, nrec.rdata, nrec.rdlength)) + { + return (n + 1); + } + } + + node->nrrs++; + + node->rrs = realloc(node->rrs, (node->nrrs * sizeof(*(node->rrs)))); + if (node->rrs == NULL) { + perror("realloc"); + exit(EXIT_FAILURE); + } + + memset(&(node->rrs[node->nrrs - 1]), 0, sizeof(node->rrs[0])); + node->rrs[node->nrrs - 1] = nrec; + *modified = 1; + + return (node->nrrs); +} + +void +reverse_labels(const char *str, char *pbuf) { + const char *sptr = str, *end = NULL; + + if (!sptr || !*sptr) { + return; + } + + sptr += (strlen(sptr) - 1); + end = sptr + 1; + *pbuf = 0; + + if (*sptr == '.') { + sptr--; + end--; + } + + while (sptr >= str) { + if ((*sptr != '.') && (sptr != str)) { + sptr--; + continue; + } + + if (sptr == str) { + strncat(pbuf, sptr, (end - sptr)); + break; + } + + strncat(pbuf, sptr + 1, (end - (sptr + 1))); + strcat(pbuf, "."); + end = sptr--; + } + + if (pbuf[strlen(pbuf) - 1] == '.') { + pbuf[strlen(pbuf) - 1] = 0; + } + + return; +} + +/* Parse trailing zone options as specified in a cstr line */ +unsigned long +parse_zone_options(const char *str) { + char tmpstr[8192] = { 0 }; + char *tok = NULL, *sptr = NULL; + unsigned long result = 0; + + if (!str || !*str) { + return (0); + } + + strncpy(tmpstr, str, sizeof(tmpstr) - 1); + + tok = strtok_r(tmpstr, " ", &sptr); + + while (tok) { + if (!strcasecmp(tok, "policy")) { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + + if (!strcasecmp(tok, "passthru")) { + result |= ZOPT_POLICY_PASSTHRU; + } else if (!strcasecmp(tok, "drop")) { + result |= ZOPT_POLICY_DROP; + } else if (!strcasecmp(tok, "tcp-only")) { + result |= ZOPT_POLICY_TCP_ONLY; + } else if (!strcasecmp(tok, "nxdomain")) { + result |= ZOPT_POLICY_NXDOMAIN; + } else if (!strcasecmp(tok, "nodata")) { + result |= ZOPT_POLICY_NODATA; + } else if (!strcasecmp(tok, "given")) { + result |= ZOPT_POLICY_GIVEN; + } else if (!strcasecmp(tok, "disabled")) { + result |= ZOPT_POLICY_DISABLED; + } else if (!strcasecmp(tok, "no-op")) { + ; + } + } else { + if (!strcasecmp(tok, "max-policy-ttl")) { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + } else if (!strcasecmp(tok, "recursive-only")) { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + + if (!strcasecmp(tok, "yes")) { + result |= ZOPT_RECURSIVE_ONLY; + } else if (!strcasecmp(tok, "no")) { + result |= ZOPT_NOT_RECURSIVE_ONLY; + } + + } else { + if (!strcasecmp(tok, "qname-as-ns")) { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + + if (!strcasecmp(tok, "yes")) { + result |= ZOPT_QNAME_AS_NS; + } + + } else if (!strcasecmp(tok, "ip-as-ns")) { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + + if (!strcasecmp(tok, "yes")) { + result |= ZOPT_IP_AS_NS; + } + + } else if (!strcasecmp(tok, + "qname-wait-recurse")) + { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + + if (!strcasecmp(tok, "no")) { + result |= + ZOPT_NO_QNAME_WAIT_RECURSE; + } + + } else if (!strcasecmp(tok, + "nsip-wait-recurse")) + { + tok = strtok_r(NULL, " ", &sptr); + if (tok == NULL) { + break; + } + + if (!strcasecmp(tok, "no")) { + result |= + ZOPT_NO_NSIP_WAIT_RECURSE; + } + } + } + } + + tok = strtok_r(NULL, " ", &sptr); + } + + /* LIBRPZ_POLICY_CNAME, */ + return (result); +} + +/* + * Parse an update string and attempt to add any relevant data to the node + * and policy RR tables. + */ +int +apply_update(const char *updstr, trpz_result_t **presults, size_t *pnresults, + trpz_zone_t **pzones, size_t *pnzones, int is_static, + unsigned long flags, char **errp) { + trpz_result_t *res = NULL; + char cmdbuf[64] = { 0 }, nodebuf[256] = { 0 }, rrbuf[32] = { 0 }, + databuf[256] = { 0 }; + char *nend = NULL; + librpz_policy_t policy = LIBRPZ_POLICY_UNDEFINED; + librpz_trig_t trig = LIBRPZ_TRIG_QNAME; + unsigned int ttl; + ssize_t n, zidx = -1; + size_t ndlen = 0, last_matchlen = 0; + int nfield, zupd = 0; + + nfield = sscanf(updstr, "%63s %255s %u %31s %255s", cmdbuf, nodebuf, + &ttl, rrbuf, databuf); + if (nfield < 1) { + return (-1); + } + + /* + * Special case for handling zone additions; here the 'ttl' field + * becomes a serial. + */ + if (!strcasecmp(cmdbuf, "zone")) { + trpz_zone_t *zptr = NULL; + bool qname_as_ns = false, ip_as_ns = false, + not_recursive_only = false, do_inc = false; + bool no_qname_as_ns = false, no_ip_as_ns = false, + recursive_only = false, no_nsip_wait_recurse = false; + + if (nfield < 3) { + return (-1); + } + + if (!strcasecmp(rrbuf, "qname_as_ns")) { + qname_as_ns = true; + } else if (!strcasecmp(rrbuf, "ip_as_ns")) { + ip_as_ns = true; + } else if (!strcasecmp(rrbuf, "not_recursive_only")) { + not_recursive_only = true; + } else if (!strcasecmp(rrbuf, "inc")) { + do_inc = true; + } else if (!strcasecmp(rrbuf, "no_qname_as_ns")) { + no_qname_as_ns = true; + } else if (!strcasecmp(rrbuf, "no_ip_as_ns")) { + no_ip_as_ns = true; + } else if (!strcasecmp(rrbuf, "recursive_only")) { + recursive_only = true; + } else if (!strcasecmp(rrbuf, "no_nsip_wait_recurse")) { + no_nsip_wait_recurse = true; + } + + if (flags & ZOPT_RECURSIVE_ONLY) { + recursive_only = true; + not_recursive_only = false; + } else if (flags & ZOPT_NOT_RECURSIVE_ONLY) { + recursive_only = false; + not_recursive_only = true; + } + + if (flags & ZOPT_NO_NSIP_WAIT_RECURSE) { + no_nsip_wait_recurse = true; + } + + for (n = 0; (size_t)n < *pnzones; n++) { + if (!strcmp((*pzones)[n].name, nodebuf)) { + /* + * Force override of serial. But only if + * serial is non-zero. + */ + if (ttl) { + if (do_inc) { + (*pzones)[n].serial += ttl; + } else { + (*pzones)[n].serial = ttl; + } + } + + (*pzones)[n].has_update = 0; + + if (qname_as_ns) { + (*pzones)[n].qname_as_ns = true; + } + + if (ip_as_ns) { + (*pzones)[n].ip_as_ns = true; + } + + if (no_qname_as_ns) { + (*pzones)[n].qname_as_ns = false; + } + + if (no_ip_as_ns) { + (*pzones)[n].ip_as_ns = false; + } + + if (not_recursive_only) { + (*pzones)[n].not_recursive_only = true; + } + + if (recursive_only) { + (*pzones)[n].not_recursive_only = false; + } + + if (flags & ZOPT_NO_QNAME_WAIT_RECURSE) { + (*pzones)[n].no_qname_wait_recurse = + true; + } + + if (no_nsip_wait_recurse) { + (*pzones)[n].no_nsip_wait_recurse = + true; + } + + return (0); + } + } + + (*pnzones)++; + + *pzones = realloc(*pzones, (*pnzones * sizeof(**pzones))); + if (*pzones == NULL) { + perror("realloc"); + exit(EXIT_FAILURE); + } + + zptr = &(*pzones)[*pnzones - 1]; + *zptr = (trpz_zone_t){ + .serial = ttl, + .qname_as_ns = qname_as_ns, + .ip_as_ns = ip_as_ns, + .flags = flags, + .not_recursive_only = not_recursive_only, + }; + + if (qname_as_ns) { + (*pzones)[n].qname_as_ns = true; + } + + if (ip_as_ns) { + (*pzones)[n].ip_as_ns = true; + } + + if (flags & ZOPT_NO_QNAME_WAIT_RECURSE) { + (*pzones)[n].no_qname_wait_recurse = true; + } + + if (no_nsip_wait_recurse) { + (*pzones)[n].no_nsip_wait_recurse = true; + } + + strncpy(zptr->name, nodebuf, LIBRPZ_MAXDOMAIN + 1); + if (zptr->name[strlen(zptr->name) - 1] == '.') { + zptr->name[strlen(zptr->name) - 1] = 0; + } + + return (0); + } else if (nfield != 5) { + return (-1); + } + + if (strcasecmp(cmdbuf, "add")) { + fprintf(stderr, "Warning: only update add action is currently " + "supported!\n"); + return (-1); + } + + if (!strcasecmp(rrbuf, "A")) { + policy = LIBRPZ_POLICY_RECORD; + } else if (!strcasecmp(rrbuf, "CNAME")) { + if (!strcmp(databuf, ".")) { + policy = LIBRPZ_POLICY_NXDOMAIN; + } else if (!strcmp(databuf, "*.")) { + policy = LIBRPZ_POLICY_NODATA; + } else if (!strcasecmp(databuf, "rpz-passthru.")) { + policy = LIBRPZ_POLICY_PASSTHRU; + } else if (!strcasecmp(databuf, "rpz-drop.")) { + policy = LIBRPZ_POLICY_DROP; + } else if (!strcasecmp(databuf, "rpz-tcp-only.")) { + policy = LIBRPZ_POLICY_TCP_ONLY; + } else { + policy = LIBRPZ_POLICY_RECORD; + } + + } else if (!strcasecmp(rrbuf, "TXT")) { + char *ftext = NULL; + + ftext = strstr(updstr, databuf); + if (ftext == NULL) { + fprintf(stderr, "Error parsing TXT record: \"%s\"\n", + updstr); + return (-1); + } + + if (*ftext == '"') { + *ftext++ = 0; + + if (ftext[strlen(ftext) - 1] == '"') { + ftext[strlen(ftext) - 1] = 0; + } + } + + strncpy(databuf, ftext, sizeof(databuf)); + databuf[sizeof(databuf) - 1] = 0; + policy = LIBRPZ_POLICY_RECORD; + } else if (!strcasecmp(rrbuf, "DNAME")) { + policy = LIBRPZ_POLICY_RECORD; + } else if (!strcasecmp(rrbuf, "AAAA")) { + policy = LIBRPZ_POLICY_RECORD; + } else { + fprintf(stderr, + "Warning: target \"%s\" is not currently supported!\n", + rrbuf); + return (-1); + } + + if (policy == LIBRPZ_POLICY_UNDEFINED) { + fprintf(stderr, "Error: could not determine appropriate policy " + "for update!\n"); + return (-1); + } + + for (n = 0; (size_t)n < *pnzones; n++) { + const char *zptr = nodebuf; + size_t cmplen; + + zptr += strlen(zptr) - 1; + + if (*zptr == '.') { + zptr--; + } + + cmplen = strlen((*pzones)[n].name); + + if ((*pzones)[n].name[cmplen - 1] == '.') { + cmplen--; + } + + zptr -= (cmplen - 1); + + if ((zptr <= nodebuf) || (*(zptr - 1) != '.')) { + continue; + } + + if (!strncmp((*pzones)[n].name, zptr, cmplen)) { + /* + * We don't break immediately after a match because + * there might be a better one yet. + */ + if (cmplen > last_matchlen) { + ndlen = strlen(nodebuf) - cmplen; + + if (nodebuf[strlen(nodebuf) - 1] == '.') { + ndlen--; + } + + /* + * Account for the period between the node name + * and zone name. + */ + ndlen--; + zidx = n; + last_matchlen = cmplen; + } + } + } + + if (memmem(nodebuf, ndlen, ".rpz-", 5)) { + char *tptr = nodebuf + ndlen - 1; + size_t slen; + + while (strncmp(tptr, ".rpz-", 5)) { + tptr--; + } + + slen = nodebuf + ndlen - tptr; + nend = tptr; + + if (slen == 7 && !memcmp(tptr, ".rpz-ip", 7)) { + trig = LIBRPZ_TRIG_IP; + } else if (slen == 9 && !memcmp(tptr, ".rpz-nsip", 9)) { + trig = LIBRPZ_TRIG_NSIP; + } else if (slen == 14 && !memcmp(tptr, ".rpz-client-ip", 14)) { + trig = LIBRPZ_TRIG_CLIENT_IP; + } else if (slen == 12 && !memcmp(tptr, ".rpz-nsdname", 12)) { + trig = LIBRPZ_TRIG_NSDNAME; + } else { + fprintf(stderr, "Warning: unknown suffix \"%s\"\n", + tptr); + nend = NULL; + } + + /* We saved the trigger value, so shave that part off. */ + *tptr = 0; + } + + if (zidx == -1) { + return (0); + } + + nodebuf[ndlen] = 0; + + /* + * The original, deprecated PASSTHRU encoding of a CNAME pointing + * to the trigger QNAME might still be in use in local, private + * policy zones, and so it is still recognized by RPZ subscriber + * implementations as of 2016. + */ + if ((policy == LIBRPZ_POLICY_RECORD) && !strcasecmp(rrbuf, "cname")) { + char tmpname[512] = { 0 }; + + strncpy(tmpname, databuf, sizeof(tmpname) - 1); + + if ((nodebuf[strlen(nodebuf) - 1] == '.') && + (tmpname[strlen(tmpname) - 1] != '.')) + { + size_t tlen = strlen(tmpname); + + tmpname[tlen] = '.'; + tmpname[tlen + 1] = 0; + } else if ((nodebuf[strlen(nodebuf) - 1] != '.') && + (tmpname[strlen(tmpname) - 1] == '.')) + { + tmpname[strlen(tmpname) - 1] = 0; + } + + /* A special case of PASSTHRU (with trailing characters) */ + if (nend != NULL && + (strlen(databuf) == (size_t)(nend - nodebuf)) && + !strncmp(databuf, nodebuf, (nend - nodebuf))) + { + policy = LIBRPZ_POLICY_PASSTHRU; + } + + if (!strcmp(nodebuf, tmpname)) { + policy = LIBRPZ_POLICY_PASSTHRU; + } + } + + res = apply_update_to_set(presults, pnresults, pzones, nodebuf, zidx, + ttl, trig, policy, &zupd, flags, errp); + + if (res) { + if (zupd && !is_static) { + (*pzones)[zidx].has_update = 1; + } else if (is_static) { + res->flags |= NODE_FLAG_STATIC_DATA; + } + + if (policy == LIBRPZ_POLICY_RECORD) { + /* + * Policy/RR change does not seem to prompt zone + * serial increment. (has_update) + */ + if (add_other_rr(res, rrbuf, databuf, ttl, &zupd) < 0) { + fprintf(stderr, + "Error: could not add policy record %s " + "/ %s\n", + rrbuf, databuf); + return (-1); + } + } + } + + return (0); +} + +/* + * XXX: memory leak. Also, does not properly preserve "static" node entries, + * as envisioned. + */ +static void +free_nodes(trpz_result_t **presults, size_t *pnresults) { + size_t n, tot; + + if ((!presults || !*presults) && pnresults) { + *pnresults = 0; + } + + if (!presults || !*presults) { + return; + } + + tot = *pnresults; + + for (n = tot; n > 0; n--) { + trpz_result_t *res = &((*presults)[n - 1]); + size_t m; + + if (res->canonical) { + free(res->canonical); + } + + if (res->dname) { + free(res->dname); + } + + for (m = 0; m < res->nrrs; m++) { + if (res->rrs[m].rdata) { + free(res->rrs[m].rdata); + } + } + + if (res->rrs) { + free(res->rrs); + } + } + + free(*presults); + *presults = NULL; + *pnresults = 0; + + return; +} + +/* + * Perform only sanity checking on a data file's contents. + * + * Note that this function only really exists to facilitate the logging of error + * messages that may be expected to occur upon encounter with certain invalid + * node data in unit tests. + * + * fname is the pathname of the data file to be checked. + * errp is a pointer to an error string that may be set if this function fails. + * It is the responsibility of the caller to free this pointer if it is returned + * non-NULL. + * + * This function returns 0 on success, or -1 on failure, possibly setting *errp + * on failure. + */ +int +sanity_check_data_file(const char *fname, char **errp) { + FILE *f = NULL; + int result = -1; + + if (errp) { + *errp = NULL; + } + + f = fopen(fname, "r"); + if (f == NULL) { + fprintf(stderr, "couldn't sanity check %s\n", fname); + perror("fopen"); + return (-1); + } + + while (!feof(f)) { + char line[1024] = { 0 }, cmdbuf[64] = { 0 }, + nodebuf[256] = { 0 }, rrbuf[32] = { 0 }, + databuf[256] = { 0 }; + char *lptr = line; + int nfield; + unsigned int ttl; + + if (!fgets(line, sizeof(line) - 1, f)) { + break; + } + + if (!line[0]) { + continue; + } + + if (line[strlen(line) - 1] == '\n') { + line[strlen(line) - 1] = 0; + } + + if (!line[0] || line[0] == ';') { + continue; + } + + while (*lptr && !isspace(*lptr)) { + lptr++; + } + + *lptr++ = 0; + + if (!strcasecmp(line, "server") || !strcasecmp(line, "send") || + !strcasecmp(line, "wipe") || + !strcasecmp(line, "rollback") || + !strcasecmp(line, "restart")) + { + continue; + } else if (strcasecmp(line, "static") && + strcasecmp(line, "update")) + { + if (errp) { + *errp = str_printf("Found unknown instruction " + "directive: \"%s\"\n", + line); + } + + goto out; + } + + while (isspace(*lptr)) { + lptr++; + } + + nfield = sscanf(lptr, "%63s %255s %u %31s %255s", cmdbuf, + nodebuf, &ttl, rrbuf, databuf); + + /* We don't care about checking zones - only node entries. */ + if (nfield != 5) { + continue; + } + + if (strcasecmp(cmdbuf, "add")) { + continue; + } + + if (strcasecmp(rrbuf, "A") && strcasecmp(rrbuf, "CNAME") && + strcasecmp(rrbuf, "TXT") && strcasecmp(rrbuf, "DNAME") && + strcasecmp(rrbuf, "AAAA")) + { + if (errp) { + *errp = str_printf("Target \"%s\" is not " + "currently supported!\n", + rrbuf); + } + + goto out; + } + + if (strstr(nodebuf, ".rpz-")) { + char abuf[64], tmpname[512]; + char *tptr = nodebuf; + size_t slen; + int family; + + while (strncmp(tptr, ".rpz-", 5)) { + tptr++; + } + + slen = nodebuf + strlen(nodebuf) - tptr; + + if (!(slen >= 8 && !memcmp(tptr, ".rpz-ip.", 8)) && + !(slen >= 10 && !memcmp(tptr, ".rpz-nsip.", 10)) && + !(slen >= 15 && + !memcmp(tptr, ".rpz-client-ip.", 15)) && + !(slen >= 13 && !memcmp(tptr, ".rpz-nsdname.", 13))) + { + continue; + } + + strncpy(tmpname, nodebuf, sizeof(tmpname) - 1); + tmpname[sizeof(tmpname) - 1] = 0; + + *tptr = 0; + + if (get_address_info(nodebuf, &family, abuf, tmpname, + errp) < 0) + { + goto out; + } + } + } + + result = 0; + +out: + fclose(f); + + return (result); +} + +/* Load a database of nodes from a given filename. */ +int +load_all_updates(const char *fname, trpz_result_t **presults, size_t *pnresults, + trpz_zone_t **pzones, size_t *pnzones, char **errp) { + FILE *f = NULL; + + f = fopen(fname, "r"); + if (f == NULL) { + fprintf(stderr, "couldn't load updates from %s\n", fname); + perror("fopen"); + return (-1); + } + + while (!feof(f)) { + char line[1024] = { 0 }; + char *lptr = line; + int is_static = 0; + + if (!fgets(line, sizeof(line) - 1, f)) { + break; + } + + if (!line[0]) { + continue; + } + + if (line[strlen(line) - 1] == '\n') { + line[strlen(line) - 1] = 0; + } + + if (!line[0]) { + strcpy(line, "send"); + } + + if (!line[0] || line[0] == ';') { + continue; + } + + while (*lptr && !isspace(*lptr)) { + lptr++; + } + + *lptr++ = 0; + + if (!strcasecmp(line, "server")) { + continue; + } else if (!strcasecmp(line, "send")) { + size_t n; + + for (n = 0; n < *pnzones; n++) { + if ((*pzones)[n].has_update) { + (*pzones)[n].serial += 1; + (*pzones)[n].rollback += 1; + (*pzones)[n].has_update = 0; + } + } + + continue; + } else if (!strcasecmp(line, "wipe") || + !strcasecmp(line, "rollback")) + { + size_t n; + int rollback; + + rollback = strcasecmp(line, "rollback") == 0; + + free_nodes(presults, pnresults); + + /* Now push forward the serial by # rollback */ + for (n = 0; n < *pnzones; n++) { + if (rollback) { + (*pzones)[n].serial += + (*pzones)[n].rollback; + (*pzones)[n].rollback = 0; + } + + memset((*pzones)[n].has_triggers, 0, + sizeof((*pzones)[n].has_triggers)); + } + + continue; + } else if (!strcasecmp(line, "static")) { + is_static = 1; + } else if (!strcasecmp(line, "restart")) { + size_t n; + + for (n = 0; n < *pnzones; n++) { + (*pzones)[n].serial = 1; + (*pzones)[n].rollback = 0; + } + + continue; + } else if (strcasecmp(line, "update")) { + fprintf(stderr, + "Warning: skipping unknown instruction " + "directive: \"%s\"\n", + line); + continue; + } + + /* Everything here is an update */ + while (isspace(*lptr)) { + lptr++; + } + + if (apply_update(lptr, presults, pnresults, pzones, pnzones, + is_static, 0, errp) == -1) + { + fprintf(stderr, + "Error: could not apply update \"%s\"\n", lptr); + return (-1); + } + } + + fclose(f); + + return (0); +} + +#define WDNS_MAXLEN_NAME 255 +int +wdns_str_to_name(const char *str, uint8_t **pbuf, bool downcase) { + const char *p = NULL; + size_t label_len; + ssize_t slen; + uint8_t c, *oclen = NULL, *data = NULL; + int res = -1; + + assert(pbuf != NULL); + + p = str; + slen = strlen(str); + + if (slen == 1 && *p == '.') { + *pbuf = malloc(1); + *pbuf[0] = 0; + return (1); + } + + res = 0; + *pbuf = malloc(WDNS_MAXLEN_NAME); + + data = *pbuf; + label_len = 0; + oclen = data++; + res++; + + for (;;) { + c = *p++; + label_len++; + + if (slen == 0) { + /* end of input */ + if (res == WDNS_MAXLEN_NAME) { + res = -1; + goto out; + } + *oclen = --label_len; + *data++ = '\0'; + res++; + break; + } + + if (res >= WDNS_MAXLEN_NAME) { + res = -1; + } + + if (c >= 'A' && c <= 'Z') { + /* an upper case letter; downcase it */ + if (downcase) { + c |= 0x20; + } + *data++ = c; + res++; + } else if (c == '\\' && !isdigit(*p)) { + /* an escaped character */ + if (slen <= 0) { + res = -1; + goto out; + } + *data++ = *p; + res++; + p++; + slen--; + } else if (c == '\\' && slen >= 3) { + /* an escaped octet */ + char d[4]; + char *endptr = NULL; + long int val; + + d[0] = *p++; + d[1] = *p++; + d[2] = *p++; + d[3] = '\0'; + slen -= 3; + if (!isdigit(d[0]) || !isdigit(d[1]) || !isdigit(d[2])) + { + goto out; + } + val = strtol(d, &endptr, 10); + if (endptr != NULL && *endptr == '\0' && val >= 0 && + val <= 255) + { + uint8_t uval; + + uval = (uint8_t)val; + *data++ = uval; + res++; + } else { + goto out; + } + } else if (c == '\\') { + /* should not occur */ + goto out; + } else if (c == '.') { + /* end of label */ + *oclen = --label_len; + if (label_len == 0) { + goto out; + } + oclen = data++; + if (slen > 1) { + res++; + } + label_len = 0; + } else if (c != '\0') { + *data++ = c; + res++; + } + + slen--; + } + + return (res); + +out: + free(*pbuf); + return (-1); +} diff --git a/bin/tests/system/rpz/testlib/test-data.h b/bin/tests/system/rpz/testlib/test-data.h new file mode 100644 index 0000000000..d8027f8b27 --- /dev/null +++ b/bin/tests/system/rpz/testlib/test-data.h @@ -0,0 +1,124 @@ +/* + * 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. + */ + +/* + * Limited implementation of the DNSRPS API for testing purposes. + * + * Copyright (c) 2016-2017 Farsight Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LIBRPZ_LIB_OPEN 2 +#include + +#include "trpz.h" + +#define NODE_FLAG_IPV6_ADDRESS 0x1 +#define NODE_FLAG_STATIC_DATA 0x2 + +#define ZOPT_POLICY_PASSTHRU 0x0001 +#define ZOPT_POLICY_DROP 0x0002 +#define ZOPT_POLICY_TCP_ONLY 0x0004 +#define ZOPT_POLICY_NXDOMAIN 0x0008 +#define ZOPT_POLICY_NODATA 0x0010 +#define ZOPT_POLICY_GIVEN 0x0020 +#define ZOPT_POLICY_DISABLED 0x0040 + +#define ZOPT_RECURSIVE_ONLY 0x0100 +#define ZOPT_NOT_RECURSIVE_ONLY 0x0200 +#define ZOPT_QNAME_AS_NS 0x0400 +#define ZOPT_IP_AS_NS 0x0800 + +#define ZOPT_QNAME_WAIT_RECURSE 0x1000 +#define ZOPT_NO_QNAME_WAIT_RECURSE 0x2000 +#define ZOPT_NO_NSIP_WAIT_RECURSE 0x4000 + +typedef struct { + char name[256]; + uint32_t serial; + int has_update; + size_t rollback; + int has_triggers[2][LIBRPZ_TRIG_NSIP + 1]; + bool forgotten; + bool qname_as_ns, ip_as_ns; + bool not_recursive_only; + bool no_qname_wait_recurse, no_nsip_wait_recurse; + unsigned long flags; +} trpz_zone_t; + +typedef struct { + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t rdlength; + uint8_t *rdata; + unsigned int rrn; +} trpz_rr_t; + +typedef struct { + char *canonical; + char *dname; + librpz_result_t result; + uint32_t ttl; + trpz_rr_t *rrs; + size_t nrrs, rridx; + librpz_policy_t poverride, hidden_policy; + unsigned long flags; + librpz_trig_t match_trig; +} trpz_result_t; + +#define DECL_NODE(canon, name, policy, znum, trig) \ + { canon, name, { 0, 0, policy, policy, znum, znum, trig, true } }, + +#define NUM_ZONES_SNAPSHOT1 20 + +extern const rpz_soa_t g_soa_record; + +#define WDNS_PRESLEN_NAME 1025 +extern size_t +wdns_domain_to_str(const uint8_t *src, size_t src_len, char *dst); +extern int +wdns_str_to_name(const char *str, uint8_t **pbuf, bool downcase); + +extern void +reverse_labels(const char *str, char *pbuf); + +extern rpz_soa_t * +parse_serial(unsigned char *rdata, size_t rdlen); + +extern int +load_all_updates(const char *fname, trpz_result_t **presults, size_t *pnresults, + trpz_zone_t **pzones, size_t *pnzones, char **errp); +extern int +apply_update(const char *updstr, trpz_result_t **presults, size_t *pnresults, + trpz_zone_t **pzones, size_t *pnzones, int is_static, + unsigned long flags, char **errp); +extern int +sanity_check_data_file(const char *fname, char **errp); + +extern unsigned long +parse_zone_options(const char *str); + +extern int +get_address_info(const char *astr, int *pfamily, char *pbuf, + const char *optname, char **errp); diff --git a/bin/tests/system/rpz/testlib/trpz.h b/bin/tests/system/rpz/testlib/trpz.h new file mode 100644 index 0000000000..96ffce047c --- /dev/null +++ b/bin/tests/system/rpz/testlib/trpz.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +/* + * Limited implementation of the DNSRPS API for testing purposes. + * + * Copyright (c) 2016-2017 Farsight Security, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRPZ_H +#define TRPZ_H + +#include +#include +#include + +#define TARGET_ZONE "rpz-test.example.com" + +/* This should be in the librpz.h include. */ +union socku { + struct sockaddr sa; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + struct sockaddr_un sun; +}; + +typedef struct { + const char *mname; + const char *rname; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; +} rpz_soa_t; + +#endif diff --git a/configure.ac b/configure.ac index 7ebbb0d2c5..5adc5df049 100644 --- a/configure.ac +++ b/configure.ac @@ -1430,7 +1430,7 @@ no) AC_MSG_RESULT(no) ;; *) - AC_MSG_ERROR("--enable-querytrace requires yes or no (not $enable_querytrace)") + AC_MSG_ERROR(["--enable-querytrace requires yes or no (not $enable_querytrace)"]) ;; esac @@ -1522,6 +1522,8 @@ AS_IF([test "$enable_dnsrps" != "no"],[ AC_DEFINE([USE_DNSRPS], [1], [Enable DNS Response Policy Service API]) ]) +AM_CONDITIONAL([DNSRPS], [test "$enable_dnsrps" != "no"]) + AC_CHECK_HEADERS([glob.h]) # @@ -1590,7 +1592,9 @@ AC_CONFIG_FILES([bin/tests/Makefile bin/tests/system/conf.sh bin/tests/system/dyndb/driver/Makefile bin/tests/system/dlzexternal/driver/Makefile - bin/tests/system/hooks/driver/Makefile]) + bin/tests/system/hooks/driver/Makefile + bin/tests/system/rpz/testlib/Makefile + ]) AC_CONFIG_FILES([bin/tests/system/ifconfig.sh], [chmod +x bin/tests/system/ifconfig.sh]) diff --git a/lib/dns/dnsrps.c b/lib/dns/dnsrps.c index a322c9900b..e47ab411ea 100644 --- a/lib/dns/dnsrps.c +++ b/lib/dns/dnsrps.c @@ -98,12 +98,6 @@ dnsrps_log_fnc(librpz_log_level_t level, void *ctxt, const char *buf) { } switch (level) { - case LIBRPZ_LOG_FATAL: - case LIBRPZ_LOG_ERROR: /* errors */ - default: - isc_level = DNS_RPZ_ERROR_LEVEL; - break; - case LIBRPZ_LOG_TRACE1: /* big events such as dnsrpzd starts */ isc_level = DNS_RPZ_INFO_LEVEL; break; @@ -119,6 +113,12 @@ dnsrps_log_fnc(librpz_log_level_t level, void *ctxt, const char *buf) { case LIBRPZ_LOG_TRACE4: /* librpz lookups */ isc_level = DNS_RPZ_DEBUG_LEVEL3; break; + + case LIBRPZ_LOG_FATAL: + case LIBRPZ_LOG_ERROR: /* errors */ + default: + isc_level = DNS_RPZ_ERROR_LEVEL; + break; } isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, isc_level, "dnsrps: %s", buf); @@ -319,9 +319,6 @@ dns_dnsrps_2policy(librpz_policy_t rps_policy) { dns_rpz_type_t dns_dnsrps_trig2type(librpz_trig_t trig) { switch (trig) { - case LIBRPZ_TRIG_BAD: - default: - return (DNS_RPZ_TYPE_BAD); case LIBRPZ_TRIG_CLIENT_IP: return (DNS_RPZ_TYPE_CLIENT_IP); case LIBRPZ_TRIG_QNAME: @@ -332,6 +329,9 @@ dns_dnsrps_trig2type(librpz_trig_t trig) { return (DNS_RPZ_TYPE_NSDNAME); case LIBRPZ_TRIG_NSIP: return (DNS_RPZ_TYPE_NSIP); + case LIBRPZ_TRIG_BAD: + default: + return (DNS_RPZ_TYPE_BAD); } } @@ -341,9 +341,6 @@ dns_dnsrps_trig2type(librpz_trig_t trig) { librpz_trig_t dns_dnsrps_type2trig(dns_rpz_type_t type) { switch (type) { - case DNS_RPZ_TYPE_BAD: - default: - return (LIBRPZ_TRIG_BAD); case DNS_RPZ_TYPE_CLIENT_IP: return (LIBRPZ_TRIG_CLIENT_IP); case DNS_RPZ_TYPE_QNAME: @@ -354,6 +351,9 @@ dns_dnsrps_type2trig(dns_rpz_type_t type) { return (LIBRPZ_TRIG_NSDNAME); case DNS_RPZ_TYPE_NSIP: return (LIBRPZ_TRIG_NSIP); + case DNS_RPZ_TYPE_BAD: + default: + return (LIBRPZ_TRIG_BAD); } } @@ -493,6 +493,16 @@ rpsdb_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, REQUIRE(node == &rpsdb->data_node); switch (rpsdb->result.policy) { + case LIBRPZ_POLICY_NXDOMAIN: + return (DNS_R_NXDOMAIN); + + case LIBRPZ_POLICY_NODATA: + return (DNS_R_NXRRSET); + + case LIBRPZ_POLICY_RECORD: + case LIBRPZ_POLICY_CNAME: + break; + case LIBRPZ_POLICY_UNDEFINED: case LIBRPZ_POLICY_DELETED: case LIBRPZ_POLICY_PASSTHRU: @@ -505,16 +515,6 @@ rpsdb_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, "impossible dnsrps policy %d at %s:%d", rpsdb->result.policy, __FILE__, __LINE__); return (DNS_R_SERVFAIL); - - case LIBRPZ_POLICY_NXDOMAIN: - return (DNS_R_NXDOMAIN); - - case LIBRPZ_POLICY_NODATA: - return (DNS_R_NXRRSET); - - case LIBRPZ_POLICY_RECORD: - case LIBRPZ_POLICY_CNAME: - break; } if (type == dns_rdatatype_soa) { diff --git a/lib/dns/include/dns/librpz.h b/lib/dns/include/dns/librpz.h index 60496be8ee..dd09ac87b1 100644 --- a/lib/dns/include/dns/librpz.h +++ b/lib/dns/include/dns/librpz.h @@ -55,6 +55,8 @@ #define LIBDEF_F(f) #endif /* ifdef LIBRPZ_INTERNAL */ +#define LIBRPZ_MAXDOMAIN 255 + /* * Response Policy Zone triggers. * Comparisons of trigger precedences require @@ -125,7 +127,7 @@ typedef struct librpz_prefix { typedef uint8_t librpz_dsize_t; typedef struct librpz_domain { librpz_dsize_t size; /* of only .d */ - uint8_t d[0]; /* variable length wire format */ + uint8_t d[]; /* variable length wire format */ } librpz_domain_t; /* @@ -133,7 +135,7 @@ typedef struct librpz_domain { */ typedef struct librpz_domain_buf { librpz_dsize_t size; - uint8_t d[NS_MAXCDNAME]; + uint8_t d[LIBRPZ_MAXDOMAIN]; } librpz_domain_buf_t; /* @@ -145,7 +147,7 @@ typedef struct { uint16_t class; /* network byte order */ uint32_t ttl; /* network byte order */ uint16_t rdlength; /* network byte order */ - uint8_t rdata[0]; /* variable length */ + uint8_t rdata[]; /* variable length */ } librpz_rr_t; /* @@ -169,8 +171,7 @@ typedef struct librpz_result { librpz_dznum_t dznum; /* dnsrpzd zone number */ librpz_cznum_t cznum; /* librpz client zone number */ librpz_trig_t trig : LIBRPZ_TRIG_SIZE; - bool log : 1; /* log rewrite given librpz_log_level - * */ + bool log : 1; /* log rewrite at given log level */ } librpz_result_t; /** diff --git a/lib/ns/query.c b/lib/ns/query.c index 2fe7386c3c..4ce1c4d7ab 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -3348,6 +3348,8 @@ dnsrps_ck(librpz_emsg_t *emsg, ns_client_t *client, rpsdb_t *rpsdb, isc_region_t region; librpz_domain_buf_t pname_buf; + CTRACE(ISC_LOG_DEBUG(3), "dnsrps_ck"); + if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) { return (-1); } @@ -3396,18 +3398,18 @@ static bool dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st, dns_rdatatype_t qtype, dns_rdataset_t **p_rdatasetp, bool recursed) { - rpsdb_t *rpsdb; + rpsdb_t *rpsdb = NULL; librpz_domain_buf_t pname_buf; isc_region_t region; - dns_zone_t *p_zone; - dns_db_t *p_db; - dns_dbnode_t *p_node; + dns_zone_t *p_zone = NULL; + dns_db_t *p_db = NULL; + dns_dbnode_t *p_node = NULL; dns_rpz_policy_t policy; - dns_fixedname_t foundf; - dns_name_t *found; dns_rdatatype_t foundtype, searchtype; isc_result_t result; + CTRACE(ISC_LOG_DEBUG(3), "dnsrps_set_p"); + rpsdb = (rpsdb_t *)st->rpsdb; if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) { @@ -3437,9 +3439,6 @@ dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st, region.length = pname_buf.size; dns_name_fromregion(st->p_name, ®ion); - p_zone = NULL; - p_db = NULL; - p_node = NULL; rpz_ready(client, p_rdatasetp); dns_db_attach(st->rpsdb, &p_db); policy = dns_dnsrps_2policy(rpsdb->result.policy); @@ -3453,6 +3452,9 @@ dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st, result = DNS_R_NXRRSET; policy = DNS_RPZ_POLICY_NODATA; } else { + dns_fixedname_t foundf; + dns_name_t *found = NULL; + /* * Get the next (and so first) RR from the policy node. * If it is a CNAME, then look for it regardless of the @@ -3464,6 +3466,7 @@ dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st, { return (false); } + if (foundtype == dns_rdatatype_cname) { searchtype = dns_rdatatype_cname; } else { @@ -3511,6 +3514,8 @@ dnsrps_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr, librpz_emsg_t emsg; isc_result_t result; + CTRACE(ISC_LOG_DEBUG(3), "dnsrps_rewrite_ip"); + st = client->query.rpz_st; rpsdb = (rpsdb_t *)st->rpsdb; @@ -3567,6 +3572,8 @@ dnsrps_rewrite_name(ns_client_t *client, dns_name_t *trig_name, bool recursed, librpz_emsg_t emsg; isc_result_t result; + CTRACE(ISC_LOG_DEBUG(3), "dnsrps_rewrite_name"); + st = client->query.rpz_st; rpsdb = (rpsdb_t *)st->rpsdb; @@ -4201,6 +4208,7 @@ rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, isc_result_t qresult, if (st->rpsdb != NULL) { dns_db_detach(&st->rpsdb); } + CTRACE(ISC_LOG_DEBUG(3), "dns_dnsrps_rewrite_init"); result = dns_dnsrps_rewrite_init( &emsg, st, rpzs, client->query.qname, client->manager->mctx, RECURSIONOK(client));