/*	$NetBSD: ns_sort.c,v 1.2.2.1 1999/12/04 16:56:02 he Exp $	*/

#if !defined(lint) && !defined(SABER)
static const char sccsid[] = "@(#)ns_sort.c	4.10 (Berkeley) 3/3/91";
static const char rcsid[] = "Id: ns_sort.c,v 8.5 1999/10/13 16:39:12 vixie Exp";
#endif /* not lint */

/*
 * Copyright (c) 1986, 1990
 *    The Regents of the University of California.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the University of
 * 	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies, and that
 * the name of Digital Equipment Corporation not be used in advertising or
 * publicity pertaining to distribution of the document or software without
 * specific, written prior permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

/*
 * Portions Copyright (c) 1996-1999 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

/*
 * Sorting should really be handled by the resolver, but:
 *  1) There are too many brain dead resolvers out there that can't be replaced.
 *  2) It would be a pain to individually configure all those resolvers anyway.
 *
 * Here's the scoop:
 *
 * To enable address sorting in responses, you need to supply the sortlist
 * statement in the config file.  The sortlist statement takes an
 * address match list and interprets it even more specially than the
 * topology statement does.
 *
 * Each top level statement in the sortlist must itself be an explicit
 * address match list with one or two elements.  The first element
 * (which may be an IP address, an IP prefix, an ACL name or nested
 * address match list) of each top level list is checked against the
 * source address of the query until a match is found.
 *
 * Once the source address of the query has been matched, if the top level
 * statement contains only one element, the actual primitive element that
 * matched the source address is used to select the address in the response
 * to move to the beginning of the response.  If the statement is a list
 * of two elements, then the second element is treated like the address
 * match list in a topology statement.  Each top level element is assigned
 * a distance and the address in the response with the minimum distance is
 * moved to the beginning of the response.
 * 
 * In the following example, any queries received from any of the addresses
 * of the host itself will get responses preferring addresses on any of
 * the locally connected networks.  Next most preferred are addresses on
 * the 192.168.1/24 network, and after that either the 192.168.2/24 or
 * 192.168.3/24 network with no preference shown between these two networks.
 * Queries received from a host on the 192.168.1/24 network will prefer
 * other addresses on that network to the 192.168.2/24 and 192.168.3/24
 * networks.  Queries received from a host on the 192.168.4/24 or the
 * 192.168.5/24 network will only prefer other addresses on their
 * directly connected networks.
 *
 * sortlist {
 *            {
 *              localhost;
 *              {
 *                localnets;
 *                192.168.1/24;
 *                { 192,168.2/24; 192.168.3/24; };
 *              };
 *            };
 *            {
 *              192.168.1/24;
 *              {
 *                192.168.1/24;
 *                { 192.168.2/24; 192.168.3/24; };
 *              };
 *            };
 *            {
 *              192.168.2/24;
 *              {
 *                192.168.2/24;
 *                { 192.168.1/24; 192.168.3/24; };
 *              };
 *            };
 *            {
 *              192.168.3/24;
 *              {
 *                192.168.3/24;
 *                { 192.168.1/24; 192.168.2/24; };
 *              };
 *            };
 *	      {
 *              { 192.168.4/24; 192.168.5/24; };
 *            };
 * };
 *
 * 
 * The following example will give reasonable behaviour for the local host
 * and hosts on directly connected networks.  It is similar to the behavior
 * of the address sort in BIND 4.9.x.  Responses sent to queries from the
 * local host will favor any of the directly connected networks.  Responses
 * sent to queries from any other hosts on a directly connected network will
 * prefer addresses on that same network.  Responses to other queries will
 * not be sorted.
 *
 * sortlist {
 *            { localhost; localnets; };
 *            { localnets; };
 * };
 *
 * XXX - it wouldb e nice to have an ACL called "source" that matched the
 *       source address of a query so that a host could be configured to
 *       automatically prefer itself, and an ACL called "sourcenet", that
 *       would return the primitive IP match element that matched the source
 *       address so that you could do:
 *          { localnets; { sourcenet; { other stuff ...}; };
 *       and automatically get similar behaviour to what you get with:
 *          { localnets; };
 *
 */

#include "port_before.h"

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <syslog.h>
#include <resolv.h>
#include <string.h>

#include <isc/eventlib.h>
#include <isc/logging.h>

#include "port_after.h"

#include "named.h"

static int sort_rr(u_char *cp, u_char *eom, int ancount, ip_match_list iml);

static int ip_match_address_elt(ip_match_list, struct in_addr,
				ip_match_element *);

void
sort_response(u_char *cp, u_char *eom, int ancount, struct sockaddr_in *from) {
	struct in_addr address;
	struct ip_match_element imelement;
	ip_match_element imetl, imematch, imeprimitive;
	struct ip_match_list imlist;
	ip_match_list iml;
	int indirect, matched;

	if (server_options->sortlist == NULL)
		return;

	if (from->sin_family != AF_INET)
		return;

	address = from->sin_addr;

	for (imetl = server_options->sortlist->first; imetl != NULL;
	     imetl = imetl->next) {
		if (imetl->type == ip_match_indirect)
			imematch = imetl->u.indirect.list->first;
		else
			/*
			 * allow a bare pattern as a top level statement
			 * and treat it like {pattern;};
			 */
			imematch = imetl;

		switch (imematch->type) {
		case ip_match_pattern:
			indirect = 0;
			break;
		case ip_match_indirect:
			indirect = 1;
			break;
		case ip_match_localhost:
			imematch->u.indirect.list = local_addresses;
			indirect = 1;
			break;
		case ip_match_localnets:
			imematch->u.indirect.list = local_networks;
			indirect = 1;
			break;
		default:
			panic("unexpected ime type in ip_match_address()",
			      NULL);
		}
		if (indirect) {
			imeprimitive = NULL;
			matched = ip_match_address_elt(imematch->u.indirect.list,
						       address, &imeprimitive);
			if (matched >= 0) {
				if (imematch->flags & IP_MATCH_NEGATE)
					/* Don't sort */
					return;
			} else 
				continue;
		} else {
			if (ina_onnet(address, imematch->u.direct.address,
				      imematch->u.direct.mask)) {
				if (imematch->flags & IP_MATCH_NEGATE)
					/* Don't sort */
					return;
				else
					imeprimitive = imematch;
			} else
				continue;
		}
		if (imetl != imematch && imematch->next != NULL) {
			/*
			 * Not a bare pattern at the top level, but a two
			 * element list
			 */
			switch (imematch->next->type) {
			case ip_match_pattern:
			case ip_match_localhost:
			case ip_match_localnets:
				imelement = *(imematch->next);
				imelement.next = NULL;
				iml = &imlist;
				iml->first = iml->last = &imelement;
				break;
			case ip_match_indirect:
				iml = imematch->next->u.indirect.list;
				break;
			default:
				panic("unexpected ime type in ip_match_address()",
				      NULL);
			}
		} else if (imeprimitive) {
			imelement = *imeprimitive;
			imelement.next = NULL;
			iml = &imlist;
			iml->first = iml->last = &imelement;
		} else {
			/* Don't sort because we'd just use "any" */
			return;
		}
		sort_rr(cp, eom, ancount, iml);
		break;
	}

	return;
}

static int
sort_rr(u_char *cp, u_char *eom, int ancount, ip_match_list iml) {
	int type, class, dlen, n, c, distance, closest;
	struct in_addr inaddr;
	u_char *rr1 = NULL, *rrbest, *cpstart;

	rr1 = NULL;
	cpstart = cp;
	for (c = ancount; c > 0; --c) {
	    n = dn_skipname(cp, eom);
	    if (n < 0)
		return (1);		/* bogus, stop processing */
	    cp += n;
	    if (cp + QFIXEDSZ > eom)
		return (1);
	    GETSHORT(type, cp);
	    GETSHORT(class, cp);
	    cp += INT32SZ;
	    GETSHORT(dlen, cp);
	    if (dlen > eom - cp)
		return (1);		/* bogus, stop processing */
	    switch (type) {
	    case T_A:
	    	switch (class) {
	    	case C_IN:
	    	case C_HS:
	    		memcpy((char *)&inaddr, cp, INADDRSZ);
			/* Find the address with the minimum distance */
			if (rr1 == NULL) {
				rr1 = cp;
				rrbest = cp;
				closest = distance_of_address(iml, inaddr);
			} else {
				distance = distance_of_address(iml, inaddr);
				if (distance < closest) {
					rrbest = cp;
					closest = distance;
				}
			}
	    		break;
	    	}
	    	break;
	    }
	    cp += dlen;
	}
	if (rr1 != rrbest && rr1 != NULL) {
	    	memcpy((char *)&inaddr, rrbest, INADDRSZ);
		memcpy(rrbest, rr1, INADDRSZ);
	    	memcpy(rr1, (char *)&inaddr, INADDRSZ);
	}
	return (0);
}

/*
 * Just like ip_match_address(), but also returns a pointer to the primitive
 * element that matched.
 */

static int
ip_match_address_elt(ip_match_list iml, struct in_addr address,
		     ip_match_element *imep) {
	ip_match_element ime;
	int ret;
	int indirect;

	INSIST(iml != NULL);
	for (ime = iml->first; ime != NULL; ime = ime->next) {
		switch (ime->type) {
		case ip_match_pattern:
			indirect = 0;
			break;
		case ip_match_indirect:
			indirect = 1;
			break;
		case ip_match_localhost:
			ime->u.indirect.list = local_addresses;
			indirect = 1;
			break;
		case ip_match_localnets:
			ime->u.indirect.list = local_networks;
			indirect = 1;
			break;
		default:
			panic("unexpected ime type in ip_match_address()",
			      NULL);
		}
		if (indirect) {
			ret = ip_match_address_elt(ime->u.indirect.list,
						   address, imep);
			if (ret >= 0) {
				if (ime->flags & IP_MATCH_NEGATE)
					ret = (ret) ? 0 : 1;
				return (ret);
			}
		} else {
			if (ina_onnet(address, ime->u.direct.address,
				      ime->u.direct.mask)) {
				*imep = ime;
				if (ime->flags & IP_MATCH_NEGATE)
					return (0);
				else
					return (1);
			}
		}
	}
	return (-1);
}
