summaryrefslogtreecommitdiff
path: root/ipsecmod/ipsecmod.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipsecmod/ipsecmod.c')
-rw-r--r--ipsecmod/ipsecmod.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c
new file mode 100644
index 0000000000000..3e4ee6a535083
--- /dev/null
+++ b/ipsecmod/ipsecmod.c
@@ -0,0 +1,515 @@
+/*
+ * ipsecmod/ipsecmod.c - facilitate opportunistic IPsec module
+ *
+ * Copyright (c) 2017, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file contains a module that facilitates opportunistic IPsec. It does so
+ * by also quering for the IPSECKEY for A/AAAA queries and calling a
+ * configurable hook (eg. signaling an IKE daemon) before replying.
+ */
+
+#include "config.h"
+#ifdef USE_IPSECMOD
+#include "ipsecmod/ipsecmod.h"
+#include "ipsecmod/ipsecmod-whitelist.h"
+#include "util/fptr_wlist.h"
+#include "util/regional.h"
+#include "util/net_help.h"
+#include "util/config_file.h"
+#include "services/cache/dns.h"
+#include "sldns/wire2str.h"
+
+/** Apply configuration to ipsecmod module 'global' state. */
+static int
+ipsecmod_apply_cfg(struct ipsecmod_env* ipsecmod_env, struct config_file* cfg)
+{
+ if(!cfg->ipsecmod_hook || (cfg->ipsecmod_hook && !cfg->ipsecmod_hook[0])) {
+ log_err("ipsecmod: missing ipsecmod-hook.");
+ return 0;
+ }
+ if(cfg->ipsecmod_whitelist &&
+ !ipsecmod_whitelist_apply_cfg(ipsecmod_env, cfg))
+ return 0;
+ return 1;
+}
+
+int
+ipsecmod_init(struct module_env* env, int id)
+{
+ struct ipsecmod_env* ipsecmod_env = (struct ipsecmod_env*)calloc(1,
+ sizeof(struct ipsecmod_env));
+ if(!ipsecmod_env) {
+ log_err("malloc failure");
+ return 0;
+ }
+ env->modinfo[id] = (void*)ipsecmod_env;
+ ipsecmod_env->whitelist = NULL;
+ if(!ipsecmod_apply_cfg(ipsecmod_env, env->cfg)) {
+ log_err("ipsecmod: could not apply configuration settings.");
+ return 0;
+ }
+ return 1;
+}
+
+void
+ipsecmod_deinit(struct module_env* env, int id)
+{
+ struct ipsecmod_env* ipsecmod_env;
+ if(!env || !env->modinfo[id])
+ return;
+ ipsecmod_env = (struct ipsecmod_env*)env->modinfo[id];
+ /* Free contents. */
+ ipsecmod_whitelist_delete(ipsecmod_env->whitelist);
+ free(ipsecmod_env);
+ env->modinfo[id] = NULL;
+}
+
+/** New query for ipsecmod. */
+static int
+ipsecmod_new(struct module_qstate* qstate, int id)
+{
+ struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)regional_alloc(
+ qstate->region, sizeof(struct ipsecmod_qstate));
+ memset(iq, 0, sizeof(*iq));
+ qstate->minfo[id] = iq;
+ if(!iq)
+ return 0;
+ /* Initialise it. */
+ iq->enabled = qstate->env->cfg->ipsecmod_enabled;
+ iq->is_whitelisted = ipsecmod_domain_is_whitelisted(
+ (struct ipsecmod_env*)qstate->env->modinfo[id], qstate->qinfo.qname,
+ qstate->qinfo.qname_len, qstate->qinfo.qclass);
+ return 1;
+}
+
+/**
+ * Exit module with an error status.
+ * @param qstate: query state
+ * @param id: module id.
+ */
+static void
+ipsecmod_error(struct module_qstate* qstate, int id)
+{
+ qstate->ext_state[id] = module_error;
+ qstate->return_rcode = LDNS_RCODE_SERVFAIL;
+}
+
+/**
+ * Generate a request for the IPSECKEY.
+ *
+ * @param qstate: query state that is the parent.
+ * @param id: module id.
+ * @param name: what name to query for.
+ * @param namelen: length of name.
+ * @param qtype: query type.
+ * @param qclass: query class.
+ * @param flags: additional flags, such as the CD bit (BIT_CD), or 0.
+ * @return false on alloc failure.
+ */
+static int
+generate_request(struct module_qstate* qstate, int id, uint8_t* name,
+ size_t namelen, uint16_t qtype, uint16_t qclass, uint16_t flags)
+{
+ struct module_qstate* newq;
+ struct query_info ask;
+ ask.qname = name;
+ ask.qname_len = namelen;
+ ask.qtype = qtype;
+ ask.qclass = qclass;
+ ask.local_alias = NULL;
+ log_query_info(VERB_ALGO, "ipsecmod: generate request", &ask);
+ fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
+ if(!(*qstate->env->attach_sub)(qstate, &ask,
+ (uint16_t)(BIT_RD|flags), 0, 0, &newq)){
+ log_err("Could not generate request: out of memory");
+ return 0;
+ }
+ qstate->ext_state[id] = module_wait_subquery;
+ return 1;
+}
+
+/**
+ * Prepare the data and call the hook.
+ *
+ * @param qstate: query state.
+ * @param iq: ipsecmod qstate.
+ * @param ie: ipsecmod environment.
+ * @return true on success, false otherwise.
+ */
+static int
+call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq,
+ struct ipsecmod_env* ATTR_UNUSED(ie))
+{
+ size_t slen, tempdata_len, tempstring_len, i;
+ char str[65535], *s, *tempstring;
+ int w;
+ struct ub_packed_rrset_key* rrset_key;
+ struct packed_rrset_data* rrset_data;
+ uint8_t *tempdata;
+
+ /* Check if a shell is available */
+ if(system(NULL) == 0) {
+ log_err("ipsecmod: no shell available for ipsecmod-hook");
+ return 0;
+ }
+
+ /* Zero the buffer. */
+ s = str;
+ slen = sizeof(str);
+ memset(s, 0, slen);
+
+ /* Copy the hook into the buffer. */
+ sldns_str_print(&s, &slen, "%s", qstate->env->cfg->ipsecmod_hook);
+ /* Put space into the buffer. */
+ sldns_str_print(&s, &slen, " ");
+ /* Copy the qname into the buffer. */
+ tempstring = sldns_wire2str_dname(qstate->qinfo.qname,
+ qstate->qinfo.qname_len);
+ if(!tempstring) {
+ log_err("ipsecmod: out of memory when calling the hook");
+ return 0;
+ }
+ sldns_str_print(&s, &slen, "\"%s\"", tempstring);
+ free(tempstring);
+ /* Put space into the buffer. */
+ sldns_str_print(&s, &slen, " ");
+ /* Copy the IPSECKEY TTL into the buffer. */
+ rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
+ sldns_str_print(&s, &slen, "\"%ld\"", (long)rrset_data->ttl);
+ /* Put space into the buffer. */
+ sldns_str_print(&s, &slen, " ");
+ /* Copy the A/AAAA record(s) into the buffer. Start and end this section
+ * with a double quote. */
+ rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo,
+ qstate->return_msg->rep);
+ rrset_data = (struct packed_rrset_data*)rrset_key->entry.data;
+ sldns_str_print(&s, &slen, "\"");
+ for(i=0; i<rrset_data->count; i++) {
+ if(i > 0) {
+ /* Put space into the buffer. */
+ sldns_str_print(&s, &slen, " ");
+ }
+ /* Ignore the first two bytes, they are the rr_data len. */
+ w = sldns_wire2str_rdata_buf(rrset_data->rr_data[i] + 2,
+ rrset_data->rr_len[i] - 2, s, slen, qstate->qinfo.qtype);
+ if(w < 0) {
+ /* Error in printout. */
+ return -1;
+ } else if((size_t)w >= slen) {
+ s = NULL; /* We do not want str to point outside of buffer. */
+ slen = 0;
+ return -1;
+ } else {
+ s += w;
+ slen -= w;
+ }
+ }
+ sldns_str_print(&s, &slen, "\"");
+ /* Put space into the buffer. */
+ sldns_str_print(&s, &slen, " ");
+ /* Copy the IPSECKEY record(s) into the buffer. Start and end this section
+ * with a double quote. */
+ sldns_str_print(&s, &slen, "\"");
+ rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
+ for(i=0; i<rrset_data->count; i++) {
+ if(i > 0) {
+ /* Put space into the buffer. */
+ sldns_str_print(&s, &slen, " ");
+ }
+ /* Ignore the first two bytes, they are the rr_data len. */
+ tempdata = rrset_data->rr_data[i] + 2;
+ tempdata_len = rrset_data->rr_len[i] - 2;
+ /* Save the buffer pointers. */
+ tempstring = s; tempstring_len = slen;
+ w = sldns_wire2str_ipseckey_scan(&tempdata, &tempdata_len, &s, &slen,
+ NULL, 0);
+ /* There was an error when parsing the IPSECKEY; reset the buffer
+ * pointers to their previous values. */
+ if(w == -1){
+ s = tempstring; slen = tempstring_len;
+ }
+ }
+ sldns_str_print(&s, &slen, "\"");
+ verbose(VERB_ALGO, "ipsecmod: hook command: '%s'", str);
+ /* ipsecmod-hook should return 0 on success. */
+ if(system(str) != 0)
+ return 0;
+ return 1;
+}
+
+/**
+ * Handle an ipsecmod module event with a query
+ * @param qstate: query state (from the mesh), passed between modules.
+ * contains qstate->env module environment with global caches and so on.
+ * @param iq: query state specific for this module. per-query.
+ * @param ie: environment specific for this module. global.
+ * @param id: module id.
+ */
+static void
+ipsecmod_handle_query(struct module_qstate* qstate,
+ struct ipsecmod_qstate* iq, struct ipsecmod_env* ie, int id)
+{
+ struct ub_packed_rrset_key* rrset_key;
+ struct packed_rrset_data* rrset_data;
+ size_t i;
+ /* Pass to next module if we are not enabled and whitelisted. */
+ if(!(iq->enabled && iq->is_whitelisted)) {
+ qstate->ext_state[id] = module_wait_module;
+ return;
+ }
+ /* New query, check if the query is for an A/AAAA record and disable
+ * caching for other modules. */
+ if(!iq->ipseckey_done) {
+ if(qstate->qinfo.qtype == LDNS_RR_TYPE_A ||
+ qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) {
+ char type[16];
+ sldns_wire2str_type_buf(qstate->qinfo.qtype, type,
+ sizeof(type));
+ verbose(VERB_ALGO, "ipsecmod: query for %s; engaging",
+ type);
+ qstate->no_cache_store = 1;
+ }
+ /* Pass request to next module. */
+ qstate->ext_state[id] = module_wait_module;
+ return;
+ }
+ /* IPSECKEY subquery is finished. */
+ /* We have an IPSECKEY answer. */
+ if(iq->ipseckey_rrset) {
+ rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
+ if(rrset_data) {
+ /* If bogus return SERVFAIL. */
+ if(!qstate->env->cfg->ipsecmod_ignore_bogus &&
+ rrset_data->security == sec_status_bogus) {
+ log_err("ipsecmod: bogus IPSECKEY");
+ ipsecmod_error(qstate, id);
+ return;
+ }
+ /* We have a valid IPSECKEY reply, call hook. */
+ if(!call_hook(qstate, iq, ie) &&
+ qstate->env->cfg->ipsecmod_strict) {
+ log_err("ipsecmod: ipsecmod-hook failed");
+ ipsecmod_error(qstate, id);
+ return;
+ }
+ /* Make sure the A/AAAA's TTL is equal/less than the
+ * ipsecmod_max_ttl. */
+ rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo,
+ qstate->return_msg->rep);
+ rrset_data = (struct packed_rrset_data*)rrset_key->entry.data;
+ if(rrset_data->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) {
+ /* Update TTL for rrset to fixed value. */
+ rrset_data->ttl = qstate->env->cfg->ipsecmod_max_ttl;
+ for(i=0; i<rrset_data->count+rrset_data->rrsig_count; i++)
+ rrset_data->rr_ttl[i] = qstate->env->cfg->ipsecmod_max_ttl;
+ /* Also update reply_info's TTL */
+ if(qstate->return_msg->rep->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) {
+ qstate->return_msg->rep->ttl =
+ qstate->env->cfg->ipsecmod_max_ttl;
+ qstate->return_msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(
+ qstate->return_msg->rep->ttl);
+ }
+ }
+ }
+ }
+ /* Store A/AAAA in cache. */
+ if(!dns_cache_store(qstate->env, &qstate->qinfo,
+ qstate->return_msg->rep, 0, qstate->prefetch_leeway,
+ 0, qstate->region, qstate->query_flags)) {
+ log_err("ipsecmod: out of memory caching record");
+ }
+ qstate->ext_state[id] = module_finished;
+}
+
+/**
+ * Handle an ipsecmod module event with a response from the iterator.
+ * @param qstate: query state (from the mesh), passed between modules.
+ * contains qstate->env module environment with global caches and so on.
+ * @param iq: query state specific for this module. per-query.
+ * @param ie: environment specific for this module. global.
+ * @param id: module id.
+ */
+static void
+ipsecmod_handle_response(struct module_qstate* qstate,
+ struct ipsecmod_qstate* ATTR_UNUSED(iq),
+ struct ipsecmod_env* ATTR_UNUSED(ie), int id)
+{
+ /* Pass to previous module if we are not enabled and whitelisted. */
+ if(!(iq->enabled && iq->is_whitelisted)) {
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
+ /* check if the response is for an A/AAAA query. */
+ if((qstate->qinfo.qtype == LDNS_RR_TYPE_A ||
+ qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) &&
+ /* check that we had an answer for the A/AAAA query. */
+ qstate->return_msg &&
+ reply_find_answer_rrset(&qstate->return_msg->qinfo,
+ qstate->return_msg->rep) &&
+ /* check that another module didn't SERVFAIL. */
+ qstate->return_rcode == LDNS_RCODE_NOERROR) {
+ char type[16];
+ sldns_wire2str_type_buf(qstate->qinfo.qtype, type,
+ sizeof(type));
+ verbose(VERB_ALGO, "ipsecmod: response for %s; generating IPSECKEY "
+ "subquery", type);
+ /* generate an IPSECKEY query. */
+ if(!generate_request(qstate, id, qstate->qinfo.qname,
+ qstate->qinfo.qname_len, LDNS_RR_TYPE_IPSECKEY,
+ qstate->qinfo.qclass, 0)) {
+ log_err("ipsecmod: could not generate subquery.");
+ ipsecmod_error(qstate, id);
+ }
+ return;
+ }
+ /* we are done with the query. */
+ qstate->ext_state[id] = module_finished;
+}
+
+void
+ipsecmod_operate(struct module_qstate* qstate, enum module_ev event, int id,
+ struct outbound_entry* outbound)
+{
+ struct ipsecmod_env* ie = (struct ipsecmod_env*)qstate->env->modinfo[id];
+ struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)qstate->minfo[id];
+ verbose(VERB_QUERY, "ipsecmod[module %d] operate: extstate:%s event:%s",
+ id, strextstate(qstate->ext_state[id]), strmodulevent(event));
+ if(iq) log_query_info(VERB_QUERY, "ipsecmod operate: query",
+ &qstate->qinfo);
+
+ /* create ipsecmod_qstate. */
+ if((event == module_event_new || event == module_event_pass) &&
+ iq == NULL) {
+ if(!ipsecmod_new(qstate, id)) {
+ ipsecmod_error(qstate, id);
+ return;
+ }
+ iq = (struct ipsecmod_qstate*)qstate->minfo[id];
+ }
+ if(iq && (event == module_event_pass || event == module_event_new)) {
+ ipsecmod_handle_query(qstate, iq, ie, id);
+ return;
+ }
+ if(iq && (event == module_event_moddone)) {
+ ipsecmod_handle_response(qstate, iq, ie, id);
+ return;
+ }
+ if(iq && outbound) {
+ /* cachedb does not need to process responses at this time
+ * ignore it.
+ cachedb_process_response(qstate, iq, ie, id, outbound, event);
+ */
+ return;
+ }
+ if(event == module_event_error) {
+ verbose(VERB_ALGO, "got called with event error, giving up");
+ ipsecmod_error(qstate, id);
+ return;
+ }
+ if(!iq && (event == module_event_moddone)) {
+ /* during priming, module done but we never started. */
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
+
+ log_err("ipsecmod: bad event %s", strmodulevent(event));
+ ipsecmod_error(qstate, id);
+ return;
+}
+
+void
+ipsecmod_inform_super(struct module_qstate* qstate, int id,
+ struct module_qstate* super)
+{
+ struct ipsecmod_qstate* siq;
+ log_query_info(VERB_ALGO, "ipsecmod: inform_super, sub is",
+ &qstate->qinfo);
+ log_query_info(VERB_ALGO, "super is", &super->qinfo);
+ siq = (struct ipsecmod_qstate*)super->minfo[id];
+ if(!siq) {
+ verbose(VERB_ALGO, "super has no ipsecmod state");
+ return;
+ }
+
+ if(qstate->return_msg) {
+ struct ub_packed_rrset_key* rrset_key = reply_find_answer_rrset(
+ &qstate->return_msg->qinfo, qstate->return_msg->rep);
+ if(rrset_key) {
+ /* We have an answer. */
+ /* Copy to super's region. */
+ rrset_key = packed_rrset_copy_region(rrset_key, super->region, 0);
+ siq->ipseckey_rrset = rrset_key;
+ if(!rrset_key) {
+ log_err("ipsecmod: out of memory.");
+ }
+ }
+ }
+ /* Notify super to proceed. */
+ siq->ipseckey_done = 1;
+}
+
+void
+ipsecmod_clear(struct module_qstate* qstate, int id)
+{
+ if(!qstate)
+ return;
+ qstate->minfo[id] = NULL;
+}
+
+size_t
+ipsecmod_get_mem(struct module_env* env, int id)
+{
+ struct ipsecmod_env* ie = (struct ipsecmod_env*)env->modinfo[id];
+ if(!ie)
+ return 0;
+ return sizeof(*ie) + ipsecmod_whitelist_get_mem(ie->whitelist);
+}
+
+/**
+ * The ipsecmod function block
+ */
+static struct module_func_block ipsecmod_block = {
+ "ipsecmod",
+ &ipsecmod_init, &ipsecmod_deinit, &ipsecmod_operate,
+ &ipsecmod_inform_super, &ipsecmod_clear, &ipsecmod_get_mem
+};
+
+struct module_func_block*
+ipsecmod_get_funcblock(void)
+{
+ return &ipsecmod_block;
+}
+#endif /* USE_IPSECMOD */