#ifdef HAVE_TLS_1_3
void log_crypto_err(const char* str); /* in server.c */
static SSL_CTX*
create_ssl_context()
{
SSL_CTX *ctx;
unsigned char protos[] = { 3, 'd', 'o', 't' };
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
log_msg(LOG_ERR, "xfrd tls: Unable to create SSL ctxt");
}
else if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
SSL_CTX_free(ctx);
log_msg(LOG_ERR, "xfrd tls: Unable to set default SSL verify paths");
return NULL;
}
/* Only trust 1.3 as per the specification */
else if (!SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION)) {
SSL_CTX_free(ctx);
log_msg(LOG_ERR, "xfrd tls: Unable to set minimum TLS version 1.3");
return NULL;
}
if (SSL_CTX_set_alpn_protos(ctx, protos, sizeof(protos)) != 0) {
SSL_CTX_free(ctx);
log_msg(LOG_ERR, "xfrd tls: Unable to set ALPN protocols");
return NULL;
}
return ctx;
}
static int
tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
int err = X509_STORE_CTX_get_error(ctx);
int depth = X509_STORE_CTX_get_error_depth(ctx);
// report the specific cert error here - will need custom verify code if
// SPKI pins are supported
if (!preverify_ok)
log_msg(LOG_ERR, "xfrd tls: TLS verify failed - (%d) depth: %d error: %s",
err,
depth,
X509_verify_cert_error_string(err));
return preverify_ok;
}
static int
setup_ssl(struct xfrd_tcp_pipeline* tp, struct xfrd_tcp_set* tcp_set,
const char* auth_domain_name)
{
if (!tcp_set->ssl_ctx) {
log_msg(LOG_ERR, "xfrd tls: No TLS CTX, cannot set up XFR-over-TLS");
return 0;
}
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: setting up TLS for tls_auth domain name %s",
auth_domain_name));
tp->ssl = SSL_new((SSL_CTX*)tcp_set->ssl_ctx);
if(!tp->ssl) {
log_msg(LOG_ERR, "xfrd tls: Unable to create TLS object");
return 0;
}
SSL_set_connect_state(tp->ssl);
(void)SSL_set_mode(tp->ssl, SSL_MODE_AUTO_RETRY);
if(!SSL_set_fd(tp->ssl, tp->tcp_w->fd)) {
log_msg(LOG_ERR, "xfrd tls: Unable to set TLS fd");
SSL_free(tp->ssl);
tp->ssl = NULL;
return 0;
}
int password_cb(char *buf, int size, int ATTR_UNUSED(rwflag), void *u)
{
strlcpy(buf, (char*)u, size);
return strlen(buf);
}
#endif
/* sort tcppipe, first on IP address, for an IPaddresss, sort on num_unused */
static int
xfrd_pipe_cmp(const void* a, const void* b)
{
const struct xfrd_tcp_pipeline* x = (struct xfrd_tcp_pipeline*)a;
const struct xfrd_tcp_pipeline* y = (struct xfrd_tcp_pipeline*)b;
int r;
if(x == y)
return 0;
if(y->key.ip_len != x->key.ip_len)
/* subtraction works because nonnegative and small numbers */
return (int)y->key.ip_len - (int)x->key.ip_len;
r = memcmp(&x->key.ip, &y->key.ip, x->key.ip_len);
if(r != 0)
return r;
/* sort that num_unused is sorted ascending, */
if(x->key.num_unused != y->key.num_unused) {
return (x->key.num_unused < y->key.num_unused) ? -1 : 1;
}
/* different pipelines are different still, even with same numunused*/
return (uintptr_t)x < (uintptr_t)y ? -1 : 1;
}
struct xfrd_tcp_set* xfrd_tcp_set_create(struct region* region, const char *tls_cert_bundle, int tcp_max, int tcp_pipeline)
{
int i;
struct xfrd_tcp_set* tcp_set = region_alloc(region,
sizeof(struct xfrd_tcp_set));
memset(tcp_set, 0, sizeof(struct xfrd_tcp_set));
tcp_set->tcp_state = NULL;
tcp_set->tcp_max = tcp_max;
tcp_set->tcp_pipeline = tcp_pipeline;
tcp_set->tcp_count = 0;
tcp_set->tcp_waiting_first = 0;
tcp_set->tcp_waiting_last = 0;
#ifdef HAVE_TLS_1_3
/* Set up SSL context */
tcp_set->ssl_ctx = create_ssl_context();
if (tcp_set->ssl_ctx == NULL)
log_msg(LOG_ERR, "xfrd: XFR-over-TLS not available");
else if (tls_cert_bundle && tls_cert_bundle[0] && SSL_CTX_load_verify_locations(
tcp_set->ssl_ctx, tls_cert_bundle, NULL) != 1) {
log_msg(LOG_ERR, "xfrd tls: Unable to set the certificate bundle file %s",
tls_cert_bundle);
}
#else
(void)tls_cert_bundle;
log_msg(LOG_INFO, "xfrd: No TLS 1.3 support - XFR-over-TLS not available");
#endif
tcp_set->tcp_state = region_alloc(region,
sizeof(*tcp_set->tcp_state)*tcp_set->tcp_max);
for(i=0; i<tcp_set->tcp_max; i++)
tcp_set->tcp_state[i] = xfrd_tcp_pipeline_create(region,
tcp_pipeline);
tcp_set->pipetree = rbtree_create(region, &xfrd_pipe_cmp);
return tcp_set;
}
static struct xfrd_tcp_pipeline*
pipeline_find(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
{
rbnode_type* sme = NULL;
struct xfrd_tcp_pipeline* r;
/* smaller buf than a full pipeline with 64kb ID array, only need
* the front part with the key info, this front part contains the
* members that the compare function uses. */
struct xfrd_tcp_pipeline_key k, *key=&k;
key->node.key = key;
key->ip_len = xfrd_acl_sockaddr_to(zone->master, &key->ip);
key->num_unused = set->tcp_pipeline;
/* lookup existing tcp transfer to the master with highest unused */
if(rbtree_find_less_equal(set->pipetree, key, &sme)) {
/* exact match, strange, fully unused tcp cannot be open */
assert(0);
}
if(!sme)
return NULL;
r = (struct xfrd_tcp_pipeline*)sme->key;
/* <= key pointed at, is the master correct ? */
if(r->key.ip_len != key->ip_len)
return NULL;
if(memcmp(&r->key.ip, &key->ip, key->ip_len) != 0)
return NULL;
/* correct master, is there a slot free for this transfer? */
if(r->key.num_unused == 0)
return NULL;
return r;
}
/* remove first from write-wait list */
static void
tcp_pipe_sendlist_popfirst(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone)
{
tp->tcp_send_first = zone->tcp_send_next;
if(tp->tcp_send_first)
tp->tcp_send_first->tcp_send_prev = NULL;
else tp->tcp_send_last = NULL;
zone->in_tcp_send = 0;
}
/* remove zone from tcp pipe ID map */
static void
tcp_pipe_id_remove(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone,
int alsotree)
{
assert(tp->key.num_unused < tp->pipe_num && tp->key.num_unused >= 0);
if(alsotree)
xfrd_tcp_pipeline_remove_id(tp, zone->query_id);
tp->unused[tp->key.num_unused] = zone->query_id;
/* must remove and re-add for sort order in tree */
(void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->key.node);
tp->key.num_unused++;
(void)rbtree_insert(xfrd->tcp_set->pipetree, &tp->key.node);
}
/* stop the tcp pipe (and all its zones need to retry) */
static void
xfrd_tcp_pipe_stop(struct xfrd_tcp_pipeline* tp)
{
struct xfrd_tcp_pipeline_id* zid;
int conn = -1;
assert(tp->key.num_unused < tp->pipe_num); /* at least one 'in-use' */
assert(tp->pipe_num - tp->key.num_unused > tp->key.num_skip); /* at least one 'nonskip' */
/* need to retry for all the zones connected to it */
/* these could use different lists and go to a different nextmaster*/
RBTREE_FOR(zid, struct xfrd_tcp_pipeline_id*, tp->zone_per_id) {
xfrd_zone_type* zone = zid->zone;
if(zone && zone != TCP_NULL_SKIP) {
assert(zone->query_id == zid->id);
conn = zone->tcp_conn;
zone->tcp_conn = -1;
zone->tcp_waiting = 0;
tcp_pipe_sendlist_remove(tp, zone);
tcp_pipe_id_remove(tp, zone, 0);
xfrd_set_refresh_now(zone);
}
}
xfrd_tcp_pipeline_cleanup(tp);
assert(conn != -1);
/* now release the entire tcp pipe */
xfrd_tcp_pipe_release(xfrd->tcp_set, tp, conn);
}
/* add a zone to the pipeline, it starts to want to write its query */
static void
pipeline_setup_new_zone(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp,
xfrd_zone_type* zone)
{
/* assign the ID */
int idx;
assert(tp->key.num_unused > 0);
/* we pick a random ID, even though it is TCP anyway */
idx = random_generate(tp->key.num_unused);
zone->query_id = tp->unused[idx];
tp->unused[idx] = tp->unused[tp->key.num_unused-1];
xfrd_tcp_pipeline_insert_id(tp, zone->query_id, zone);
/* decrement unused counter, and fixup tree */
(void)rbtree_delete(set->pipetree, &tp->key.node);
tp->key.num_unused--;
(void)rbtree_insert(set->pipetree, &tp->key.node);
/* add to sendlist, at end */
zone->tcp_send_next = NULL;
zone->tcp_send_prev = tp->tcp_send_last;
zone->in_tcp_send = 1;
if(tp->tcp_send_last)
tp->tcp_send_last->tcp_send_next = zone;
else tp->tcp_send_first = zone;
tp->tcp_send_last = zone;
/* is it first in line? */
if(tp->tcp_send_first == zone) {
xfrd_tcp_setup_write_packet(tp, zone);
/* add write to event handler */
tcp_pipe_reset_timeout(tp);
}
}
if(!xfrd_tcp_open(set, tp, zone)) {
zone->tcp_conn = -1;
set->tcp_count --;
xfrd_set_refresh_now(zone);
return;
}
/* ip and ip_len set by tcp_open */
xfrd_tcp_pipeline_init(tp);
/* insert into tree */
(void)rbtree_insert(set->pipetree, &tp->key.node);
xfrd_deactivate_zone(zone);
xfrd_unset_timer(zone);
pipeline_setup_new_zone(set, tp, zone);
return;
}
/* check for a pipeline to the same master with unused ID */
if((tp = pipeline_find(set, zone))!= NULL) {
int i;
if(zone->zone_handler.ev_fd != -1)
xfrd_udp_release(zone);
for(i=0; i<set->tcp_max; i++) {
if(set->tcp_state[i] == tp)
zone->tcp_conn = i;
}
xfrd_deactivate_zone(zone);
xfrd_unset_timer(zone);
pipeline_setup_new_zone(set, tp, zone);
return;
}
/* wait, at end of line */
DEBUG(DEBUG_XFRD,2, (LOG_INFO, "xfrd: max number of tcp "
"connections (%d) reached.", set->tcp_max));
zone->tcp_waiting_next = 0;
zone->tcp_waiting_prev = set->tcp_waiting_last;
zone->tcp_waiting = 1;
if(!set->tcp_waiting_last) {
set->tcp_waiting_first = zone;
set->tcp_waiting_last = zone;
} else {
set->tcp_waiting_last->tcp_waiting_next = zone;
set->tcp_waiting_last = zone;
}
xfrd_deactivate_zone(zone);
xfrd_unset_timer(zone);
}
int
xfrd_tcp_open(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp,
xfrd_zone_type* zone)
{
int fd, family, conn;
struct timeval tv;
assert(zone->tcp_conn != -1);
/* if there is no next master, fallback to use the first one */
/* but there really should be a master set */
if(!zone->master) {
zone->master = zone->zone_options->pattern->request_xfr;
zone->master_num = 0;
}
/* Check if an tls_auth name is configured which means we should try to
establish an SSL connection */
if (zone->master->tls_auth_options &&
zone->master->tls_auth_options->auth_domain_name) {
#ifdef HAVE_TLS_1_3
if (!setup_ssl(tp, set, zone->master->tls_auth_options->auth_domain_name)) {
log_msg(LOG_ERR, "xfrd: Cannot setup TLS on pipeline for %s to %s",
zone->apex_str, zone->master->ip_address_spec);
close(fd);
xfrd_set_refresh_now(zone);
return 0;
}
/* Load client certificate (if provided) */
if (zone->master->tls_auth_options->client_cert &&
zone->master->tls_auth_options->client_key) {
if (SSL_CTX_use_certificate_chain_file(set->ssl_ctx,
zone->master->tls_auth_options->client_cert) != 1) {
log_msg(LOG_ERR, "xfrd tls: Unable to load client certificate from file %s", zone->master->tls_auth_options->client_cert);
}
if (zone->master->tls_auth_options->client_key_pw) {
SSL_CTX_set_default_passwd_cb(set->ssl_ctx, password_cb);
SSL_CTX_set_default_passwd_cb_userdata(set->ssl_ctx, zone->master->tls_auth_options->client_key_pw);
}
if (SSL_CTX_use_PrivateKey_file(set->ssl_ctx, zone->master->tls_auth_options->client_key, SSL_FILETYPE_PEM) != 1) {
log_msg(LOG_ERR, "xfrd tls: Unable to load private key from file %s", zone->master->tls_auth_options->client_key);
}
}
void
xfrd_tcp_setup_write_packet(struct xfrd_tcp_pipeline* tp, xfrd_zone_type* zone)
{
struct xfrd_tcp* tcp = tp->tcp_w;
assert(zone->tcp_conn != -1);
assert(zone->tcp_waiting == 0);
/* start AXFR or IXFR for the zone */
if(zone->soa_disk_acquired == 0 || zone->master->use_axfr_only ||
zone->master->ixfr_disabled ||
/* if zone expired, after the first round, do not ask for
* IXFR any more, but full AXFR (of any serial number) */
(zone->state == xfrd_zone_expired && zone->round_num != 0)) {
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "request full zone transfer "
"(AXFR) for %s to %s",
zone->apex_str, zone->master->ip_address_spec));
xfrd_setup_packet(tcp->packet, TYPE_AXFR, CLASS_IN, zone->apex,
zone->query_id);
xfrd_prepare_zone_xfr(zone, TYPE_AXFR);
} else {
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "request incremental zone "
"transfer (IXFR) for %s to %s",
zone->apex_str, zone->master->ip_address_spec));
/* remove first zone from sendlist */
tcp_pipe_sendlist_popfirst(tp, zone);
/* see if other zone wants to write; init; let it write (now) */
/* and use a loop, because 64k stack calls is a too much */
while(tp->tcp_send_first) {
/* setup to write for this zone */
xfrd_tcp_setup_write_packet(tp, tp->tcp_send_first);
/* attempt to write for this zone (if success, continue loop)*/
#ifdef HAVE_TLS_1_3
if (tp->ssl)
ret = conn_write_ssl(tcp, tp->ssl);
else
#endif
ret = conn_write(tcp);
if(ret == -1) {
log_msg(LOG_ERR, "xfrd: failed writing tcp %s", strerror(errno));
xfrd_tcp_pipe_stop(tp);
return;
}
if(ret == 0)
return; /* write again later */
tcp_pipe_sendlist_popfirst(tp, tp->tcp_send_first);
}
/* if sendlist empty, remove WRITE from event */
/* listen to READ, and not WRITE events */
assert(tp->tcp_send_first == NULL);
tcp_pipe_reset_timeout(tp);
}
} else if(tp->handshake_want == SSL_ERROR_SSL) {
log_crypto_err("xfrd: TLS handshake failed");
} else {
log_msg(LOG_ERR, "xfrd: TLS handshake failed "
"with value: %d", tp->handshake_want);
}
xfrd_tcp_pipe_stop(tp);
return;
}
} else
#endif
ret = conn_read(tcp);
if(ret == -1) {
if(errno != 0)
log_msg(LOG_ERR, "xfrd: failed reading tcp %s", strerror(errno));
else
log_msg(LOG_ERR, "xfrd: failed reading tcp: closed");
xfrd_tcp_pipe_stop(tp);
return;
}
if(ret == 0)
return;
/* completed msg */
buffer_flip(tcp->packet);
/* see which ID number it is, if skip, handle skip, NULL: warn */
if(tcp->msglen < QHEADERSZ) {
/* too short for DNS header, skip it */
DEBUG(DEBUG_XFRD,1, (LOG_INFO,
"xfrd: tcp skip response that is too short"));
tcp_conn_ready_for_reading(tcp);
return;
}
zone = xfrd_tcp_pipeline_lookup_id(tp, ID(tcp->packet));
if(!zone || zone == TCP_NULL_SKIP) {
/* no zone for this id? skip it */
DEBUG(DEBUG_XFRD,1, (LOG_INFO,
"xfrd: tcp skip response with %s ID",
zone?"set-to-skip":"unknown"));
tcp_conn_ready_for_reading(tcp);
return;
}
assert(zone->tcp_conn != -1);
/* handle message for zone */
pkt_result = xfrd_handle_received_xfr_packet(zone, tcp->packet);
/* setup for reading the next packet on this connection */
tcp_conn_ready_for_reading(tcp);
switch(pkt_result) {
case xfrd_packet_more:
/* wait for next packet */
break;
case xfrd_packet_newlease:
/* set to skip if more packets with this ID */
xfrd_tcp_pipeline_skip_id(tp, zone->query_id);
tp->key.num_skip++;
/* fall through to remove zone from tp */
/* fallthrough */
case xfrd_packet_transfer:
if(zone->zone_options->pattern->multi_master_check) {
xfrd_tcp_release(xfrd->tcp_set, zone);
xfrd_make_request(zone);
break;
}
xfrd_tcp_release(xfrd->tcp_set, zone);
assert(zone->round_num == -1);
break;
case xfrd_packet_notimpl:
xfrd_disable_ixfr(zone);
xfrd_tcp_release(xfrd->tcp_set, zone);
/* query next server */
xfrd_make_request(zone);
break;
case xfrd_packet_bad:
case xfrd_packet_tcp:
default:
/* set to skip if more packets with this ID */
xfrd_tcp_pipeline_skip_id(tp, zone->query_id);
tp->key.num_skip++;
xfrd_tcp_release(xfrd->tcp_set, zone);
/* query next server */
xfrd_make_request(zone);
break;
}
}
void
xfrd_tcp_release(struct xfrd_tcp_set* set, xfrd_zone_type* zone)
{
int conn = zone->tcp_conn;
struct xfrd_tcp_pipeline* tp = set->tcp_state[conn];
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s released tcp conn to %s",
zone->apex_str, zone->master->ip_address_spec));
assert(zone->tcp_conn != -1);
assert(zone->tcp_waiting == 0);
zone->tcp_conn = -1;
zone->tcp_waiting = 0;
/* remove from tcp_send list */
tcp_pipe_sendlist_remove(tp, zone);
/* remove it from the ID list */
if(xfrd_tcp_pipeline_lookup_id(tp, zone->query_id) != TCP_NULL_SKIP)
tcp_pipe_id_remove(tp, zone, 1);
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: released tcp pipe now %d unused",
tp->key.num_unused));
/* if pipe was full, but no more, then see if waiting element is
* for the same master, and can fill the unused ID */
if(tp->key.num_unused == 1 && set->tcp_waiting_first) {
#ifdef INET6
struct sockaddr_storage to;
#else
struct sockaddr_in to;
#endif
socklen_t to_len = xfrd_acl_sockaddr_to(
set->tcp_waiting_first->master, &to);
if(to_len == tp->key.ip_len && memcmp(&to, &tp->key.ip, to_len) == 0) {
/* use this connection for the waiting zone */
zone = set->tcp_waiting_first;
assert(zone->tcp_conn == -1);
zone->tcp_conn = conn;
tcp_zone_waiting_list_popfirst(set, zone);
if(zone->zone_handler.ev_fd != -1)
xfrd_udp_release(zone);
xfrd_unset_timer(zone);
pipeline_setup_new_zone(set, tp, zone);
return;
}
/* waiting zone did not go to same server */
}
/* if all unused, or only skipped leftover, close the pipeline */
if(tp->key.num_unused >= tp->pipe_num || tp->key.num_skip >= tp->pipe_num - tp->key.num_unused)
xfrd_tcp_pipe_release(set, tp, conn);
}
void
xfrd_tcp_pipe_release(struct xfrd_tcp_set* set, struct xfrd_tcp_pipeline* tp,
int conn)
{
DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: tcp pipe released"));
/* one handler per tcp pipe */
if(tp->handler_added)
event_del(&tp->handler);
tp->handler_added = 0;
#ifdef HAVE_TLS_1_3
/* close SSL */
if (tp->ssl) {
DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "xfrd: Shutting down TLS"));
SSL_shutdown(tp->ssl);
SSL_free(tp->ssl);
tp->ssl = NULL;
}
#endif
/* fd in tcp_r and tcp_w is the same, close once */
if(tp->tcp_r->fd != -1)
close(tp->tcp_r->fd);
tp->tcp_r->fd = -1;
tp->tcp_w->fd = -1;
/* remove from pipetree */
(void)rbtree_delete(xfrd->tcp_set->pipetree, &tp->key.node);
/* a waiting zone can use the free tcp slot (to another server) */
/* if that zone fails to set-up or connect, we try to start the next
* waiting zone in the list */
while(set->tcp_count == set->tcp_max && set->tcp_waiting_first) {
/* pop first waiting process */
xfrd_zone_type* zone = set->tcp_waiting_first;
/* start it */
assert(zone->tcp_conn == -1);
zone->tcp_conn = conn;
tcp_zone_waiting_list_popfirst(set, zone);
/* stop udp (if any) */
if(zone->zone_handler.ev_fd != -1)
xfrd_udp_release(zone);
if(!xfrd_tcp_open(set, tp, zone)) {
zone->tcp_conn = -1;
xfrd_set_refresh_now(zone);
/* try to start the next zone (if any) */
continue;
}
/* re-init this tcppipe */
/* ip and ip_len set by tcp_open */
xfrd_tcp_pipeline_init(tp);
/* insert into tree */
(void)rbtree_insert(set->pipetree, &tp->key.node);
/* setup write */
xfrd_unset_timer(zone);
pipeline_setup_new_zone(set, tp, zone);
/* started a task, no need for cleanups, so return */
return;
}
/* no task to start, cleanup */
assert(!set->tcp_waiting_first);
set->tcp_count --;
assert(set->tcp_count >= 0);
}