/* rbldnsd_dnset.c
 * Zone type which consists of a set of (possible wildcarded)
 * domain names, all sharing the same (A,TXT) result.
 */

#include <syslog.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rbldnsd.h"
#include "dns.h"
#include "mempool.h"
#include "sstr.h"

/*
 * We store all domain names in a sorted array, using binary
 * search to find an entry.  There are two similar arrays -
 * for plain entries and for wildcard entries - with all
 * variables indexed by EP and EW.
 */

struct zonedata {
  int n[2]; /* number of entries */
  int a[2]; /* entries allocated (used only when loading) */
  unsigned char **e[2]; /* entries: plain and wildcard */
  unsigned maxlab[2]; /* max level of labels */
  ip4addr_t r_a; /* result: address */
  char *r_txt; /* result: text */
  struct mempool mp; /* mempool for domain names */
};

/* indexes */
#define EP 0
#define EW 1

void dnset_free(struct zonedata *z) {
  if (z) {
    mp_free(&z->mp);
    if (z->r_txt) sstrfree(z->r_txt);
    if (z->e[EP]) free(z->e[EP]);
    if (z->e[EW]) free(z->e[EW]);
    free(z);
  }
}

static int
dnset_addent(struct zonedata *z, int idx, unsigned char *dn) {
  int a = 0, b = z->n[idx] - 1, m, r;
  unsigned char **e = z->e[idx];
  unsigned labels;
  while(a <= b) {
    m = (a + b) >> 1;
    r = strcmp(e[m], dn);
    if (!r) return 1;
    if (r < 0) a = m + 1;
    else b = m - 1;
  }
  if (z->n[idx] >= z->a[idx]) {
    z->a[idx] = z->a[idx] ? z->a[idx] << 1 : 32;
    e = z->e[idx] = (unsigned char **)erealloc(e, z->a[idx] * sizeof(*e));
    if (!e) return 0;
  }
  for(m = z->n[idx]++; m > a; --m)
    e[m] = e[m-1];
  if (!(e[m] = (unsigned char*)mp_estrdup(&z->mp, dn))) return 0;
  labels = dns_dnlabels(dn);
  if (z->maxlab[idx] < labels) z->maxlab[idx] = labels;
  return 1;
}

static int
dnset_loadfile(FILE *f, struct zonedata *z) {
  char buf[512];
  int lineno = 0;
  unsigned l;
  char *p;
  unsigned char dn[DNS_MAXDN];

  while(fgets(buf, sizeof(buf), f)) {
    ++lineno;
    l = strlen(buf);
    if (l && buf[l-1] == '\n') buf[--l] = '\0';
    if (!l || buf[0] == '#') continue;
    if (lineno == 1 && buf[0] == ':') {
      if (!addrtxt(buf, &z->r_a, &p)) {
        zlog(LOG_WARNING, 1, "invalid default entry");
	continue;
      }
      if (!z->r_a) z->r_a = R_A_DEFAULT;
      if (*p && !(z->r_txt = esstrdup(p)))
        return 0;
      continue;
    }
    for(p = buf; *p; ++p)
      if (*p == ' ' || *p == '\t' || *p == '#') break;
    *p = '\0';
    if (!dns_ptodn(buf, dn, DNS_MAXDN)) {
      zlog(LOG_WARNING, lineno, "invalid domain name `%s'", buf);
      continue;
    }
    dns_dntol(dn, dn);
    if (dn[0] == 1 && dn[1] == '*') {
      if (!dnset_addent(z, EW, dn + 2)) return 0;
    }
    else {
      if (!dnset_addent(z, EP, dn)) return 0;
    }
  }

  return 1;
}

struct zonedata *dnset_load(FILE *f) {
  int r;
  struct zonedata *z = (struct zonedata *)emalloc(sizeof(*z));
  if (!z) return NULL;
  memset(z, 0, sizeof(*z));
  z->r_a = R_A_DEFAULT;
  if (!dnset_loadfile(f, z)) {
    dnset_free(z);
    return NULL;
  }
  for(r = 0; r < 2; ++r) {
    if (z->a[r] != z->n[r])
      z->e[r] = (unsigned char **)
        realloc(z->e[r], z->n[r] * sizeof(unsigned char *));
  }
  return z;
}

static int
dnset_find(unsigned char **e, int b, const unsigned char *q) {
  int a = 0, m, r;
  while(a <= b) {
    m = (a + b) >> 1;
    r = strcmp(e[m], q);
    if (!r) return 1;
    else if (r < 0) a = m + 1;
    else b = m - 1;
  }
  return 0;
}

int dnset_query(const struct zonedata *const z, struct dnspacket *p,
	    const unsigned char *const query, unsigned qflg)
{
  const unsigned char *dn = query;
  unsigned labels = dns_dnlabels(dn);
  if (labels > z->maxlab[EP] || !z->n[EP] ||
      !dnset_find(z->e[EP], z->n[EP] - 1, dn)) {
    /* try wildcard */
    while(labels > z->maxlab[EW])
      --labels, dn += 1 + *dn;
    for(;;) {
      if (!*dn) return 0;
      if (dnset_find(z->e[EW], z->n[EW]-1, dn)) break;
      dn += 1 + *dn;
    }
  }
  if (qflg & QUERY_A) addrec_a(p, z->r_a);
  if (qflg & QUERY_TXT && z->r_txt) {
    char name[DNS_MAXDOMAIN+1];
    dns_dntop(query, name, sizeof(name));
    addrec_txt(p, z->r_txt, name);
  }
  return 1;
}

