xref: /openbsd-src/usr.sbin/unbound/ipsecmod/ipsecmod.c (revision 98bc733b08604094f4138174a0ee0bb9faaca4bd)
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