12be9e038Ssthen /* 22be9e038Ssthen * ipsecmod/ipsecmod.c - facilitate opportunistic IPsec module 32be9e038Ssthen * 42be9e038Ssthen * Copyright (c) 2017, NLnet Labs. All rights reserved. 52be9e038Ssthen * 62be9e038Ssthen * This software is open source. 72be9e038Ssthen * 82be9e038Ssthen * Redistribution and use in source and binary forms, with or without 92be9e038Ssthen * modification, are permitted provided that the following conditions 102be9e038Ssthen * are met: 112be9e038Ssthen * 122be9e038Ssthen * Redistributions of source code must retain the above copyright notice, 132be9e038Ssthen * this list of conditions and the following disclaimer. 142be9e038Ssthen * 152be9e038Ssthen * Redistributions in binary form must reproduce the above copyright notice, 162be9e038Ssthen * this list of conditions and the following disclaimer in the documentation 172be9e038Ssthen * and/or other materials provided with the distribution. 182be9e038Ssthen * 192be9e038Ssthen * Neither the name of the NLNET LABS nor the names of its contributors may 202be9e038Ssthen * be used to endorse or promote products derived from this software without 212be9e038Ssthen * specific prior written permission. 222be9e038Ssthen * 232be9e038Ssthen * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 242be9e038Ssthen * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 252be9e038Ssthen * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 262be9e038Ssthen * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 272be9e038Ssthen * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 282be9e038Ssthen * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 292be9e038Ssthen * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 302be9e038Ssthen * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 312be9e038Ssthen * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 322be9e038Ssthen * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 332be9e038Ssthen * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 342be9e038Ssthen */ 352be9e038Ssthen 362be9e038Ssthen /** 372be9e038Ssthen * \file 382be9e038Ssthen * 392be9e038Ssthen * This file contains a module that facilitates opportunistic IPsec. It does so 40e21c60efSsthen * by also querying for the IPSECKEY for A/AAAA queries and calling a 412be9e038Ssthen * configurable hook (eg. signaling an IKE daemon) before replying. 422be9e038Ssthen */ 432be9e038Ssthen 442be9e038Ssthen #include "config.h" 452be9e038Ssthen #ifdef USE_IPSECMOD 462be9e038Ssthen #include "ipsecmod/ipsecmod.h" 472be9e038Ssthen #include "ipsecmod/ipsecmod-whitelist.h" 482be9e038Ssthen #include "util/fptr_wlist.h" 492be9e038Ssthen #include "util/regional.h" 502be9e038Ssthen #include "util/net_help.h" 512be9e038Ssthen #include "util/config_file.h" 522be9e038Ssthen #include "services/cache/dns.h" 532be9e038Ssthen #include "sldns/wire2str.h" 542be9e038Ssthen 552be9e038Ssthen /** Apply configuration to ipsecmod module 'global' state. */ 562be9e038Ssthen static int 572be9e038Ssthen ipsecmod_apply_cfg(struct ipsecmod_env* ipsecmod_env, struct config_file* cfg) 582be9e038Ssthen { 592be9e038Ssthen if(!cfg->ipsecmod_hook || (cfg->ipsecmod_hook && !cfg->ipsecmod_hook[0])) { 602be9e038Ssthen log_err("ipsecmod: missing ipsecmod-hook."); 612be9e038Ssthen return 0; 622be9e038Ssthen } 632be9e038Ssthen if(cfg->ipsecmod_whitelist && 642be9e038Ssthen !ipsecmod_whitelist_apply_cfg(ipsecmod_env, cfg)) 652be9e038Ssthen return 0; 662be9e038Ssthen return 1; 672be9e038Ssthen } 682be9e038Ssthen 692be9e038Ssthen int 702be9e038Ssthen ipsecmod_init(struct module_env* env, int id) 712be9e038Ssthen { 722be9e038Ssthen struct ipsecmod_env* ipsecmod_env = (struct ipsecmod_env*)calloc(1, 732be9e038Ssthen sizeof(struct ipsecmod_env)); 742be9e038Ssthen if(!ipsecmod_env) { 752be9e038Ssthen log_err("malloc failure"); 762be9e038Ssthen return 0; 772be9e038Ssthen } 782be9e038Ssthen env->modinfo[id] = (void*)ipsecmod_env; 792be9e038Ssthen ipsecmod_env->whitelist = NULL; 802be9e038Ssthen if(!ipsecmod_apply_cfg(ipsecmod_env, env->cfg)) { 812be9e038Ssthen log_err("ipsecmod: could not apply configuration settings."); 822be9e038Ssthen return 0; 832be9e038Ssthen } 842be9e038Ssthen return 1; 852be9e038Ssthen } 862be9e038Ssthen 872be9e038Ssthen void 882be9e038Ssthen ipsecmod_deinit(struct module_env* env, int id) 892be9e038Ssthen { 902be9e038Ssthen struct ipsecmod_env* ipsecmod_env; 912be9e038Ssthen if(!env || !env->modinfo[id]) 922be9e038Ssthen return; 932be9e038Ssthen ipsecmod_env = (struct ipsecmod_env*)env->modinfo[id]; 942be9e038Ssthen /* Free contents. */ 952be9e038Ssthen ipsecmod_whitelist_delete(ipsecmod_env->whitelist); 962be9e038Ssthen free(ipsecmod_env); 972be9e038Ssthen env->modinfo[id] = NULL; 982be9e038Ssthen } 992be9e038Ssthen 1002be9e038Ssthen /** New query for ipsecmod. */ 1012be9e038Ssthen static int 1022be9e038Ssthen ipsecmod_new(struct module_qstate* qstate, int id) 1032be9e038Ssthen { 1042be9e038Ssthen struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)regional_alloc( 1052be9e038Ssthen qstate->region, sizeof(struct ipsecmod_qstate)); 1062be9e038Ssthen qstate->minfo[id] = iq; 1072be9e038Ssthen if(!iq) 1082be9e038Ssthen return 0; 1092be9e038Ssthen /* Initialise it. */ 110ebf5bb73Ssthen memset(iq, 0, sizeof(*iq)); 1112be9e038Ssthen iq->enabled = qstate->env->cfg->ipsecmod_enabled; 1122be9e038Ssthen iq->is_whitelisted = ipsecmod_domain_is_whitelisted( 1132be9e038Ssthen (struct ipsecmod_env*)qstate->env->modinfo[id], qstate->qinfo.qname, 1142be9e038Ssthen qstate->qinfo.qname_len, qstate->qinfo.qclass); 1152be9e038Ssthen return 1; 1162be9e038Ssthen } 1172be9e038Ssthen 1182be9e038Ssthen /** 1192be9e038Ssthen * Exit module with an error status. 1202be9e038Ssthen * @param qstate: query state 1212be9e038Ssthen * @param id: module id. 1222be9e038Ssthen */ 1232be9e038Ssthen static void 1242be9e038Ssthen ipsecmod_error(struct module_qstate* qstate, int id) 1252be9e038Ssthen { 1262be9e038Ssthen qstate->ext_state[id] = module_error; 1272be9e038Ssthen qstate->return_rcode = LDNS_RCODE_SERVFAIL; 1282be9e038Ssthen } 1292be9e038Ssthen 1302be9e038Ssthen /** 1312be9e038Ssthen * Generate a request for the IPSECKEY. 1322be9e038Ssthen * 1332be9e038Ssthen * @param qstate: query state that is the parent. 1342be9e038Ssthen * @param id: module id. 1352be9e038Ssthen * @param name: what name to query for. 1362be9e038Ssthen * @param namelen: length of name. 1372be9e038Ssthen * @param qtype: query type. 1382be9e038Ssthen * @param qclass: query class. 1392be9e038Ssthen * @param flags: additional flags, such as the CD bit (BIT_CD), or 0. 1402be9e038Ssthen * @return false on alloc failure. 1412be9e038Ssthen */ 1422be9e038Ssthen static int 1432be9e038Ssthen generate_request(struct module_qstate* qstate, int id, uint8_t* name, 1442be9e038Ssthen size_t namelen, uint16_t qtype, uint16_t qclass, uint16_t flags) 1452be9e038Ssthen { 1462be9e038Ssthen struct module_qstate* newq; 1472be9e038Ssthen struct query_info ask; 1482be9e038Ssthen ask.qname = name; 1492be9e038Ssthen ask.qname_len = namelen; 1502be9e038Ssthen ask.qtype = qtype; 1512be9e038Ssthen ask.qclass = qclass; 1522be9e038Ssthen ask.local_alias = NULL; 1532be9e038Ssthen log_query_info(VERB_ALGO, "ipsecmod: generate request", &ask); 154191f22c6Ssthen 155191f22c6Ssthen /* Explicitly check for cycle before trying to attach. Will result in 156191f22c6Ssthen * cleaner error message. The attach_sub code also checks for cycle but the 157191f22c6Ssthen * message will be out of memory in both cases then. */ 158191f22c6Ssthen fptr_ok(fptr_whitelist_modenv_detect_cycle(qstate->env->detect_cycle)); 159191f22c6Ssthen if((*qstate->env->detect_cycle)(qstate, &ask, 160191f22c6Ssthen (uint16_t)(BIT_RD|flags), 0, 0)) { 161191f22c6Ssthen verbose(VERB_ALGO, "Could not generate request: cycle detected"); 162191f22c6Ssthen return 0; 163191f22c6Ssthen } 164191f22c6Ssthen 1652be9e038Ssthen fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub)); 1662be9e038Ssthen if(!(*qstate->env->attach_sub)(qstate, &ask, 1672be9e038Ssthen (uint16_t)(BIT_RD|flags), 0, 0, &newq)){ 1682be9e038Ssthen log_err("Could not generate request: out of memory"); 1692be9e038Ssthen return 0; 1702be9e038Ssthen } 1712be9e038Ssthen qstate->ext_state[id] = module_wait_subquery; 1722be9e038Ssthen return 1; 1732be9e038Ssthen } 1742be9e038Ssthen 1752be9e038Ssthen /** 176ebf5bb73Ssthen * Check if the string passed is a valid domain name with safe characters to 177ebf5bb73Ssthen * pass to a shell. 178ebf5bb73Ssthen * This will only allow: 179ebf5bb73Ssthen * - digits 180ebf5bb73Ssthen * - alphas 181ebf5bb73Ssthen * - hyphen (not at the start) 182ebf5bb73Ssthen * - dot (not at the start, or the only character) 183ebf5bb73Ssthen * - underscore 184ebf5bb73Ssthen * @param s: pointer to the string. 185ebf5bb73Ssthen * @param slen: string's length. 186ebf5bb73Ssthen * @return true if s only contains safe characters; false otherwise. 187ebf5bb73Ssthen */ 188ebf5bb73Ssthen static int 189ebf5bb73Ssthen domainname_has_safe_characters(char* s, size_t slen) { 190ebf5bb73Ssthen size_t i; 191ebf5bb73Ssthen for(i = 0; i < slen; i++) { 192ebf5bb73Ssthen if(s[i] == '\0') return 1; 193ebf5bb73Ssthen if((s[i] == '-' && i != 0) 194ebf5bb73Ssthen || (s[i] == '.' && (i != 0 || s[1] == '\0')) 195ebf5bb73Ssthen || (s[i] == '_') || (s[i] >= '0' && s[i] <= '9') 196ebf5bb73Ssthen || (s[i] >= 'A' && s[i] <= 'Z') 197ebf5bb73Ssthen || (s[i] >= 'a' && s[i] <= 'z')) { 198ebf5bb73Ssthen continue; 199ebf5bb73Ssthen } 200ebf5bb73Ssthen return 0; 201ebf5bb73Ssthen } 202ebf5bb73Ssthen return 1; 203ebf5bb73Ssthen } 204ebf5bb73Ssthen 205ebf5bb73Ssthen /** 206ebf5bb73Ssthen * Check if the stringified IPSECKEY RDATA contains safe characters to pass to 207ebf5bb73Ssthen * a shell. 208ebf5bb73Ssthen * This is only relevant for checking the gateway when the gateway type is 3 209ebf5bb73Ssthen * (domainname). 210ebf5bb73Ssthen * @param s: pointer to the string. 211ebf5bb73Ssthen * @param slen: string's length. 212ebf5bb73Ssthen * @return true if s contains only safe characters; false otherwise. 213ebf5bb73Ssthen */ 214ebf5bb73Ssthen static int 215ebf5bb73Ssthen ipseckey_has_safe_characters(char* s, size_t slen) { 216ebf5bb73Ssthen int precedence, gateway_type, algorithm; 217ebf5bb73Ssthen char* gateway; 218ebf5bb73Ssthen gateway = (char*)calloc(slen, sizeof(char)); 219ebf5bb73Ssthen if(!gateway) { 220ebf5bb73Ssthen log_err("ipsecmod: out of memory when calling the hook"); 221ebf5bb73Ssthen return 0; 222ebf5bb73Ssthen } 223ebf5bb73Ssthen if(sscanf(s, "%d %d %d %s ", 224ebf5bb73Ssthen &precedence, &gateway_type, &algorithm, gateway) != 4) { 225ebf5bb73Ssthen free(gateway); 226ebf5bb73Ssthen return 0; 227ebf5bb73Ssthen } 228ebf5bb73Ssthen if(gateway_type != 3) { 229ebf5bb73Ssthen free(gateway); 230ebf5bb73Ssthen return 1; 231ebf5bb73Ssthen } 232ebf5bb73Ssthen if(domainname_has_safe_characters(gateway, slen)) { 233ebf5bb73Ssthen free(gateway); 234ebf5bb73Ssthen return 1; 235ebf5bb73Ssthen } 236ebf5bb73Ssthen free(gateway); 237ebf5bb73Ssthen return 0; 238ebf5bb73Ssthen } 239ebf5bb73Ssthen 240ebf5bb73Ssthen /** 2412be9e038Ssthen * Prepare the data and call the hook. 2422be9e038Ssthen * 2432be9e038Ssthen * @param qstate: query state. 2442be9e038Ssthen * @param iq: ipsecmod qstate. 2452be9e038Ssthen * @param ie: ipsecmod environment. 2462be9e038Ssthen * @return true on success, false otherwise. 2472be9e038Ssthen */ 2482be9e038Ssthen static int 2492be9e038Ssthen call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq, 2502be9e038Ssthen struct ipsecmod_env* ATTR_UNUSED(ie)) 2512be9e038Ssthen { 2522be9e038Ssthen size_t slen, tempdata_len, tempstring_len, i; 2532be9e038Ssthen char str[65535], *s, *tempstring; 254ebf5bb73Ssthen int w = 0, w_temp, qtype; 2552be9e038Ssthen struct ub_packed_rrset_key* rrset_key; 2562be9e038Ssthen struct packed_rrset_data* rrset_data; 2572be9e038Ssthen uint8_t *tempdata; 2582be9e038Ssthen 2592be9e038Ssthen /* Check if a shell is available */ 2602be9e038Ssthen if(system(NULL) == 0) { 2612be9e038Ssthen log_err("ipsecmod: no shell available for ipsecmod-hook"); 2622be9e038Ssthen return 0; 2632be9e038Ssthen } 2642be9e038Ssthen 2652be9e038Ssthen /* Zero the buffer. */ 2662be9e038Ssthen s = str; 2672be9e038Ssthen slen = sizeof(str); 2682be9e038Ssthen memset(s, 0, slen); 2692be9e038Ssthen 2702be9e038Ssthen /* Copy the hook into the buffer. */ 271ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "%s", qstate->env->cfg->ipsecmod_hook); 2722be9e038Ssthen /* Put space into the buffer. */ 273ebf5bb73Ssthen w += sldns_str_print(&s, &slen, " "); 2742be9e038Ssthen /* Copy the qname into the buffer. */ 2752be9e038Ssthen tempstring = sldns_wire2str_dname(qstate->qinfo.qname, 2762be9e038Ssthen qstate->qinfo.qname_len); 2772be9e038Ssthen if(!tempstring) { 2782be9e038Ssthen log_err("ipsecmod: out of memory when calling the hook"); 2792be9e038Ssthen return 0; 2802be9e038Ssthen } 281ebf5bb73Ssthen if(!domainname_has_safe_characters(tempstring, strlen(tempstring))) { 282ebf5bb73Ssthen log_err("ipsecmod: qname has unsafe characters"); 283ebf5bb73Ssthen free(tempstring); 284ebf5bb73Ssthen return 0; 285ebf5bb73Ssthen } 286ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "\"%s\"", tempstring); 2872be9e038Ssthen free(tempstring); 2882be9e038Ssthen /* Put space into the buffer. */ 289ebf5bb73Ssthen w += sldns_str_print(&s, &slen, " "); 2902be9e038Ssthen /* Copy the IPSECKEY TTL into the buffer. */ 2912be9e038Ssthen rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data; 292ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "\"%ld\"", (long)rrset_data->ttl); 2932be9e038Ssthen /* Put space into the buffer. */ 294ebf5bb73Ssthen w += sldns_str_print(&s, &slen, " "); 2952be9e038Ssthen rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo, 2962be9e038Ssthen qstate->return_msg->rep); 297ebf5bb73Ssthen /* Double check that the records are indeed A/AAAA. 298ebf5bb73Ssthen * This should never happen as this function is only executed for A/AAAA 299ebf5bb73Ssthen * queries but make sure we don't pass anything other than A/AAAA to the 300ebf5bb73Ssthen * shell. */ 301ebf5bb73Ssthen qtype = ntohs(rrset_key->rk.type); 302ebf5bb73Ssthen if(qtype != LDNS_RR_TYPE_AAAA && qtype != LDNS_RR_TYPE_A) { 303ebf5bb73Ssthen log_err("ipsecmod: Answer is not of A or AAAA type"); 304ebf5bb73Ssthen return 0; 305ebf5bb73Ssthen } 3062be9e038Ssthen rrset_data = (struct packed_rrset_data*)rrset_key->entry.data; 307ebf5bb73Ssthen /* Copy the A/AAAA record(s) into the buffer. Start and end this section 308ebf5bb73Ssthen * with a double quote. */ 309ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "\""); 3102be9e038Ssthen for(i=0; i<rrset_data->count; i++) { 3112be9e038Ssthen if(i > 0) { 3122be9e038Ssthen /* Put space into the buffer. */ 313ebf5bb73Ssthen w += sldns_str_print(&s, &slen, " "); 3142be9e038Ssthen } 3152be9e038Ssthen /* Ignore the first two bytes, they are the rr_data len. */ 316ebf5bb73Ssthen w_temp = sldns_wire2str_rdata_buf(rrset_data->rr_data[i] + 2, 3172be9e038Ssthen rrset_data->rr_len[i] - 2, s, slen, qstate->qinfo.qtype); 318ebf5bb73Ssthen if(w_temp < 0) { 3192be9e038Ssthen /* Error in printout. */ 320ebf5bb73Ssthen log_err("ipsecmod: Error in printing IP address"); 321ebf5bb73Ssthen return 0; 322ebf5bb73Ssthen } else if((size_t)w_temp >= slen) { 3232be9e038Ssthen s = NULL; /* We do not want str to point outside of buffer. */ 3242be9e038Ssthen slen = 0; 325ebf5bb73Ssthen log_err("ipsecmod: shell command too long"); 326ebf5bb73Ssthen return 0; 3272be9e038Ssthen } else { 328ebf5bb73Ssthen s += w_temp; 329ebf5bb73Ssthen slen -= w_temp; 330ebf5bb73Ssthen w += w_temp; 3312be9e038Ssthen } 3322be9e038Ssthen } 333ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "\""); 3342be9e038Ssthen /* Put space into the buffer. */ 335ebf5bb73Ssthen w += sldns_str_print(&s, &slen, " "); 3362be9e038Ssthen /* Copy the IPSECKEY record(s) into the buffer. Start and end this section 3372be9e038Ssthen * with a double quote. */ 338ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "\""); 3392be9e038Ssthen rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data; 3402be9e038Ssthen for(i=0; i<rrset_data->count; i++) { 3412be9e038Ssthen if(i > 0) { 3422be9e038Ssthen /* Put space into the buffer. */ 343ebf5bb73Ssthen w += sldns_str_print(&s, &slen, " "); 3442be9e038Ssthen } 3452be9e038Ssthen /* Ignore the first two bytes, they are the rr_data len. */ 3462be9e038Ssthen tempdata = rrset_data->rr_data[i] + 2; 3472be9e038Ssthen tempdata_len = rrset_data->rr_len[i] - 2; 3482be9e038Ssthen /* Save the buffer pointers. */ 3492be9e038Ssthen tempstring = s; tempstring_len = slen; 350ebf5bb73Ssthen w_temp = sldns_wire2str_ipseckey_scan(&tempdata, &tempdata_len, &s, 351ebf5bb73Ssthen &slen, NULL, 0, NULL); 3522be9e038Ssthen /* There was an error when parsing the IPSECKEY; reset the buffer 3532be9e038Ssthen * pointers to their previous values. */ 354ebf5bb73Ssthen if(w_temp == -1) { 3552be9e038Ssthen s = tempstring; slen = tempstring_len; 356ebf5bb73Ssthen } else if(w_temp > 0) { 357ebf5bb73Ssthen if(!ipseckey_has_safe_characters( 358ebf5bb73Ssthen tempstring, tempstring_len - slen)) { 359ebf5bb73Ssthen log_err("ipsecmod: ipseckey has unsafe characters"); 360ebf5bb73Ssthen return 0; 361ebf5bb73Ssthen } 362ebf5bb73Ssthen w += w_temp; 3632be9e038Ssthen } 3642be9e038Ssthen } 365ebf5bb73Ssthen w += sldns_str_print(&s, &slen, "\""); 366ebf5bb73Ssthen if(w >= (int)sizeof(str)) { 367ebf5bb73Ssthen log_err("ipsecmod: shell command too long"); 368ebf5bb73Ssthen return 0; 369ebf5bb73Ssthen } 370ebf5bb73Ssthen verbose(VERB_ALGO, "ipsecmod: shell command: '%s'", str); 3712be9e038Ssthen /* ipsecmod-hook should return 0 on success. */ 3722be9e038Ssthen if(system(str) != 0) 3732be9e038Ssthen return 0; 3742be9e038Ssthen return 1; 3752be9e038Ssthen } 3762be9e038Ssthen 3772be9e038Ssthen /** 3782be9e038Ssthen * Handle an ipsecmod module event with a query 3792be9e038Ssthen * @param qstate: query state (from the mesh), passed between modules. 3802be9e038Ssthen * contains qstate->env module environment with global caches and so on. 3812be9e038Ssthen * @param iq: query state specific for this module. per-query. 3822be9e038Ssthen * @param ie: environment specific for this module. global. 3832be9e038Ssthen * @param id: module id. 3842be9e038Ssthen */ 3852be9e038Ssthen static void 3862be9e038Ssthen ipsecmod_handle_query(struct module_qstate* qstate, 3872be9e038Ssthen struct ipsecmod_qstate* iq, struct ipsecmod_env* ie, int id) 3882be9e038Ssthen { 3892be9e038Ssthen struct ub_packed_rrset_key* rrset_key; 3902be9e038Ssthen struct packed_rrset_data* rrset_data; 3912be9e038Ssthen size_t i; 3922be9e038Ssthen /* Pass to next module if we are not enabled and whitelisted. */ 3932be9e038Ssthen if(!(iq->enabled && iq->is_whitelisted)) { 3942be9e038Ssthen qstate->ext_state[id] = module_wait_module; 3952be9e038Ssthen return; 3962be9e038Ssthen } 3972be9e038Ssthen /* New query, check if the query is for an A/AAAA record and disable 3982be9e038Ssthen * caching for other modules. */ 3992be9e038Ssthen if(!iq->ipseckey_done) { 4002be9e038Ssthen if(qstate->qinfo.qtype == LDNS_RR_TYPE_A || 4012be9e038Ssthen qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) { 4022be9e038Ssthen char type[16]; 4032be9e038Ssthen sldns_wire2str_type_buf(qstate->qinfo.qtype, type, 4042be9e038Ssthen sizeof(type)); 4052be9e038Ssthen verbose(VERB_ALGO, "ipsecmod: query for %s; engaging", 4062be9e038Ssthen type); 4072be9e038Ssthen qstate->no_cache_store = 1; 4082be9e038Ssthen } 4092be9e038Ssthen /* Pass request to next module. */ 4102be9e038Ssthen qstate->ext_state[id] = module_wait_module; 4112be9e038Ssthen return; 4122be9e038Ssthen } 4132be9e038Ssthen /* IPSECKEY subquery is finished. */ 4142be9e038Ssthen /* We have an IPSECKEY answer. */ 4152be9e038Ssthen if(iq->ipseckey_rrset) { 4162be9e038Ssthen rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data; 4172be9e038Ssthen if(rrset_data) { 4182be9e038Ssthen /* If bogus return SERVFAIL. */ 4192be9e038Ssthen if(!qstate->env->cfg->ipsecmod_ignore_bogus && 4202be9e038Ssthen rrset_data->security == sec_status_bogus) { 4212be9e038Ssthen log_err("ipsecmod: bogus IPSECKEY"); 422191f22c6Ssthen errinf(qstate, "ipsecmod: bogus IPSECKEY"); 4232be9e038Ssthen ipsecmod_error(qstate, id); 4242be9e038Ssthen return; 4252be9e038Ssthen } 4262be9e038Ssthen /* We have a valid IPSECKEY reply, call hook. */ 4272be9e038Ssthen if(!call_hook(qstate, iq, ie) && 4282be9e038Ssthen qstate->env->cfg->ipsecmod_strict) { 4292be9e038Ssthen log_err("ipsecmod: ipsecmod-hook failed"); 430191f22c6Ssthen errinf(qstate, "ipsecmod: ipsecmod-hook failed"); 4312be9e038Ssthen ipsecmod_error(qstate, id); 4322be9e038Ssthen return; 4332be9e038Ssthen } 4342be9e038Ssthen /* Make sure the A/AAAA's TTL is equal/less than the 4352be9e038Ssthen * ipsecmod_max_ttl. */ 4362be9e038Ssthen rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo, 4372be9e038Ssthen qstate->return_msg->rep); 4382be9e038Ssthen rrset_data = (struct packed_rrset_data*)rrset_key->entry.data; 4392be9e038Ssthen if(rrset_data->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) { 4402be9e038Ssthen /* Update TTL for rrset to fixed value. */ 4412be9e038Ssthen rrset_data->ttl = qstate->env->cfg->ipsecmod_max_ttl; 4422be9e038Ssthen for(i=0; i<rrset_data->count+rrset_data->rrsig_count; i++) 4432be9e038Ssthen rrset_data->rr_ttl[i] = qstate->env->cfg->ipsecmod_max_ttl; 4442be9e038Ssthen /* Also update reply_info's TTL */ 4452be9e038Ssthen if(qstate->return_msg->rep->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) { 4462be9e038Ssthen qstate->return_msg->rep->ttl = 4472be9e038Ssthen qstate->env->cfg->ipsecmod_max_ttl; 4482be9e038Ssthen qstate->return_msg->rep->prefetch_ttl = PREFETCH_TTL_CALC( 4492be9e038Ssthen qstate->return_msg->rep->ttl); 4507bc20e6dSsthen qstate->return_msg->rep->serve_expired_ttl = qstate->return_msg->rep->ttl + 4517bc20e6dSsthen qstate->env->cfg->serve_expired_ttl; 4522be9e038Ssthen } 4532be9e038Ssthen } 4542be9e038Ssthen } 4552be9e038Ssthen } 4562be9e038Ssthen /* Store A/AAAA in cache. */ 4572be9e038Ssthen if(!dns_cache_store(qstate->env, &qstate->qinfo, 4582be9e038Ssthen qstate->return_msg->rep, 0, qstate->prefetch_leeway, 459d1e2768aSsthen 0, qstate->region, qstate->query_flags, qstate->qstarttime)) { 4602be9e038Ssthen log_err("ipsecmod: out of memory caching record"); 4612be9e038Ssthen } 4622be9e038Ssthen qstate->ext_state[id] = module_finished; 4632be9e038Ssthen } 4642be9e038Ssthen 4652be9e038Ssthen /** 4662be9e038Ssthen * Handle an ipsecmod module event with a response from the iterator. 4672be9e038Ssthen * @param qstate: query state (from the mesh), passed between modules. 4682be9e038Ssthen * contains qstate->env module environment with global caches and so on. 4692be9e038Ssthen * @param iq: query state specific for this module. per-query. 4702be9e038Ssthen * @param ie: environment specific for this module. global. 4712be9e038Ssthen * @param id: module id. 4722be9e038Ssthen */ 4732be9e038Ssthen static void 4742be9e038Ssthen ipsecmod_handle_response(struct module_qstate* qstate, 4752be9e038Ssthen struct ipsecmod_qstate* ATTR_UNUSED(iq), 4762be9e038Ssthen struct ipsecmod_env* ATTR_UNUSED(ie), int id) 4772be9e038Ssthen { 4782be9e038Ssthen /* Pass to previous module if we are not enabled and whitelisted. */ 4792be9e038Ssthen if(!(iq->enabled && iq->is_whitelisted)) { 4802be9e038Ssthen qstate->ext_state[id] = module_finished; 4812be9e038Ssthen return; 4822be9e038Ssthen } 4832be9e038Ssthen /* check if the response is for an A/AAAA query. */ 4842be9e038Ssthen if((qstate->qinfo.qtype == LDNS_RR_TYPE_A || 4852be9e038Ssthen qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) && 4862be9e038Ssthen /* check that we had an answer for the A/AAAA query. */ 4872be9e038Ssthen qstate->return_msg && 4882be9e038Ssthen reply_find_answer_rrset(&qstate->return_msg->qinfo, 4892be9e038Ssthen qstate->return_msg->rep) && 4902be9e038Ssthen /* check that another module didn't SERVFAIL. */ 4912be9e038Ssthen qstate->return_rcode == LDNS_RCODE_NOERROR) { 4922be9e038Ssthen char type[16]; 4932be9e038Ssthen sldns_wire2str_type_buf(qstate->qinfo.qtype, type, 4942be9e038Ssthen sizeof(type)); 4952be9e038Ssthen verbose(VERB_ALGO, "ipsecmod: response for %s; generating IPSECKEY " 4962be9e038Ssthen "subquery", type); 4972be9e038Ssthen /* generate an IPSECKEY query. */ 4982be9e038Ssthen if(!generate_request(qstate, id, qstate->qinfo.qname, 4992be9e038Ssthen qstate->qinfo.qname_len, LDNS_RR_TYPE_IPSECKEY, 5002be9e038Ssthen qstate->qinfo.qclass, 0)) { 5012be9e038Ssthen log_err("ipsecmod: could not generate subquery."); 502191f22c6Ssthen errinf(qstate, "ipsecmod: could not generate subquery."); 5032be9e038Ssthen ipsecmod_error(qstate, id); 5042be9e038Ssthen } 5052be9e038Ssthen return; 5062be9e038Ssthen } 5072be9e038Ssthen /* we are done with the query. */ 5082be9e038Ssthen qstate->ext_state[id] = module_finished; 5092be9e038Ssthen } 5102be9e038Ssthen 5112be9e038Ssthen void 5122be9e038Ssthen ipsecmod_operate(struct module_qstate* qstate, enum module_ev event, int id, 5132be9e038Ssthen struct outbound_entry* outbound) 5142be9e038Ssthen { 5152be9e038Ssthen struct ipsecmod_env* ie = (struct ipsecmod_env*)qstate->env->modinfo[id]; 5162be9e038Ssthen struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)qstate->minfo[id]; 5172be9e038Ssthen verbose(VERB_QUERY, "ipsecmod[module %d] operate: extstate:%s event:%s", 5182be9e038Ssthen id, strextstate(qstate->ext_state[id]), strmodulevent(event)); 5192be9e038Ssthen if(iq) log_query_info(VERB_QUERY, "ipsecmod operate: query", 5202be9e038Ssthen &qstate->qinfo); 5212be9e038Ssthen 5222be9e038Ssthen /* create ipsecmod_qstate. */ 5232be9e038Ssthen if((event == module_event_new || event == module_event_pass) && 5242be9e038Ssthen iq == NULL) { 5252be9e038Ssthen if(!ipsecmod_new(qstate, id)) { 526191f22c6Ssthen errinf(qstate, "ipsecmod: could not ipsecmod_new"); 5272be9e038Ssthen ipsecmod_error(qstate, id); 5282be9e038Ssthen return; 5292be9e038Ssthen } 5302be9e038Ssthen iq = (struct ipsecmod_qstate*)qstate->minfo[id]; 5312be9e038Ssthen } 5322be9e038Ssthen if(iq && (event == module_event_pass || event == module_event_new)) { 5332be9e038Ssthen ipsecmod_handle_query(qstate, iq, ie, id); 5342be9e038Ssthen return; 5352be9e038Ssthen } 5362be9e038Ssthen if(iq && (event == module_event_moddone)) { 5372be9e038Ssthen ipsecmod_handle_response(qstate, iq, ie, id); 5382be9e038Ssthen return; 5392be9e038Ssthen } 5402be9e038Ssthen if(iq && outbound) { 5412be9e038Ssthen /* cachedb does not need to process responses at this time 5422be9e038Ssthen * ignore it. 5432be9e038Ssthen cachedb_process_response(qstate, iq, ie, id, outbound, event); 5442be9e038Ssthen */ 5452be9e038Ssthen return; 5462be9e038Ssthen } 5472be9e038Ssthen if(event == module_event_error) { 5482be9e038Ssthen verbose(VERB_ALGO, "got called with event error, giving up"); 549191f22c6Ssthen errinf(qstate, "ipsecmod: got called with event error"); 5502be9e038Ssthen ipsecmod_error(qstate, id); 5512be9e038Ssthen return; 5522be9e038Ssthen } 5532be9e038Ssthen if(!iq && (event == module_event_moddone)) { 5542be9e038Ssthen /* during priming, module done but we never started. */ 5552be9e038Ssthen qstate->ext_state[id] = module_finished; 5562be9e038Ssthen return; 5572be9e038Ssthen } 5582be9e038Ssthen 5592be9e038Ssthen log_err("ipsecmod: bad event %s", strmodulevent(event)); 560191f22c6Ssthen errinf(qstate, "ipsecmod: operate got bad event"); 5612be9e038Ssthen ipsecmod_error(qstate, id); 5622be9e038Ssthen return; 5632be9e038Ssthen } 5642be9e038Ssthen 5652be9e038Ssthen void 5662be9e038Ssthen ipsecmod_inform_super(struct module_qstate* qstate, int id, 5672be9e038Ssthen struct module_qstate* super) 5682be9e038Ssthen { 5692be9e038Ssthen struct ipsecmod_qstate* siq; 5702be9e038Ssthen log_query_info(VERB_ALGO, "ipsecmod: inform_super, sub is", 5712be9e038Ssthen &qstate->qinfo); 5722be9e038Ssthen log_query_info(VERB_ALGO, "super is", &super->qinfo); 5732be9e038Ssthen siq = (struct ipsecmod_qstate*)super->minfo[id]; 5742be9e038Ssthen if(!siq) { 5752be9e038Ssthen verbose(VERB_ALGO, "super has no ipsecmod state"); 5762be9e038Ssthen return; 5772be9e038Ssthen } 5782be9e038Ssthen 5792be9e038Ssthen if(qstate->return_msg) { 5802be9e038Ssthen struct ub_packed_rrset_key* rrset_key = reply_find_answer_rrset( 5812be9e038Ssthen &qstate->return_msg->qinfo, qstate->return_msg->rep); 5822be9e038Ssthen if(rrset_key) { 5832be9e038Ssthen /* We have an answer. */ 5842be9e038Ssthen /* Copy to super's region. */ 5852be9e038Ssthen rrset_key = packed_rrset_copy_region(rrset_key, super->region, 0); 5862be9e038Ssthen siq->ipseckey_rrset = rrset_key; 5872be9e038Ssthen if(!rrset_key) { 5882be9e038Ssthen log_err("ipsecmod: out of memory."); 5892be9e038Ssthen } 5902be9e038Ssthen } 5912be9e038Ssthen } 5922be9e038Ssthen /* Notify super to proceed. */ 5932be9e038Ssthen siq->ipseckey_done = 1; 5942be9e038Ssthen } 5952be9e038Ssthen 5962be9e038Ssthen void 5972be9e038Ssthen ipsecmod_clear(struct module_qstate* qstate, int id) 5982be9e038Ssthen { 5992be9e038Ssthen if(!qstate) 6002be9e038Ssthen return; 6012be9e038Ssthen qstate->minfo[id] = NULL; 6022be9e038Ssthen } 6032be9e038Ssthen 6042be9e038Ssthen size_t 6052be9e038Ssthen ipsecmod_get_mem(struct module_env* env, int id) 6062be9e038Ssthen { 6072be9e038Ssthen struct ipsecmod_env* ie = (struct ipsecmod_env*)env->modinfo[id]; 6082be9e038Ssthen if(!ie) 6092be9e038Ssthen return 0; 6102be9e038Ssthen return sizeof(*ie) + ipsecmod_whitelist_get_mem(ie->whitelist); 6112be9e038Ssthen } 6122be9e038Ssthen 6132be9e038Ssthen /** 6142be9e038Ssthen * The ipsecmod function block 6152be9e038Ssthen */ 6162be9e038Ssthen static struct module_func_block ipsecmod_block = { 6172be9e038Ssthen "ipsecmod", 618*98bc733bSsthen NULL, NULL, &ipsecmod_init, &ipsecmod_deinit, &ipsecmod_operate, 6192be9e038Ssthen &ipsecmod_inform_super, &ipsecmod_clear, &ipsecmod_get_mem 6202be9e038Ssthen }; 6212be9e038Ssthen 6222be9e038Ssthen struct module_func_block* 6232be9e038Ssthen ipsecmod_get_funcblock(void) 6242be9e038Ssthen { 6252be9e038Ssthen return &ipsecmod_block; 6262be9e038Ssthen } 6272be9e038Ssthen #endif /* USE_IPSECMOD */ 628