#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/syslog.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <arpa/nameser.h>

#include <exec/nodes.h>
#include <exec/lists.h>

#include <proto/exec.h>

char VersionTag[]="\0$VER: JoranNameserver 0.3 (14.12.93)";

struct hostinfo {
	struct MinNode Node;
	char name[MAXDNAME];
	LONG netnumber;
};
struct MinList hostlist;
char Line[1024];
char hostname[MAXHOSTNAMELEN];
char *curnet="";

void readnamedhosts(void)
{
	FILE *f;
	
	f=fopen("AmiTCP:db/named.hosts","r");
	if(!f)
	{
		perror("AmiTCP:db/named.hosts");
		exit(1);
	}
	NewList((struct List *)&hostlist);
	
	while(fgets(Line,sizeof(Line),f))
	{
		char *p,*a[6];
		char curdname[MAXDNAME];
		LONG netnumber;
		int i;
		
		if(p=strchr(Line,';')) *p='\0';		// Verwijderen commentaar
		a[0]=strtok(Line,"\t\n ");
		for(i=1;i<6;i++) a[i]=strtok(NULL,"\t\n ");
		netnumber=-1;
		for(i=0;i<6;i++) if(!a[i] || !strcmp(a[i],"IN")) break;
		if(i>0) strcpy(curdname,a[0]);
		if(i<4 && a[i+1] && !strcmp(a[i+1],"A")) netnumber=inet_addr(a[i+2]);

		if(netnumber!=-1)
		{
			struct hostinfo *hi;
			
			if(hi=malloc(sizeof(struct hostinfo)))
			{
				strcpy(hi->name,curdname);
				if(hi->name[strlen(hi->name)-1]!='.') strcat(hi->name,curnet);
				else hi->name[strlen(hi->name)-1]='\0';
				hi->netnumber=netnumber;
#ifdef DEBUG
				printf("add: %s(%s)\n",hi->name,Inet_NtoA(hi->netnumber));
#endif
				AddTail((struct List *)&hostlist,(struct Node *)hi);
			}
		}
	}
	
	fclose(f);
}

void send_err(int s,char *msg,int msglen,struct sockaddr_in *to,LONG tolen,short rcode)
{
	HEADER *h;
	
	h=(HEADER *)msg+sizeof(HEADER);
	h->qr=1;
	h->rcode=rcode;
	sendto(s,msg,msglen,0,(struct sockaddr *)to,tolen);
	
#ifdef DEBUG
	switch(rcode)
	{
		case FORMERR:	puts("FORMERR send");break;
		case SERVFAIL:	puts("FORMFAIL send");break;
		case NXDOMAIN:	puts("NXDOMAIN send");break;
		default:			puts("UNKNOWN ERROR send");break;
	}
#endif
}

main()
{
	struct servent *sp;
	struct sockaddr_in sin;
	struct sockaddr_in from;
	struct hostinfo *hp,*hi;
	LONG fromlen;
	char msg[PACKETSZ];
	char *lmsg;
	char dnbuf[MAXDNAME];
	char *cp,*eom;
	int msglen;
	int s,n;
	short type,class;
	
	sp=getservbyname("domain","udp");
	if(!sp)
	{
		fprintf(stderr,"nameserver: tcp/nameserver: unknown service\n");
		exit(1);
	}
	
	s=socket(AF_INET,SOCK_DGRAM,0);
	if(s==-1)
	{
		perror("nameserver: create socket");
		exit(1);
	}
	
	bzero(&sin,sizeof(sin));
	sin.sin_port=sp->s_port;

	if(bind(s,(struct sockaddr *)&sin,sizeof(sin))==-1)
	{
		perror("nameserver: bind");
		exit(1);
	}
	
	if(gethostname(hostname,sizeof(hostname))==-1)
	{
		perror("nameserver: gethostname");
		exit(1);
	}
	if(cp=strchr(hostname,'.')) curnet=cp;
	
	readnamedhosts();
	
#ifdef DEBUG
	printf("nameserver started\n");
#endif
	for(;;)
	{
		fromlen=sizeof(from);
		if((msglen=recvfrom(s,msg,sizeof(msg),0,(struct sockaddr *)&from,&fromlen))==-1)
		{
			perror("nameserver: recvfrom");
		}
		else
		{
			HEADER *h;
			h=(HEADER *)msg;
			cp=msg+sizeof(HEADER);
			eom=msg+msglen;
#ifdef DEBUG
			printf(	"datagram received:\n"
						"  from   : %s\n"
						"  opcode : %d\n"
						"  qdqount: %d\n"
						,inet_ntoa(from.sin_addr)
						,h->opcode
						,h->qdcount
			);
#endif
			switch(h->opcode)
			{
				case QUERY:
					if(h->qdcount!=1 || h->ancount || h->nscount || h->arcount)
					{
						h->qdcount=0;
						h->ancount=0;
						h->nscount=0;
						h->arcount=0;
						send_err(s,msg,msglen,&from,fromlen,FORMERR);
						continue;
					}
					if((n=dn_expand(msg,eom,cp,dnbuf,sizeof(dnbuf)))<0)
					{
						send_err(s,msg,msglen,&from,fromlen,FORMERR);
						continue;
					}
					cp+=n;
					GETSHORT(type, cp);
					GETSHORT(class, cp);
					if(cp>eom)
					{
						send_err(s,msg,msglen,&from,fromlen,FORMERR);
						continue;
					}
#ifdef DEBUG
					printf("  host   : %s\n",dnbuf);
					printf("  type   : %d\n",type);
					printf("  class  : %d\n",class);
#endif
					if(type!=T_A || class!=C_IN)
					{
						send_err(s,msg,msglen,&from,fromlen,NOTIMP);
						continue;
					}
					
					hp=NULL;
					for(hi=(struct hostinfo *)hostlist.mlh_Head;hi!=(struct hostinfo *)&hostlist.mlh_Tail;hi=(struct hostinfo *)hi->Node.mln_Succ)
					{
						if(!strcmp(hi->name,dnbuf))
						{
							hp=hi;
							break;
						}
					}	

					if(!hp)
					{
						send_err(s,msg,msglen,&from,fromlen,NXDOMAIN);
						continue;
					}
					
					lmsg=msg+sizeof(msg);
					if((n=dn_comp(dnbuf,cp,lmsg-cp,NULL,NULL))<0)
					{
						send_err(s,msg,msglen,&from,fromlen,SERVFAIL);
						continue;						
					}
					cp+=n;
					if(cp+14>lmsg)
					{
						send_err(s,msg,msglen,&from,fromlen,SERVFAIL);
						continue;			
					}
					
					PUTSHORT(type, cp);
					PUTSHORT(class, cp);
					PUTLONG(100000,cp);
					PUTSHORT(sizeof(LONG),cp);
					PUTLONG(hi->netnumber,cp);
					
					h->qr=1;
					h->rcode=NOERROR;
					h->ancount=1;
					sendto(s,msg,cp-msg,0,(struct sockaddr *)&from,fromlen);
#ifdef DEBUG
					printf("answer: %s(%s) send\n",dnbuf,Inet_NtoA(hi->netnumber));
#endif
					break;
				default:
					send_err(s,msg,msglen,&from,fromlen,NOTIMP);
			}
		}
	}
}
