]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/unbound/ipsecmod/ipsecmod.c
Upgrade Unbound to 1.6.4. More to follow.
[FreeBSD/FreeBSD.git] / contrib / unbound / ipsecmod / ipsecmod.c
1 /*
2  * ipsecmod/ipsecmod.c - facilitate opportunistic IPsec module
3  *
4  * Copyright (c) 2017, NLnet Labs. All rights reserved.
5  *
6  * This software is open source.
7  * 
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 
12  * Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  * 
15  * Redistributions in binary form must reproduce the above copyright notice,
16  * this list of conditions and the following disclaimer in the documentation
17  * and/or other materials provided with the distribution.
18  * 
19  * Neither the name of the NLNET LABS nor the names of its contributors may
20  * be used to endorse or promote products derived from this software without
21  * specific prior written permission.
22  * 
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35
36 /**
37  * \file
38  *
39  * This file contains a module that facilitates opportunistic IPsec. It does so
40  * by also quering for the IPSECKEY for A/AAAA queries and calling a
41  * configurable hook (eg. signaling an IKE daemon) before replying.
42  */
43
44 #include "config.h"
45 #ifdef USE_IPSECMOD
46 #include "ipsecmod/ipsecmod.h"
47 #include "ipsecmod/ipsecmod-whitelist.h"
48 #include "util/fptr_wlist.h"
49 #include "util/regional.h"
50 #include "util/net_help.h"
51 #include "util/config_file.h"
52 #include "services/cache/dns.h"
53 #include "sldns/wire2str.h"
54
55 /** Apply configuration to ipsecmod module 'global' state. */
56 static int
57 ipsecmod_apply_cfg(struct ipsecmod_env* ipsecmod_env, struct config_file* cfg)
58 {
59         if(!cfg->ipsecmod_hook || (cfg->ipsecmod_hook && !cfg->ipsecmod_hook[0])) {
60                 log_err("ipsecmod: missing ipsecmod-hook.");
61                 return 0;
62         }
63         if(cfg->ipsecmod_whitelist &&
64                 !ipsecmod_whitelist_apply_cfg(ipsecmod_env, cfg))
65                 return 0;
66         return 1;
67 }
68
69 int
70 ipsecmod_init(struct module_env* env, int id)
71 {
72         struct ipsecmod_env* ipsecmod_env = (struct ipsecmod_env*)calloc(1,
73                 sizeof(struct ipsecmod_env));
74         if(!ipsecmod_env) {
75                 log_err("malloc failure");
76                 return 0;
77         }
78         env->modinfo[id] = (void*)ipsecmod_env;
79         ipsecmod_env->whitelist = NULL;
80         if(!ipsecmod_apply_cfg(ipsecmod_env, env->cfg)) {
81                 log_err("ipsecmod: could not apply configuration settings.");
82                 return 0;
83         }
84         return 1;
85 }
86
87 void
88 ipsecmod_deinit(struct module_env* env, int id)
89 {
90         struct ipsecmod_env* ipsecmod_env;
91         if(!env || !env->modinfo[id])
92                 return;
93         ipsecmod_env = (struct ipsecmod_env*)env->modinfo[id];
94         /* Free contents. */
95         ipsecmod_whitelist_delete(ipsecmod_env->whitelist);
96         free(ipsecmod_env);
97         env->modinfo[id] = NULL;
98 }
99
100 /** New query for ipsecmod. */
101 static int
102 ipsecmod_new(struct module_qstate* qstate, int id)
103 {
104         struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)regional_alloc(
105                 qstate->region, sizeof(struct ipsecmod_qstate));
106         memset(iq, 0, sizeof(*iq));
107         qstate->minfo[id] = iq;
108         if(!iq)
109                 return 0;
110         /* Initialise it. */
111         iq->enabled = qstate->env->cfg->ipsecmod_enabled;
112         iq->is_whitelisted = ipsecmod_domain_is_whitelisted(
113                 (struct ipsecmod_env*)qstate->env->modinfo[id], qstate->qinfo.qname,
114                 qstate->qinfo.qname_len, qstate->qinfo.qclass);
115         return 1;
116 }
117
118 /**
119  * Exit module with an error status.
120  * @param qstate: query state
121  * @param id: module id.
122  */
123 static void
124 ipsecmod_error(struct module_qstate* qstate, int id)
125 {
126         qstate->ext_state[id] = module_error;
127         qstate->return_rcode = LDNS_RCODE_SERVFAIL;
128 }
129
130 /**
131  * Generate a request for the IPSECKEY.
132  *
133  * @param qstate: query state that is the parent.
134  * @param id: module id.
135  * @param name: what name to query for.
136  * @param namelen: length of name.
137  * @param qtype: query type.
138  * @param qclass: query class.
139  * @param flags: additional flags, such as the CD bit (BIT_CD), or 0.
140  * @return false on alloc failure.
141  */
142 static int
143 generate_request(struct module_qstate* qstate, int id, uint8_t* name,
144         size_t namelen, uint16_t qtype, uint16_t qclass, uint16_t flags)
145 {
146         struct module_qstate* newq;
147         struct query_info ask;
148         ask.qname = name;
149         ask.qname_len = namelen;
150         ask.qtype = qtype;
151         ask.qclass = qclass;
152         ask.local_alias = NULL;
153         log_query_info(VERB_ALGO, "ipsecmod: generate request", &ask);
154         fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
155         if(!(*qstate->env->attach_sub)(qstate, &ask,
156                 (uint16_t)(BIT_RD|flags), 0, 0, &newq)){
157                 log_err("Could not generate request: out of memory");
158                 return 0;
159         }
160         qstate->ext_state[id] = module_wait_subquery;
161         return 1;
162 }
163
164 /**
165  *  Prepare the data and call the hook.
166  *
167  *  @param qstate: query state.
168  *  @param iq: ipsecmod qstate.
169  *  @param ie: ipsecmod environment.
170  *  @return true on success, false otherwise.
171  */
172 static int
173 call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq,
174         struct ipsecmod_env* ATTR_UNUSED(ie))
175 {
176         size_t slen, tempdata_len, tempstring_len, i;
177         char str[65535], *s, *tempstring;
178         int w;
179         struct ub_packed_rrset_key* rrset_key;
180         struct packed_rrset_data* rrset_data;
181         uint8_t *tempdata;
182
183         /* Check if a shell is available */
184         if(system(NULL) == 0) {
185                 log_err("ipsecmod: no shell available for ipsecmod-hook");
186                 return 0;
187         }
188
189         /* Zero the buffer. */
190         s = str;
191         slen = sizeof(str);
192         memset(s, 0, slen);
193
194         /* Copy the hook into the buffer. */
195         sldns_str_print(&s, &slen, "%s", qstate->env->cfg->ipsecmod_hook);
196         /* Put space into the buffer. */
197         sldns_str_print(&s, &slen, " ");
198         /* Copy the qname into the buffer. */
199         tempstring = sldns_wire2str_dname(qstate->qinfo.qname,
200                 qstate->qinfo.qname_len);
201         if(!tempstring) {
202                 log_err("ipsecmod: out of memory when calling the hook");
203                 return 0;
204         }
205         sldns_str_print(&s, &slen, "\"%s\"", tempstring);
206         free(tempstring);
207         /* Put space into the buffer. */
208         sldns_str_print(&s, &slen, " ");
209         /* Copy the IPSECKEY TTL into the buffer. */
210         rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
211         sldns_str_print(&s, &slen, "\"%ld\"", (long)rrset_data->ttl);
212         /* Put space into the buffer. */
213         sldns_str_print(&s, &slen, " ");
214         /* Copy the A/AAAA record(s) into the buffer. Start and end this section
215          * with a double quote. */
216         rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo,
217                 qstate->return_msg->rep);
218         rrset_data = (struct packed_rrset_data*)rrset_key->entry.data;
219         sldns_str_print(&s, &slen, "\"");
220         for(i=0; i<rrset_data->count; i++) {
221                 if(i > 0) {
222                         /* Put space into the buffer. */
223                         sldns_str_print(&s, &slen, " ");
224                 }
225                 /* Ignore the first two bytes, they are the rr_data len. */
226                 w = sldns_wire2str_rdata_buf(rrset_data->rr_data[i] + 2,
227                         rrset_data->rr_len[i] - 2, s, slen, qstate->qinfo.qtype);
228                 if(w < 0) {
229                         /* Error in printout. */
230                         return -1;
231                 } else if((size_t)w >= slen) {
232                         s = NULL; /* We do not want str to point outside of buffer. */
233                         slen = 0;
234                         return -1;
235                 } else {
236                         s += w;
237                         slen -= w;
238                 }
239         }
240         sldns_str_print(&s, &slen, "\"");
241         /* Put space into the buffer. */
242         sldns_str_print(&s, &slen, " ");
243         /* Copy the IPSECKEY record(s) into the buffer. Start and end this section
244          * with a double quote. */
245         sldns_str_print(&s, &slen, "\"");
246         rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
247         for(i=0; i<rrset_data->count; i++) {
248                 if(i > 0) {
249                         /* Put space into the buffer. */
250                         sldns_str_print(&s, &slen, " ");
251                 }
252                 /* Ignore the first two bytes, they are the rr_data len. */
253                 tempdata = rrset_data->rr_data[i] + 2;
254                 tempdata_len = rrset_data->rr_len[i] - 2;
255                 /* Save the buffer pointers. */
256                 tempstring = s; tempstring_len = slen;
257                 w = sldns_wire2str_ipseckey_scan(&tempdata, &tempdata_len, &s, &slen,
258                         NULL, 0);
259                 /* There was an error when parsing the IPSECKEY; reset the buffer
260                  * pointers to their previous values. */
261                 if(w == -1){
262                         s = tempstring; slen = tempstring_len;
263                 }
264         }
265         sldns_str_print(&s, &slen, "\"");
266         verbose(VERB_ALGO, "ipsecmod: hook command: '%s'", str);
267         /* ipsecmod-hook should return 0 on success. */
268         if(system(str) != 0)
269                 return 0;
270         return 1;
271 }
272
273 /**
274  * Handle an ipsecmod module event with a query
275  * @param qstate: query state (from the mesh), passed between modules.
276  *      contains qstate->env module environment with global caches and so on.
277  * @param iq: query state specific for this module.  per-query.
278  * @param ie: environment specific for this module.  global.
279  * @param id: module id.
280  */
281 static void
282 ipsecmod_handle_query(struct module_qstate* qstate,
283         struct ipsecmod_qstate* iq, struct ipsecmod_env* ie, int id)
284 {
285         struct ub_packed_rrset_key* rrset_key;
286         struct packed_rrset_data* rrset_data;
287         size_t i;
288         /* Pass to next module if we are not enabled and whitelisted. */
289         if(!(iq->enabled && iq->is_whitelisted)) {
290                 qstate->ext_state[id] = module_wait_module;
291                 return;
292         }
293         /* New query, check if the query is for an A/AAAA record and disable
294          * caching for other modules. */
295         if(!iq->ipseckey_done) {
296                 if(qstate->qinfo.qtype == LDNS_RR_TYPE_A ||
297                         qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) {
298                         char type[16];
299                         sldns_wire2str_type_buf(qstate->qinfo.qtype, type,
300                                 sizeof(type));
301                         verbose(VERB_ALGO, "ipsecmod: query for %s; engaging",
302                                 type);
303                         qstate->no_cache_store = 1;
304                 }
305                 /* Pass request to next module. */
306                 qstate->ext_state[id] = module_wait_module;
307                 return;
308         }
309         /* IPSECKEY subquery is finished. */
310         /* We have an IPSECKEY answer. */
311         if(iq->ipseckey_rrset) {
312                 rrset_data = (struct packed_rrset_data*)iq->ipseckey_rrset->entry.data;
313                 if(rrset_data) {
314                         /* If bogus return SERVFAIL. */
315                         if(!qstate->env->cfg->ipsecmod_ignore_bogus &&
316                                 rrset_data->security == sec_status_bogus) {
317                                 log_err("ipsecmod: bogus IPSECKEY");
318                                 ipsecmod_error(qstate, id);
319                                 return;
320                         }
321                         /* We have a valid IPSECKEY reply, call hook. */
322                         if(!call_hook(qstate, iq, ie) &&
323                                 qstate->env->cfg->ipsecmod_strict) {
324                                 log_err("ipsecmod: ipsecmod-hook failed");
325                                 ipsecmod_error(qstate, id);
326                                 return;
327                         }
328                         /* Make sure the A/AAAA's TTL is equal/less than the
329                          * ipsecmod_max_ttl. */
330                         rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo,
331                                 qstate->return_msg->rep);
332                         rrset_data = (struct packed_rrset_data*)rrset_key->entry.data;
333                         if(rrset_data->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) {
334                                 /* Update TTL for rrset to fixed value. */
335                                 rrset_data->ttl = qstate->env->cfg->ipsecmod_max_ttl;
336                                 for(i=0; i<rrset_data->count+rrset_data->rrsig_count; i++)
337                                         rrset_data->rr_ttl[i] = qstate->env->cfg->ipsecmod_max_ttl;
338                                 /* Also update reply_info's TTL */
339                                 if(qstate->return_msg->rep->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) {
340                                         qstate->return_msg->rep->ttl =
341                                                 qstate->env->cfg->ipsecmod_max_ttl;
342                                         qstate->return_msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(
343                                                 qstate->return_msg->rep->ttl);
344                                 }
345                         }
346                 }
347         }
348         /* Store A/AAAA in cache. */
349         if(!dns_cache_store(qstate->env, &qstate->qinfo,
350                 qstate->return_msg->rep, 0, qstate->prefetch_leeway,
351                 0, qstate->region, qstate->query_flags)) {
352                 log_err("ipsecmod: out of memory caching record");
353         }
354         qstate->ext_state[id] = module_finished;
355 }
356
357 /**
358  * Handle an ipsecmod module event with a response from the iterator.
359  * @param qstate: query state (from the mesh), passed between modules.
360  *      contains qstate->env module environment with global caches and so on.
361  * @param iq: query state specific for this module.  per-query.
362  * @param ie: environment specific for this module.  global.
363  * @param id: module id.
364  */
365 static void
366 ipsecmod_handle_response(struct module_qstate* qstate,
367         struct ipsecmod_qstate* ATTR_UNUSED(iq),
368         struct ipsecmod_env* ATTR_UNUSED(ie), int id)
369 {
370         /* Pass to previous module if we are not enabled and whitelisted. */
371         if(!(iq->enabled && iq->is_whitelisted)) {
372                 qstate->ext_state[id] = module_finished;
373                 return;
374         }
375         /* check if the response is for an A/AAAA query. */
376         if((qstate->qinfo.qtype == LDNS_RR_TYPE_A ||
377                 qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) &&
378                 /* check that we had an answer for the A/AAAA query. */
379                 qstate->return_msg &&
380                 reply_find_answer_rrset(&qstate->return_msg->qinfo,
381                 qstate->return_msg->rep) &&
382                 /* check that another module didn't SERVFAIL. */
383                 qstate->return_rcode == LDNS_RCODE_NOERROR) {
384                 char type[16];
385                 sldns_wire2str_type_buf(qstate->qinfo.qtype, type,
386                         sizeof(type));
387                 verbose(VERB_ALGO, "ipsecmod: response for %s; generating IPSECKEY "
388                         "subquery", type);
389                 /* generate an IPSECKEY query. */
390                 if(!generate_request(qstate, id, qstate->qinfo.qname,
391                         qstate->qinfo.qname_len, LDNS_RR_TYPE_IPSECKEY,
392                         qstate->qinfo.qclass, 0)) {
393                         log_err("ipsecmod: could not generate subquery.");
394                         ipsecmod_error(qstate, id);
395                 }
396                 return;
397         }
398         /* we are done with the query. */
399         qstate->ext_state[id] = module_finished;
400 }
401
402 void
403 ipsecmod_operate(struct module_qstate* qstate, enum module_ev event, int id,
404         struct outbound_entry* outbound)
405 {
406         struct ipsecmod_env* ie = (struct ipsecmod_env*)qstate->env->modinfo[id];
407         struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)qstate->minfo[id];
408         verbose(VERB_QUERY, "ipsecmod[module %d] operate: extstate:%s event:%s",
409                 id, strextstate(qstate->ext_state[id]), strmodulevent(event));
410         if(iq) log_query_info(VERB_QUERY, "ipsecmod operate: query",
411                 &qstate->qinfo);
412
413         /* create ipsecmod_qstate. */
414         if((event == module_event_new || event == module_event_pass) &&
415                 iq == NULL) {
416                 if(!ipsecmod_new(qstate, id)) {
417                         ipsecmod_error(qstate, id);
418                         return;
419                 }
420                 iq = (struct ipsecmod_qstate*)qstate->minfo[id];
421         }
422         if(iq && (event == module_event_pass || event == module_event_new)) {
423                 ipsecmod_handle_query(qstate, iq, ie, id);
424                 return;
425         }
426         if(iq && (event == module_event_moddone)) {
427                 ipsecmod_handle_response(qstate, iq, ie, id);
428                 return;
429         }
430         if(iq && outbound) {
431                 /* cachedb does not need to process responses at this time
432                  * ignore it.
433                 cachedb_process_response(qstate, iq, ie, id, outbound, event);
434                 */
435                 return;
436         }
437         if(event == module_event_error) {
438                 verbose(VERB_ALGO, "got called with event error, giving up");
439                 ipsecmod_error(qstate, id);
440                 return;
441         }
442         if(!iq && (event == module_event_moddone)) {
443                 /* during priming, module done but we never started. */
444                 qstate->ext_state[id] = module_finished;
445                 return;
446         }
447
448         log_err("ipsecmod: bad event %s", strmodulevent(event));
449         ipsecmod_error(qstate, id);
450         return;
451 }
452
453 void
454 ipsecmod_inform_super(struct module_qstate* qstate, int id,
455         struct module_qstate* super)
456 {
457         struct ipsecmod_qstate* siq;
458         log_query_info(VERB_ALGO, "ipsecmod: inform_super, sub is",
459                 &qstate->qinfo);
460         log_query_info(VERB_ALGO, "super is", &super->qinfo);
461         siq = (struct ipsecmod_qstate*)super->minfo[id];
462         if(!siq) {
463                 verbose(VERB_ALGO, "super has no ipsecmod state");
464                 return;
465         }
466
467         if(qstate->return_msg) {
468                 struct ub_packed_rrset_key* rrset_key = reply_find_answer_rrset(
469                         &qstate->return_msg->qinfo, qstate->return_msg->rep);
470                 if(rrset_key) {
471                         /* We have an answer. */
472                         /* Copy to super's region. */
473                         rrset_key = packed_rrset_copy_region(rrset_key, super->region, 0);
474                         siq->ipseckey_rrset = rrset_key;
475                         if(!rrset_key) {
476                                 log_err("ipsecmod: out of memory.");
477                         }
478                 }
479         }
480         /* Notify super to proceed. */
481         siq->ipseckey_done = 1;
482 }
483
484 void
485 ipsecmod_clear(struct module_qstate* qstate, int id)
486 {
487         if(!qstate)
488                 return;
489         qstate->minfo[id] = NULL;
490 }
491
492 size_t
493 ipsecmod_get_mem(struct module_env* env, int id)
494 {
495         struct ipsecmod_env* ie = (struct ipsecmod_env*)env->modinfo[id];
496         if(!ie)
497                 return 0;
498         return sizeof(*ie) + ipsecmod_whitelist_get_mem(ie->whitelist);
499 }
500
501 /**
502  * The ipsecmod function block
503  */
504 static struct module_func_block ipsecmod_block = {
505         "ipsecmod",
506         &ipsecmod_init, &ipsecmod_deinit, &ipsecmod_operate,
507         &ipsecmod_inform_super, &ipsecmod_clear, &ipsecmod_get_mem
508 };
509
510 struct module_func_block*
511 ipsecmod_get_funcblock(void)
512 {
513         return &ipsecmod_block;
514 }
515 #endif /* USE_IPSECMOD */