1/*	$NetBSD: lookup.c,v 1.1 2024/02/18 20:57:32 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16/*! \file */
17
18#include <stdbool.h>
19
20#include <isc/mem.h>
21#include <isc/netaddr.h>
22#include <isc/string.h> /* Required for HP/UX (and others?) */
23#include <isc/task.h>
24#include <isc/util.h>
25
26#include <dns/db.h>
27#include <dns/events.h>
28#include <dns/lookup.h>
29#include <dns/rdata.h>
30#include <dns/rdataset.h>
31#include <dns/rdatastruct.h>
32#include <dns/resolver.h>
33#include <dns/result.h>
34#include <dns/view.h>
35
36struct dns_lookup {
37	/* Unlocked. */
38	unsigned int magic;
39	isc_mem_t *mctx;
40	isc_mutex_t lock;
41	dns_rdatatype_t type;
42	dns_fixedname_t name;
43	/* Locked by lock. */
44	unsigned int options;
45	isc_task_t *task;
46	dns_view_t *view;
47	dns_lookupevent_t *event;
48	dns_fetch_t *fetch;
49	unsigned int restarts;
50	bool canceled;
51	dns_rdataset_t rdataset;
52	dns_rdataset_t sigrdataset;
53};
54
55#define LOOKUP_MAGIC	ISC_MAGIC('l', 'o', 'o', 'k')
56#define VALID_LOOKUP(l) ISC_MAGIC_VALID((l), LOOKUP_MAGIC)
57
58#define MAX_RESTARTS 16
59
60static void
61lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event);
62
63static void
64fetch_done(isc_task_t *task, isc_event_t *event) {
65	dns_lookup_t *lookup = event->ev_arg;
66	dns_fetchevent_t *fevent;
67
68	UNUSED(task);
69	REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
70	REQUIRE(VALID_LOOKUP(lookup));
71	REQUIRE(lookup->task == task);
72	fevent = (dns_fetchevent_t *)event;
73	REQUIRE(fevent->fetch == lookup->fetch);
74
75	lookup_find(lookup, fevent);
76}
77
78static isc_result_t
79start_fetch(dns_lookup_t *lookup) {
80	isc_result_t result;
81
82	/*
83	 * The caller must be holding the lookup's lock.
84	 */
85
86	REQUIRE(lookup->fetch == NULL);
87
88	result = dns_resolver_createfetch(
89		lookup->view->resolver, dns_fixedname_name(&lookup->name),
90		lookup->type, NULL, NULL, NULL, NULL, 0, 0, 0, NULL,
91		lookup->task, fetch_done, lookup, &lookup->rdataset,
92		&lookup->sigrdataset, &lookup->fetch);
93
94	return (result);
95}
96
97static isc_result_t
98build_event(dns_lookup_t *lookup) {
99	dns_name_t *name = NULL;
100	dns_rdataset_t *rdataset = NULL;
101	dns_rdataset_t *sigrdataset = NULL;
102
103	name = isc_mem_get(lookup->mctx, sizeof(dns_name_t));
104	dns_name_init(name, NULL);
105	dns_name_dup(dns_fixedname_name(&lookup->name), lookup->mctx, name);
106
107	if (dns_rdataset_isassociated(&lookup->rdataset)) {
108		rdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
109		dns_rdataset_init(rdataset);
110		dns_rdataset_clone(&lookup->rdataset, rdataset);
111	}
112
113	if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
114		sigrdataset = isc_mem_get(lookup->mctx, sizeof(dns_rdataset_t));
115		dns_rdataset_init(sigrdataset);
116		dns_rdataset_clone(&lookup->sigrdataset, sigrdataset);
117	}
118
119	lookup->event->name = name;
120	lookup->event->rdataset = rdataset;
121	lookup->event->sigrdataset = sigrdataset;
122
123	return (ISC_R_SUCCESS);
124}
125
126static isc_result_t
127view_find(dns_lookup_t *lookup, dns_name_t *foundname) {
128	isc_result_t result;
129	dns_name_t *name = dns_fixedname_name(&lookup->name);
130	dns_rdatatype_t type;
131
132	if (lookup->type == dns_rdatatype_rrsig) {
133		type = dns_rdatatype_any;
134	} else {
135		type = lookup->type;
136	}
137
138	result = dns_view_find(lookup->view, name, type, 0, 0, false, false,
139			       &lookup->event->db, &lookup->event->node,
140			       foundname, &lookup->rdataset,
141			       &lookup->sigrdataset);
142	return (result);
143}
144
145static void
146lookup_find(dns_lookup_t *lookup, dns_fetchevent_t *event) {
147	isc_result_t result;
148	bool want_restart;
149	bool send_event;
150	dns_name_t *name, *fname, *prefix;
151	dns_fixedname_t foundname, fixed;
152	dns_rdata_t rdata = DNS_RDATA_INIT;
153	unsigned int nlabels;
154	int order;
155	dns_namereln_t namereln;
156	dns_rdata_cname_t cname;
157	dns_rdata_dname_t dname;
158
159	REQUIRE(VALID_LOOKUP(lookup));
160
161	LOCK(&lookup->lock);
162
163	result = ISC_R_SUCCESS;
164	name = dns_fixedname_name(&lookup->name);
165
166	do {
167		lookup->restarts++;
168		want_restart = false;
169		send_event = true;
170
171		if (event == NULL && !lookup->canceled) {
172			fname = dns_fixedname_initname(&foundname);
173			INSIST(!dns_rdataset_isassociated(&lookup->rdataset));
174			INSIST(!dns_rdataset_isassociated(
175				&lookup->sigrdataset));
176			/*
177			 * If we have restarted then clear the old node.
178			 */
179			if (lookup->event->node != NULL) {
180				INSIST(lookup->event->db != NULL);
181				dns_db_detachnode(lookup->event->db,
182						  &lookup->event->node);
183			}
184			if (lookup->event->db != NULL) {
185				dns_db_detach(&lookup->event->db);
186			}
187			result = view_find(lookup, fname);
188			if (result == ISC_R_NOTFOUND) {
189				/*
190				 * We don't know anything about the name.
191				 * Launch a fetch.
192				 */
193				if (lookup->event->node != NULL) {
194					INSIST(lookup->event->db != NULL);
195					dns_db_detachnode(lookup->event->db,
196							  &lookup->event->node);
197				}
198				if (lookup->event->db != NULL) {
199					dns_db_detach(&lookup->event->db);
200				}
201				result = start_fetch(lookup);
202				if (result == ISC_R_SUCCESS) {
203					send_event = false;
204				}
205				goto done;
206			}
207		} else if (event != NULL) {
208			result = event->result;
209			fname = dns_fixedname_name(&event->foundname);
210			dns_resolver_destroyfetch(&lookup->fetch);
211			INSIST(event->rdataset == &lookup->rdataset);
212			INSIST(event->sigrdataset == &lookup->sigrdataset);
213		} else {
214			fname = NULL; /* Silence compiler warning. */
215		}
216
217		/*
218		 * If we've been canceled, forget about the result.
219		 */
220		if (lookup->canceled) {
221			result = ISC_R_CANCELED;
222		}
223
224		switch (result) {
225		case ISC_R_SUCCESS:
226			result = build_event(lookup);
227			if (event == NULL) {
228				break;
229			}
230			if (event->db != NULL) {
231				dns_db_attach(event->db, &lookup->event->db);
232			}
233			if (event->node != NULL) {
234				dns_db_attachnode(lookup->event->db,
235						  event->node,
236						  &lookup->event->node);
237			}
238			break;
239		case DNS_R_CNAME:
240			/*
241			 * Copy the CNAME's target into the lookup's
242			 * query name and start over.
243			 */
244			result = dns_rdataset_first(&lookup->rdataset);
245			if (result != ISC_R_SUCCESS) {
246				break;
247			}
248			dns_rdataset_current(&lookup->rdataset, &rdata);
249			result = dns_rdata_tostruct(&rdata, &cname, NULL);
250			dns_rdata_reset(&rdata);
251			if (result != ISC_R_SUCCESS) {
252				break;
253			}
254			dns_name_copynf(&cname.cname, name);
255			dns_rdata_freestruct(&cname);
256			want_restart = true;
257			send_event = false;
258			break;
259		case DNS_R_DNAME:
260			namereln = dns_name_fullcompare(name, fname, &order,
261							&nlabels);
262			INSIST(namereln == dns_namereln_subdomain);
263			/*
264			 * Get the target name of the DNAME.
265			 */
266			result = dns_rdataset_first(&lookup->rdataset);
267			if (result != ISC_R_SUCCESS) {
268				break;
269			}
270			dns_rdataset_current(&lookup->rdataset, &rdata);
271			result = dns_rdata_tostruct(&rdata, &dname, NULL);
272			dns_rdata_reset(&rdata);
273			if (result != ISC_R_SUCCESS) {
274				break;
275			}
276			/*
277			 * Construct the new query name and start over.
278			 */
279			prefix = dns_fixedname_initname(&fixed);
280			dns_name_split(name, nlabels, prefix, NULL);
281			result = dns_name_concatenate(prefix, &dname.dname,
282						      name, NULL);
283			dns_rdata_freestruct(&dname);
284			if (result == ISC_R_SUCCESS) {
285				want_restart = true;
286				send_event = false;
287			}
288			break;
289		default:
290			send_event = true;
291		}
292
293		if (dns_rdataset_isassociated(&lookup->rdataset)) {
294			dns_rdataset_disassociate(&lookup->rdataset);
295		}
296		if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
297			dns_rdataset_disassociate(&lookup->sigrdataset);
298		}
299
300	done:
301		if (event != NULL) {
302			if (event->node != NULL) {
303				dns_db_detachnode(event->db, &event->node);
304			}
305			if (event->db != NULL) {
306				dns_db_detach(&event->db);
307			}
308			isc_event_free(ISC_EVENT_PTR(&event));
309		}
310
311		/*
312		 * Limit the number of restarts.
313		 */
314		if (want_restart && lookup->restarts == MAX_RESTARTS) {
315			want_restart = false;
316			result = ISC_R_QUOTA;
317			send_event = true;
318		}
319	} while (want_restart);
320
321	if (send_event) {
322		lookup->event->result = result;
323		lookup->event->ev_sender = lookup;
324		isc_task_sendanddetach(&lookup->task,
325				       (isc_event_t **)(void *)&lookup->event);
326		dns_view_detach(&lookup->view);
327	}
328
329	UNLOCK(&lookup->lock);
330}
331
332static void
333levent_destroy(isc_event_t *event) {
334	dns_lookupevent_t *levent;
335	isc_mem_t *mctx;
336
337	REQUIRE(event->ev_type == DNS_EVENT_LOOKUPDONE);
338	mctx = event->ev_destroy_arg;
339	levent = (dns_lookupevent_t *)event;
340
341	if (levent->name != NULL) {
342		if (dns_name_dynamic(levent->name)) {
343			dns_name_free(levent->name, mctx);
344		}
345		isc_mem_put(mctx, levent->name, sizeof(dns_name_t));
346	}
347	if (levent->rdataset != NULL) {
348		dns_rdataset_disassociate(levent->rdataset);
349		isc_mem_put(mctx, levent->rdataset, sizeof(dns_rdataset_t));
350	}
351	if (levent->sigrdataset != NULL) {
352		dns_rdataset_disassociate(levent->sigrdataset);
353		isc_mem_put(mctx, levent->sigrdataset, sizeof(dns_rdataset_t));
354	}
355	if (levent->node != NULL) {
356		dns_db_detachnode(levent->db, &levent->node);
357	}
358	if (levent->db != NULL) {
359		dns_db_detach(&levent->db);
360	}
361	isc_mem_put(mctx, event, event->ev_size);
362}
363
364isc_result_t
365dns_lookup_create(isc_mem_t *mctx, const dns_name_t *name, dns_rdatatype_t type,
366		  dns_view_t *view, unsigned int options, isc_task_t *task,
367		  isc_taskaction_t action, void *arg, dns_lookup_t **lookupp) {
368	dns_lookup_t *lookup;
369	isc_event_t *ievent;
370
371	lookup = isc_mem_get(mctx, sizeof(*lookup));
372	lookup->mctx = NULL;
373	isc_mem_attach(mctx, &lookup->mctx);
374	lookup->options = options;
375
376	ievent = isc_event_allocate(mctx, lookup, DNS_EVENT_LOOKUPDONE, action,
377				    arg, sizeof(*lookup->event));
378	lookup->event = (dns_lookupevent_t *)ievent;
379	lookup->event->ev_destroy = levent_destroy;
380	lookup->event->ev_destroy_arg = mctx;
381	lookup->event->result = ISC_R_FAILURE;
382	lookup->event->name = NULL;
383	lookup->event->rdataset = NULL;
384	lookup->event->sigrdataset = NULL;
385	lookup->event->db = NULL;
386	lookup->event->node = NULL;
387
388	lookup->task = NULL;
389	isc_task_attach(task, &lookup->task);
390
391	isc_mutex_init(&lookup->lock);
392
393	dns_fixedname_init(&lookup->name);
394
395	dns_name_copynf(name, dns_fixedname_name(&lookup->name));
396
397	lookup->type = type;
398	lookup->view = NULL;
399	dns_view_attach(view, &lookup->view);
400	lookup->fetch = NULL;
401	lookup->restarts = 0;
402	lookup->canceled = false;
403	dns_rdataset_init(&lookup->rdataset);
404	dns_rdataset_init(&lookup->sigrdataset);
405	lookup->magic = LOOKUP_MAGIC;
406
407	*lookupp = lookup;
408
409	lookup_find(lookup, NULL);
410
411	return (ISC_R_SUCCESS);
412}
413
414void
415dns_lookup_cancel(dns_lookup_t *lookup) {
416	REQUIRE(VALID_LOOKUP(lookup));
417
418	LOCK(&lookup->lock);
419
420	if (!lookup->canceled) {
421		lookup->canceled = true;
422		if (lookup->fetch != NULL) {
423			INSIST(lookup->view != NULL);
424			dns_resolver_cancelfetch(lookup->fetch);
425		}
426	}
427
428	UNLOCK(&lookup->lock);
429}
430
431void
432dns_lookup_destroy(dns_lookup_t **lookupp) {
433	dns_lookup_t *lookup;
434
435	REQUIRE(lookupp != NULL);
436	lookup = *lookupp;
437	*lookupp = NULL;
438	REQUIRE(VALID_LOOKUP(lookup));
439	REQUIRE(lookup->event == NULL);
440	REQUIRE(lookup->task == NULL);
441	REQUIRE(lookup->view == NULL);
442	if (dns_rdataset_isassociated(&lookup->rdataset)) {
443		dns_rdataset_disassociate(&lookup->rdataset);
444	}
445	if (dns_rdataset_isassociated(&lookup->sigrdataset)) {
446		dns_rdataset_disassociate(&lookup->sigrdataset);
447	}
448
449	isc_mutex_destroy(&lookup->lock);
450	lookup->magic = 0;
451	isc_mem_putanddetach(&lookup->mctx, lookup, sizeof(*lookup));
452}
453