/*
* axfr.c -- generating AXFR responses.
*
* Copyright (c) 2001-2006, NLnet Labs. All rights reserved.
*
* See LICENSE for the license.
*
*/

#include "config.h"

#include "axfr.h"
#include "dns.h"
#include "packet.h"
#include "options.h"
#include "ixfr.h"

/* draft-ietf-dnsop-rfc2845bis-06, section 5.3.1 says to sign every packet */
#define AXFR_TSIG_SIGN_EVERY_NTH        0       /* tsig sign every N packets. */

query_state_type
query_axfr(struct nsd *nsd, struct query *query, int wstats)
{
       domain_type *closest_match;
       domain_type *closest_encloser;
       int exact;
       int added;
       uint16_t total_added = 0;

       if (query->axfr_is_done)
               return QUERY_PROCESSED;

       if (query->maxlen > AXFR_MAX_MESSAGE_LEN)
               query->maxlen = AXFR_MAX_MESSAGE_LEN;

       assert(!query_overflow(query));
       /* only keep running values for most packets */
       query->tsig_prepare_it = 0;
       query->tsig_update_it = 1;
       if(query->tsig_sign_it) {
               /* prepare for next updates */
               query->tsig_prepare_it = 1;
               query->tsig_sign_it = 0;
       }

       if (query->axfr_zone == NULL) {
               domain_type* qdomain;
               /* Start AXFR.  */
               if(wstats) {
                       STATUP(nsd, raxfr);
               }
               exact = namedb_lookup(nsd->db,
                                     query->qname,
                                     &closest_match,
                                     &closest_encloser);

               qdomain = closest_encloser;
               query->axfr_zone = domain_find_zone(nsd->db, closest_encloser);

               if (!exact
                   || query->axfr_zone == NULL
                   || query->axfr_zone->apex != qdomain
                   || query->axfr_zone->soa_rrset == NULL)
               {
                       /* No SOA no transfer */
                       RCODE_SET(query->packet, RCODE_NOTAUTH);
                       return QUERY_PROCESSED;
               }
               if(wstats) {
                       ZTATUP(nsd, query->axfr_zone, raxfr);
               }

               query->axfr_current_domain = qdomain;
               query->axfr_current_rrset = NULL;
               query->axfr_current_rr = 0;
               if(query->tsig.status == TSIG_OK) {
                       query->tsig_sign_it = 1; /* sign first packet in stream */
               }

               query_add_compression_domain(query, qdomain, QHEADERSZ);

               assert(query->axfr_zone->soa_rrset->rr_count == 1);
               added = packet_encode_rr(query,
                                        query->axfr_zone->apex,
                                        &query->axfr_zone->soa_rrset->rrs[0],
                                        query->axfr_zone->soa_rrset->rrs[0].ttl);
               if (!added) {
                       /* XXX: This should never happen... generate error code? */
                       abort();
               }
               ++total_added;
       } else {
               /*
                * Query name and EDNS need not be repeated after the
                * first response packet.
                */
               query->edns.status = EDNS_NOT_PRESENT;
               buffer_set_limit(query->packet, QHEADERSZ);
               QDCOUNT_SET(query->packet, 0);
               query_prepare_response(query);
       }

       /* Add zone RRs until answer is full.  */
       while (query->axfr_current_domain != NULL &&
                       domain_is_subdomain(query->axfr_current_domain,
                                           query->axfr_zone->apex))
       {
               if (!query->axfr_current_rrset) {
                       query->axfr_current_rrset = domain_find_any_rrset(
                               query->axfr_current_domain,
                               query->axfr_zone);
                       query->axfr_current_rr = 0;
               }
               while (query->axfr_current_rrset) {
                       if (query->axfr_current_rrset != query->axfr_zone->soa_rrset
                           && query->axfr_current_rrset->zone == query->axfr_zone)
                       {
                               while (query->axfr_current_rr < query->axfr_current_rrset->rr_count) {
                                       size_t oldmaxlen = query->maxlen;
                                       if(total_added == 0)
                                               /* RR > 16K can be first RR */
                                               query->maxlen = (query->tcp?TCP_MAX_MESSAGE_LEN:UDP_MAX_MESSAGE_LEN);
                                       added = packet_encode_rr(
                                               query,
                                               query->axfr_current_domain,
                                               &query->axfr_current_rrset->rrs[query->axfr_current_rr],
                                               query->axfr_current_rrset->rrs[query->axfr_current_rr].ttl);
                                       if(total_added == 0) {
                                               query->maxlen = oldmaxlen;
                                               if(query_overflow(query)) {
                                                       if(added) {
                                                               ++total_added;
                                                               ++query->axfr_current_rr;
                                                               goto return_answer;
                                                       }
                                               }
                                       }
                                       if (!added)
                                               goto return_answer;
                                       ++total_added;
                                       ++query->axfr_current_rr;
                               }
                       }

                       query->axfr_current_rrset = query->axfr_current_rrset->next;
                       query->axfr_current_rr = 0;
               }
               assert(query->axfr_current_domain);
               query->axfr_current_domain
                       = domain_next(query->axfr_current_domain);
       }

       /* Add terminating SOA RR.  */
       assert(query->axfr_zone->soa_rrset->rr_count == 1);
       added = packet_encode_rr(query,
                                query->axfr_zone->apex,
                                &query->axfr_zone->soa_rrset->rrs[0],
                                query->axfr_zone->soa_rrset->rrs[0].ttl);
       if (added) {
               ++total_added;
               query->tsig_sign_it = 1; /* sign last packet */
               query->axfr_is_done = 1;
       }

return_answer:
       AA_SET(query->packet);
       ANCOUNT_SET(query->packet, total_added);
       NSCOUNT_SET(query->packet, 0);
       ARCOUNT_SET(query->packet, 0);

       /* check if it needs tsig signatures */
       if(query->tsig.status == TSIG_OK) {
#if AXFR_TSIG_SIGN_EVERY_NTH > 0
               if(query->tsig.updates_since_last_prepare >= AXFR_TSIG_SIGN_EVERY_NTH) {
#endif
                       query->tsig_sign_it = 1;
#if AXFR_TSIG_SIGN_EVERY_NTH > 0
               }
#endif
       }
       query_clear_compression_tables(query);
       return QUERY_IN_AXFR;
}

/* See if the query can be admitted. */
static int axfr_ixfr_can_admit_query(struct nsd* nsd, struct query* q)
{
       struct acl_options *acl = NULL;
       struct zone_options* zone_opt;
       zone_opt = zone_options_find(nsd->options, q->qname);
       if(zone_opt && q->is_proxied && acl_check_incoming_block_proxy(
               zone_opt->pattern->provide_xfr, q, &acl) == -1) {
               /* the proxy address is blocked */
               if (verbosity >= 2) {
                       char address[128], proxy[128];
                       addr2str(&q->client_addr, address, sizeof(address));
                       addr2str(&q->remote_addr, proxy, sizeof(proxy));
                       VERBOSITY(2, (LOG_INFO, "%s for %s from %s via proxy %s refused because of proxy, %s %s",
                               (q->qtype==TYPE_AXFR?"axfr":"ixfr"),
                               dname_to_string(q->qname, NULL),
                               address, proxy,
                               (acl?acl->ip_address_spec:"."),
                               (acl ? ( acl->nokey    ? "NOKEY"
                                     : acl->blocked  ? "BLOCKED"
                                     : acl->key_name )
                                   : "no acl matches")));
               }
               RCODE_SET(q->packet, RCODE_REFUSE);
               /* RFC8914 - Extended DNS Errors
                * 4.19.  Extended DNS Error Code 18 - Prohibited */
               q->edns.ede = EDE_PROHIBITED;
               return 0;
       }
       if(!zone_opt ||
          acl_check_incoming(zone_opt->pattern->provide_xfr, q, &acl)==-1)
       {
               if (verbosity >= 2) {
                       char a[128];
                       addr2str(&q->client_addr, a, sizeof(a));
                       VERBOSITY(2, (LOG_INFO, "%s for %s from %s refused, %s",
                               (q->qtype==TYPE_AXFR?"axfr":"ixfr"),
                               dname_to_string(q->qname, NULL), a, acl?"blocked":"no acl matches"));
               }
               DEBUG(DEBUG_XFRD,1, (LOG_INFO, "%s refused, %s",
                       (q->qtype==TYPE_AXFR?"axfr":"ixfr"),
                       acl?"blocked":"no acl matches"));
               if (!zone_opt) {
                       RCODE_SET(q->packet, RCODE_NOTAUTH);
               } else {
                       RCODE_SET(q->packet, RCODE_REFUSE);
                       /* RFC8914 - Extended DNS Errors
                        * 4.19.  Extended DNS Error Code 18 - Prohibited */
                       q->edns.ede = EDE_PROHIBITED;
               }
               return 0;
       }
       DEBUG(DEBUG_XFRD,1, (LOG_INFO, "%s admitted acl %s %s",
               (q->qtype==TYPE_AXFR?"axfr":"ixfr"),
               acl->ip_address_spec, acl->key_name?acl->key_name:"NOKEY"));
       if (verbosity >= 1) {
               char a[128];
               addr2str(&q->client_addr, a, sizeof(a));
               VERBOSITY(1, (LOG_INFO, "%s for %s from %s",
                       (q->qtype==TYPE_AXFR?"axfr":"ixfr"),
                       dname_to_string(q->qname, NULL), a));
       }
       return 1;
}

/*
* Answer if this is an AXFR or IXFR query.
*/
query_state_type
answer_axfr_ixfr(struct nsd *nsd, struct query *q)
{
       /* Is it AXFR? */
       switch (q->qtype) {
       case TYPE_AXFR:
               if (q->tcp) {
                       if(!axfr_ixfr_can_admit_query(nsd, q))
                               return QUERY_PROCESSED;
                       return query_axfr(nsd, q, 1);
               }
               /* AXFR over UDP queries are discarded. */
               RCODE_SET(q->packet, RCODE_IMPL);
               return QUERY_PROCESSED;
       case TYPE_IXFR:
               if(!axfr_ixfr_can_admit_query(nsd, q)) {
                       /* get rid of authority section, if present */
                       NSCOUNT_SET(q->packet, 0);
                       ARCOUNT_SET(q->packet, 0);
                       if(QDCOUNT(q->packet) > 0 && (size_t)QHEADERSZ+4+
                               q->qname->name_size <= buffer_limit(q->packet)) {
                               buffer_set_position(q->packet, QHEADERSZ+4+
                                       q->qname->name_size);
                       }
                       return QUERY_PROCESSED;
               }
               return query_ixfr(nsd, q);
       default:
               return QUERY_DISCARDED;
       }
}