/*
 * securechasetrace.c
 * Where all the hard work concerning secure tracing is done
 *
 * (c) 2005, 2006 NLnet Labs
 *
 * See the file LICENSE for the license
 *
 */

#include "drill.h"
#include <ldns/ldns.h>

#define SELF "[S]"  /* self sig ok */
#define TRUST "[T]" /* chain from parent */
#define BOGUS "[B]" /* bogus */

#if 0
/* See if there is a key/ds in trusted that matches
 * a ds in *ds. 
 */
static ldns_rr_list *
ds_key_match(ldns_rr_list *ds, ldns_rr_list *trusted)
{
	size_t i, j;
	bool match;
	ldns_rr *rr_i, *rr_j;
	ldns_rr_list *keys;

	if (!trusted || !ds) {
		return NULL;
	}

	match = false;
	keys = ldns_rr_list_new();
	if (!keys) {
		return NULL;
	}

	if (!ds || !trusted) {
		return NULL;
	}

	for (i = 0; i < ldns_rr_list_rr_count(trusted); i++) {
		rr_i = ldns_rr_list_rr(trusted, i);
		for (j = 0; j < ldns_rr_list_rr_count(ds); j++) {

			rr_j = ldns_rr_list_rr(ds, j);
			if (ldns_rr_compare_ds(rr_i, rr_j)) {
				match = true;
				/* only allow unique RRs to match */
				ldns_rr_set_push_rr(keys, rr_i); 
			}
		}
	}
	if (match) {
		return keys;
	} else {
		return NULL;
	}
}
#endif

ldns_pkt *
get_dnssec_pkt(ldns_resolver *r, ldns_rdf *name, ldns_rr_type t) 
{
	ldns_pkt *p = NULL;
	p = ldns_resolver_query(r, name, t, LDNS_RR_CLASS_IN, 0); 
	if (!p) {
		return NULL;
	} else {
		return p;
	}
}

/*
 * generic function to get some RRset from a nameserver
 * and possible some signatures too (that would be the day...)
 */
static ldns_pkt_type
get_dnssec_rr(ldns_pkt *p, ldns_rdf *name, ldns_rr_type t, 
	ldns_rr_list **rrlist, ldns_rr_list **sig)
{
	ldns_pkt_type pt = LDNS_PACKET_UNKNOWN;
	ldns_rr_list *rr = NULL;
	ldns_rr_list *sigs = NULL;

	if (!p) {
		return LDNS_PACKET_UNKNOWN;
	}

	pt = ldns_pkt_reply_type(p);
	if (pt == LDNS_PACKET_NXDOMAIN || pt == LDNS_PACKET_NODATA) {
		return pt;
	}
		
	if (name) {
		rr = ldns_pkt_rr_list_by_name_and_type(p, name, t, LDNS_SECTION_ANSWER);
		sigs = ldns_pkt_rr_list_by_name_and_type(p, name, LDNS_RR_TYPE_RRSIG, 
				LDNS_SECTION_ANSWER);
	} else {
               /* A DS-referral - get the DS records if they are there */
               rr = ldns_pkt_rr_list_by_type(p, t, LDNS_SECTION_AUTHORITY);
               sigs = ldns_pkt_rr_list_by_type(p, LDNS_RR_TYPE_RRSIG,
                               LDNS_SECTION_AUTHORITY);
	}
	if (sig) {
		ldns_rr_list_cat(*sig, sigs);
	}
	if (rrlist) {
		*rrlist = rr;
	}
	return LDNS_PACKET_ANSWER;
}

/* 
 * retrieve keys for this zone
 */
static ldns_pkt_type
get_key(ldns_pkt *p, ldns_rdf *apexname, ldns_rr_list **rrlist, ldns_rr_list **opt_sig)
{
	return get_dnssec_rr(p, apexname, LDNS_RR_TYPE_DNSKEY, rrlist, opt_sig);
}

/*
 * check to see if we can find a DS rrset here which we can then follow
 */
static ldns_pkt_type
get_ds(ldns_pkt *p, ldns_rdf *ownername, ldns_rr_list **rrlist, ldns_rr_list **opt_sig)
{
	return get_dnssec_rr(p, ownername, LDNS_RR_TYPE_DS, rrlist, opt_sig);
}

ldns_pkt *
do_secure_trace(ldns_resolver *local_res, ldns_rdf *name, ldns_rr_type t,
		ldns_rr_class c, ldns_rr_list *trusted_keys)
{
	ldns_resolver *res;
	ldns_pkt *p, *local_p;
	ldns_rr_list *new_nss_a;
	ldns_rr_list *new_nss_aaaa;
	ldns_rr_list *new_nss;
	ldns_rr_list *ns_addr;
	uint16_t loop_count;
	ldns_rdf *pop; 
	ldns_rdf **labels;
	ldns_status status, st;
	ssize_t i;
	size_t j;
	uint8_t labels_count;
	ldns_pkt_type pt;

	/* dnssec */
	ldns_rr_list *key_list;
	ldns_rr_list *key_sig_list;
	ldns_rr_list *ds_list;
	ldns_rr_list *ds_sig_list;

	loop_count = 0;
	new_nss_a = NULL;
	new_nss_aaaa = NULL;
	new_nss = NULL;
	ns_addr = NULL;
	key_list = NULL;
	ds_list = NULL;
	pt = LDNS_PACKET_UNKNOWN;

	p = ldns_pkt_new();
	local_p = ldns_pkt_new();
	res = ldns_resolver_new();
	key_sig_list = ldns_rr_list_new();
	ds_sig_list = ldns_rr_list_new();

	if (!p || !local_p || !res) {
		error("Memory allocation failed");
		return NULL;
	}
	
	/* transfer some properties of local_res to res */
	ldns_resolver_set_ip6(res, 
			ldns_resolver_ip6(local_res));
	ldns_resolver_set_port(res, 
			ldns_resolver_port(local_res));
	ldns_resolver_set_debug(res, 
			ldns_resolver_debug(local_res));
	ldns_resolver_set_fail(res, 
			ldns_resolver_fail(local_res));
	ldns_resolver_set_usevc(res, 
			ldns_resolver_usevc(local_res));
	ldns_resolver_set_random(res, 
			ldns_resolver_random(local_res));
	ldns_resolver_set_recursive(local_res, true);

	ldns_resolver_set_recursive(res, false);
	ldns_resolver_set_dnssec_cd(res, false);
	ldns_resolver_set_dnssec(res, true);

	labels_count = ldns_dname_label_count(name);
	labels = LDNS_XMALLOC(ldns_rdf*, labels_count + 2);
	if (!labels) {
		return NULL;
	}
	labels[0] = ldns_dname_new_frm_str(LDNS_ROOT_LABEL_STR);
	labels[1] = name;
	for(i = 2 ; i < (ssize_t)labels_count + 2; i++) {
		labels[i] = ldns_dname_left_chop(labels[i - 1]);
	}

	/* get the nameserver for the label
	 * ask: dnskey and ds for the label 
	 */
	for(i = (ssize_t)labels_count + 1; i > 0; i--) {
		/* this tries to get the nameserver for the node we
		 * currently have. This fails sometimes, because of 
		 * caching, or the failure to cache. A better way would
		 * be to do a trace from the root to the nameserver (a non
		 * DNSSEC trace). After that you can just query for
		 * the DNSKEY and DS and perform the validation magic
		 */
		status = ldns_resolver_send(&local_p, local_res, labels[i], LDNS_RR_TYPE_NS, c, 0);
		new_nss = ldns_pkt_rr_list_by_type(local_p,
					LDNS_RR_TYPE_NS, LDNS_SECTION_ANSWER);
 		if (!new_nss) {
			/* lame ass servers put them in the auth section */
			new_nss = ldns_pkt_rr_list_by_type(local_p,
					LDNS_RR_TYPE_NS, LDNS_SECTION_AUTHORITY);
		} else {
			ldns_rr_list_print(stdout, new_nss);
		}

		for(j = 0; j < ldns_rr_list_rr_count(new_nss); j++) {
			pop = ldns_rr_rdf(ldns_rr_list_rr(new_nss, j), 0);
			if (!pop) {
				break;
			}
			/* retrieve it's addresses */
			ns_addr = ldns_rr_list_cat_clone(ns_addr,
				ldns_get_rr_list_addr_by_name(local_res, pop, c, 0));
		}

		if (ns_addr) {
			if (ldns_resolver_push_nameserver_rr_list(res, ns_addr) != 
					LDNS_STATUS_OK) {
				error("Error adding new nameservers");
				ldns_pkt_free(local_p); 
				return NULL;
			}
		} else {
			error("Could not find the nameserver ip addr; abort");
			ldns_pkt_free(local_p);
			return NULL;
		}
		
		if (ldns_resolver_nameserver_count(res) == 0) {
			error("No nameservers found for this node");
			return NULL;
		}

		p = get_dnssec_pkt(res, labels[i], LDNS_RR_TYPE_DNSKEY);
		pt = get_key(p, labels[i], &key_list, &key_sig_list);
		if (key_sig_list) {
			if (key_list) {
				if ((st = ldns_verify(key_list, key_sig_list, key_list, NULL)) ==
						LDNS_STATUS_OK) {
					print_rr_list_abbr(stdout, key_list, SELF);

					ldns_rr_list_push_rr_list(trusted_keys, key_list);
				} else {
					print_rr_list_abbr(stdout, key_list, BOGUS);
				}
			} else {
				mesg("No DNSKEY");
			}
		}

		p = get_dnssec_pkt(res, labels[i], LDNS_RR_TYPE_DS);
		pt = get_ds(p, labels[i], &ds_list, &ds_sig_list);
		if (!ds_list) {
			/* we might get lucky and get a DS referral wehn
			 * asking for the key of the query name */
			p = get_dnssec_pkt(res, name, LDNS_RR_TYPE_DNSKEY);
			pt = get_ds(p, NULL, &ds_list, &ds_sig_list); 
		}
		if (ds_sig_list) {
			if (ds_list) {
				if ((st = ldns_verify(ds_list, ds_sig_list, trusted_keys, NULL)) ==
						LDNS_STATUS_OK) {
					print_rr_list_abbr(stdout, ds_list, SELF);
				} else {
					print_rr_list_abbr(stdout, ds_list, BOGUS);
				}
			} else {
				mesg("No DS");
			}
		}
		ds_list = NULL;
		new_nss_aaaa = NULL;
		new_nss_a = NULL;
		new_nss = NULL;
		ns_addr = NULL;
		key_list = NULL;
		while((pop = ldns_resolver_pop_nameserver(res))) { /* remove it */ }
		puts("");
	}
	printf(";;" SELF " self sig OK; " BOGUS " bogus; " TRUST " trusted\n");
	return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1