/*
 * DNS KEY lookup helper - command implementation
 * Copyright (C) 2002 Michael Richardson <mcr@freeswan.org>
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
#include <unistd.h> 

#include <freeswan.h>

#include <errno.h>
#include <arpa/nameser.h>
#include <lwres/netdb.h>
#include <time.h>

#include <isc/lang.h>
#include <isc/magic.h>
#include <isc/types.h>
#include <isc/result.h>
#include <isc/mem.h>
#include <isc/buffer.h>
#include <isc/region.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdatastruct.h>


#include "lwdnsq.h"

void output_transaction_line(dnskey_glob *gs,
			    char *id,
			    int ttl,
			    char *cmd,
			    char *data)
{
	time_t t;

	t=time(NULL);
	
	/* regularlize time for regression testing */
	if(gs->regress) {
		t=3145915;
	}

	if(data) {
		fprintf(gs->cmdproto_out,
			"%s %ld %d %s %s\n",
			id, t, ttl, cmd, data);
	} else {
		fprintf(gs->cmdproto_out,
			"%s %ld %d %s\n",
			id, t, ttl, cmd);
	}
		
}
	
void output_transaction_line_limited(dnskey_glob *gs,
				   char *id,
				   int ttl,
				   char *cmd,
				   int   max,
				   char *data)
{
	time_t t;

	t=time(NULL);
	
	/* regularlize time for regression testing */
	if(gs->regress) {
		t=3145915;
	}

	fprintf(gs->cmdproto_out,
			"%s %ld %d %s %.*s\n",
			id, t, ttl, cmd, max, data);
}
	
			    

int follow_possible_cname(dnskey_glob *gs,
			  isc_mem_t   *iscmem,
			  char        *id,
			  char        *fqdn,
			  dns_rdatatype_t wantedtype,
			  struct rrsetinfo **pres)
{
	char simplebuf[80];
	int success;
	struct rrsetinfo *res;
	struct rrsetinfo *res_cname;
	dns_name_t fqdn_name;
	isc_buffer_t *cname_text;
	char cname_buf[DNS_NAME_MAXTEXT];
	char cname_buf2[DNS_NAME_MAXTEXT];
	int i;

 restart:
	/* convert to dns_name_t so that we can compare */
	{
		isc_buffer_t fqdn_src;
		isc_buffer_t *fqdn_dst;

		memset(&fqdn_name, 0, sizeof(fqdn_name));
		dns_name_init(&fqdn_name, NULL);
		fqdn_dst=NULL;

		isc_buffer_init(&fqdn_src, fqdn, strlen(fqdn));
		isc_buffer_add(&fqdn_src, strlen(fqdn));
		isc_buffer_allocate(iscmem, &fqdn_dst, strlen(fqdn)+1);

		if(dns_name_fromtext(&fqdn_name,
				     &fqdn_src,
				     NULL,
				     1,
				     fqdn_dst) != ISC_R_SUCCESS) {
			return 0;
		}
	}
				 
	cname_text=NULL;
	if(isc_buffer_allocate(iscmem, &cname_text, DNS_NAME_MAXTEXT)) {
		return 0;
	}

	success=lwres_getrrsetbyname(fqdn,
				     dns_rdataclass_in,
				     dns_rdatatype_cname,
				     /* flags */ 0,
				     &res);
	
	switch(success) {
	case ERRSET_NONAME:
	case ERRSET_NODATA:
		/* no, no CNAME found */
		return 0;
		
	case 0:
		/* aha! found a CNAME */
		break;

	default:
		/* some other error */
		snprintf(simplebuf, sizeof(simplebuf), "%d", success);
		output_transaction_line(gs, id, 0, "FATAL", simplebuf);
		return 0;
	}

	/* now process out the CNAMEs, and look them up, one by one...
	 * there should be only one... We just use the first one that works.
	 */

	if(res->rri_flags & RRSET_VALIDATED) {
		output_transaction_line(gs, id, 0, "DNSSEC", "OKAY");
	} else {
		output_transaction_line(gs, id, 0, "DNSSEC", "not present");
	}

	res_cname=NULL;

	for(i=0; res_cname==NULL && i<res->rri_nrdatas; i++) {
		struct rdatainfo *ri = &res->rri_rdatas[i];
		isc_region_t  region;
		dns_rdata_t   rd;
		dns_rdata_cname_t cn;

		memset(&region, 0, sizeof(region));
		memset(&rd,     0, sizeof(rd));
		
		region.base   =  ri->rdi_data;
		region.length =  ri->rdi_length;

		dns_rdata_fromregion(&rd, dns_rdataclass_in,
				     dns_rdatatype_cname, &region);

		/* we set mctx to NULL, which means that the tenure for
		 * the stuff pointed to by cn will persist only as long
		 * as rd persists.
		 */
		if(dns_rdata_tostruct(&rd, &cn, NULL) != ISC_R_SUCCESS) {
			/* failed, try next one */
			continue;
		}

		if(dns_name_totext(&cn.cname, ISC_TRUE, cname_text) !=
		   ISC_R_SUCCESS) {
			continue;
		}

		cname_buf[0]='\0';
		strncat(cname_buf,
			isc_buffer_base(cname_text),
			isc_buffer_usedlength(cname_text));

		/* format out a text version */
		snprintf(simplebuf, sizeof(simplebuf), 
			 "%s \nCNAMEFROM %s\n", cname_buf, fqdn);
		output_transaction_line(gs, id, 0, "CNAME", simplebuf);
		
		/* check for loops in the CNAMEs! */
		if(dns_name_equal(&fqdn_name, &cn.cname) == ISC_TRUE) {
			continue;
		}

		/* okay, so look this new thing up */		
		success=lwres_getrrsetbyname(cname_buf,
					     dns_rdataclass_in,
					     wantedtype,
					     /* flags */ 0,
					     &res_cname);
		
		switch(success) {
		case ERRSET_NODATA:
			/* wow, might have found a second CNAME! */
			strcpy(cname_buf2, cname_buf);
			fqdn=cname_buf2;
			goto restart;
		
		case 0:
			/* aha! the data we wanted! */
			break;
			
		default:
			/* some other error */
			snprintf(simplebuf, sizeof(simplebuf), "%d", success);
			output_transaction_line(gs, id, 0, "FATAL", simplebuf);

		case ERRSET_NONAME:
			if(res_cname) {
				lwres_freerrset(res_cname);
			}
			res_cname=NULL;
			break;
		}
	}

	if(res) {
		lwres_freerrset(res);
	}

	if(res_cname) {
		/* got something good! */
		*pres=res_cname;
		return 1;
	}

/* screwed: */
	/* clean crap up */
		

	return -1;
}


void lookup_thing(dnskey_glob *gs,
		  dns_rdatatype_t wantedtype,
		  char *wantedtype_name,
		  char *id,
		  char *fqdn)
{
	isc_mem_t    *iscmem;
	isc_buffer_t *iscbuf;
	int success;
	char simplebuf[80], typebuf[16];
	struct rrsetinfo *res;
	int  i;

	iscmem=NULL;
	iscbuf=NULL;
	output_transaction_line(gs, id, 0, "START", NULL);

	if(isc_mem_create(0,0,&iscmem) != ISC_R_SUCCESS ||
	   isc_buffer_allocate(iscmem, &iscbuf, LWDNSQ_RESULT_LEN_MAX)) {
	screwed:
		output_transaction_line(gs, id, 0, "FATAL", "isc buffer error");
		goto done;
	}

	res=NULL;
	success=lwres_getrrsetbyname(fqdn,
				     dns_rdataclass_in,
				     wantedtype,
				     /* flags */ 0,
				     &res);
	
	switch(success) {
	case ERRSET_NONAME:
		snprintf(simplebuf, sizeof(simplebuf),
			 "RR of type %s for %s was not found",
			 wantedtype_name, fqdn);
		output_transaction_line(gs, id, 0, "RETRY", 
					simplebuf);
		goto done;
		
	case ERRSET_NODATA:
		if(follow_possible_cname(gs, iscmem, id,
					 fqdn,
					 wantedtype,
					 &res) == 1) {
			break;
		} else {
			snprintf(simplebuf, sizeof(simplebuf),
				 "RR of type %s for %s was not found (tried CNAMEs)",
				 wantedtype_name, fqdn);
			output_transaction_line(gs, id, 0, "RETRY", 
						simplebuf);
			goto done;
		}

	case ERRSET_NOMEMORY:
		snprintf(simplebuf, sizeof(simplebuf),
			 "ran out of memory while looking up RR of type %s for %s",
				 wantedtype_name, fqdn);
		output_transaction_line(gs, id, 0, "FATAL", simplebuf);
		goto done;
		
	case ERRSET_INVAL:
		snprintf(simplebuf, sizeof(simplebuf),
			 "invalid input while looking up RR of type %s for %s",
				 wantedtype_name, fqdn);
		output_transaction_line(gs, id, 0, "RETRY", simplebuf);
		goto done;

	default:
		snprintf(simplebuf, sizeof(simplebuf), " unknown error %d", success);
		output_transaction_line(gs, id, 0, "RETRY", simplebuf);
		goto done;
		
	case 0:
		/* everything okay */
		break;
	}

	if(res->rri_flags & RRSET_VALIDATED) {
		output_transaction_line(gs, id, 0, "DNSSEC", "OKAY");
		snprintf(typebuf, sizeof(typebuf), "AD-%s", wantedtype_name);
		wantedtype_name=typebuf;
	} else {
		output_transaction_line(gs, id, 0, "DNSSEC", "not present");
	}

	output_transaction_line(gs, id, 0, "NAME", res->rri_name);

	for(i=0; i<res->rri_nrdatas; i++) {
		struct rdatainfo *ri = &res->rri_rdatas[i];
		isc_region_t  region;
		dns_rdata_t    rd;

		isc_buffer_clear(iscbuf);
		memset(&region, 0, sizeof(region));
		memset(&rd,     0, sizeof(rd));
		
		region.base   =  ri->rdi_data;
		region.length =  ri->rdi_length;

		dns_rdata_fromregion(&rd, dns_rdataclass_in,
				     wantedtype, &region);
		if(dns_rdata_totext(&rd, NULL, iscbuf) != ISC_R_SUCCESS) {
			goto screwed;
		}
		
		output_transaction_line_limited(gs, id, 0, wantedtype_name,
					(int)isc_buffer_usedlength(iscbuf),
					(char *)isc_buffer_base(iscbuf));
	}
		
	for(i=0; i<res->rri_nsigs; i++) {
		struct rdatainfo *ri = &res->rri_sigs[i];
		isc_region_t  region;
		dns_rdata_t    rd;

		isc_buffer_clear(iscbuf);
		memset(&region, 0, sizeof(region));
		memset(&rd,     0, sizeof(rd));
		
		region.base   =  ri->rdi_data;
		region.length =  ri->rdi_length;

		dns_rdata_fromregion(&rd, dns_rdataclass_in,
				     dns_rdatatype_sig, &region);
		if(dns_rdata_totext(&rd, NULL, iscbuf) != ISC_R_SUCCESS) {
			goto screwed;
		}
		
		output_transaction_line_limited(gs, id, 0, "SIG",
					(int)isc_buffer_usedlength(iscbuf),
					(char *)isc_buffer_base(iscbuf));
	}
	
		
 done:
	output_transaction_line(gs, id, 0, "DONE", NULL);

	if(res) {
		lwres_freerrset(res);
	}

	return;
}

void lookup_key(dnskey_glob *gs,
		int    argc,
		char **argv)
{
	char *id;
	char *fqdn;
	char simplebuf[80];

	/* process arguments */
	/* KEY 31459 east.uml.freeswan.org */
	if(argc!=3) {
		snprintf(simplebuf, sizeof(simplebuf), "wrong number of arguments %d", argc);
		output_transaction_line(gs, "0", 0, "FATAL", simplebuf);
		return;
	}

	id=argv[1];
	fqdn=argv[2];
	
	lookup_thing(gs, dns_rdatatype_key, "KEY", id, fqdn);
}

void lookup_key4(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_key6(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}


void lookup_txt(dnskey_glob *gs,
		int    argc,
		char **argv)
{
	char *id;
	char *fqdn;
	char simplebuf[80];

	/* process arguments */
	/* KEY 31459 east.uml.freeswan.org */
	if(argc != 3) {
		snprintf(simplebuf, sizeof(simplebuf), "wrong number of arguments to TXT: %d", argc);
		output_transaction_line(gs, "0", 0, "FATAL", simplebuf);
		return;
	}

	id=argv[1];
	fqdn=argv[2];

	lookup_thing(gs, dns_rdatatype_txt, "TXT", id, fqdn);
}

void lookup_txt4(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_txt6(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_ipseckey(dnskey_glob *gs,
		int    argc,
		char **argv)
{
  abort();
  return;
}

void lookup_ipseckey4(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_ipseckey6(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_oe4(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_oe6(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_a(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}

void lookup_aaaa(dnskey_glob *gs,
		 int    argc,
		 char **argv)
{
  abort();
  return;
}





	
/*
 * $Log: cmds.c,v $
 * Revision 1.5  2002/12/12 06:03:41  mcr
 * 	added --regress option to force times to be regular
 *
 * Revision 1.4  2002/11/25 18:37:28  mcr
 * 	added AD- marking of each record that was DNSSEC verified.
 *
 * Revision 1.3  2002/11/16 02:53:53  mcr
 * 	lwdnsq - with new contract added.
 *
 * Revision 1.2  2002/11/12 04:33:44  mcr
 * 	print DNSSEC status as we process CNAMEs.
 *
 * Revision 1.1  2002/10/30 02:25:31  mcr
 * 	renamed version of files from dnskey/
 *
 * Revision 1.4  2002/10/18 23:11:02  mcr
 * 	if we get ENOENT, then see if we can get a CNAME. If so, then
 * 	follow it.
 * 	Be careful when following them to avoid recursion.
 *
 * Revision 1.3  2002/10/18 04:08:47  mcr
 * 	use -ldns routines to decode lwres results and format them nicely.
 *
 * Revision 1.2  2002/10/09 20:13:34  mcr
 * 	first set of real code - lookup KEY records in forward.
 *
 * Revision 1.1  2002/09/30 18:55:54  mcr
 * 	skeleton for dnskey helper program.
 *
 * Revision 1.1  2002/09/30 16:50:23  mcr
 * 	documentation for "dnskey" helper
 *
 * Local variables:
 * c-file-style: "linux"
 * c-basic-offset: 2
 * End:
 *
 */
