1# -*- coding: utf-8 -*- 2''' 3 inplace_callbacks.py: python module showcasing inplace callback function 4 registration and functionality. 5 6 Copyright (c) 2016, NLnet Labs. 7 8 This software is open source. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 * Redistributions of source code must retain the above copyright notice, 15 this list of conditions and the following disclaimer. 16 17 * Redistributions in binary form must reproduce the above copyright notice, 18 this list of conditions and the following disclaimer in the documentation 19 and/or other materials provided with the distribution. 20 21 * Neither the name of the organization nor the names of its 22 contributors may be used to endorse or promote products derived from this 23 software without specific prior written permission. 24 25 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE 29 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 POSSIBILITY OF SUCH DAMAGE. 36''' 37#Try: 38# - dig @localhost nlnetlabs.nl +ednsopt=65002: 39# This query *could* be answered from cache. If so, unbound will reply 40# with the same EDNS option 65002, but with hexdata 'deadbeef' as data. 41# 42# - dig @localhost bogus.nlnetlabs.nl txt: 43# This query returns SERVFAIL as the txt record of bogus.nlnetlabs.nl is 44# intentionally bogus. The reply will contain an empty EDNS option 45# with option code 65003. 46# (unbound needs to be validating for this example to work) 47 48# Useful functions: 49# register_inplace_cb_reply(inplace_reply_callback, env, id): 50# Register the reply_callback function as an inplace callback function 51# when answering with a resolved query. 52# Return True on success, False on failure. 53# 54# register_inplace_cb_reply_cache(inplace_reply_cache_callback, env, id): 55# Register the reply_cache_callback function as an inplace callback 56# function when answering from cache. 57# Return True on success, False on failure. 58# 59# register_inplace_cb_reply_local(inplace_reply_local_callback, env, id): 60# Register the reply_local_callback function as an inplace callback 61# function when answering from local data or chaos reply. 62# Return True on success, False on failure. 63# 64# register_inplace_cb_reply_servfail(inplace_reply_servfail_callback, env, id): 65# Register the reply_servfail_callback function as an inplace callback 66# function when answering with servfail. 67# Return True on success, False on failure. 68# 69# Examples on how to use the functions are given in this file. 70 71 72def inplace_reply_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, 73 region): 74 """Function that will be registered as an inplace callback function. 75 It will be called when answering with a resolved query. 76 :param qinfo: query_info struct; 77 :param qstate: module qstate. It contains the available opt_lists; It 78 SHOULD NOT be altered; 79 :param rep: reply_info struct; 80 :param rcode: return code for the query; 81 :param edns: edns_data to be sent to the client side. It SHOULD NOT be 82 altered; 83 :param opt_list_out: the list with the EDNS options that will be sent as a 84 reply. It can be populated with EDNS options; 85 :param region: region to allocate temporary data. Needs to be used when we 86 want to append a new option to opt_list_out. 87 :return: True on success, False on failure. 88 """ 89 log_info("python: called back while replying.") 90 return True 91 92 93def inplace_cache_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, 94 region): 95 """Function that will be registered as an inplace callback function. 96 It will be called when answering from the cache. 97 :param qinfo: query_info struct; 98 :param qstate: module qstate. None; 99 :param rep: reply_info struct; 100 :param rcode: return code for the query; 101 :param edns: edns_data sent from the client side. The list with the EDNS 102 options is accessible through edns.opt_list. It SHOULD NOT be 103 altered; 104 :param opt_list_out: the list with the EDNS options that will be sent as a 105 reply. It can be populated with EDNS options; 106 :param region: region to allocate temporary data. Needs to be used when we 107 want to append a new option to opt_list_out. 108 :return: True on success, False on failure. 109 110 For demonstration purposes we want to see if EDNS option 65002 is present 111 and reply with a new value. 112 """ 113 log_info("python: called back while answering from cache.") 114 # Inspect the incoming EDNS options. 115 if not edns_opt_list_is_empty(edns.opt_list): 116 log_info("python: available EDNS options:") 117 for o in edns.opt_list_iter: 118 log_info("python: Code: {}, Data: '{}'".format(o.code, 119 "".join('{:02x}'.format(x) for x in o.data))) 120 if o.code == 65002: 121 log_info("python: *found option code 65002*") 122 123 # add to opt_list 124 # Data MUST be represented in a bytearray. 125 b = bytearray.fromhex("deadbeef") 126 if edns_opt_list_append(opt_list_out, o.code, b, region): 127 log_info("python: *added new option code 65002*") 128 else: 129 log_info("python: *failed to add new option code 65002*") 130 return False 131 break 132 133 return True 134 135 136def inplace_local_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, 137 region): 138 """Function that will be registered as an inplace callback function. 139 It will be called when answering from local data. 140 :param qinfo: query_info struct; 141 :param qstate: module qstate. None; 142 :param rep: reply_info struct; 143 :param rcode: return code for the query; 144 :param edns: edns_data sent from the client side. The list with the 145 EDNS options is accessible through edns.opt_list. It 146 SHOULD NOT be altered; 147 :param opt_list_out: the list with the EDNS options that will be sent as a 148 reply. It can be populated with EDNS options; 149 :param region: region to allocate temporary data. Needs to be used when we 150 want to append a new option to opt_list_out. 151 :return: True on success, False on failure. 152 """ 153 log_info("python: called back while replying with local data or chaos" 154 " reply.") 155 return True 156 157 158def inplace_servfail_callback(qinfo, qstate, rep, rcode, edns, opt_list_out, 159 region): 160 """Function that will be registered as an inplace callback function. 161 It will be called when answering with SERVFAIL. 162 :param qinfo: query_info struct; 163 :param qstate: module qstate. If not None the relevant opt_lists are 164 available here; 165 :param rep: reply_info struct. None; 166 :param rcode: return code for the query. LDNS_RCODE_SERVFAIL; 167 :param edns: edns_data to be sent to the client side. If qstate is None 168 edns.opt_list contains the EDNS options sent from the client 169 side. It SHOULD NOT be altered; 170 :param opt_list_out: the list with the EDNS options that will be sent as a 171 reply. It can be populated with EDNS options; 172 :param region: region to allocate temporary data. Needs to be used when we 173 want to append a new option to opt_list_out. 174 :return: True on success, False on failure. 175 176 For demonstration purposes we want to reply with an empty EDNS code '65003'. 177 """ 178 log_info("python: called back while servfail.") 179 b = bytearray.fromhex("") 180 edns_opt_list_append(opt_list_out, 65003, b, region) 181 return True 182 183 184def init_standard(id, env): 185 """New version of the init function. 186 The function's signature is the same as the C counterpart and allows for 187 extra functionality during init. 188 ..note:: This function is preferred by unbound over the old init function. 189 ..note:: The previously accessible configuration options can now be found in 190 env.cgf. 191 """ 192 log_info("python: inited script {}".format(env.cfg.python_script)) 193 194 # Register the inplace_reply_callback function as an inplace callback 195 # function when answering a resolved query. 196 if not register_inplace_cb_reply(inplace_reply_callback, env, id): 197 return False 198 199 # Register the inplace_cache_callback function as an inplace callback 200 # function when answering from cache. 201 if not register_inplace_cb_reply_cache(inplace_cache_callback, env, id): 202 return False 203 204 # Register the inplace_local_callback function as an inplace callback 205 # function when answering from local data. 206 if not register_inplace_cb_reply_local(inplace_local_callback, env, id): 207 return False 208 209 # Register the inplace_servfail_callback function as an inplace callback 210 # function when answering with SERVFAIL. 211 if not register_inplace_cb_reply_servfail(inplace_servfail_callback, env, id): 212 return False 213 214 return True 215 216 217def init(id, cfg): 218 """Previous version init function. 219 ..note:: This function is still supported for backwards compatibility when 220 the init_standard function is missing. When init_standard is 221 present this function SHOULD be omitted to avoid confusion to the 222 reader. 223 """ 224 return True 225 226 227def deinit(id): return True 228 229 230def inform_super(id, qstate, superqstate, qdata): return True 231 232 233def operate(id, event, qstate, qdata): 234 if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS): 235 qstate.ext_state[id] = MODULE_WAIT_MODULE 236 return True 237 238 elif event == MODULE_EVENT_MODDONE: 239 qstate.ext_state[id] = MODULE_FINISHED 240 return True 241 242 log_err("pythonmod: Unknown event") 243 qstate.ext_state[id] = MODULE_ERROR 244 return True 245