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

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

/**
 * trace down from the root to name
 */

/* same naive method as in drill0.9 
 * We resolver _ALL_ the names, which is ofcourse not needed
 * We _do_ use the local resolver to do that, so it still is
 * fast, but it can be made to run much faster
 */
ldns_pkt *
do_trace(ldns_resolver *local_res, ldns_rdf *name, ldns_rr_type t,
		ldns_rr_class c)
{
	ldns_resolver *res;
	ldns_pkt *p;
	ldns_rr_list *new_nss_a;
	ldns_rr_list *new_nss_aaaa;
	ldns_rr_list *final_answer;
	ldns_rr_list *new_nss;
	ldns_rr_list *hostnames;
	ldns_rr_list *ns_addr;
	uint16_t loop_count;
	ldns_rdf *pop; 
	ldns_status status;
	size_t i;
	
	loop_count = 0;
	new_nss_a = NULL;
	new_nss_aaaa = NULL;
	new_nss = NULL;
	ns_addr = NULL;
	final_answer = NULL;
	p = ldns_pkt_new();
	res = ldns_resolver_new();

	if (!p || !res) {
                error("Memory allocation failed");
                return NULL;
        }

	/* transfer some properties of local_res to res,
	 * because they were given on the commandline */
	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_dnssec(res, 
			ldns_resolver_dnssec(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(res, false);

	/* setup the root nameserver in the new resolver */
	if (ldns_resolver_push_nameserver_rr_list(res, global_dns_root) != LDNS_STATUS_OK) {
		return NULL;
	}

	/* this must be a real query to local_res */
	status = ldns_resolver_send(&p, local_res, ldns_dname_new_frm_str("."), LDNS_RR_TYPE_NS, c, 0);
	/* p can still be NULL */


	if (ldns_pkt_empty(p)) {
		warning("No root server information received");
	} 
	
	if (status == LDNS_STATUS_OK) {
		if (!ldns_pkt_empty(p)) {
			drill_pkt_print(stdout, local_res, p);
		}
	} else {
		error("cannot use local resolver");
		return NULL;
	}

	status = ldns_resolver_send(&p, res, name, t, c, 0);

	while(status == LDNS_STATUS_OK && 
	      ldns_pkt_reply_type(p) == LDNS_PACKET_REFERRAL) {

		if (!p) {
			/* some error occurred, bail out */
			return NULL;
		}

		new_nss_a = ldns_pkt_rr_list_by_type(p,
				LDNS_RR_TYPE_A, LDNS_SECTION_ADDITIONAL);
		new_nss_aaaa = ldns_pkt_rr_list_by_type(p,
				LDNS_RR_TYPE_AAAA, LDNS_SECTION_ADDITIONAL);
		new_nss = ldns_pkt_rr_list_by_type(p,
				LDNS_RR_TYPE_NS, LDNS_SECTION_AUTHORITY);

		if (qdebug != -1) {
			ldns_rr_list_print(stdout, new_nss);
		}
		/* checks itself for qdebug */
		drill_pkt_print_footer(stdout, local_res, p);
		
		/* remove the old nameserver from the resolver */
		while((pop = ldns_resolver_pop_nameserver(res))) { /* do it */ }

		/* also check for new_nss emptyness */

		if (!new_nss_aaaa && !new_nss_a) {
			/* 
			 * no nameserver found!!! 
			 * try to resolve the names we do got 
			 */
			for(i = 0; i < ldns_rr_list_rr_count(new_nss); i++) {
				/* get the name of the nameserver */
				pop = ldns_rr_rdf(ldns_rr_list_rr(new_nss, i), 0);
				if (!pop) {
					break;
				}

				ldns_rr_list_print(stdout, new_nss);
				ldns_rdf_print(stdout, pop);
				/* 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(p); 
					return NULL;
				}
				ldns_rr_list_free(ns_addr);
			} else {
				ldns_rr_list_print(stdout, ns_addr);
				error("Could not find the nameserver ip addr; abort");
				ldns_pkt_free(p);
				return NULL;
			}
		}

		/* add the new ones */
		if (new_nss_aaaa) {
			if (ldns_resolver_push_nameserver_rr_list(res, new_nss_aaaa) != 
					LDNS_STATUS_OK) {
				error("adding new nameservers");
				ldns_pkt_free(p); 
				return NULL;
			}
		}
		if (new_nss_a) {
			if (ldns_resolver_push_nameserver_rr_list(res, new_nss_a) != 
					LDNS_STATUS_OK) {
				error("adding new nameservers");
				ldns_pkt_free(p); 
				return NULL;
			}
		}

		if (loop_count++ > 20) {
			/* unlikely that we are doing something usefull */
			error("Looks like we are looping");
			ldns_pkt_free(p); 
			return NULL;
		}
		
		status = ldns_resolver_send(&p, res, name, t, c, 0);
		new_nss_aaaa = NULL;
		new_nss_a = NULL;
		ns_addr = NULL;
	}

	status = ldns_resolver_send(&p, res, name, t, c, 0);

	if (!p) {
		return NULL;
	}

	hostnames = ldns_get_rr_list_name_by_addr(local_res, 
			ldns_pkt_answerfrom(p), 0, 0);

	new_nss = ldns_pkt_authority(p);
	final_answer = ldns_pkt_answer(p);

	if (qdebug != -1) {
		ldns_rr_list_print(stdout, final_answer);
		ldns_rr_list_print(stdout, new_nss);

	}
	drill_pkt_print_footer(stdout, local_res, p);
	ldns_pkt_free(p); 
	return NULL;
}


/**
 * Chase the given rr to a known and trusted key
 *
 * Based on drill 0.9
 *
 * the last argument prev_key_list, if not null, and type == DS, then the ds
 * rr list we have must all be a ds for the keys in this list
 */
ldns_status
do_chase(ldns_resolver *res, ldns_rdf *name, ldns_rr_type type, ldns_rr_class c,
		ldns_rr_list *trusted_keys, ldns_pkt *pkt_o, uint16_t qflags, ldns_rr_list *prev_key_list)
{
	ldns_rr_list *rrset = NULL;
	ldns_status result;
	
	ldns_rr_list *sigs;
	ldns_rr *cur_sig;
	uint16_t sig_i;
	ldns_rr_list *keys;
	ldns_rr_list *nsecs;
	uint16_t nsec_i;
	uint16_t key_i;
	uint16_t tkey_i;
	ldns_pkt *pkt;
	size_t i,j;
/*	ldns_rr_list *tmp_list;*/
	bool key_matches_ds;
	
	ldns_lookup_table *lt;
	const ldns_rr_descriptor *descriptor;
	
	pkt = ldns_pkt_clone(pkt_o);
	if (!name) {
		mesg("No name to chase");
		ldns_pkt_free(pkt);
		return LDNS_STATUS_EMPTY_LABEL;
	}
	
	if (!trusted_keys || ldns_rr_list_rr_count(trusted_keys) < 1) {
		warning("No trusted keys specified");
	}
	
	if (pkt) {
		rrset = ldns_pkt_rr_list_by_name_and_type(pkt,
				name,
				type,
				LDNS_SECTION_ANSWER
				);
		if (!rrset) {
			/* nothing in answer, try authority */
			rrset = ldns_pkt_rr_list_by_name_and_type(pkt,
					name,
					type,
					LDNS_SECTION_AUTHORITY
					);
		}
	} else {
		/* no packet? */
		return LDNS_STATUS_MEM_ERR;
	}
	
	if (!rrset) {
		/* not found in original packet, try again */
		ldns_pkt_free(pkt);
		pkt = NULL;
		pkt = ldns_resolver_query(res, name, type, c, qflags);
		
		if (!pkt) {
			return LDNS_STATUS_NETWORK_ERR;
		}
		rrset =	ldns_pkt_rr_list_by_name_and_type(pkt,
				name,
				type,
				LDNS_SECTION_ANSWER
				);
	}

	sigs = ldns_pkt_rr_list_by_name_and_type(pkt,
			name,
			LDNS_RR_TYPE_RRSIG,
			LDNS_SECTION_ANY_NOQUESTION
			);
	
	if (rrset) {
		for (sig_i = 0; sig_i < ldns_rr_list_rr_count(sigs); sig_i++) {
			cur_sig = ldns_rr_clone(ldns_rr_list_rr(sigs, sig_i));
			
			keys = ldns_pkt_rr_list_by_name_and_type(pkt,
					ldns_rr_rdf(cur_sig, 7),
					LDNS_RR_TYPE_DNSKEY,
					LDNS_SECTION_ANY_NOQUESTION
					);
			
			if (qdebug != -1) {
				printf(";; Data set: ");
				ldns_rdf_print(stdout, name);

				lt = ldns_lookup_by_id(ldns_rr_classes, c);
				if (lt) {
					printf("\t%s\t", lt->name);
				} else {
					printf("\tCLASS%d\t", c);
				}

				descriptor = ldns_rr_descript(type);

				if (descriptor->_name) {
					printf("%s\t", descriptor->_name);
				} else {
					/* exceptions for qtype */
					if (type == 251) {
						printf("IXFR ");
					} else if (type == 252) {
						printf("AXFR ");
					} else if (type == 253) {
						printf("MAILB ");
					} else if (type == 254) {
						printf("MAILA ");
					} else if (type == 255) {
						printf("ANY ");
					} else {
						printf("TYPE%d\t", type);
					}
				}
				
				printf("\n");
				printf(";; Signed by: ");
				ldns_rdf_print(stdout, ldns_rr_rdf(cur_sig, 7));
				printf("\n");
				if (type == LDNS_RR_TYPE_DS && prev_key_list) {
					for (j = 0; j < ldns_rr_list_rr_count(rrset); j++) {
						key_matches_ds = false;
						for (i = 0; i < ldns_rr_list_rr_count(prev_key_list); i++) {
							if (ldns_rr_compare_ds(ldns_rr_list_rr(prev_key_list, i),
									       ldns_rr_list_rr(rrset, j))) {
								key_matches_ds = true;
							}
						}
						if (!key_matches_ds) {
							/* For now error */
							fprintf(stderr, ";; error no DS for key\n");
							return LDNS_STATUS_ERR;
						}
					}
				}
			}

			if (!keys) {
				ldns_pkt_free(pkt);
				pkt = NULL;
				pkt = ldns_resolver_query(res,
						ldns_rr_rdf(cur_sig, 7),
						LDNS_RR_TYPE_DNSKEY, c, qflags);
				if (!pkt) {
					ldns_rr_list_deep_free(rrset);
					ldns_rr_list_deep_free(sigs);
					return LDNS_STATUS_NETWORK_ERR;
				}

				keys = ldns_pkt_rr_list_by_name_and_type(pkt,
						ldns_rr_rdf(cur_sig, 7),
						LDNS_RR_TYPE_DNSKEY,
						LDNS_SECTION_ANY_NOQUESTION
						);
			}
			if(!keys) {
				mesg("No key for data found in that zone!");
				ldns_rr_list_deep_free(rrset);
				ldns_rr_list_deep_free(sigs);
				ldns_pkt_free(pkt);
				ldns_rr_free(cur_sig);
				return LDNS_STATUS_CRYPTO_NO_DNSKEY;
			} else {
				result = LDNS_STATUS_ERR;
				for (key_i = 0; key_i < ldns_rr_list_rr_count(keys); key_i++) {
					/* only check matching keys */

					if (ldns_calc_keytag(ldns_rr_list_rr(keys, key_i))
					    ==
					    ldns_rdf2native_int16(ldns_rr_rrsig_keytag(cur_sig))
					   ) {
						result = ldns_verify_rrsig(rrset, cur_sig, ldns_rr_list_rr(keys, key_i));
						if (result == LDNS_STATUS_OK) {
							for (tkey_i = 0; tkey_i < ldns_rr_list_rr_count(trusted_keys); tkey_i++) {
								if (ldns_rr_compare_ds(ldns_rr_list_rr(keys, key_i),
										   ldns_rr_list_rr(trusted_keys, tkey_i)
										  )) {
									mesg("Key is trusted");
									ldns_rr_list_deep_free(rrset);
									ldns_rr_list_deep_free(sigs);
									ldns_rr_list_deep_free(keys);
									ldns_pkt_free(pkt);
									ldns_rr_free(cur_sig);
									return LDNS_STATUS_OK;
								}
							}
							result = do_chase(res, ldns_rr_rdf(cur_sig, 7), LDNS_RR_TYPE_DS, c, trusted_keys, pkt, qflags, keys);
							ldns_rr_list_deep_free(rrset);
							ldns_rr_list_deep_free(sigs);
							ldns_rr_list_deep_free(keys);
							ldns_pkt_free(pkt);
							ldns_rr_free(cur_sig);
							return result;
						}
					}
				}
				if (result != LDNS_STATUS_OK) {
					ldns_rr_list_deep_free(rrset);
					ldns_rr_list_deep_free(sigs);
					ldns_rr_list_deep_free(keys);
					ldns_pkt_free(pkt);
					ldns_rr_free(cur_sig);
					return result;
				}
				ldns_rr_list_deep_free(keys);
			}
			ldns_rr_free(cur_sig);
		}
		ldns_rr_list_deep_free(rrset);
	}
	if (rrset && ldns_rr_list_rr_count(sigs) > 0) {
		ldns_rr_list_deep_free(sigs);
		ldns_pkt_free(pkt);
		return LDNS_STATUS_CRYPTO_NO_TRUSTED_DNSKEY;
	} else {
		/* Try to see if there are NSECS in the packet */
		nsecs = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_NSEC, LDNS_SECTION_ANY);
		result = LDNS_STATUS_CRYPTO_NO_RRSIG;
		
		for (nsec_i = 0; nsec_i < ldns_rr_list_rr_count(nsecs); nsec_i++) {
			/* there are four options:
			 * - name equals ownername and is covered by the type bitmap
			 * - name equals ownername but is not covered by the type bitmap
			 * - name falls within nsec coverage but is not equal to the owner name
			 * - name falls outside of nsec coverage
			 */
			if (ldns_dname_compare(ldns_rr_owner(ldns_rr_list_rr(nsecs, nsec_i)), name) == 0) {
				if (ldns_nsec_bitmap_covers_type(ldns_rr_rdf(ldns_rr_list_rr(nsecs, nsec_i), 1), type)) {
					/* Error, according to the nsec this rrset is signed */
					result = LDNS_STATUS_CRYPTO_NO_RRSIG;
				} else {
					/* ok nsec denies existence, chase the nsec now */
					printf(";; Existence of data set with this type denied by NSEC\n");
					result = do_chase(res, ldns_rr_owner(ldns_rr_list_rr(nsecs, nsec_i)), LDNS_RR_TYPE_NSEC, c, trusted_keys, pkt, qflags, NULL);
					if (result == LDNS_STATUS_OK) {
						ldns_pkt_free(pkt);
						printf(";; Verifiably insecure.\n");
						ldns_rr_list_deep_free(nsecs);
						return result;
					}
				}
			} else if (ldns_nsec_covers_name(ldns_rr_list_rr(nsecs, nsec_i), name)) {
				/* Verifably insecure? chase the covering nsec */
				printf(";; Existence of data set with this name denied by NSEC\n");
				result = do_chase(res, ldns_rr_owner(ldns_rr_list_rr(nsecs, nsec_i)), LDNS_RR_TYPE_NSEC, c, trusted_keys, pkt, qflags, NULL);
				if (result == LDNS_STATUS_OK) {
					ldns_pkt_free(pkt);
					printf(";; Verifiably insecure.\n");
					ldns_rr_list_deep_free(nsecs);
					return result;
				}
			} else {
				/* nsec has nothing to do with this data */
			}
		}
		ldns_pkt_free(pkt);
		ldns_rr_list_deep_free(nsecs);
		return result;
	}
}



syntax highlighted by Code2HTML, v. 0.9.1