1/*
2 * Copyright (C) 2004, 2005, 2007, 2013  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000, 2001, 2003  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id: lookup.c,v 1.21 2007/06/18 23:47:40 tbox Exp $ */
19
20/*! \file */
21
22#include <config.h>
23
24#include <isc/mem.h>
25#include <isc/netaddr.h>
26#include <isc/string.h>		/* Required for HP/UX (and others?) */
27#include <isc/task.h>
28#include <isc/util.h>
29
30#include <dns/db.h>
31#include <dns/events.h>
32#include <dns/lookup.h>
33#include <dns/rdata.h>
34#include <dns/rdataset.h>
35#include <dns/rdatastruct.h>
36#include <dns/resolver.h>
37#include <dns/result.h>
38#include <dns/view.h>
39
40struct dns_lookup {
41	/* Unlocked. */
42	unsigned int		magic;
43	isc_mem_t *		mctx;
44	isc_mutex_t		lock;
45	dns_rdatatype_t		type;
46	dns_fixedname_t		name;
47	/* Locked by lock. */
48	unsigned int		options;
49	isc_task_t *		task;
50	dns_view_t *		view;
51	dns_lookupevent_t *	event;
52	dns_fetch_t *		fetch;
53	unsigned int		restarts;
54	isc_boolean_t		canceled;
55	dns_rdataset_t		rdataset;
56	dns_rdataset_t		sigrdataset;
57};
58
59#define LOOKUP_MAGIC			ISC_MAGIC('l', 'o', 'o', 'k')
60#define VALID_LOOKUP(l)			ISC_MAGIC_VALID((l), LOOKUP_MAGIC)
61
62#define MAX_RESTARTS 16
63
64static void lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event);
65
66static void
67fetch_done(isc_task_t *task, isc_event_t *event) {
68	dns_lookup_t *lookup = event->ev_arg;
69	dns_fetchevent_t *fevent;
70
71	UNUSED(task);
72	REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
73	REQUIRE(VALID_LOOKUP(lookup));
74	REQUIRE(lookup->task == task);
75	fevent = (dns_fetchevent_t *)event;
76	REQUIRE(fevent->fetch == lookup->fetch);
77
78	lookup_find(lookup, fevent);
79}
80
81static inline isc_result_t
82start_fetch(dns_lookup_t *lookup) {
83	isc_result_t result;
84
85	/*
86	 * The caller must be holding the lookup's lock.
87	 */
88
89	REQUIRE(lookup->fetch == NULL);
90
91	result = dns_resolver_createfetch(lookup->view->resolver,
92					  dns_fixedname_name(&lookup->name),
93					  lookup->type,
94					  NULL, NULL, NULL, 0,
95					  lookup->task, fetch_done, lookup,
96					  &lookup->rdataset,
97					  &lookup->sigrdataset,
98					  &lookup->fetch);
99
100	return (result);
101}
102
103static isc_result_t
104build_event(dns_lookup_t *lookup) {
105	dns_name_t *name = NULL;
106	dns_rdataset_t *rdataset = NULL;
107	dns_rdataset_t *sigrdataset = NULL;
108	isc_result_t result;
109
110	name = isc_mem_get(lookup->mctx, sizeof(dns_name_t));
111	if (name == NULL) {
112		result = ISC_R_NOMEMORY;
113		goto fail;
114	}
115	dns_name_init(name, NULL);
116	result = dns_name_dup(dns_fixedname_name(&lookup->name),
117			      lookup->mctx, name);
118	if (result != ISC_R_SUCCESS)
119		goto fail;
120
121	if (dns_rdataset_isassociated(&lookup->rdataset)) {
122		rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
123		if (rdataset == NULL) {
124			result = ISC_R_NOMEMORY;
125			goto fail;
126		}
127		dns_rdataset_init(rdataset);
128		dns_rdataset_clone(&lookup->rdataset, rdataset);
129	}
130
131	if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
132		sigrdataset = isc_mem_get(lookup->mctx,
133					  sizeof(dns_rdataset_t));
134		if (sigrdataset == NULL) {
135			result = ISC_R_NOMEMORY;
136			goto fail;
137		}
138		dns_rdataset_init(sigrdataset);
139		dns_rdataset_clone(&lookup->sigrdataset, sigrdataset);
140	}
141
142	lookup->event->name = name;
143	lookup->event->rdataset = rdataset;
144	lookup->event->sigrdataset = sigrdataset;
145
146	return (ISC_R_SUCCESS);
147
148 fail:
149	if (name != NULL) {
150		if (dns_name_dynamic(name))
151			dns_name_free(name, lookup->mctx);
152		isc_mem_put(lookup->mctx, name, sizeof(dns_name_t));
153	}
154	if (rdataset != NULL) {
155		if (dns_rdataset_isassociated(rdataset))
156			dns_rdataset_disassociate(rdataset);
157		isc_mem_put(lookup->mctx, rdataset, sizeof(dns_rdataset_t));
158	}
159	return (result);
160}
161
162static isc_result_t
163view_find(dns_lookup_t *lookup, dns_name_t *foundname) {
164	isc_result_t result;
165	dns_name_t *name = dns_fixedname_name(&lookup->name);
166	dns_rdatatype_t type;
167
168	if (lookup->type == dns_rdatatype_rrsig)
169		type = dns_rdatatype_any;
170	else
171		type = lookup->type;
172
173	result = dns_view_find(lookup->view, name, type, 0, 0, ISC_FALSE,
174			       &lookup->event->db, &lookup->event->node,
175			       foundname, &lookup->rdataset,
176			       &lookup->sigrdataset);
177	return (result);
178}
179
180static void
181lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) {
182	isc_result_t result;
183	isc_boolean_t want_restart;
184	isc_boolean_t send_event;
185	dns_name_t *name, *fname, *prefix;
186	dns_fixedname_t foundname, fixed;
187	dns_rdata_t rdata = DNS_RDATA_INIT;
188	unsigned int nlabels;
189	int order;
190	dns_namereln_t namereln;
191	dns_rdata_cname_t cname;
192	dns_rdata_dname_t dname;
193
194	REQUIRE(VALID_LOOKUP(lookup));
195
196	LOCK(&lookup->lock);
197
198	result = ISC_R_SUCCESS;
199	name = dns_fixedname_name(&lookup->name);
200
201	do {
202		lookup->restarts++;
203		want_restart = ISC_FALSE;
204		send_event = ISC_TRUE;
205
206		if (event == NULL && !lookup->canceled) {
207			dns_fixedname_init(&foundname);
208			fname = dns_fixedname_name(&foundname);
209			INSIST(!dns_rdataset_isassociated(&lookup->rdataset));
210			INSIST(!dns_rdataset_isassociated
211						(&lookup->sigrdataset));
212			/*
213			 * If we have restarted then clear the old node.				 */
214			if  (lookup->event->node != NULL) {
215				INSIST(lookup->event->db != NULL);
216				dns_db_detachnode(lookup->event->db,
217						 &lookup->event->node);
218			}
219			if (lookup->event->db != NULL)
220				dns_db_detach(&lookup->event->db);
221			result = view_find(lookup, fname);
222			if (result == ISC_R_NOTFOUND) {
223				/*
224				 * We don't know anything about the name.
225				 * Launch a fetch.
226				 */
227				if  (lookup->event->node != NULL) {
228					INSIST(lookup->event->db != NULL);
229					dns_db_detachnode(lookup->event->db,
230							 &lookup->event->node);
231				}
232				if (lookup->event->db != NULL)
233					dns_db_detach(&lookup->event->db);
234				result = start_fetch(lookup);
235				if (result == ISC_R_SUCCESS)
236					send_event = ISC_FALSE;
237				goto done;
238			}
239		} else if (event != NULL) {
240			result = event->result;
241			fname = dns_fixedname_name(&event->foundname);
242			dns_resolver_destroyfetch(&lookup->fetch);
243			INSIST(event->rdataset == &lookup->rdataset);
244			INSIST(event->sigrdataset == &lookup->sigrdataset);
245		} else
246			fname = NULL;	/* Silence compiler warning. */
247
248		/*
249		 * If we've been canceled, forget about the result.
250		 */
251		if (lookup->canceled)
252			result = ISC_R_CANCELED;
253
254		switch (result) {
255		case ISC_R_SUCCESS:
256			result = build_event(lookup);
257			if (event == NULL)
258				break;
259			if (event->db != NULL)
260				dns_db_attach(event->db, &lookup->event->db);
261			if (event->node != NULL)
262				dns_db_attachnode(lookup->event->db,
263						  event->node,
264						  &lookup->event->node);
265			break;
266		case DNS_R_CNAME:
267			/*
268			 * Copy the CNAME's target into the lookup's
269			 * query name and start over.
270			 */
271			result = dns_rdataset_first(&lookup->rdataset);
272			if (result != ISC_R_SUCCESS)
273				break;
274			dns_rdataset_current(&lookup->rdataset, &rdata);
275			result = dns_rdata_tostruct(&rdata, &cname, NULL);
276			dns_rdata_reset(&rdata);
277			if (result != ISC_R_SUCCESS)
278				break;
279			result = dns_name_copy(&cname.cname, name, NULL);
280			dns_rdata_freestruct(&cname);
281			if (result == ISC_R_SUCCESS) {
282				want_restart = ISC_TRUE;
283				send_event = ISC_FALSE;
284			}
285			break;
286		case DNS_R_DNAME:
287			namereln = dns_name_fullcompare(name, fname, &order,
288							&nlabels);
289			INSIST(namereln == dns_namereln_subdomain);
290			/*
291			 * Get the target name of the DNAME.
292			 */
293			result = dns_rdataset_first(&lookup->rdataset);
294			if (result != ISC_R_SUCCESS)
295				break;
296			dns_rdataset_current(&lookup->rdataset, &rdata);
297			result = dns_rdata_tostruct(&rdata, &dname, NULL);
298			dns_rdata_reset(&rdata);
299			if (result != ISC_R_SUCCESS)
300				break;
301			/*
302			 * Construct the new query name and start over.
303			 */
304			dns_fixedname_init(&fixed);
305			prefix = dns_fixedname_name(&fixed);
306			dns_name_split(name, nlabels, prefix, NULL);
307			result = dns_name_concatenate(prefix, &dname.dname,
308						      name, NULL);
309			dns_rdata_freestruct(&dname);
310			if (result == ISC_R_SUCCESS) {
311				want_restart = ISC_TRUE;
312				send_event = ISC_FALSE;
313			}
314			break;
315		default:
316			send_event = ISC_TRUE;
317		}
318
319		if (dns_rdataset_isassociated(&lookup->rdataset))
320			dns_rdataset_disassociate(&lookup->rdataset);
321		if (dns_rdataset_isassociated(&lookup->sigrdataset))
322			dns_rdataset_disassociate(&lookup->sigrdataset);
323
324	done:
325		if (event != NULL) {
326			if (event->node != NULL)
327				dns_db_detachnode(event->db, &event->node);
328			if (event->db != NULL)
329				dns_db_detach(&event->db);
330			isc_event_free(ISC_EVENT_PTR(&event));
331		}
332
333		/*
334		 * Limit the number of restarts.
335		 */
336		if (want_restart && lookup->restarts == MAX_RESTARTS) {
337			want_restart = ISC_FALSE;
338			result = ISC_R_QUOTA;
339			send_event = ISC_TRUE;
340		}
341
342	} while (want_restart);
343
344	if (send_event) {
345		lookup->event->result = result;
346		lookup->event->ev_sender = lookup;
347		isc_task_sendanddetach(&lookup->task,
348				       (isc_event_t **)&lookup->event);
349		dns_view_detach(&lookup->view);
350	}
351
352	UNLOCK(&lookup->lock);
353}
354
355static void
356levent_destroy(isc_event_t *event) {
357	dns_lookupevent_t *levent;
358	isc_mem_t *mctx;
359
360	REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE);
361	mctx = event->ev_destroy_arg;
362	levent = (dns_lookupevent_t *)event;
363
364	if (levent->name != NULL) {
365		if (dns_name_dynamic(levent->name))
366			dns_name_free(levent->name, mctx);
367		isc_mem_put(mctx, levent->name, sizeof(dns_name_t));
368	}
369	if (levent->rdataset != NULL) {
370		dns_rdataset_disassociate(levent->rdataset);
371		isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t));
372	}
373	if (levent->sigrdataset != NULL) {
374		dns_rdataset_disassociate(levent->sigrdataset);
375		isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t));
376	}
377	if (levent->node != NULL)
378		dns_db_detachnode(levent->db, &levent->node);
379	if (levent->db != NULL)
380		dns_db_detach(&levent->db);
381	isc_mem_put(mctx, event, event->ev_size);
382}
383
384isc_result_t
385dns_lookup_create(isc_mem_t *mctx, dns_name_t *name, dns_rdatatype_t type,
386		  dns_view_t *view, unsigned int options, isc_task_t *task,
387		  isc_taskaction_t action, void *arg, dns_lookup_t **lookupp)
388{
389	isc_result_t result;
390	dns_lookup_t *lookup;
391	isc_event_t *ievent;
392
393	lookup = isc_mem_get(mctx, sizeof(*lookup));
394	if (lookup == NULL)
395		return (ISC_R_NOMEMORY);
396	lookup->mctx = NULL;
397	isc_mem_attach(mctx, &lookup->mctx);
398	lookup->options = options;
399
400	ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE,
401				    action, arg, sizeof(*lookup->event));
402	if (ievent == NULL) {
403		result = ISC_R_NOMEMORY;
404		goto cleanup_lookup;
405	}
406	lookup->event = (dns_lookupevent_t *)ievent;
407	lookup->event->ev_destroy = levent_destroy;
408	lookup->event->ev_destroy_arg = mctx;
409	lookup->event->result = ISC_R_FAILURE;
410	lookup->event->name = NULL;
411	lookup->event->rdataset = NULL;
412	lookup->event->sigrdataset = NULL;
413	lookup->event->db = NULL;
414	lookup->event->node = NULL;
415
416	lookup->task = NULL;
417	isc_task_attach(task, &lookup->task);
418
419	result = isc_mutex_init(&lookup->lock);
420	if (result != ISC_R_SUCCESS)
421		goto cleanup_event;
422
423	dns_fixedname_init(&lookup->name);
424
425	result = dns_name_copy(name, dns_fixedname_name(&lookup->name), NULL);
426	if (result != ISC_R_SUCCESS)
427		goto cleanup_lock;
428
429	lookup->type = type;
430	lookup->view = NULL;
431	dns_view_attach(view, &lookup->view);
432	lookup->fetch = NULL;
433	lookup->restarts = 0;
434	lookup->canceled = ISC_FALSE;
435	dns_rdataset_init(&lookup->rdataset);
436	dns_rdataset_init(&lookup->sigrdataset);
437	lookup->magic = LOOKUP_MAGIC;
438
439	*lookupp = lookup;
440
441	lookup_find(lookup, NULL);
442
443	return (ISC_R_SUCCESS);
444
445 cleanup_lock:
446	DESTROYLOCK(&lookup->lock);
447
448 cleanup_event:
449	ievent = (isc_event_t *)lookup->event;
450	isc_event_free(&ievent);
451	lookup->event = NULL;
452
453	isc_task_detach(&lookup->task);
454
455 cleanup_lookup:
456	isc_mem_putanddetach(&mctx, lookup, sizeof(*lookup));
457
458	return (result);
459}
460
461void
462dns_lookup_cancel(dns_lookup_t *lookup) {
463	REQUIRE(VALID_LOOKUP(lookup));
464
465	LOCK(&lookup->lock);
466
467	if (!lookup->canceled) {
468		lookup->canceled = ISC_TRUE;
469		if (lookup->fetch != NULL) {
470			INSIST(lookup->view != NULL);
471			dns_resolver_cancelfetch(lookup->fetch);
472		}
473	}
474
475	UNLOCK(&lookup->lock);
476}
477
478void
479dns_lookup_destroy(dns_lookup_t **lookupp) {
480	dns_lookup_t *lookup;
481
482	REQUIRE(lookupp != NULL);
483	lookup = *lookupp;
484	REQUIRE(VALID_LOOKUP(lookup));
485	REQUIRE(lookup->event == NULL);
486	REQUIRE(lookup->task == NULL);
487	REQUIRE(lookup->view == NULL);
488	if (dns_rdataset_isassociated(&lookup->rdataset))
489		dns_rdataset_disassociate(&lookup->rdataset);
490	if (dns_rdataset_isassociated(&lookup->sigrdataset))
491		dns_rdataset_disassociate(&lookup->sigrdataset);
492
493	DESTROYLOCK(&lookup->lock);
494	lookup->magic = 0;
495	isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup));
496
497	*lookupp = NULL;
498}
499