Files
bind9/bin/tests/system/rpz/testlib/dummylib.c
Ondřej Surý 29caa6d1f0 Explicitly cast chars to unsigned chars for <ctype.h> functions
Apply the semantic patch to catch all the places where we pass 'char' to
the <ctype.h> family of functions (isalpha() and friends, toupper(),
tolower()).
2023-09-22 08:29:17 +02:00

2232 lines
49 KiB
C

/*
* 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/endian.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;
} 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;
ssize_t *base_zones;
size_t nbase_zones;
} 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 bool
domain_ntop(const u_char *src, char *dst, size_t dstsiz);
static bool
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)
LIBRPZ_PF(3, 0);
void
trpz_log(librpz_log_level_t level, void *ctx, const char *p, ...)
LIBRPZ_PF(3, 4);
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) LIBRPZ_PF(2, 0);
void
trpz_pemsg(librpz_emsg_t *emsg, const char *fmt, ...) LIBRPZ_PF(2, 3);
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_rsp_t *trsp, ssize_t zone) {
size_t n;
if (trsp == NULL || trsp->base_zones == NULL || trsp->nbase_zones == 0)
{
return (false);
}
for (n = 0; n < trsp->nbase_zones; n++) {
if (trsp->base_zones[n] == BASE_ZONE_ANY ||
trsp->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) {
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;
rdatap += rlen + mlen;
ISC_U32TO8_BE(rdatap, psoa->serial);
rdatap += 4;
ISC_U32TO8_BE(rdatap, psoa->refresh);
rdatap += 4;
ISC_U32TO8_BE(rdatap, psoa->retry);
rdatap += 4;
ISC_U32TO8_BE(rdatap, psoa->expire);
rdatap += 4;
ISC_U32TO8_BE(rdatap, psoa->minimum);
used += (4 * 5);
SET_IF_NOT_NULL(rdlenp, used);
return (true);
}
static void
do_log(librpz_log_level_t level, void *ctx, const char *fmt, va_list args)
LIBRPZ_PF(3, 0);
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;
char *tmp = NULL;
updfile = getenv("DNSRPS_TEST_UPDATE_FILE");
if (updfile == NULL) {
return;
}
tmp = strdup(updfile);
if (tmp == NULL) {
return;
}
fname = strtok_r(tmp, ":", &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, "%s", errp);
free(errp);
}
fname = strtok_r(NULL, ":", &last);
}
free(tmp);
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((unsigned char)*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((unsigned char)*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((unsigned char)*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) {
trpz_client_t *client = (trpz_client_t *)(*clientp);
if (client->cstr != NULL) {
free(client->cstr);
}
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;
char *tmp = NULL;
updfile = getenv("DNSRPS_TEST_UPDATE_FILE");
if (updfile == NULL) {
return (0);
}
tmp = strdup(updfile);
if (tmp == NULL) {
return (-1);
}
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 != NULL) {
fprintf(stderr, "Error loading updates: %s\n", errp);
free(errp);
}
if (ret < 0) {
free(tmp);
return (-1);
}
fname = strtok_r(NULL, ":", &last);
}
free(tmp);
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].canonical != NULL) {
free(trsp->all_nodes[n].canonical);
}
if (trsp->all_nodes[n].dname != NULL) {
free(trsp->all_nodes[n].dname);
}
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;
assert(*rspp == NULL);
clear_all_updates(result);
result->base_zones = get_cstr_zones(cli->cstr, result,
&(result->nbase_zones));
if (result->base_zones == NULL) {
trpz_pemsg(emsg, "no valid policy zone specified");
clear_all_updates(result);
free(result);
return (false);
}
if (apply_all_updates(result) < 0) {
trpz_pemsg(emsg, "internal error loading test data 1");
clear_all_updates(result);
free(result->base_zones);
free(result);
return (false);
}
*rspp = (librpz_rsp_t *)result;
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 != NULL && *rspp != NULL) {
trpz_rsp_t *trsp = (trpz_rsp_t *)*rspp;
*rspp = NULL;
clear_all_updates(trsp);
if (trsp->base_zones != NULL) {
free(trsp->base_zones);
}
free(trsp);
}
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 (origin != NULL) {
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);
}
if (rrp != NULL) {
*rrp = rres;
} else {
free(rdbuf);
}
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,
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,
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)) == 0) {
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 || ipstr[1] < 0 || ipstr[2] < 0 ||
ipstr[3] < 0 || ipstr[4] < 0)
{
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 != NULL) {
*pmask = nmask;
}
return (((a1 & m) == (a2 & m)) ? 0 : 1);
}
} else {
return (-1);
}
if (strcmp(addrstr, abuf) == 0) {
if (pmask != NULL) {
*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,
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,
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 > 0U && 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, 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 bool
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 (false);
}
memset(dst, 0, dstsiz);
while (*sptr) {
if (((dptr + *sptr) > dend)) {
return (false);
}
if (sptr != src) {
*dptr++ = '.';
}
memmove(dptr, sptr + 1, *sptr);
dptr += *sptr;
sptr += *sptr;
sptr++;
}
return (true);
}
static bool
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 (false);
}
tptr = tmps;
SET_IF_NOT_NULL(dstlen, 0);
while (tptr && *tptr) {
tok = strsep(&tptr, ".");
if (((dptr + strlen(tok) + 1) > dend)) {
free(tmps);
return (false);
}
*dptr++ = strlen(tok);
memmove(dptr, tok, strlen(tok));
dptr += strlen(tok);
if (dstlen != NULL) {
(*dstlen) += (1 + strlen(tok));
}
}
if (dptr >= dend) {
free(tmps);
return (false);
}
*dptr = 0;
if (dstlen != NULL) {
(*dstlen)++;
}
free(tmps);
return (true);
}
/* 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;
}
SET_IF_NOT_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) {
int 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);
if (nrd < 0) {
trpz_pemsg(
emsg,
"Error packing "
"domain");
return (false);
}
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));
if (nrdata != NULL) {
free(nrdata);
}
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);
if (nrdata != NULL) {
free(nrdata);
}
}
result->next_rr = this_rr->rrn;
trsp->rstack[0].result.next_rr = this_rr->rrn;
last_result->rridx++;
} else {
SET_IF_NOT_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);
}