1/*
2 * Copyright (C) 2004-2007, 2009  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000, 2001  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: lwdgabn.c,v 1.24 2009/09/02 23:48:01 tbox Exp $ */
19
20/*! \file */
21
22#include <config.h>
23
24#include <stdlib.h>
25
26#include <isc/netaddr.h>
27#include <isc/sockaddr.h>
28#include <isc/socket.h>
29#include <isc/string.h>		/* Required for HP/UX (and others?) */
30#include <isc/util.h>
31
32#include <dns/adb.h>
33#include <dns/events.h>
34#include <dns/result.h>
35
36#include <named/types.h>
37#include <named/lwaddr.h>
38#include <named/lwdclient.h>
39#include <named/lwresd.h>
40#include <named/lwsearch.h>
41#include <named/sortlist.h>
42
43#define NEED_V4(c)	((((c)->find_wanted & LWRES_ADDRTYPE_V4) != 0) \
44			 && ((c)->v4find == NULL))
45#define NEED_V6(c)	((((c)->find_wanted & LWRES_ADDRTYPE_V6) != 0) \
46			 && ((c)->v6find == NULL))
47
48static isc_result_t start_find(ns_lwdclient_t *);
49static void restart_find(ns_lwdclient_t *);
50static void init_gabn(ns_lwdclient_t *);
51
52/*%
53 * Destroy any finds.  This can be used to "start over from scratch" and
54 * should only be called when events are _not_ being generated by the finds.
55 */
56static void
57cleanup_gabn(ns_lwdclient_t *client) {
58	ns_lwdclient_log(50, "cleaning up client %p", client);
59
60	if (client->v6find != NULL) {
61		if (client->v6find == client->v4find)
62			client->v6find = NULL;
63		else
64			dns_adb_destroyfind(&client->v6find);
65	}
66	if (client->v4find != NULL)
67		dns_adb_destroyfind(&client->v4find);
68}
69
70static void
71setup_addresses(ns_lwdclient_t *client, dns_adbfind_t *find, unsigned int at) {
72	dns_adbaddrinfo_t *ai;
73	lwres_addr_t *addr;
74	int af;
75	const struct sockaddr *sa;
76	isc_result_t result;
77
78	if (at == DNS_ADBFIND_INET)
79		af = AF_INET;
80	else
81		af = AF_INET6;
82
83	ai = ISC_LIST_HEAD(find->list);
84	while (ai != NULL && client->gabn.naddrs < LWRES_MAX_ADDRS) {
85		sa = &ai->sockaddr.type.sa;
86		if (sa->sa_family != af)
87			goto next;
88
89		addr = &client->addrs[client->gabn.naddrs];
90
91		result = lwaddr_lwresaddr_fromsockaddr(addr, &ai->sockaddr);
92		if (result != ISC_R_SUCCESS)
93			goto next;
94
95		ns_lwdclient_log(50, "adding address %p, family %d, length %d",
96				 addr->address, addr->family, addr->length);
97
98		client->gabn.naddrs++;
99		REQUIRE(!LWRES_LINK_LINKED(addr, link));
100		LWRES_LIST_APPEND(client->gabn.addrs, addr, link);
101
102	next:
103		ai = ISC_LIST_NEXT(ai, publink);
104	}
105}
106
107typedef struct {
108	isc_netaddr_t address;
109	int rank;
110} rankedaddress;
111
112static int
113addr_compare(const void *av, const void *bv) {
114	const rankedaddress *a = (const rankedaddress *) av;
115	const rankedaddress *b = (const rankedaddress *) bv;
116	return (a->rank - b->rank);
117}
118
119static void
120sort_addresses(ns_lwdclient_t *client) {
121	unsigned int naddrs;
122	rankedaddress *addrs;
123	isc_netaddr_t remote;
124	dns_addressorderfunc_t order;
125	const void *arg;
126	ns_lwresd_t *lwresd = client->clientmgr->listener->manager;
127	unsigned int i;
128	isc_result_t result;
129
130	naddrs = client->gabn.naddrs;
131
132	if (naddrs <= 1 || lwresd->view->sortlist == NULL)
133		return;
134
135	addrs = isc_mem_get(lwresd->mctx, sizeof(rankedaddress) * naddrs);
136	if (addrs == NULL)
137		return;
138
139	isc_netaddr_fromsockaddr(&remote, &client->address);
140	ns_sortlist_byaddrsetup(lwresd->view->sortlist,
141				&remote, &order, &arg);
142	if (order == NULL) {
143		isc_mem_put(lwresd->mctx, addrs,
144			    sizeof(rankedaddress) * naddrs);
145		return;
146	}
147	for (i = 0; i < naddrs; i++) {
148		result = lwaddr_netaddr_fromlwresaddr(&addrs[i].address,
149						      &client->addrs[i]);
150		INSIST(result == ISC_R_SUCCESS);
151		addrs[i].rank = (*order)(&addrs[i].address, arg);
152	}
153	qsort(addrs, naddrs, sizeof(rankedaddress), addr_compare);
154	for (i = 0; i < naddrs; i++) {
155		result = lwaddr_lwresaddr_fromnetaddr(&client->addrs[i],
156						      &addrs[i].address);
157		INSIST(result == ISC_R_SUCCESS);
158	}
159
160	isc_mem_put(lwresd->mctx, addrs, sizeof(rankedaddress) * naddrs);
161}
162
163static void
164generate_reply(ns_lwdclient_t *client) {
165	isc_result_t result;
166	int lwres;
167	isc_region_t r;
168	lwres_buffer_t lwb;
169	ns_lwdclientmgr_t *cm;
170
171	cm = client->clientmgr;
172	lwb.base = NULL;
173
174	ns_lwdclient_log(50, "generating gabn reply for client %p", client);
175
176	/*
177	 * We must make certain the client->find is not still active.
178	 * If it is either the v4 or v6 answer, just set it to NULL and
179	 * let the cleanup code destroy it.  Otherwise, destroy it now.
180	 */
181	if (client->find == client->v4find || client->find == client->v6find)
182		client->find = NULL;
183	else
184		if (client->find != NULL)
185			dns_adb_destroyfind(&client->find);
186
187	/*
188	 * perhaps there are some here?
189	 */
190	if (NEED_V6(client) && client->v4find != NULL)
191		client->v6find = client->v4find;
192
193	/*
194	 * Run through the finds we have and wire them up to the gabn
195	 * structure.
196	 */
197	LWRES_LIST_INIT(client->gabn.addrs);
198	if (client->v4find != NULL)
199		setup_addresses(client, client->v4find, DNS_ADBFIND_INET);
200	if (client->v6find != NULL)
201		setup_addresses(client, client->v6find, DNS_ADBFIND_INET6);
202
203	/*
204	 * If there are no addresses, try the next element in the search
205	 * path, if there are any more.  Otherwise, fall through into
206	 * the error handling code below.
207	 */
208	if (client->gabn.naddrs == 0) {
209		do {
210			result = ns_lwsearchctx_next(&client->searchctx);
211			if (result == ISC_R_SUCCESS) {
212				cleanup_gabn(client);
213				result = start_find(client);
214				if (result == ISC_R_SUCCESS)
215					return;
216			}
217		} while (result == ISC_R_SUCCESS);
218	}
219
220	/*
221	 * Render the packet.
222	 */
223	client->pkt.recvlength = LWRES_RECVLENGTH;
224	client->pkt.authtype = 0; /* XXXMLG */
225	client->pkt.authlength = 0;
226
227	/*
228	 * If there are no addresses, return failure.
229	 */
230	if (client->gabn.naddrs != 0)
231		client->pkt.result = LWRES_R_SUCCESS;
232	else
233		client->pkt.result = LWRES_R_NOTFOUND;
234
235	sort_addresses(client);
236
237	lwres = lwres_gabnresponse_render(cm->lwctx, &client->gabn,
238					  &client->pkt, &lwb);
239	if (lwres != LWRES_R_SUCCESS)
240		goto out;
241
242	r.base = lwb.base;
243	r.length = lwb.used;
244	client->sendbuf = r.base;
245	client->sendlength = r.length;
246	result = ns_lwdclient_sendreply(client, &r);
247	if (result != ISC_R_SUCCESS)
248		goto out;
249
250	NS_LWDCLIENT_SETSEND(client);
251
252	/*
253	 * All done!
254	 */
255	cleanup_gabn(client);
256
257	return;
258
259 out:
260	cleanup_gabn(client);
261
262	if (lwb.base != NULL)
263		lwres_context_freemem(client->clientmgr->lwctx,
264				      lwb.base, lwb.length);
265
266	ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
267}
268
269/*
270 * Take the current real name, move it to an alias slot (if any are
271 * open) then put this new name in as the real name for the target.
272 *
273 * Return success if it can be rendered, otherwise failure.  Note that
274 * not having enough alias slots open is NOT a failure.
275 */
276static isc_result_t
277add_alias(ns_lwdclient_t *client) {
278	isc_buffer_t b;
279	isc_result_t result;
280	isc_uint16_t naliases;
281
282	b = client->recv_buffer;
283
284	/*
285	 * Render the new name to the buffer.
286	 */
287	result = dns_name_totext(dns_fixedname_name(&client->target_name),
288				 ISC_TRUE, &client->recv_buffer);
289	if (result != ISC_R_SUCCESS)
290		return (result);
291
292	/*
293	 * Are there any open slots?
294	 */
295	naliases = client->gabn.naliases;
296	if (naliases < LWRES_MAX_ALIASES) {
297		client->gabn.aliases[naliases] = client->gabn.realname;
298		client->gabn.aliaslen[naliases] = client->gabn.realnamelen;
299		client->gabn.naliases++;
300	}
301
302	/*
303	 * Save this name away as the current real name.
304	 */
305	client->gabn.realname = (char *)(b.base) + b.used;
306	client->gabn.realnamelen = client->recv_buffer.used - b.used;
307
308	return (ISC_R_SUCCESS);
309}
310
311static isc_result_t
312store_realname(ns_lwdclient_t *client) {
313	isc_buffer_t b;
314	isc_result_t result;
315	dns_name_t *tname;
316
317	b = client->recv_buffer;
318
319	tname = dns_fixedname_name(&client->target_name);
320	result = ns_lwsearchctx_current(&client->searchctx, tname);
321	if (result != ISC_R_SUCCESS)
322		return (result);
323
324	/*
325	 * Render the new name to the buffer.
326	 */
327	result = dns_name_totext(tname, ISC_TRUE, &client->recv_buffer);
328	if (result != ISC_R_SUCCESS)
329		return (result);
330
331	/*
332	 * Save this name away as the current real name.
333	 */
334	client->gabn.realname = (char *) b.base + b.used;
335	client->gabn.realnamelen = client->recv_buffer.used - b.used;
336
337	return (ISC_R_SUCCESS);
338}
339
340static void
341process_gabn_finddone(isc_task_t *task, isc_event_t *ev) {
342	ns_lwdclient_t *client = ev->ev_arg;
343	isc_eventtype_t evtype;
344	isc_boolean_t claimed;
345
346	ns_lwdclient_log(50, "find done for task %p, client %p", task, client);
347
348	evtype = ev->ev_type;
349	isc_event_free(&ev);
350
351	/*
352	 * No more info to be had?  If so, we have all the good stuff
353	 * right now, so we can render things.
354	 */
355	claimed = ISC_FALSE;
356	if (evtype == DNS_EVENT_ADBNOMOREADDRESSES) {
357		if (NEED_V4(client)) {
358			client->v4find = client->find;
359			claimed = ISC_TRUE;
360		}
361		if (NEED_V6(client)) {
362			client->v6find = client->find;
363			claimed = ISC_TRUE;
364		}
365		if (client->find != NULL) {
366			if (claimed)
367				client->find = NULL;
368			else
369				dns_adb_destroyfind(&client->find);
370
371		}
372		generate_reply(client);
373		return;
374	}
375
376	/*
377	 * We probably don't need this find anymore.  We're either going to
378	 * reissue it, or an error occurred.  Either way, we're done with
379	 * it.
380	 */
381	if ((client->find != client->v4find)
382	    && (client->find != client->v6find)) {
383		dns_adb_destroyfind(&client->find);
384	} else {
385		client->find = NULL;
386	}
387
388	/*
389	 * We have some new information we can gather.  Run off and fetch
390	 * it.
391	 */
392	if (evtype == DNS_EVENT_ADBMOREADDRESSES) {
393		restart_find(client);
394		return;
395	}
396
397	/*
398	 * An error or other strangeness happened.  Drop this query.
399	 */
400	cleanup_gabn(client);
401	ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
402}
403
404static void
405restart_find(ns_lwdclient_t *client) {
406	unsigned int options;
407	isc_result_t result;
408	isc_boolean_t claimed;
409
410	ns_lwdclient_log(50, "starting find for client %p", client);
411
412	/*
413	 * Issue a find for the name contained in the request.  We won't
414	 * set the bit that says "anything is good enough" -- we want it
415	 * all.
416	 */
417	options = 0;
418	options |= DNS_ADBFIND_WANTEVENT;
419	options |= DNS_ADBFIND_RETURNLAME;
420
421	/*
422	 * Set the bits up here to mark that we want this address family
423	 * and that we do not currently have a find pending.  We will
424	 * set that bit again below if it turns out we will get an event.
425	 */
426	if (NEED_V4(client))
427		options |= DNS_ADBFIND_INET;
428	if (NEED_V6(client))
429		options |= DNS_ADBFIND_INET6;
430
431 find_again:
432	INSIST(client->find == NULL);
433	result = dns_adb_createfind(client->clientmgr->view->adb,
434				    client->clientmgr->task,
435				    process_gabn_finddone, client,
436				    dns_fixedname_name(&client->target_name),
437				    dns_rootname, 0, options, 0,
438				    dns_fixedname_name(&client->target_name),
439				    client->clientmgr->view->dstport,
440				    &client->find);
441
442	/*
443	 * Did we get an alias?  If so, save it and re-issue the query.
444	 */
445	if (result == DNS_R_ALIAS) {
446		ns_lwdclient_log(50, "found alias, restarting query");
447		dns_adb_destroyfind(&client->find);
448		cleanup_gabn(client);
449		result = add_alias(client);
450		if (result != ISC_R_SUCCESS) {
451			ns_lwdclient_log(50,
452					 "out of buffer space adding alias");
453			ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
454			return;
455		}
456		goto find_again;
457	}
458
459	ns_lwdclient_log(50, "find returned %d (%s)", result,
460			 isc_result_totext(result));
461
462	/*
463	 * Did we get an error?
464	 */
465	if (result != ISC_R_SUCCESS) {
466		if (client->find != NULL)
467			dns_adb_destroyfind(&client->find);
468		cleanup_gabn(client);
469		ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
470		return;
471	}
472
473	claimed = ISC_FALSE;
474
475	/*
476	 * Did we get our answer to V4 addresses?
477	 */
478	if (NEED_V4(client)
479	    && ((client->find->query_pending & DNS_ADBFIND_INET) == 0)) {
480		ns_lwdclient_log(50, "client %p ipv4 satisfied by find %p",
481				 client, client->find);
482		claimed = ISC_TRUE;
483		client->v4find = client->find;
484	}
485
486	/*
487	 * Did we get our answer to V6 addresses?
488	 */
489	if (NEED_V6(client)
490	    && ((client->find->query_pending & DNS_ADBFIND_INET6) == 0)) {
491		ns_lwdclient_log(50, "client %p ipv6 satisfied by find %p",
492				 client, client->find);
493		claimed = ISC_TRUE;
494		client->v6find = client->find;
495	}
496
497	/*
498	 * If we're going to get an event, set our internal pending flag
499	 * and return.  When we get an event back we'll do the right
500	 * thing, basically by calling this function again, perhaps with a
501	 * new target name.
502	 *
503	 * If we have both v4 and v6, and we are still getting an event,
504	 * we have a programming error, so die hard.
505	 */
506	if ((client->find->options & DNS_ADBFIND_WANTEVENT) != 0) {
507		ns_lwdclient_log(50, "event will be sent");
508		INSIST(client->v4find == NULL || client->v6find == NULL);
509		return;
510	}
511	ns_lwdclient_log(50, "no event will be sent");
512	if (claimed)
513		client->find = NULL;
514	else
515		dns_adb_destroyfind(&client->find);
516
517	/*
518	 * We seem to have everything we asked for, or at least we are
519	 * able to respond with things we've learned.
520	 */
521
522	generate_reply(client);
523}
524
525static isc_result_t
526start_find(ns_lwdclient_t *client) {
527	isc_result_t result;
528
529	/*
530	 * Initialize the real name and alias arrays in the reply we're
531	 * going to build up.
532	 */
533	init_gabn(client);
534
535	result = store_realname(client);
536	if (result != ISC_R_SUCCESS)
537		return (result);
538	restart_find(client);
539	return (ISC_R_SUCCESS);
540
541}
542
543static void
544init_gabn(ns_lwdclient_t *client) {
545	int i;
546
547	/*
548	 * Initialize the real name and alias arrays in the reply we're
549	 * going to build up.
550	 */
551	for (i = 0; i < LWRES_MAX_ALIASES; i++) {
552		client->aliases[i] = NULL;
553		client->aliaslen[i] = 0;
554	}
555	for (i = 0; i < LWRES_MAX_ADDRS; i++) {
556		client->addrs[i].family = 0;
557		client->addrs[i].length = 0;
558		memset(client->addrs[i].address, 0, LWRES_ADDR_MAXLEN);
559		LWRES_LINK_INIT(&client->addrs[i], link);
560	}
561
562	client->gabn.naliases = 0;
563	client->gabn.naddrs = 0;
564	client->gabn.realname = NULL;
565	client->gabn.aliases = client->aliases;
566	client->gabn.realnamelen = 0;
567	client->gabn.aliaslen = client->aliaslen;
568	LWRES_LIST_INIT(client->gabn.addrs);
569	client->gabn.base = NULL;
570	client->gabn.baselen = 0;
571
572	/*
573	 * Set up the internal buffer to point to the receive region.
574	 */
575	isc_buffer_init(&client->recv_buffer, client->buffer, LWRES_RECVLENGTH);
576}
577
578/*
579 * When we are called, we can be assured that:
580 *
581 *	client->sockaddr contains the address we need to reply to,
582 *
583 *	client->pkt contains the packet header data,
584 *
585 *	the packet "checks out" overall -- any MD5 hashes or crypto
586 *	bits have been verified,
587 *
588 *	"b" points to the remaining data after the packet header
589 *	was parsed off.
590 *
591 *	We are in a the RECVDONE state.
592 *
593 * From this state we will enter the SEND state if we happen to have
594 * everything we need or we need to return an error packet, or to the
595 * FINDWAIT state if we need to look things up.
596 */
597void
598ns_lwdclient_processgabn(ns_lwdclient_t *client, lwres_buffer_t *b) {
599	isc_result_t result;
600	lwres_gabnrequest_t *req;
601	ns_lwdclientmgr_t *cm;
602	isc_buffer_t namebuf;
603
604	REQUIRE(NS_LWDCLIENT_ISRECVDONE(client));
605
606	cm = client->clientmgr;
607	req = NULL;
608
609	result = lwres_gabnrequest_parse(client->clientmgr->lwctx,
610					 b, &client->pkt, &req);
611	if (result != LWRES_R_SUCCESS)
612		goto out;
613	if (req->name == NULL)
614		goto out;
615
616	isc_buffer_init(&namebuf, req->name, req->namelen);
617	isc_buffer_add(&namebuf, req->namelen);
618
619	dns_fixedname_init(&client->target_name);
620	dns_fixedname_init(&client->query_name);
621	result = dns_name_fromtext(dns_fixedname_name(&client->query_name),
622				   &namebuf, NULL, 0, NULL);
623	if (result != ISC_R_SUCCESS)
624		goto out;
625	ns_lwsearchctx_init(&client->searchctx,
626			    cm->listener->manager->search,
627			    dns_fixedname_name(&client->query_name),
628			    cm->listener->manager->ndots);
629	ns_lwsearchctx_first(&client->searchctx);
630
631	client->find_wanted = req->addrtypes;
632	ns_lwdclient_log(50, "client %p looking for addrtypes %08x",
633			 client, client->find_wanted);
634
635	/*
636	 * We no longer need to keep this around.
637	 */
638	lwres_gabnrequest_free(client->clientmgr->lwctx, &req);
639
640	/*
641	 * Start the find.
642	 */
643	result = start_find(client);
644	if (result != ISC_R_SUCCESS)
645		goto out;
646
647	return;
648
649	/*
650	 * We're screwed.  Return an error packet to our caller.
651	 */
652 out:
653	if (req != NULL)
654		lwres_gabnrequest_free(client->clientmgr->lwctx, &req);
655
656	ns_lwdclient_errorpktsend(client, LWRES_R_FAILURE);
657}
658