--- /dev/null
+/*
+ * 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 <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/util.h>
+
+#include <dns/librpz.h>
+
+#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);
+}
--- /dev/null
+/*
+ * 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 <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/util.h>
+
+#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);
+}