#include "byte.h"
#include "case.h"
#include "env.h"
#include "buffer.h"
#include "ip.h"
#include "uint_t.h"
#include "ndelay.h"
#include "socket_if.h"
#include "droproot.h"
#include "qlog.h"
#include "response.h"
#include "dns.h"
#include "alloc.h"
#include "str.h"
#include "edns0.h"
#include "logmsg.h"

uint16 dnsport = 53;
extern char *fatal;
extern char *starting;
extern int respond(char *,char *,char *);
extern void initialize(void);
extern int ipv4socket;
unsigned int max_response_len = MSGSIZE;
int flagdualstack = 0;
int flagipv6anycast = 0;

static char ip[16];
static uint16 port;

static char buf[MSGSIZE+1];
static int len;

static char *q;

void nomem()
{
  logmsg(fatal,111,FATAL,"out of memory");
}

static int doit(void)
{
  unsigned int pos;
  char header[12];
  char qtype[2];
  char qclass[2];

  if (len >= sizeof(buf)) goto NOQ;
  pos = dns_packet_copy(buf,len,0,header,12); if (!pos) goto NOQ;
  if (header[2] & 128) goto NOQ;
  if (header[4]) goto NOQ;
  if (header[5] != 1) goto NOQ;

  pos = dns_packet_getname(buf,len,pos,&q); if (!pos) goto NOQ;
  pos = dns_packet_copy(buf,len,pos,qtype,2); if (!pos) goto NOQ;
  pos = dns_packet_copy(buf,len,pos,qclass,2); if (!pos) goto NOQ;

  if (!response_query(q,qtype,qclass)) goto NOQ;
  response_id(header);
  if (byte_equal(qclass,2,DNS_C_IN))
    response[2] |= 4;
  else
    if (byte_diff(qclass,2,DNS_C_ANY)) goto WEIRDCLASS;
  response[3] &= ~128;
  if (!(header[2] & 1)) response[2] &= ~1;

  if (header[2] & 126) goto NOTIMP;
  if (byte_equal(qtype,2,DNS_T_AXFR)) goto NOTIMP;

  case_lowerb(q,dns_domain_length(q));
  if (!respond(q,qtype,ip)) {
    qlog(ip,port,header,q,qtype," - ");
    return 0;
  }
  qlog(ip,port,header,q,qtype," + ");
  return 1;

  NOTIMP:
  response[3] &= ~15;
  response[3] |= 4;
  qlog(ip,port,header,q,qtype," I ");
  return 1;

  WEIRDCLASS:
  response[3] &= ~15;
  response[3] |= 1;
  qlog(ip,port,header,q,qtype," C ");
  return 1;

  NOQ:
  qlog(ip,port,"\0\0","","\0\0"," / ");
  return 0;
}

int main()
{
  char *x;
  int udp53;
  stralloc ifname = {0};
  uint32 ifidx = 0;

  x = env_get("IP");
  if (!x)
    logmsg(fatal,111,ERROR,"IP not set");
  if (case_equals(x,"::")) {
    flagipv6anycast = 1;
  } else if (case_equals(x,":0")) {
    flagdualstack = 1;
    byte_copy(x,2,"::");
  }
  if (!ip6_ifscan(x,ip,&ifname))
    logmsg(fatal,111,FATAL,B("unable to parse IPv6 address: ",x));

  if (ifname.len > 1) ifidx = socket_getifidx(ifname.s);
  if (ip6_isv4mapped(ip)) { ipv4socket = 1; ifidx = 0; } 

  udp53 = socket_udp();
  if (udp53 == -1)
    logmsg(fatal,111,FATAL,"unable to create UDP socket");
 
  if (flagdualstack) socket_dualstack(udp53); 
  if (flagipv6anycast) socket_ip6anycast(udp53);
  if (socket_bind_reuse(udp53,ip,dnsport,ifidx) == -1)
    logmsg(fatal,111,FATAL,"unable to bind UDP socket");

  droproot(fatal);

  initialize();

  ndelay_off(udp53);
  socket_tryreservein(udp53,65536);

  log_start(starting,ip,ifidx);

  for (;;) {
    len = socket_recv(udp53,buf,sizeof(buf),ip,&port,&ifidx);
    if (len < 0) continue;
    if (!doit()) continue;
    if (flagdualstack) 
      socket_send6(udp53,response,response_len,ip,port,ifidx);
    else
      socket_send(udp53,response,response_len,ip,port,ifidx);
    /* may block for buffer space; if it fails, too bad */
  }
}
