Branch data Line data Source code
1 : : // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : :
15 : : // Enable this if you use s# variants with PyArg_ParseTuple(), see
16 : : // http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
17 : : #define PY_SSIZE_T_CLEAN
18 : :
19 : : // Python.h needs to be placed at the head of the program file, see:
20 : : // http://docs.python.org/py3k/extending/extending.html#a-simple-example
21 : : #include <Python.h>
22 : :
23 : : #include <sys/types.h>
24 : : #include <sys/socket.h>
25 : : #include <netinet/in.h>
26 : : #include <netdb.h>
27 : : #include <string.h>
28 : :
29 : : #include <cassert>
30 : : #include <memory>
31 : : #include <string>
32 : : #include <sstream>
33 : : #include <stdexcept>
34 : :
35 : : #include <boost/scoped_ptr.hpp>
36 : : #include <boost/lexical_cast.hpp>
37 : :
38 : : #include <exceptions/exceptions.h>
39 : :
40 : : #include <util/buffer.h>
41 : : #include <util/python/pycppwrapper_util.h>
42 : :
43 : : #include <dns/name.h>
44 : : #include <dns/rrclass.h>
45 : : #include <dns/rrtype.h>
46 : : #include <dns/rrttl.h>
47 : : #include <dns/rdata.h>
48 : : #include <dns/tsigrecord.h>
49 : :
50 : : #include <acl/dns.h>
51 : : #include <acl/ip_check.h>
52 : :
53 : : #include "dns.h"
54 : : #include "dns_requestcontext_python.h"
55 : :
56 : : using namespace std;
57 : : using boost::scoped_ptr;
58 : : using boost::lexical_cast;
59 : : using namespace isc;
60 : : using namespace isc::dns;
61 : : using namespace isc::dns::rdata;
62 : : using namespace isc::util::python;
63 : : using namespace isc::acl::dns;
64 : : using namespace isc::acl::dns::python;
65 : :
66 : : namespace isc {
67 : : namespace acl {
68 : : namespace dns {
69 : : namespace python {
70 : :
71 [ + - ]: 144 : struct s_RequestContext::Data {
72 : : // The constructor.
73 : : Data(const char* const remote_addr, const unsigned short remote_port,
74 : : const char* tsig_data, const Py_ssize_t tsig_len)
75 : 3 : {
76 [ + + ]: 75 : createRemoteAddr(remote_addr, remote_port);
77 [ - + ]: 72 : createTSIGRecord(tsig_data, tsig_len);
78 : : }
79 : :
80 : : // A convenient type converter from sockaddr_storage to sockaddr
81 : : const struct sockaddr& getRemoteSockaddr() const {
82 : 79 : const void* p = &remote_ss;
83 : : return (*static_cast<const struct sockaddr*>(p));
84 : : }
85 : :
86 : : // The remote (source) IP address of the request. Note that it needs
87 : : // a reference to remote_ss. That's why the latter is stored within
88 : : // this structure.
89 : : scoped_ptr<IPAddress> remote_ipaddr;
90 : :
91 : : // The effective length of remote_ss. It's necessary for getnameinfo()
92 : : // called from sockaddrToText (__str__ backend).
93 : : socklen_t remote_salen;
94 : :
95 : : // The TSIG record included in the request, if any. If the request
96 : : // doesn't contain a TSIG, this will be NULL.
97 : : scoped_ptr<TSIGRecord> tsig_record;
98 : :
99 : : private:
100 : : // A helper method for the constructor that is responsible for constructing
101 : : // the remote address.
102 : 75 : void createRemoteAddr(const char* const remote_addr,
103 : : const unsigned short remote_port)
104 : : {
105 : : struct addrinfo hints, *res;
106 : : memset(&hints, 0, sizeof(hints));
107 : 75 : hints.ai_family = AF_UNSPEC;
108 : 75 : hints.ai_socktype = SOCK_DGRAM;
109 : 75 : hints.ai_protocol = IPPROTO_UDP;
110 : 75 : hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
111 : : const int error(getaddrinfo(remote_addr,
112 : 75 : lexical_cast<string>(remote_port).c_str(),
113 [ + - ]: 75 : &hints, &res));
114 [ + + ]: 75 : if (error != 0) {
115 [ + - ][ + - ]: 9 : isc_throw(InvalidParameter, "Failed to convert [" << remote_addr
[ + - ][ + - ]
[ + - ]
116 : : << "]:" << remote_port << ", " << gai_strerror(error));
117 : : }
118 [ - + ]: 72 : assert(sizeof(remote_ss) > res->ai_addrlen);
119 : 72 : memcpy(&remote_ss, res->ai_addr, res->ai_addrlen);
120 : 72 : remote_salen = res->ai_addrlen;
121 : 72 : freeaddrinfo(res);
122 : :
123 [ + - ]: 72 : remote_ipaddr.reset(new IPAddress(getRemoteSockaddr()));
124 : 72 : }
125 : :
126 : : // A helper method for the constructor that is responsible for constructing
127 : : // the request TSIG.
128 : 72 : void createTSIGRecord(const char* tsig_data, const Py_ssize_t tsig_len) {
129 [ + + ]: 72 : if (tsig_len == 0) {
130 : 72 : return;
131 : : }
132 : :
133 : : // Re-construct the TSIG record from the passed binary. This should
134 : : // normally succeed because we are generally expected to be called
135 : : // from the frontend .py, which converts a valid TSIGRecord in its
136 : : // wire format. If some evil or buggy python program directly calls
137 : : // us with bogus data, validation in libdns++ will trigger an
138 : : // exception, which will be caught and converted to a Python exception
139 : : // in RequestContext_init().
140 : 19 : isc::util::InputBuffer b(tsig_data, tsig_len);
141 : 38 : const Name key_name(b);
142 [ + - ]: 19 : const RRType tsig_type(b.readUint16());
143 [ + - ]: 19 : const RRClass tsig_class(b.readUint16());
144 : 19 : const RRTTL ttl(b.readUint32());
145 [ + - ]: 19 : const size_t rdlen(b.readUint16());
146 : : const ConstRdataPtr rdata = createRdata(tsig_type, tsig_class, b,
147 [ + - ]: 19 : rdlen);
148 : : tsig_record.reset(new TSIGRecord(key_name, tsig_class, ttl,
149 [ + - ][ + - ]: 19 : *rdata, 0));
150 : : }
151 : :
152 : : private:
153 : : struct sockaddr_storage remote_ss;
154 : : };
155 : :
156 : : } // namespace python
157 : : } // namespace dns
158 : : } // namespace acl
159 : : } // namespace isc
160 : :
161 : :
162 : : //
163 : : // Definition of the classes
164 : : //
165 : :
166 : : // For each class, we need a struct, a helper functions (init, destroy,
167 : : // and static wrappers around the methods we export), a list of methods,
168 : : // and a type description
169 : :
170 : : //
171 : : // RequestContext
172 : : //
173 : :
174 : : // Trivial constructor.
175 : 0 : s_RequestContext::s_RequestContext() : cppobj(NULL), data_(NULL) {
176 : 0 : }
177 : :
178 : : // Import pydoc text
179 : : #include "dns_requestcontext_inc.cc"
180 : :
181 : : namespace {
182 : : // This list contains the actual set of functions we have in
183 : : // python. Each entry has
184 : : // 1. Python method name
185 : : // 2. Our static function here
186 : : // 3. Argument type
187 : : // 4. Documentation
188 : : PyMethodDef RequestContext_methods[] = {
189 : : { NULL, NULL, 0, NULL }
190 : : };
191 : :
192 : : int
193 : 79 : RequestContext_init(PyObject* po_self, PyObject* args, PyObject*) {
194 : 79 : s_RequestContext* const self = static_cast<s_RequestContext*>(po_self);
195 : :
196 : : try {
197 : : // In this initial implementation, the constructor is simple: It
198 : : // takes two parameters. The first parameter should be a Python
199 : : // socket address object.
200 : : // For IPv4, it's ('address test', numeric_port); for IPv6,
201 : : // it's ('address text', num_port, num_flowid, num_zoneid).
202 : : // The second parameter is wire-format TSIG record in the form of
203 : : // Python byte data. If the TSIG isn't included in the request,
204 : : // its length will be 0.
205 : : // Below, we parse the argument in the most straightforward way.
206 : : // As the constructor becomes more complicated, we should probably
207 : : // make it more structural (for example, we should first retrieve
208 : : // the python objects, and parse them recursively)
209 : :
210 : : const char* remote_addr;
211 : : unsigned short remote_port;
212 : : unsigned int remote_flowinfo; // IPv6 only, unused here
213 : : unsigned int remote_zoneid; // IPv6 only, unused here
214 : : const char* tsig_data;
215 : : Py_ssize_t tsig_len;
216 : :
217 [ + + ][ + + ]: 93 : if (PyArg_ParseTuple(args, "(sH)y#", &remote_addr, &remote_port,
[ + + ]
218 [ + - ]: 79 : &tsig_data, &tsig_len) ||
219 : : PyArg_ParseTuple(args, "(sHII)y#", &remote_addr, &remote_port,
220 : : &remote_flowinfo, &remote_zoneid,
221 [ + - ]: 14 : &tsig_data, &tsig_len))
222 : : {
223 : : // We need to clear the error in case the first call to ParseTuple
224 : : // fails.
225 [ + - ]: 75 : PyErr_Clear();
226 : :
227 : : auto_ptr<s_RequestContext::Data> dataptr(
228 : : new s_RequestContext::Data(remote_addr, remote_port,
229 [ + - ]: 78 : tsig_data, tsig_len));
230 : 72 : self->cppobj = new RequestContext(*dataptr->remote_ipaddr,
231 [ + - ]: 72 : dataptr->tsig_record.get());
232 : 72 : self->data_ = dataptr.release();
233 : 72 : return (0);
234 : : }
235 : 6 : } catch (const exception& ex) {
236 : : const string ex_what = "Failed to construct RequestContext object: " +
237 [ - + ][ - + ]: 6 : string(ex.what());
238 [ - + ][ - + ]: 3 : PyErr_SetString(getACLException("Error"), ex_what.c_str());
239 : : return (-1);
240 [ + - ]: 3 : } catch (...) {
241 : : PyErr_SetString(PyExc_RuntimeError,
242 [ # # ]: 0 : "Unexpected exception in constructing RequestContext");
243 : : return (-1);
244 : : }
245 : :
246 : : PyErr_SetString(PyExc_TypeError,
247 : 4 : "Invalid arguments to RequestContext constructor");
248 : :
249 : 79 : return (-1);
250 : : }
251 : :
252 : : void
253 : 81 : RequestContext_destroy(PyObject* po_self) {
254 : 81 : s_RequestContext* const self = static_cast<s_RequestContext*>(po_self);
255 : :
256 : 81 : delete self->cppobj;
257 [ + + ]: 81 : delete self->data_;
258 : 81 : Py_TYPE(self)->tp_free(self);
259 : 81 : }
260 : :
261 : : // A helper function for __str__()
262 : : string
263 : 7 : sockaddrToText(const struct sockaddr& sa, socklen_t sa_len) {
264 : : char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
265 [ - + ]: 7 : if (getnameinfo(&sa, sa_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
266 : 7 : NI_NUMERICHOST | NI_NUMERICSERV)) {
267 : : // In this context this should never fail.
268 [ # # ]: 0 : isc_throw(Unexpected, "Unexpected failure in getnameinfo");
269 : : }
270 : :
271 [ + - ][ + - ]: 14 : return ("[" + string(hbuf) + "]:" + string(sbuf));
272 : : }
273 : :
274 : : // for the __str__() method. This method is provided mainly for internal
275 : : // testing.
276 : : PyObject*
277 : 7 : RequestContext_str(PyObject* po_self) {
278 : : const s_RequestContext* const self =
279 : 7 : static_cast<s_RequestContext*>(po_self);
280 : :
281 : : try {
282 [ + - + - ]: 14 : stringstream objss;
283 [ + - ][ + - ]: 7 : objss << "<" << requestcontext_type.tp_name << " object, "
[ + - ]
284 [ + - ]: 7 : << "remote_addr="
285 : 14 : << sockaddrToText(self->data_->getRemoteSockaddr(),
286 [ + - ][ + - ]: 14 : self->data_->remote_salen);
287 [ + + ]: 7 : if (self->data_->tsig_record) {
288 [ + - ][ + - ]: 2 : objss << ", key=" << self->data_->tsig_record->getName();
289 : : }
290 [ + - ]: 7 : objss << ">";
291 [ + - ]: 7 : return (Py_BuildValue("s", objss.str().c_str()));
292 : 0 : } catch (const exception& ex) {
293 : : const string ex_what =
294 : : "Failed to convert RequestContext object to text: " +
295 [ # # ][ # # ]: 0 : string(ex.what());
296 [ # # ]: 0 : PyErr_SetString(PyExc_RuntimeError, ex_what.c_str());
297 [ # # ]: 0 : } catch (...) {
298 : : PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
299 [ # # ]: 0 : "converting RequestContext object to text");
300 : : }
301 : : return (NULL);
302 : : }
303 : : } // end of unnamed namespace
304 : :
305 : : namespace isc {
306 : : namespace acl {
307 : : namespace dns {
308 : : namespace python {
309 : : // This defines the complete type for reflection in python and
310 : : // parsing of PyObject* to s_RequestContext
311 : : // Most of the functions are not actually implemented and NULL here.
312 : : PyTypeObject requestcontext_type = {
313 : : PyVarObject_HEAD_INIT(NULL, 0)
314 : : "isc.acl._dns.RequestContext",
315 : : sizeof(s_RequestContext), // tp_basicsize
316 : : 0, // tp_itemsize
317 : : RequestContext_destroy, // tp_dealloc
318 : : NULL, // tp_print
319 : : NULL, // tp_getattr
320 : : NULL, // tp_setattr
321 : : NULL, // tp_reserved
322 : : NULL, // tp_repr
323 : : NULL, // tp_as_number
324 : : NULL, // tp_as_sequence
325 : : NULL, // tp_as_mapping
326 : : NULL, // tp_hash
327 : : NULL, // tp_call
328 : : RequestContext_str, // tp_str
329 : : NULL, // tp_getattro
330 : : NULL, // tp_setattro
331 : : NULL, // tp_as_buffer
332 : : Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags
333 : : RequestContext_doc,
334 : : NULL, // tp_traverse
335 : : NULL, // tp_clear
336 : : NULL, // tp_richcompare
337 : : 0, // tp_weaklistoffset
338 : : NULL, // tp_iter
339 : : NULL, // tp_iternext
340 : : RequestContext_methods, // tp_methods
341 : : NULL, // tp_members
342 : : NULL, // tp_getset
343 : : NULL, // tp_base
344 : : NULL, // tp_dict
345 : : NULL, // tp_descr_get
346 : : NULL, // tp_descr_set
347 : : 0, // tp_dictoffset
348 : : RequestContext_init, // tp_init
349 : : NULL, // tp_alloc
350 : : PyType_GenericNew, // tp_new
351 : : NULL, // tp_free
352 : : NULL, // tp_is_gc
353 : : NULL, // tp_bases
354 : : NULL, // tp_mro
355 : : NULL, // tp_cache
356 : : NULL, // tp_subclasses
357 : : NULL, // tp_weaklist
358 : : NULL, // tp_del
359 : : 0 // tp_version_tag
360 : : };
361 : :
362 : : bool
363 : 2 : initModulePart_RequestContext(PyObject* mod) {
364 : : // We initialize the static description object with PyType_Ready(),
365 : : // then add it to the module. This is not just a check! (leaving
366 : : // this out results in segmentation faults)
367 [ + - ]: 2 : if (PyType_Ready(&requestcontext_type) < 0) {
368 : : return (false);
369 : : }
370 : 2 : void* p = &requestcontext_type;
371 [ + - ]: 2 : if (PyModule_AddObject(mod, "RequestContext",
372 : 2 : static_cast<PyObject*>(p)) < 0) {
373 : : return (false);
374 : : }
375 : 2 : Py_INCREF(&requestcontext_type);
376 : :
377 : 2 : return (true);
378 : : }
379 : : } // namespace python
380 : : } // namespace dns
381 : : } // namespace acl
382 : 2 : } // namespace isc
|