Exemplo n.º 1
0
ya_result
nsec3_icmtl_replay_execute(nsec3_icmtl_replay *replay)
{
    bool nsec3param_added = FALSE;
    
    if(!treeset_avl_isempty(&replay->nsec3param_add))
    {
        treeset_avl_iterator n3p_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3param_add, &n3p_avl_iter);

        while(treeset_avl_iterator_hasnext(&n3p_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&n3p_avl_iter);
            zdb_ttlrdata* nsec3param = (zdb_ttlrdata*)node->data;
            
            nsec3_zone* n3 = nsec3_zone_get_from_rdata(replay->zone, nsec3param->rdata_size, nsec3param->rdata_pointer);
            
            if(n3 == NULL)
            {
                /*
                 * add the record
                 */
                
                zdb_packed_ttlrdata *packed_ttlrdata;
                ZDB_RECORD_ZALLOC(packed_ttlrdata, 0, nsec3param->rdata_size ,nsec3param->rdata_pointer);
                zdb_record_insert(&replay->zone->apex->resource_record_set, TYPE_NSEC3PARAM, packed_ttlrdata);
                
                nsec3_zone_add_from_rdata(replay->zone, nsec3param->rdata_size, nsec3param->rdata_pointer);
                //nsec3_load_chain_init(nsec3param->rdata_pointer, nsec3param->rdata_size);
                
                nsec3param_added = TRUE;
            }
            
            zdb_ttlrdata_delete(nsec3param);
            
            node->key = NULL;
            node->data = NULL;
        }
        
        treeset_avl_destroy(&replay->nsec3param_add);
    }
    
    if(!treeset_avl_isempty(&replay->nsec3_del))
    {
        /* stuff to delete */

        treeset_avl_iterator ts_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3_del, &ts_avl_iter);

        while(treeset_avl_iterator_hasnext(&ts_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&ts_avl_iter);
            u8 *fqdn = (u8*)node->key;
            zdb_ttlrdata *ttlrdata = (zdb_ttlrdata*)node->data;

#ifndef NDEBUG
            log_debug("journal: NSEC3: post/del %{dnsname}", fqdn);
#endif
            treeset_node *add_node;

            if((add_node = treeset_avl_find(&replay->nsec3_add, fqdn)) != NULL)
            {
                /* replace */

#ifndef NDEBUG
                log_debug("journal: NSEC3: upd %{dnsname}", fqdn);

                rdata_desc type_len_rdata = {TYPE_NSEC3, ttlrdata->rdata_size, ttlrdata->rdata_pointer };
                log_debug("journal: NSEC3: - %{typerdatadesc}", &type_len_rdata);
#endif

                zdb_ttlrdata *add_ttlrdata = (zdb_ttlrdata *)add_node->data;

#ifndef NDEBUG
                rdata_desc add_type_len_rdata = {TYPE_NSEC3, add_ttlrdata->rdata_size, add_ttlrdata->rdata_pointer };
                log_debug("journal: NSEC3: + %{typerdatadesc}", &add_type_len_rdata);
#endif

                /*
                 * The node may need an update of the type bitmap
                 * After all changes (del/upd/add) all the added records should be matched again (check)
                 *
                 * nsec3_zone_item_get_by_name();
                 * nsec3_zone_item_update_bitmap(item, rdata, rdata_len)
                 */

                nsec3_zone_item *add_item = nsec3_zone_item_find_by_record(replay->zone, fqdn, ttlrdata->rdata_size, ttlrdata->rdata_pointer);
                
                if(add_item != NULL)
                {
                    nsec3_zone_item_update_bitmap(add_item, add_ttlrdata->rdata_pointer, add_ttlrdata->rdata_size);

                    u8* add_key = add_node->key;
                    treeset_avl_delete(&replay->nsec3_add, fqdn);
                    zdb_ttlrdata_delete(add_ttlrdata);
                    free(add_key);
                }
                else
                {
                    log_err("journal: NSEC3: %{dnsname} has not been found in the NSEC3 database (del/add)", fqdn);
                    
                    return ERROR;
                }
            }
            else
            {
#ifndef NDEBUG
                log_debug("journal: NSEC3: del %{dnsname}", fqdn);

                rdata_desc type_len_rdata = {TYPE_NSEC3, ttlrdata->rdata_size, ttlrdata->rdata_pointer };
                log_debug("journal: NSEC3: - %{typerdatadesc}", &type_len_rdata);
#endif

                /* delete */

                nsec3_zone_item *add_item = nsec3_zone_item_find_by_record(replay->zone, fqdn, ttlrdata->rdata_size, ttlrdata->rdata_pointer);

                if(add_item != NULL)
                {
                    nsec3_remove_nsec3_by_name(replay->zone, fqdn, ttlrdata->rdata_pointer);
                }
                else
                {
                    log_err("journal: NSEC3: %{dnsname} has not been found in the NSEC3 database (del)", fqdn);
                }

                /*
                 * The node has to be deleted
                 */
            }

            zdb_ttlrdata_delete(ttlrdata);
            free(fqdn);
            
            node->key = NULL;
            node->data = NULL;
        }

        treeset_avl_destroy(&replay->nsec3_del);
    }
    if(!treeset_avl_isempty(&replay->nsec3_add))
    {
        /* stuff to add */

        treeset_avl_iterator ts_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3_add, &ts_avl_iter);

        while(treeset_avl_iterator_hasnext(&ts_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&ts_avl_iter);
            u8 *fqdn = (u8*)node->key;

#ifndef NDEBUG
            log_debug("journal: NSEC3: post/add %{dnsname}", fqdn);
#endif

            zdb_ttlrdata *ttlrdata = (zdb_ttlrdata*)node->data;

#ifndef NDEBUG
            log_debug("journal: NSEC3: add %{dnsname}", fqdn);

            rdata_desc type_len_rdata = {TYPE_NSEC3, ttlrdata->rdata_size, ttlrdata->rdata_pointer };
            log_debug("journal: NSEC3: + %{typerdatadesc}", &type_len_rdata);
#endif

            /*
             * The node must be added.  It should not exist already.
             * After all changes (del/upd/add) all the added records should be matched again (check)
             */

            nsec3_zone_item *add_item = nsec3_zone_item_find_by_record(replay->zone, fqdn, ttlrdata->rdata_size, ttlrdata->rdata_pointer);
            
            if(add_item != NULL)
            {
                log_err("journal: NSEC3: already exists");
                
                nsec3_zone *n3 = replay->zone->nsec.nsec3;
                
                if(n3 != NULL )
                {
                    zdb_packed_ttlrdata *nsec3;
                    zdb_packed_ttlrdata *nsec3_rrsig;
                    u8 owner[256];
                    
                    nsec3_zone_item_to_zdb_packed_ttlrdata(n3,
                                                    add_item,
                                                    replay->zone->origin,
                                                    owner,
                                                    600,
                                                    &nsec3,
                                                    &nsec3_rrsig);
                    
#ifndef NDEBUG
                    rdata_desc type_len_rdata = {TYPE_NSEC3, nsec3->rdata_size, nsec3->rdata_start };
                    log_debug("journal: NSEC3: ? %{typerdatadesc}", &type_len_rdata);
#endif
                    
                    free(nsec3);
                    
                    nsec3_remove_nsec3_by_digest(replay->zone, add_item->digest, ttlrdata->rdata_pointer);
                }
            }
            
            nsec3_add_nsec3_by_name(replay->zone, fqdn, ttlrdata->rdata_pointer, ttlrdata->rdata_size);

            zdb_ttlrdata_delete(ttlrdata);
            free(fqdn);
            
            node->key = NULL;
            node->data = NULL;
        }

        treeset_avl_destroy(&replay->nsec3_add);
    }
    if(!treeset_avl_isempty(&replay->nsec3rrsig_del))
    {
        /* stuff to add */

        treeset_avl_iterator ts_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3rrsig_del, &ts_avl_iter);

        while(treeset_avl_iterator_hasnext(&ts_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&ts_avl_iter);
            u8 *fqdn = (u8*)node->key;
            
#ifndef NDEBUG
            log_debug("journal: NSEC3: post/add %{dnsname}", fqdn);
#endif

            zdb_ttlrdata *nsec3_rrsig = (zdb_ttlrdata*)node->data;

#ifndef NDEBUG
            log_debug("journal: NSEC3: add %{dnsname}", fqdn);

            rdata_desc type_len_rdata = {TYPE_RRSIG, ZDB_RECORD_PTR_RDATASIZE(nsec3_rrsig), ZDB_RECORD_PTR_RDATAPTR(nsec3_rrsig) };
            log_debug("journal: NSEC3: + %{typerdatadesc}", &type_len_rdata);
#endif

            /*
             * The node must be added.  It should not exist already.
             * After all changes (del/upd/add) all the added records should be matched again (check)
             */
            nsec3_zone_item *item = nsec3_zone_item_find_by_name_ext(replay->zone, fqdn, NULL);

            if(item != NULL)
            {
                nsec3_zone_item_rrsig_del(item, nsec3_rrsig);
            }

            zdb_ttlrdata_delete(nsec3_rrsig);
            free(fqdn);
            
            node->key = NULL;
            node->data = NULL;
        }

        treeset_avl_destroy(&replay->nsec3rrsig_del);
    }
    if(!treeset_avl_isempty(&replay->nsec3rrsig_add))
    {
        /* stuff to add */

        treeset_avl_iterator ts_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3rrsig_add, &ts_avl_iter);

        while(treeset_avl_iterator_hasnext(&ts_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&ts_avl_iter);
            u8 *fqdn = (u8*)node->key;

#ifndef NDEBUG
            log_debug("journal: NSEC3: post/add %{dnsname}", fqdn);
#endif

            zdb_packed_ttlrdata *nsec3_rrsig = (zdb_packed_ttlrdata*)node->data;

#ifndef NDEBUG
            log_debug("journal: NSEC3: add %{dnsname}", fqdn);

            rdata_desc type_len_rdata = {TYPE_RRSIG, ZDB_PACKEDRECORD_PTR_RDATASIZE(nsec3_rrsig), ZDB_PACKEDRECORD_PTR_RDATAPTR(nsec3_rrsig) };
            log_debug("journal: NSEC3: + %{typerdatadesc}", &type_len_rdata);
#endif

            /*
             * The node must be added.  It should not exist already.
             * After all changes (del/upd/add) all the added records should be matched again (check)
             */
            nsec3_zone_item *item = nsec3_zone_item_find_by_name_ext(replay->zone, fqdn, NULL);

            if(item != NULL)
            {
                nsec3_zone_item_rrsig_add(item, nsec3_rrsig);
            }
            else
            {
                ZDB_RECORD_ZFREE(nsec3_rrsig);
            }

            free(fqdn);
            
            node->key = NULL;
            node->data = NULL;
        }

        treeset_avl_destroy(&replay->nsec3rrsig_add);
    }
    if(!treeset_avl_isempty(&replay->nsec3_labels))
    {
        /* labels to update */

        treeset_avl_iterator ts_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3_labels, &ts_avl_iter);

        while(treeset_avl_iterator_hasnext(&ts_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&ts_avl_iter);
            u8 *fqdn = (u8*)node->key;
            zdb_rr_label *rr_label = (zdb_rr_label*)node->data;

#ifndef NDEBUG
            log_debug("journal: NSEC3: lbl %{dnsname} (%{dnslabel})", fqdn, rr_label->name);
#endif
            
            /*
             * The fqdn/label should be updated for self & star match.
             */

            if(rr_label->nsec.nsec3 == NULL)
            {
                nsec3_label_link(replay->zone, rr_label, fqdn);
            }
            
            free(fqdn);
            
            node->key = NULL;
            node->data = NULL;
        }

        treeset_avl_destroy(&replay->nsec3_labels);
    }
    
    /**/
    
    if(nsec3param_added)
    {
        /*
         * ALL the labels of the zone have to be linked again.
         */
        
        zdb_zone_label_iterator label_iterator;
        
        u8 fqdn[MAX_DOMAIN_LENGTH];
        
        
        zdb_zone_label_iterator_init(replay->zone, &label_iterator);

        while(zdb_zone_label_iterator_hasnext(&label_iterator))
        {
            
            zdb_zone_label_iterator_nextname(&label_iterator, fqdn);

            zdb_rr_label* label = zdb_zone_label_iterator_next(&label_iterator);
         
            nsec3_label_link(replay->zone, label, fqdn);
        }
    }
    
    if(!treeset_avl_isempty(&replay->nsec3param_del))
    {
        treeset_avl_iterator n3p_avl_iter;
        treeset_avl_iterator_init(&replay->nsec3param_del, &n3p_avl_iter);

        while(treeset_avl_iterator_hasnext(&n3p_avl_iter))
        {
            treeset_node *node = treeset_avl_iterator_next_node(&n3p_avl_iter);
            zdb_ttlrdata* nsec3param = (zdb_ttlrdata*)node->data;
            
            nsec3_zone* n3 = nsec3_zone_get_from_rdata(replay->zone, nsec3param->rdata_size, nsec3param->rdata_pointer);
            
            if(n3 == NULL)
            {
                nsec3_zone_destroy(replay->zone, n3);
                
                zdb_record_delete_exact(&replay->zone->apex->resource_record_set, TYPE_NSEC3PARAM, nsec3param);
            }
            
            zdb_ttlrdata_delete(nsec3param);
            
            node->key = NULL;
            node->data = NULL;
        }
        
        treeset_avl_destroy(&replay->nsec3param_del);
    }    
    
    return SUCCESS;
}
Exemplo n.º 2
0
ya_result
zdb_icmtl_replay_commit(zdb_zone *zone, input_stream *is, u32 *current_serialp)
{
    ya_result ret;
    /*
     * 0: DELETE, 1: ADD
     * The mode is switched every time an SOA is found.
     */

    yassert(zdb_zone_islocked(zone));
    
#if ZDB_HAS_NSEC3_SUPPORT
    bool has_nsec3 = zdb_zone_is_nsec3(zone);
#endif
#if ZDB_HAS_NSEC_SUPPORT
    bool has_nsec = zdb_zone_is_nsec(zone);
#endif
    
#if ZDB_HAS_NSEC3_SUPPORT && ZDB_HAS_NSEC_SUPPORT
    if(has_nsec3 && has_nsec)
    {
        log_err("journal: %{dnsname}: zone has both NSEC and NSEC3 status, which is not supported by YADIFA", zone->origin);
        return ERROR;
    }
#endif
    
    u8 mode = 1; // the first SOA will switch the mode to delete
    s32 changes = 0;
    
    zdb_ttlrdata ttlrdata;
    dns_resource_record rr;
    dns_resource_record_init(&rr);
    const u8 *fqdn = rr.name;
    dnslabel_vector labels;

    ttlrdata.next = NULL;
    


    /*
     * The plan for NSEC3 :
     * Store the fqdn + type class ttl rdata in collections
     * => the delete collection
     * => the add collection
     * Then there is the NSEC3 covered labels: keep a reference to them for later
     *
     * When a pass of SOA-/SOA+ has finished:
     * _ replace the NSEC3 in both collections (reading from delete)
     * _ delete NSEC3 to delete
     * _ add NSEC3 to add
     *
     * _ and finally update the NSEC3 for the labels kept above
     */

#if ZDB_HAS_NSEC3_SUPPORT
    chain_replay nsec3replay;
    nsec3_chain_replay_init(&nsec3replay, zone);
#endif
    
#if ZDB_HAS_NSEC_SUPPORT
    chain_replay nsecreplay;
    nsec_chain_replay_init(&nsecreplay, zone);
#endif
    
#if ZDB_HAS_NSEC3_SUPPORT
    ptr_set downed_fqdn = PTR_SET_DNSNAME_EMPTY;
#endif

    /* 
     * At this point : the next record, if it exists AND is not an SOA , has to be deleted
     * 
     */
    
    bool did_remove_soa = FALSE;

    // something has to be committed

    for(;;)
    {
        /*
         * read the full record
         * 
         * == 0 : no record (EOF)
         *  < 0 : failed
         */

        if((ret = dns_resource_record_read(&rr, is)) <= 0)
        {
            if(ISOK(ret))
            {
                log_info("journal: %{dnsname}: reached the end of the journal page", zone->origin);
            }
            else
            {
                log_err("journal: %{dnsname}: broken journal: %r", zone->origin, ret);
                logger_flush(); // broken journal (bad, keep me)
            }

            break;
        }
        
        ttlrdata.ttl = ntohl(rr.tctr.ttl);
        ttlrdata.rdata_pointer = rr.rdata;
        ttlrdata.rdata_size = rr.rdata_size;

        /*
         * Stop at the SOA
         */

        if(rr.tctr.qtype == TYPE_SOA)
        {
            mode ^= 1;

            if(mode == 0)
            {
                /* ADD */

#if ZDB_HAS_NSEC3_SUPPORT
                // NSEC3
                {
                    ret = nsec3replay.vtbl->execute(&nsec3replay);

                    if(FAIL(ret))
                    {
                        dns_resource_record_clear(&rr);
                        // DO NOT: input_stream_close(is);

                        nsec3replay.vtbl->finalise(&nsec3replay);
                        
#if ZDB_HAS_NSEC_SUPPORT
                        nsecreplay.vtbl->finalise(&nsecreplay);
#endif // ZDB_HAS_NSEC_SUPPORT
                        return ret;
                    }
                }
#endif
                
#if ZDB_HAS_NSEC_SUPPORT
                // NSEC
                ret = nsecreplay.vtbl->execute(&nsecreplay);
#endif //ZDB_HAS_NSEC_SUPPORT
            }
        }

        if(!did_remove_soa)
        {
            log_info("journal: %{dnsname}: removing obsolete SOA", zone->origin);

            if(FAIL(ret = zdb_record_delete(&zone->apex->resource_record_set, TYPE_SOA)))
            {
                /**
                * complain
                */

                log_err("journal: %{dnsname}: removing current SOA gave an error: %r", zone->origin, ret);

                /* That's VERY bad ... */

                changes = ret;

                break;
            }

            did_remove_soa = TRUE;
        }

        s32 top = dnsname_to_dnslabel_vector(fqdn, labels);

        if(mode == 0)
        {
            /*
             * "TO DEL" record
             */

#if ICMTL_DUMP_JOURNAL_RECORDS
            rdata_desc type_len_rdata = {rr.tctr.qtype, rr.rdata_size, rr.rdata };
            log_debug("journal: del %{dnsname} %{typerdatadesc}", fqdn, &type_len_rdata);
            logger_flush();
#endif
            bool added_in_chain = FALSE;

#if ZDB_HAS_NSEC3_SUPPORT
            
            // 0 : proceed
            // 1 : ignore
            // ? : error
            
            if((added_in_chain = (nsec3replay.vtbl->record_del(&nsec3replay, fqdn, rr.tctr.qtype, &ttlrdata) != 0)))
            {
                // add everything up until a non-empty terminal is found (the apex will thus be automatically avoided)
                // if the record is a delegation, add everything down too
                /*
                if((top > zone->origin_vector.size) &&
                        (rr.tctr.qtype != TYPE_NSEC3) &&
                        (   (rr.tctr.qtype != TYPE_RRSIG) ||
                            ((rr.tctr.qtype == TYPE_RRSIG) && (GET_U16_AT_P(ZDB_RECORD_PTR_RDATAPTR(&ttlrdata)) != TYPE_NSEC3))
                        )
                    )
                {
                    
                }
                */
                ++changes;
            }
            else
            {
                if(top > zone->origin_vector.size)
                {
                    const u8 *above_fqdn = fqdn;
                    for(int i = 1; i < top - zone->origin_vector.size; ++i)
                    {
                        zdb_rr_label *above = zdb_rr_label_find_exact(zone->apex, &labels[i], top - zone->origin_vector.size - 1 - i);
                        if(above != NULL)
                        {
                            if(btree_notempty(above->resource_record_set))
                            {
                                break;
                            }
                        }

                        above_fqdn += above_fqdn[0] + 1;
                        nsec3replay.vtbl->record_del(&nsec3replay, above_fqdn, TYPE_NONE, NULL);
                    }

                    zdb_rr_label *rr_label = zdb_rr_label_find_exact(zone->apex, labels, (top - zone->origin_vector.size) - 1);
                    if(rr_label != NULL)
                    {
                        zdb_rr_label_forall_children_of_fqdn(rr_label, fqdn, zdb_icmtl_replay_commit_label_forall_nsec3_del_cb, &nsec3replay);
                    }
                }
            }
#endif
#if ZDB_HAS_NSEC_SUPPORT
            
            // 0 : proceed
            // 1 : ignore
            // ? : error
            
            if(!added_in_chain && (added_in_chain = nsecreplay.vtbl->record_del(&nsecreplay, fqdn, rr.tctr.qtype, &ttlrdata) != 0))
            {
                ++changes;
            }
            //else
#endif
            if(!added_in_chain)
            switch(rr.tctr.qtype)
            {
                case TYPE_SOA:
                {
                    rdata_desc rdata = {TYPE_SOA, ttlrdata.rdata_size, ttlrdata.rdata_pointer};
                    log_info("journal: %{dnsname}: SOA: del %{dnsname} %{typerdatadesc}", zone->origin, fqdn, &rdata);

                    s32 m1 = (top - zone->origin_vector.size) - 1;

                    if(m1 == -1)
                    {
                        if(FAIL(ret = zdb_record_delete_exact(&zone->apex->resource_record_set, TYPE_SOA, &ttlrdata))) /* FB done, APEX : no delegation, source is the journal */
                        {
                            if(!did_remove_soa)
                            {
                                log_err("journal: %{dnsname}: SOA: %r", zone->origin, ret);
                            }
                        }
                    }
                    else
                    {
                        if(FAIL(ret = zdb_rr_label_delete_record_exact(zone, labels, (top - zone->origin_vector.size) - 1, rr.tctr.qtype, &ttlrdata))) // source is journal
                        {
                            if(!did_remove_soa)
                            {
                                log_err("journal: %{dnsname}: SOA: (2) %r", zone->origin, ret);
                            }
                        }
                    }
                    break;
                }

                default:
                {
#if ZDB_HAS_NSEC3_SUPPORT
                    // NSEC3
                    if(rr.tctr.qtype != TYPE_NSEC3)
                    {
                        if((rr.tctr.qtype != TYPE_RRSIG) && (rrsig_get_type_covered_from_rdata(rr.rdata, rr.rdata_size) != TYPE_NSEC3))
                        {
                            if(ptr_set_avl_find(&downed_fqdn, fqdn) == NULL)
                            {
                                ptr_set_avl_insert(&downed_fqdn, dnsname_dup(fqdn));
                            }
                        }
                    }
                    else
                    {
                        if(!NSEC3_RDATA_IS_OPTOUT(rr.rdata))
                        {
                            zone->_flags &= ~ZDB_ZONE_HAS_OPTOUT_COVERAGE;
                        }
                    }
#endif
                    if(FAIL(ret = zdb_rr_label_delete_record_exact(zone, labels, (top - zone->origin_vector.size) - 1, rr.tctr.qtype, &ttlrdata))) // source is journal
                    {
                        // signatures can be removed automatically by maintenance
                        
                        if((rr.tctr.qtype != TYPE_RRSIG) && (ret != ZDB_ERROR_KEY_NOTFOUND))
                        {
                            log_err("journal: %{dnsname}: del %{dnsrr}", zone->origin, &rr);
                            log_err("journal: %{dnsname}: %{dnstype}: %r", zone->origin, &rr.tctr.qtype, ret);
                        }
                        else
                        {
                            log_debug("journal: %{dnsname}: del %{dnsrr}", zone->origin, &rr);
                            log_debug("journal: %{dnsname}: %{dnstype}: %r", zone->origin, &rr.tctr.qtype, ret);
                        }
                    }
                }
            }
        }
        else
        {
            /*
             * "TO ADD" record
             */
            
            bool added_in_chain = FALSE;
            
#if ZDB_HAS_NSEC3_SUPPORT

            // returns the number of changes taken into account (0 or 1)
            // 0 : proceed
            // 1 : ignore
            // ? : error
            
            if((added_in_chain = (nsec3replay.vtbl->record_add(&nsec3replay, fqdn, rr.tctr.qtype, &ttlrdata) != 0)))
            {
                ++changes;
            }
            else
            {
                if( (top > zone->origin_vector.size) &&
                    (rr.tctr.qtype != TYPE_NSEC3) &&
                    (   (rr.tctr.qtype != TYPE_RRSIG) ||
                        ((rr.tctr.qtype == TYPE_RRSIG) && (GET_U16_AT_P(ZDB_RECORD_PTR_RDATAPTR(&ttlrdata)) != TYPE_NSEC3))
                        )
                    )
                {
                    const u8 *above_fqdn = fqdn;
                    for(int i = 1; i < top - zone->origin_vector.size; ++i)
                    {
                        zdb_rr_label *above = zdb_rr_label_find_exact(zone->apex, &labels[i], top - zone->origin_vector.size - 1 - i);
                        if(above != NULL)
                        {
                            if(btree_notempty(above->resource_record_set))
                            {
                                break;
                            }
                        }

                        above_fqdn += above_fqdn[0] + 1;
                        nsec3replay.vtbl->record_add(&nsec3replay, above_fqdn, TYPE_NONE, NULL);
                    }

                    zdb_rr_label *rr_label = zdb_rr_label_find_exact(zone->apex, labels, (top - zone->origin_vector.size) - 1);
                    if(rr_label != NULL)
                    {
                        zdb_rr_label_forall_children_of_fqdn(rr_label, fqdn, zdb_icmtl_replay_commit_label_forall_nsec3_add_cb, &nsec3replay);
                    }
                }
            }
#endif
#if ZDB_HAS_NSEC_SUPPORT
            
            // returns the number of changes taken into account (0 or 1)
            // 0 : proceed
            // 1 : ignore
            // ? : error
            
            if(!added_in_chain && (added_in_chain = (nsecreplay.vtbl->record_add(&nsecreplay, fqdn, rr.tctr.qtype, &ttlrdata) != 0)))
            {
                ++changes;
            }
            //else
#endif
            if(!added_in_chain)
            switch(rr.tctr.qtype)
            {
#if ZDB_HAS_NSEC3_SUPPORT
                case TYPE_NSEC3CHAINSTATE:
                {
                    // create chain if missing ...
                    
                    nsec3_zone_add_from_rdata(zone, rr.rdata_size, rr.rdata);
                    
                    // add the record
                    
                    zdb_packed_ttlrdata *packed_ttlrdata;

                    ZDB_RECORD_ZALLOC_EMPTY(packed_ttlrdata, ttlrdata.ttl, rr.rdata_size);
                    packed_ttlrdata->next = NULL;
                    MEMCOPY(ZDB_PACKEDRECORD_PTR_RDATAPTR(packed_ttlrdata), rr.rdata, rr.rdata_size);
                    zdb_zone_record_add(zone, labels, top, rr.tctr.qtype, packed_ttlrdata); // class is implicit, flow verified
                    
                    break;
                }
#endif // ZDB_HAS_NSEC3_SUPPORT
#
                default:
                {
                    zdb_packed_ttlrdata *packed_ttlrdata;

                    ZDB_RECORD_ZALLOC_EMPTY(packed_ttlrdata, ttlrdata.ttl, rr.rdata_size);
                    packed_ttlrdata->next = NULL;
                    MEMCOPY(ZDB_PACKEDRECORD_PTR_RDATAPTR(packed_ttlrdata), rr.rdata, rr.rdata_size);

#if ICMTL_DUMP_JOURNAL_RECORDS
                    rdata_desc type_len_rdata = {rr.tctr.qtype, rr.rdata_size, ZDB_PACKEDRECORD_PTR_RDATAPTR(packed_ttlrdata) };
                    log_debug("journal: add %{dnsname} %{typerdatadesc}", fqdn, &type_len_rdata);
                    logger_flush();
#endif
                    if(rr.tctr.qtype == TYPE_SOA)
                    {
                        rr_soa_get_serial(ZDB_PACKEDRECORD_PTR_RDATAPTR(packed_ttlrdata), ZDB_PACKEDRECORD_PTR_RDATASIZE(packed_ttlrdata), current_serialp);
                        rdata_desc rdata = {TYPE_SOA, ZDB_PACKEDRECORD_PTR_RDATASIZE(packed_ttlrdata), ZDB_PACKEDRECORD_PTR_RDATAPTR(packed_ttlrdata)};
                        log_info("journal: %{dnsname}: SOA: add %{dnsname} %{typerdatadesc}", zone->origin, fqdn, &rdata);
                    }
                    
                    zdb_zone_record_add(zone, labels, top, rr.tctr.qtype, packed_ttlrdata); // class is implicit, flow verified
                }
            }            
        } // end if ADD

        changes++;
    }

    /*
     * Yes, I know.  If 2^32 changes (add batch + del batch) occurs then it will be seen as an error ...
     */

    if(ISOK(changes))
    {
#if ZDB_HAS_NSEC3_SUPPORT && ZDB_HAS_NSEC_SUPPORT
        
        if(has_nsec3 && has_nsec)
        {
            log_warn("journal: %{dnsname}: both NSEC3 and NSEC operations happened, which is not supported by YADIFA. Keeping the original one.", zone->origin);
            
            has_nsec3 = zdb_zone_is_nsec3(zone);
            has_nsec = zdb_zone_is_nsec(zone);
        }        
#endif
        
#if ZDB_HAS_NSEC3_SUPPORT
        nsec3replay.vtbl->execute(&nsec3replay);
#endif

#if ZDB_HAS_NSEC_SUPPORT
        nsecreplay.vtbl->execute(&nsecreplay);
#endif
    }
    
#if ZDB_HAS_NSEC3_SUPPORT
    // has_nsec3 = zdb_zone_is_nsec3(zone);
    nsec3replay.vtbl->finalise(&nsec3replay);
#endif
#if ZDB_HAS_NSEC_SUPPORT
    // has_nsec = zdb_zone_is_nsec(zone);
    nsecreplay.vtbl->finalise(&nsecreplay);
#endif
    
    dns_resource_record_clear(&rr);


    
    return changes;
}