/***************************************************************************
                          zonetransfer.cpp  -  handle zone transfers
                             -------------------
    begin                : za apr 19 2003
    copyright            : (C) 2003 by Meilof
    email                : meilof@users.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "zonetransfer.h"
#include "zones.h"
#include "primary.h"
#include <poslib/server/configuration.h>

DnsMessage *answer_axfr(pending_query *q) {
  char lb[2];

  DnsMessage *a = new DnsMessage();
  a->ID = q->message->ID;

  /* look up corresponding primary zone */
  domainname retdom;
  AuthMemZone *z;

  pthread_mutex_lock(&m_auth_zones);
  /* we don't need to look up dynamic zones (yet)? */
  z = (AuthMemZone*)lookup_authoritative_zone(q->message->questions.begin()->QNAME, &retdom, false);
  if (!z || (z->type != Z_PRIMARY && z->type != Z_SECONDARY) ||
      q->message->questions.begin()->QNAME != retdom) {
    /* no such primary zone: return NOTIMP */
    a->RCODE = RCODE_NOTIMP;
    pthread_mutex_unlock(&m_auth_zones);
    return a;
  }

  /* see if the zone is actually loaded */
  if (!z->zone_is_loaded) {
    a->RCODE = RCODE_SRVFAIL;
    pthread_mutex_unlock(&m_auth_zones);
    return a;
  }
  
  /* at first, determine whether to allow a zone transfer to this client */
  if ((z->allow_xfr == PrimaryZone::slaves_only &&
        !in_addr_list(z->slaves, &q->querier) && !in_addrrange_list(z->allow_xfr_to, &q->querier)) ||
      (z->allow_xfr == PrimaryZone::none && !in_addrrange_list(z->allow_xfr_to, &q->querier))) {
    a->RCODE = RCODE_REFUSED;
    pthread_mutex_unlock(&m_auth_zones);
    return a;
  }

  /* allright, do the zone transfer */

  /* try to build file */
  if (!datadir || !datadir[0]) {
    pos_log(context_zonedata, log_error, "%s: Request for zone transfer could not be answered because we have no data dir!", z->getfqdn().tocstr());
    a->RCODE = RCODE_SRVFAIL;
    pthread_mutex_unlock(&m_auth_zones);
    return a;
  }

  FILE *f;
  char filename[PATH_MAX];
  char serial[16];
  sprintf(serial, "%d", z->serial);
  domainname dom = z->getfqdn();
  stl_string domstr = dom.tostring();
  if (strlen(datadir) + strlen(serial) + strlen(domstr.c_str()) >= sizeof(filename)) {
    pos_log(context_zonedata, log_error, "%s: Request for zone transfer could not be answered because data dir name is too long!", z->getfqdn().tocstr());
    a->RCODE = RCODE_SRVFAIL;
    pthread_mutex_unlock(&m_auth_zones);
    return a;
  }
  
  strcpy(filename, datadir);
  strcat(filename, domstr.c_str());
  strcat(filename, serial);
  f = try_fopen(filename, "rb");
  if (!f) {
    pos_log(context_zonedata, log_error, "%s: Could not open data file %s", z->getfqdn().tocstr(), filename);
    a->RCODE = RCODE_SRVFAIL;
    pthread_mutex_unlock(&m_auth_zones);
    return a;
  }
  pthread_mutex_unlock(&m_auth_zones);
  
  delete a; a = NULL;
  char *buff = NULL;
  char rrbuff[65535];
  int len,  pos = 0, offset = 0, x = 0, n;
  bool is_soa = false;
  try {
    /* we'll build the answer DNS message ourself for performance reasons
       and because we're reading from a binary zone file either way */
    if (q->transport == T_UDP) {
      len = UDP_MSG_SIZE;
      buff = (char *)malloc(UDP_MSG_SIZE);
    } else if (q->transport == T_TCP) {
      len = 65537;
      buff = (char *)malloc(65537);
      offset = 2;
    } else {
      return NULL;
    }

    while (!feof(f)) {
      pos = offset;
      buff[pos] = q->message->ID / 256;
      buff[pos+1] = q->message->ID % 256;
      buff[pos+2] = 132; /* QR|AA|QUERY*/
      buff[pos+3] = 0; /* NOERROR */
      memset(buff+pos+4, 0, 8);
      pos += 12;
      n = 0;

      while (1) {
        /* read item */
        if (x) {
          memcpy(buff + pos, rrbuff, x);
          pos += x; n++;
          x = 0;
          continue;
        } else if (feof(f)) {
          /* switch back to SOA */
          is_soa = true;
          fseek(f, 0, SEEK_SET);
        }
        if ((x = fread(lb, 1, 2, f)) < 2) {
          if (feof(f)) {
            if (is_soa) break; else {
              is_soa = true;
              fseek(f, 0, SEEK_SET);
              if (fread(lb, 1, 2, f) < 2) break;
            }
          } else throw PException("");
        }
        x = uint16_value(lb);
        if (fread(rrbuff, 1, x, f) != (size_t)x) throw PException("");
        if (pos + x < len) {
          memcpy(buff + pos, rrbuff, x);
          pos += x; n++; x = 0;
          if (is_soa) {
            fseek(f, 0, SEEK_END);
            break;
          }
          continue;
        } else break;
      }

      /* send message */
      buff[offset+6] = n / 256;
      buff[offset+7] = n % 256;

      if (q->transport == T_TCP) {
        buff[0] = (pos - 2) / 256;
        buff[1] = (pos - 2) % 256;
        tcpsendall(q->sockid, buff, pos, conf_get_tcp_io_timeout());
      } else {
        udpsend(q->sockid, buff, pos, &q->querier);
        return NULL;
      }
    } 
  } catch (PException p) {
    if (buff) free(buff);
    if (f) fclose(f);
    a = new DnsMessage();
    a->ID = q->message->ID;
    a->RCODE = RCODE_SRVFAIL;
    return a;
  }
  if (f) fclose(f);
  return NULL;
}



syntax highlighted by Code2HTML, v. 0.9.1