1/*
2 * xfrd-catalog-zones.c -- catalog zone implementation for NSD
3 *
4 * Copyright (c) 2024, NLnet Labs. All rights reserved.
5 *
6 * See LICENSE for the license.
7 */
8#include "config.h"
9#include "difffile.h"
10#include "nsd.h"
11#include "packet.h"
12#include "xfrd-catalog-zones.h"
13#include "xfrd-notify.h"
14
15
16/******************                                        ******************
17 ******************    catalog consumer zone processing    ******************
18 ******************                                        ******************/
19
20/** process a catalog consumer zone, load if needed */
21static void xfrd_process_catalog_consumer_zone(
22		struct xfrd_catalog_consumer_zone* consumer_zone);
23
24/** make the catalog consumer zone invalid for given reason */
25static void vmake_catalog_consumer_invalid(
26	struct xfrd_catalog_consumer_zone *consumer_zone,
27	const char *format, va_list args);
28
29/** return (static) dname with label prepended to dname */
30static dname_type* label_plus_dname(const char* label,const dname_type* dname);
31
32/** delete the catalog member zone */
33static void catalog_del_consumer_member_zone(
34		struct xfrd_catalog_consumer_zone* consumer_zone,
35		struct catalog_member_zone* consumer_member_zone);
36
37#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
38/* return a single catalog consumer zone from xfrd struct */
39static inline struct xfrd_catalog_consumer_zone*
40xfrd_one_catalog_consumer_zone()
41{
42	return xfrd
43	    && xfrd->catalog_consumer_zones
44	    && xfrd->catalog_consumer_zones->count == 1
45	     ? (struct xfrd_catalog_consumer_zone*)
46	       rbtree_first(xfrd->catalog_consumer_zones) : NULL;
47}
48#endif
49
50/** return the catalog-member-pattern or NULL on error if not present */
51static inline struct pattern_options*
52catalog_member_pattern(struct xfrd_catalog_consumer_zone* consumer_zone)
53{
54	if (!consumer_zone->options->pattern
55	||  !consumer_zone->options->pattern->catalog_member_pattern)
56		return NULL;
57	return pattern_options_find(xfrd->nsd->options,
58		consumer_zone->options->pattern->catalog_member_pattern);
59}
60
61/** see if we have more zonestatistics entries and it has to be incremented */
62static inline void
63zonestat_inc_ifneeded()
64{
65#ifdef USE_ZONE_STATS
66        if(xfrd->nsd->options->zonestatnames->count != xfrd->zonestat_safe)
67                task_new_zonestat_inc(xfrd->nsd->task[xfrd->nsd->mytask],
68                        xfrd->last_task,
69                        xfrd->nsd->options->zonestatnames->count);
70#endif /* USE_ZONE_STATS */
71}
72
73
74/******************                                        ******************
75 ******************    catalog producer zone processing    ******************
76 ******************                                        ******************/
77
78/** process catalog producer zone producer_zone */
79static void xfrd_process_catalog_producer_zone(
80		struct xfrd_catalog_producer_zone* producer_zone);
81
82/** rbnode must be struct catalog_member_zone*; compares (key->member_id) */
83static int member_id_compare(const void *left, const void *right);
84
85/** return xfrd_catalog_producer_zone* pointed to by cmz' catalog-producer-zone
86 * pattern option. struct is created if necessary. returns NULL on failure. */
87static struct xfrd_catalog_producer_zone* xfrd_get_catalog_producer_zone(
88		struct catalog_member_zone* cmz);
89
90/** helper struct for generating XFR files, for conveying the catalog producer
91 *  zone content to the server process.
92 */
93struct xfrd_xfr_writer {
94	struct xfrd_catalog_producer_zone* producer_zone;
95	char packet_space[16384];
96	buffer_type packet;
97	uint32_t seq_nr; /* number of messages already handled */
98	uint32_t old_serial, new_serial; /* host byte order */
99	uint64_t xfrfilenumber; /* identifier for file to store xfr into */
100};
101
102/** initialize xfrd_xfr_writer struct xw */
103static void xfr_writer_init(struct xfrd_xfr_writer* xw,
104		struct xfrd_catalog_producer_zone* producer_zone);
105
106/** write packet from xfrd_xfr_writer struct xw to xfr file */
107static void xfr_writer_write_packet(struct xfrd_xfr_writer* xw);
108
109/** commit xfr file (send to server process), with provided log message */
110static void xfr_writer_commit(struct xfrd_xfr_writer* xw, const char *fmt,
111		...);
112
113/** try writing SOA RR with serial to packet buffer. returns 0 on failure */
114static int try_buffer_write_SOA(buffer_type* packet, const dname_type* owner,
115		uint32_t serial);
116
117/** try writing RR to packet buffer. returns 0 on failure */
118static int try_buffer_write_RR(buffer_type* packet, const dname_type* owner,
119		uint16_t rr_type, uint16_t rdata_len, const void* rdata);
120
121/** try writing PTR RR to packet buffer. returns 0 on failure */
122static inline int try_buffer_write_PTR(buffer_type* packet,
123		const dname_type* owner, const dname_type* name);
124
125/** try writing TXT RR to packet buffer. returns 0 on failure */
126static int try_buffer_write_TXT(buffer_type* packet, const dname_type* name,
127		const char *txt);
128
129/** add SOA RR with serial serial to xfrd_xfr_writer xw */
130static inline void xfr_writer_add_SOA(struct xfrd_xfr_writer* xw,
131		const dname_type* owner, uint32_t serial)
132{
133	if(try_buffer_write_SOA(&xw->packet, owner, serial))
134		return;
135	xfr_writer_write_packet(xw);
136	assert(buffer_position(&xw->packet) == 12);
137	try_buffer_write_SOA(&xw->packet, owner, serial);
138}
139
140/** add RR to xfrd_xfr_writer xw */
141static inline void xfr_writer_add_RR(struct xfrd_xfr_writer* xw,
142		const dname_type* owner,
143		uint16_t rr_type, uint16_t rdata_len, const void* rdata)
144{
145	if(try_buffer_write_RR(&xw->packet, owner, rr_type, rdata_len, rdata))
146		return;
147	xfr_writer_write_packet(xw);
148	assert(buffer_position(&xw->packet) == 12);
149	try_buffer_write_RR(&xw->packet, owner, rr_type, rdata_len, rdata);
150}
151
152/** add PTR RR to xfrd_xfr_writer xw */
153static inline void xfr_writer_add_PTR(struct xfrd_xfr_writer* xw,
154		const dname_type* owner, const dname_type* name)
155{
156	if(try_buffer_write_PTR(&xw->packet, owner, name))
157		return;
158	xfr_writer_write_packet(xw);
159	assert(buffer_position(&xw->packet) == 12);
160	try_buffer_write_PTR(&xw->packet, owner, name);
161}
162
163/** add TXT RR to xfrd_xfr_writer xw */
164static inline void xfr_writer_add_TXT(struct xfrd_xfr_writer* xw,
165		const dname_type* owner, const char* txt)
166{
167	if(try_buffer_write_TXT(&xw->packet, owner, txt))
168		return;
169	xfr_writer_write_packet(xw);
170	assert(buffer_position(&xw->packet) == 12);
171	try_buffer_write_TXT(&xw->packet, owner, txt);
172}
173
174
175/******************                                        ******************
176 ******************    catalog consumer zone processing    ******************
177 ******************                                        ******************/
178
179void
180xfrd_init_catalog_consumer_zone(xfrd_state_type* xfrd,
181		struct zone_options* zone)
182{
183	struct xfrd_catalog_consumer_zone* consumer_zone;
184
185	if ((consumer_zone = (struct xfrd_catalog_consumer_zone*)rbtree_search(
186			xfrd->catalog_consumer_zones, zone->node.key))) {
187		log_msg(LOG_ERR, "cannot initialize new catalog consumer zone:"
188				" '%s: it already exists in xfrd's catalog "
189				" consumer zones index", zone->name);
190		/* Maybe we need to reprocess it? */
191		make_catalog_consumer_valid(consumer_zone);
192		return;
193	}
194	consumer_zone = (struct xfrd_catalog_consumer_zone*)
195		region_alloc(xfrd->region,
196			sizeof(struct xfrd_catalog_consumer_zone));
197        memset(consumer_zone, 0, sizeof(struct xfrd_catalog_consumer_zone));
198        consumer_zone->node.key = zone->node.key;
199        consumer_zone->options = zone;
200	consumer_zone->member_ids.region = xfrd->region;
201	consumer_zone->member_ids.root = RBTREE_NULL;
202	consumer_zone->member_ids.count = 0;
203	consumer_zone->member_ids.cmp = member_id_compare;
204	consumer_zone->mtime.tv_sec = 0;
205	consumer_zone->mtime.tv_nsec = 0;
206
207	consumer_zone->invalid = NULL;
208	rbtree_insert(xfrd->catalog_consumer_zones,
209			(rbnode_type*)consumer_zone);
210#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
211	if ((int)xfrd->catalog_consumer_zones->count > 1) {
212		log_msg(LOG_ERR, "catalog consumer processing disabled: "
213			"only one single catalog consumer zone allowed");
214	}
215#endif
216	if(zone->pattern && zone->pattern->store_ixfr) {
217		/* Don't process ixfrs from xfrd */
218		zone->pattern->store_ixfr = 0;
219	}
220}
221
222void
223xfrd_deinit_catalog_consumer_zone(xfrd_state_type* xfrd,
224		const dname_type* dname)
225{
226	struct xfrd_catalog_consumer_zone* consumer_zone;
227	zone_type* zone;
228
229	if (!(consumer_zone =(struct xfrd_catalog_consumer_zone*)rbtree_delete(
230			xfrd->catalog_consumer_zones, dname))) {
231		log_msg(LOG_ERR, "cannot de-initialize catalog consumer zone:"
232				" '%s: it did not exist in xfrd's catalog "
233				" consumer zones index",
234				dname_to_string(dname, NULL));
235		return;
236	}
237	if (consumer_zone->member_ids.count)
238		log_msg(LOG_WARNING, "de-initialize catalog consumer zone:"
239				" '%s: will cause all member zones to be "
240				" deleted", consumer_zone->options->name);
241
242	while (consumer_zone->member_ids.count) {
243		struct catalog_member_zone* cmz = (struct catalog_member_zone*)
244			rbtree_first(&consumer_zone->member_ids)->key;
245
246		log_msg(LOG_INFO, "deleting member zone '%s' on "
247			"de-initializing catalog consumer zone '%s'",
248			cmz->options.name, consumer_zone->options->name);
249		catalog_del_consumer_member_zone(consumer_zone, cmz);
250	}
251	if ((zone = namedb_find_zone(xfrd->nsd->db, dname))) {
252		namedb_zone_delete(xfrd->nsd->db, zone);
253	}
254	region_recycle(xfrd->region, consumer_zone, sizeof(*consumer_zone));
255#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
256	if((consumer_zone = xfrd_one_catalog_consumer_zone())
257	&&  consumer_zone->options && consumer_zone->options->node.key) {
258		xfrd_zone_type* zone = (xfrd_zone_type*)rbtree_search(
259			xfrd->zones,
260			(const dname_type*)consumer_zone->options->node.key);
261
262		if(zone) {
263			zone->soa_disk_acquired = 0;
264			zone->soa_nsd_acquired = 0;
265			xfrd_handle_notify_and_start_xfr(zone, NULL);
266		}
267	}
268#endif
269}
270
271/** make the catalog consumer zone invalid for given reason */
272static void
273vmake_catalog_consumer_invalid(
274		struct xfrd_catalog_consumer_zone *consumer_zone,
275		const char *format, va_list args)
276{
277	char message[MAXSYSLOGMSGLEN];
278	if (!consumer_zone || consumer_zone->invalid) return;
279        vsnprintf(message, sizeof(message), format, args);
280	log_msg(LOG_ERR, "invalid catalog consumer zone '%s': %s",
281		consumer_zone->options->name, message);
282	consumer_zone->invalid = region_strdup(xfrd->region, message);
283}
284
285void
286make_catalog_consumer_invalid(struct xfrd_catalog_consumer_zone *consumer_zone,
287		const char *format, ...)
288{
289	va_list args;
290	if (!consumer_zone || consumer_zone->invalid) return;
291	va_start(args, format);
292	vmake_catalog_consumer_invalid(consumer_zone, format, args);
293	va_end(args);
294}
295
296void
297make_catalog_consumer_valid(struct xfrd_catalog_consumer_zone *consumer_zone)
298{
299	if (consumer_zone->invalid) {
300		region_recycle(xfrd->region, consumer_zone->invalid,
301				strlen(consumer_zone->invalid) + 1);
302		consumer_zone->invalid = NULL;
303	}
304}
305
306static dname_type*
307label_plus_dname(const char* label, const dname_type* dname)
308{
309	static struct {
310		dname_type dname;
311		uint8_t bytes[MAXDOMAINLEN + 128 /* max number of labels */];
312	} ATTR_PACKED name;
313	size_t i, ll;
314
315	if (!label || !dname || dname->label_count > 127)
316		return NULL;
317	ll = strlen(label);
318	if ((int)dname->name_size + ll + 1 > MAXDOMAINLEN)
319		return NULL;
320
321	/* In reversed order and first copy with memmove, so we can nest.
322	 * i.e. label_plus_dname(label1, label_plus_dname(label2, dname))
323	 */
324	memmove(name.bytes + dname->label_count
325			+ 1 /* label_count increases by one */
326			+ 1 /* label type/length byte for label */ + ll,
327		((void*)dname) + sizeof(dname_type) + dname->label_count,
328		dname->name_size);
329	memcpy(name.bytes + dname->label_count
330			+ 1 /* label_count increases by one */
331			+ 1 /* label type/length byte for label */, label, ll);
332	name.bytes[dname->label_count + 1] = ll; /* label type/length byte */
333	name.bytes[dname->label_count] = 0; /* first label follows last
334	                                     * label_offsets element */
335	for (i = 0; i < dname->label_count; i++)
336		name.bytes[i] = ((uint8_t*)(void*)dname)[sizeof(dname_type)+i]
337			+ 1 /* label type/length byte for label */ + ll;
338	name.dname.label_count = dname->label_count + 1 /* label_count incr. */;
339	name.dname.name_size   = dname->name_size   + ll
340	                                            + 1 /* label length */;
341	return &name.dname;
342}
343
344static void
345catalog_del_consumer_member_zone(
346		struct xfrd_catalog_consumer_zone* consumer_zone,
347		struct catalog_member_zone* consumer_member_zone)
348{
349	const dname_type* dname = consumer_member_zone->options.node.key;
350
351	/* create deletion task */
352	task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
353			xfrd->last_task, dname);
354	xfrd_set_reload_now(xfrd);
355	/* delete it in xfrd */
356	if(zone_is_slave(&consumer_member_zone->options)) {
357		xfrd_del_slave_zone(xfrd, dname);
358	}
359	xfrd_del_notify(xfrd, dname);
360#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
361	/* delete it in xfrd's catalog consumers list */
362	if(zone_is_catalog_consumer(&consumer_member_zone->options)) {
363		xfrd_deinit_catalog_consumer_zone(xfrd, dname);
364	}
365#endif
366	if(consumer_member_zone->member_id) {
367		rbtree_delete(&consumer_zone->member_ids,consumer_member_zone);
368		consumer_member_zone->node = *RBTREE_NULL;
369		region_recycle( xfrd->nsd->options->region,
370			(void*)consumer_member_zone->member_id,
371			dname_total_size(consumer_member_zone->member_id));
372		consumer_member_zone->member_id = NULL;
373	}
374	zone_options_delete(xfrd->nsd->options,&consumer_member_zone->options);
375}
376
377void xfrd_check_catalog_consumer_zonefiles(const dname_type* name)
378{
379	struct xfrd_catalog_consumer_zone* consumer_zone;
380
381#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
382	consumer_zone = xfrd_one_catalog_consumer_zone();
383	if (!consumer_zone)
384		return;
385	if (name && dname_compare(name, consumer_zone->node.key) != 0)
386		return;
387	name = consumer_zone->node.key;
388	DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Mark %s "
389		"for checking", consumer_zone->options->name));
390	make_catalog_consumer_valid(consumer_zone);
391	namedb_read_zonefile(xfrd->nsd, namedb_find_or_create_zone(
392		xfrd->nsd->db, name, consumer_zone->options), NULL, NULL);
393#else
394	if (!name) {
395		RBTREE_FOR(consumer_zone, struct xfrd_catalog_consumer_zone*,
396				xfrd->catalog_consumer_zones) {
397			make_catalog_consumer_valid(consumer_zone);
398			namedb_read_zonefile(xfrd->nsd,
399				namedb_find_or_create_zone(xfrd->nsd->db,
400					consumer_zone->options->node.key,
401					consumer_zone->options),
402				NULL, NULL);
403		}
404	} else if ((consumer_zone = (struct xfrd_catalog_consumer_zone*)
405			rbtree_search(xfrd->catalog_consumer_zones, name))) {
406		make_catalog_consumer_valid(consumer_zone);
407		namedb_read_zonefile(xfrd->nsd,
408			namedb_find_or_create_zone(
409				xfrd->nsd->db, name, consumer_zone->options),
410			NULL, NULL);
411	}
412#endif
413}
414
415const char *invalid_catalog_consumer_zone(struct zone_options* zone)
416{
417	struct xfrd_catalog_consumer_zone* consumer_zone;
418	const char *msg;
419
420	if (!zone || !zone_is_catalog_consumer(zone))
421		msg = NULL;
422
423	else if (!xfrd)
424		msg = "asked for catalog information outside of xfrd process";
425
426	else if (!xfrd->catalog_consumer_zones)
427		msg = "zone not found: "
428		      "xfrd's catalog consumer zones index is empty";
429
430#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
431	else if (xfrd->catalog_consumer_zones->count > 1)
432		return "not processing: more than one catalog consumer zone "
433		       "configured and only a single one allowed";
434#endif
435	else if (!(consumer_zone = (struct xfrd_catalog_consumer_zone*)
436	         rbtree_search(xfrd->catalog_consumer_zones, zone->node.key)))
437		msg = "zone not found in xfrd's catalog consumer zones index";
438	else
439		return consumer_zone->invalid;
440
441	if (msg)
442		log_msg(LOG_ERR, "catalog consumer zone '%s': %s",
443				zone->name, msg);
444
445	return msg;
446}
447
448void xfrd_process_catalog_consumer_zones()
449{
450	struct xfrd_catalog_consumer_zone* consumer_zone;
451
452#ifndef MULTIPLE_CATALOG_CONSUMER_ZONES
453	if((consumer_zone = xfrd_one_catalog_consumer_zone()))
454		xfrd_process_catalog_consumer_zone(consumer_zone);
455#else
456	RBTREE_FOR(consumer_zone, struct xfrd_catalog_consumer_zone*,
457			xfrd->catalog_consumer_zones) {
458		xfrd_process_catalog_consumer_zone(consumer_zone);
459	}
460#endif
461}
462
463static inline struct catalog_member_zone* cursor_cmz(rbnode_type* node)
464{ return node != RBTREE_NULL ? (struct catalog_member_zone*)node->key : NULL; }
465static inline const dname_type* cursor_member_id(rbnode_type* node)
466{ return cursor_cmz(node) ? cursor_cmz(node)->member_id : NULL; }
467
468#if !defined(NDEBUG) && 1 /* Only disable for seriously slow debugging */
469static void debug_log_consumer_members(
470		struct xfrd_catalog_consumer_zone* consumer_zone)
471{
472	rbnode_type* cursor;
473	size_t i;
474
475	for ( cursor = rbtree_first(&consumer_zone->member_ids), i = 0
476	    ; cursor != RBTREE_NULL; i++, cursor = rbtree_next(cursor)) {
477		DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Catalog member %.2zu: %s = %s",
478		      i, dname_to_string(cursor_member_id(cursor), NULL),
479		      cursor_cmz(cursor)->options.name));
480	}
481}
482#else
483# define debug_log_consumer_members(x) /* nothing */
484#endif
485
486static void
487xfrd_process_catalog_consumer_zone(
488		struct xfrd_catalog_consumer_zone* consumer_zone)
489{
490	zone_type* zone;
491	const dname_type* dname;
492	domain_type *match, *closest_encloser, *member_id, *group;
493	rrset_type *rrset;
494	size_t i;
495	uint8_t version_2_found;
496	/* Currect catalog member zone */
497	rbnode_type* cursor;
498	struct pattern_options *default_pattern = NULL;
499	/* A transfer of a catalog zone can contain deletion and adding of
500	 * the same member zone. In such cases it can occur that the member
501	 * is tried to be added before it is deleted. For these exceptional
502	 * cases, we will rewalk the zone after the first pass, to retry
503	 * adding those zones.
504	 *
505	 * Initial pass is mode "try_to_add".
506	 * If a zone cannot be added, mode is set to "retry_to_add"
507	 * If after the first pass the mode is "retry_to_add",
508	 *    mode will be set to "just_add", and a second pass is done.
509	 */
510	enum { try_to_add, retry_to_add, just_add } mode;
511
512	assert(consumer_zone);
513	if (!xfrd->nsd->db) {
514		xfrd->nsd->db = namedb_open(xfrd->nsd->options);
515	}
516	dname = (const dname_type*)consumer_zone->node.key;
517	if (dname->name_size > 247) {
518		make_catalog_consumer_invalid(consumer_zone, "name too long");
519		return;
520	}
521	if (dname->label_count > 126) {
522		make_catalog_consumer_invalid(consumer_zone,"too many labels");
523		return;
524	}
525	zone = namedb_find_zone(xfrd->nsd->db, dname);
526	if (!zone) {
527		zone = namedb_zone_create(xfrd->nsd->db, dname,
528				consumer_zone->options);
529		namedb_read_zonefile(xfrd->nsd, zone, NULL, NULL);
530	}
531	if (timespec_compare(&consumer_zone->mtime, &zone->mtime) == 0) {
532		/* Not processing unchanged catalog consumer zone */
533		return;
534	}
535	consumer_zone->mtime = zone->mtime;
536	/* start processing */
537	/* Lookup version.<consumer_zone> TXT and check that it is version 2 */
538	if(!namedb_lookup(xfrd->nsd->db, label_plus_dname("version", dname),
539				&match, &closest_encloser)
540	|| !(rrset = domain_find_rrset(match, zone, TYPE_TXT))) {
541		make_catalog_consumer_invalid(consumer_zone,
542			"'version.%s TXT RRset not found",
543			consumer_zone->options->name);
544		return;
545	}
546	version_2_found = 0;
547	for (i = 0; i < rrset->rr_count; i++) {
548		if (rrset->rrs[i].rdata_count != 1)
549			continue;
550		if (rrset->rrs[i].rdatas[0].data[0] == 2
551		&&  ((uint8_t*)(rrset->rrs[i].rdatas[0].data + 1))[0] == 1
552		&&  ((uint8_t*)(rrset->rrs[i].rdatas[0].data + 1))[1] == '2') {
553			version_2_found = 1;
554			break;
555		}
556	}
557	if (!version_2_found) {
558		make_catalog_consumer_invalid(consumer_zone,
559			"'version.%s' TXT RR with value \"2\" not found",
560			consumer_zone->options->name);
561		return;
562	}
563	/* Walk over all names under zones.<consumer_zone> */
564	if(!namedb_lookup(xfrd->nsd->db, label_plus_dname("zones", dname),
565				&match, &closest_encloser)) {
566		/* zones.<consumer_zone> does not exist, so the catalog has no
567		 * members. This is just fine. But there may be members that need
568		 * to be deleted.
569		 */
570		cursor = rbtree_first(&consumer_zone->member_ids);
571		mode = just_add;
572		goto delete_members;
573	}
574	mode = consumer_zone->member_ids.count ? try_to_add : just_add;
575retry_adding:
576	cursor = rbtree_first(&consumer_zone->member_ids);
577	for ( member_id = domain_next(match)
578	    ; member_id && domain_is_subdomain(member_id, match)
579	    ; member_id = domain_next(member_id)) {
580		domain_type *member_domain;
581		char member_domain_str[5 * MAXDOMAINLEN];
582		struct zone_options* zopt;
583		int valid_group_values;
584		struct pattern_options *pattern = NULL;
585		struct catalog_member_zone* to_add;
586
587		if (domain_dname(member_id)->label_count > dname->label_count+2
588		||  !(rrset = domain_find_rrset(member_id, zone, TYPE_PTR)))
589			continue;
590
591		/* RFC9432 Section 4.1. Member Zones:
592		 *
593		 * `` This PTR record MUST be the only record in the PTR RRset
594		 *    with the same name. The presence of more than one record
595		 *    in the RRset indicates a broken catalog zone that MUST
596		 *    NOT be processed (see Section 5.1).
597		 */
598		if (rrset->rr_count != 1) {
599			make_catalog_consumer_invalid(consumer_zone,
600				"only a single PTR RR expected on '%s'",
601				domain_to_string(member_id));
602			return;
603		}
604		/* A PTR rr always has 1 rdata element which is a dname */
605		if (rrset->rrs[0].rdata_count != 1)
606			continue;
607		member_domain = rrset->rrs[0].rdatas[0].domain;
608		domain_to_string_buf(member_domain, member_domain_str);
609		/* remove trailing dot */
610		member_domain_str[strlen(member_domain_str) - 1] = 0;
611
612		valid_group_values = 0;
613		/* Lookup group.<member_id> TXT for matching patterns  */
614		if(!namedb_lookup(xfrd->nsd->db, label_plus_dname("group",
615						domain_dname(member_id)),
616					&group, &closest_encloser)
617		|| !(rrset = domain_find_rrset(group, zone, TYPE_TXT))) {
618			; /* pass */
619
620		} else for (i = 0; i < rrset->rr_count; i++) {
621			/* Max single TXT rdata field length + '\x00' == 256 */
622			char group_value[256];
623
624			/* Looking for a single TXT rdata field */
625			if (rrset->rrs[i].rdata_count != 1
626
627			    /* rdata field should be at least 1 char */
628			||  rrset->rrs[i].rdatas[0].data[0] < 2
629
630			    /* single rdata atom with single TXT rdata field */
631			||  (uint16_t)(((uint8_t*)(rrset->rrs[i].rdatas[0].data + 1))[0])
632			  != (uint16_t) (rrset->rrs[i].rdatas[0].data[0]-1))
633				continue;
634
635			memcpy( group_value
636			      , (uint8_t*)(rrset->rrs[i].rdatas[0].data+1) + 1
637			      ,((uint8_t*)(rrset->rrs[i].rdatas[0].data+1))[0]
638			      );
639			group_value[
640			       ((uint8_t*)(rrset->rrs[i].rdatas[0].data+1))[0]
641			] = 0;
642			if ((pattern = pattern_options_find(
643					xfrd->nsd->options, group_value)))
644				valid_group_values += 1;
645		}
646		if (valid_group_values > 1) {
647	                log_msg(LOG_ERR, "member zone '%s': only a single "
648				"group property that matches a pattern is "
649				"allowed."
650				"The pattern from \"catalog-member-pattern\" "
651				"will be used instead.",
652				domain_to_string(member_id));
653			valid_group_values = 0;
654
655		} else if (valid_group_values == 1 && pattern
656				&& pattern->catalog_producer_zone) {
657	                log_msg(LOG_ERR, "member zone '%s': group property "
658				"'%s' matches a catalog producer member zone "
659				"pattern. In NSD, catalog member zones can be "
660				"either a member of a catalog consumer zone or"
661				" a catalog producer zone, but not both.",
662				domain_to_string(member_id), pattern->pname);
663			valid_group_values = 0;
664		}
665		if (valid_group_values == 1) {
666			/* pass: pattern is already set */
667			assert(pattern);
668
669		} else if (default_pattern)
670			pattern = default_pattern; /* pass */
671
672		else if (!(pattern = default_pattern =
673				catalog_member_pattern(consumer_zone))) {
674			make_catalog_consumer_invalid(consumer_zone,
675				"missing 'group.%s' TXT RR and no default "
676				"pattern from \"catalog-member-pattern\"",
677				domain_to_string(member_id));
678			return;
679		}
680		if (cursor == RBTREE_NULL)
681			; /* End of the current member zones list.
682			   * From here onwards, zones will only be added.
683			   */
684		else {
685			int cmp = 0;
686#ifndef NDEBUG
687			char member_id_str[5 * MAXDOMAINLEN];
688			domain_to_string_buf(member_id, member_id_str);
689#endif
690			DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Comparing %s with %s",
691				member_id_str,
692				dname_to_string(cursor_member_id(cursor),
693					NULL)));
694
695			while (cursor != RBTREE_NULL &&
696			       (cmp = dname_compare(
697					domain_dname(member_id),
698					cursor_member_id(cursor))) > 0) {
699				/* member_id is ahead of the current catalog
700				 * member zone pointed to by cursor.
701				 * The member zone must be deleted.
702				 */
703				struct catalog_member_zone* to_delete =
704					cursor_cmz(cursor);
705#ifndef NDEBUG
706				const char *member_id_to_delete_str =
707				   dname_to_string(to_delete->member_id, NULL);
708#endif
709				cursor = rbtree_next(cursor);
710
711				DEBUG(DEBUG_XFRD,1, (LOG_INFO,
712					"%s > %s: delete %s",
713					member_id_str,
714					member_id_to_delete_str,
715					member_id_to_delete_str));
716				catalog_del_consumer_member_zone(
717						consumer_zone, to_delete);
718				if(cursor != RBTREE_NULL)
719					DEBUG(DEBUG_XFRD,1, (LOG_INFO,
720						"Comparing %s with %s",
721						member_id_str,
722						dname_to_string(
723							cursor_member_id(cursor),
724							NULL)));
725			}
726			if (cursor != RBTREE_NULL && cmp == 0) {
727				/* member_id is also in an current catalog
728				 * member zone, and cursor is pointing
729				 * to it. So, move along ...
730				 */
731				/* ... but first check if the pattern needs
732				 * a change
733				 */
734				DEBUG(DEBUG_XFRD,1, (LOG_INFO, "%s == %s: "
735				    "Compare pattern %s with %s",
736				    member_id_str, member_id_str,
737				    cursor_cmz(cursor)->options.pattern->pname,
738				    pattern->pname));
739
740				if (cursor_cmz(cursor)->options.pattern ==
741						pattern)
742					; /* pass: Pattern remains the same */
743				else {
744					/* Changing patterns is basically
745					 * deleting and adding the zone again
746					 */
747					zopt  = &cursor_cmz(cursor)->options;
748					dname = (dname_type *)zopt->node.key;
749					task_new_del_zone(
750					    xfrd->nsd->task[xfrd->nsd->mytask],
751					    xfrd->last_task,
752					    dname);
753					xfrd_set_reload_now(xfrd);
754					if(zone_is_slave(zopt)) {
755						xfrd_del_slave_zone( xfrd
756						                   , dname);
757					}
758					xfrd_del_notify(xfrd, dname);
759#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
760					if(zone_is_catalog_consumer(zopt)) {
761						xfrd_deinit_catalog_consumer_zone(
762								xfrd, dname);
763					}
764#endif
765					/* It is a catalog consumer member,
766					 * so no need to check if it was a
767					 * catalog producer member zone to
768					 * delete and add
769					 */
770					zopt->pattern = pattern;
771					task_new_add_zone(
772					    xfrd->nsd->task[xfrd->nsd->mytask],
773					    xfrd->last_task, zopt->name,
774					    pattern->pname,
775					    getzonestatid( xfrd->nsd->options
776					                 , zopt));
777					zonestat_inc_ifneeded();
778					xfrd_set_reload_now(xfrd);
779#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
780					if(zone_is_catalog_consumer(zopt)) {
781						xfrd_init_catalog_consumer_zone(
782								xfrd, zopt);
783					}
784#endif
785					init_notify_send(xfrd->notify_zones,
786							xfrd->region, zopt);
787					if(zone_is_slave(zopt)) {
788						xfrd_init_slave_zone(
789								xfrd, zopt);
790					}
791				}
792				cursor = rbtree_next(cursor);
793				continue;
794			}
795			/* member_id is not in the current catalog member zone
796			 * list, so it must be added
797			 */
798			assert(cursor == RBTREE_NULL || cmp < 0);
799		}
800		/* See if the zone already exists */
801		zopt = zone_options_find(xfrd->nsd->options,
802				domain_dname(member_domain));
803		if (zopt) {
804			/* Produce warning if zopt is from other catalog.
805			 * Give debug message if zopt is not from this catalog.
806			 */
807			switch(mode) {
808			case try_to_add:
809				mode = retry_to_add;
810				break;
811			case just_add:
812				DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Cannot add "
813					"catalog member zone %s (from %s): "
814					"zone already exists",
815					member_domain_str,
816					domain_to_string(member_id)));
817				break;
818			default:
819				break;
820			}
821			continue;
822		}
823		/* Add member zone if not already there */
824                log_msg(LOG_INFO, "Adding '%s' PTR '%s'",
825				domain_to_string(member_id),
826				member_domain_str);
827		DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Adding %s PTR %s",
828			domain_to_string(member_id), member_domain_str));
829		to_add= catalog_member_zone_create(xfrd->nsd->options->region);
830		to_add->options.name = region_strdup(
831				xfrd->nsd->options->region, member_domain_str);
832		to_add->options.pattern = pattern;
833		if (!nsd_options_insert_zone(xfrd->nsd->options,
834					&to_add->options)) {
835	                log_msg(LOG_ERR, "bad domain name  '%s' pattern %s",
836				member_domain_str,
837				( pattern->pname ? pattern->pname: "<NULL>"));
838			zone_options_delete(xfrd->nsd->options,
839					&to_add->options);
840			continue;
841		}
842		to_add->member_id = dname_copy( xfrd->nsd->options->region
843		                           , domain_dname(member_id));
844		/* Insert into the members_id list */
845		to_add->node.key = to_add;
846		if(!rbtree_insert( &consumer_zone->member_ids, &to_add->node)){
847	                log_msg(LOG_ERR, "Error adding '%s' PTR '%s' to "
848				"consumer_zone->member_ids",
849				domain_to_string(member_id),
850				member_domain_str);
851			break;
852		} else
853			cursor = rbtree_next(&to_add->node);
854		/* make addzone task and schedule reload */
855		task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
856			xfrd->last_task, member_domain_str,
857			pattern->pname,
858			getzonestatid(xfrd->nsd->options, &to_add->options));
859		zonestat_inc_ifneeded();
860		xfrd_set_reload_now(xfrd);
861#ifdef MULTIPLE_CATALOG_CONSUMER_ZONES
862		/* add to xfrd - catalog consumer zones */
863		if(zone_is_catalog_consumer(&to_add->options)) {
864			xfrd_init_catalog_consumer_zone(xfrd,&to_add->options);
865		}
866#endif
867		/* add to xfrd - notify (for master and slaves) */
868		init_notify_send(xfrd->notify_zones, xfrd->region,
869				&to_add->options);
870		/* add to xfrd - slave */
871		if(zone_is_slave(&to_add->options)) {
872			xfrd_init_slave_zone(xfrd, &to_add->options);
873		}
874		DEBUG(DEBUG_XFRD,1, (LOG_INFO, "Added catalog "
875			"member zone %s (from %s)",
876			member_domain_str, domain_to_string(member_id)));
877	}
878delete_members:
879	while (cursor != RBTREE_NULL) {
880		/* Any current catalog member zones remaining, don't have an
881		 * member_id in the catalog anymore, so should be deleted too.
882		 */
883		struct catalog_member_zone* to_delete = cursor_cmz(cursor);
884
885		cursor = rbtree_next(cursor);
886		catalog_del_consumer_member_zone(consumer_zone, to_delete);
887	}
888	if(mode == retry_to_add) {
889		mode = just_add;
890		goto retry_adding;
891	}
892	debug_log_consumer_members(consumer_zone);
893	make_catalog_consumer_valid(consumer_zone);
894}
895
896
897/******************                                        ******************
898 ******************    catalog producer zone processing    ******************
899 ******************                                        ******************/
900
901static int member_id_compare(const void *left, const void *right)
902{
903	return dname_compare( ((struct catalog_member_zone*)left )->member_id
904	                    , ((struct catalog_member_zone*)right)->member_id);
905}
906
907static struct xfrd_catalog_producer_zone*
908xfrd_get_catalog_producer_zone(struct catalog_member_zone* cmz)
909{
910	struct zone_options *producer_zopt;
911	struct xfrd_catalog_producer_zone* producer_zone;
912	const dname_type* producer_name;
913	const char* producer_name_str;
914
915	assert(xfrd);
916	if(!cmz || !cmz->options.pattern->catalog_producer_zone)
917		return NULL;
918
919	/* TODO: Store as dname in pattern->catalog_producer_zone */
920	producer_name = dname_parse(xfrd->nsd->options->region,
921			cmz->options.pattern->catalog_producer_zone);
922	producer_zopt = zone_options_find(xfrd->nsd->options, producer_name);
923	producer_name_str = dname_to_string(producer_name, NULL);
924	region_recycle( xfrd->nsd->options->region, (void *)producer_name
925	              , dname_total_size(producer_name));
926	if(!producer_zopt) {
927		log_msg(LOG_ERR, "catalog producer zone '%s' not found for "
928			"zone '%s'", producer_name_str, cmz->options.name);
929		return NULL;
930	}
931	if(!zone_is_catalog_producer(producer_zopt)) {
932		log_msg(LOG_ERR, "cannot add catalog producer member "
933			"zone '%s' to non producer zone '%s'",
934			cmz->options.name, producer_zopt->name);
935		return NULL;
936	}
937	producer_name = (dname_type*)producer_zopt->node.key;
938	producer_zone = (struct xfrd_catalog_producer_zone*)
939		rbtree_search(xfrd->catalog_producer_zones, producer_name);
940	if (!producer_zone) {
941		/* Create a new one */
942		DEBUG(DEBUG_XFRD, 1, (LOG_INFO,"creating catalog producer zone"
943			" '%s'", producer_zopt->name));
944		producer_zone = (struct xfrd_catalog_producer_zone*)
945			region_alloc(xfrd->region, sizeof(*producer_zone));
946		memset(producer_zone , 0, sizeof(*producer_zone));
947		producer_zone->node.key = producer_zopt->node.key;
948		producer_zone->options = producer_zopt;
949		producer_zone->member_ids.region = xfrd->region;
950		producer_zone->member_ids.root = RBTREE_NULL;
951		producer_zone->member_ids.count = 0;
952		producer_zone->member_ids.cmp = member_id_compare;
953		producer_zone->serial = 0;
954		producer_zone->to_delete = NULL;
955		producer_zone->to_add = NULL;
956		producer_zone->latest_pxfr = NULL;
957		producer_zone->axfr = 1;
958		rbtree_insert(xfrd->catalog_producer_zones,
959				(rbnode_type*)producer_zone);
960	}
961	return producer_zone;
962}
963
964void
965xfrd_add_catalog_producer_member(struct catalog_member_zone* cmz)
966{
967	struct xfrd_catalog_producer_zone* producer_zone;
968	const dname_type* producer_name;
969	struct xfrd_producer_member* to_add;
970
971	assert(xfrd);
972	if (!(producer_zone = xfrd_get_catalog_producer_zone(cmz))) {
973		return;
974	}
975	producer_name = producer_zone->node.key;
976	while(!cmz->member_id) {
977		/* Make new member_id with this catalog producer */
978		char id_label[sizeof(uint32_t)*2+1];
979		uint32_t new_id = (uint32_t)random_generate(0x7fffffff);
980
981		hex_ntop((void*)&new_id, sizeof(uint32_t), id_label, sizeof(id_label));
982		id_label[sizeof(uint32_t)*2] = 0;
983		cmz->member_id = label_plus_dname(id_label,
984				label_plus_dname("zones", producer_name));
985		DEBUG(DEBUG_XFRD, 1, (LOG_INFO, "does member_id %s exist?",
986			dname_to_string(cmz->member_id, NULL)));
987		if (!rbtree_search(&producer_zone->member_ids, cmz)) {
988			cmz->member_id = dname_copy(xfrd->nsd->options->region,
989				cmz->member_id);
990			break;
991		}
992		cmz->member_id = NULL;
993	}
994	cmz->node.key = cmz;
995	rbtree_insert(&producer_zone->member_ids, &cmz->node);
996
997	/* Put data to be added to the producer zone to the to_add stack */
998	to_add = (struct xfrd_producer_member*)region_alloc(xfrd->region,
999			sizeof(struct xfrd_producer_member));
1000	to_add->member_id = cmz->member_id;
1001	to_add->member_zone_name = (dname_type*)cmz->options.node.key;
1002	to_add->group_name = cmz->options.pattern->pname;
1003	to_add->next = producer_zone->to_add;
1004	producer_zone->to_add = to_add;
1005}
1006
1007int
1008xfrd_del_catalog_producer_member(struct xfrd_state* xfrd,
1009		const dname_type* member_zone_name)
1010{
1011	struct xfrd_producer_member* to_delete;
1012	struct catalog_member_zone* cmz;
1013	struct xfrd_catalog_producer_zone* producer_zone;
1014
1015	if(!(cmz = as_catalog_member_zone(zone_options_find(xfrd->nsd->options,
1016						member_zone_name)))
1017	|| !(producer_zone = xfrd_get_catalog_producer_zone(cmz))
1018	|| !rbtree_delete(&producer_zone->member_ids, cmz))
1019		return 0;
1020	to_delete = (struct xfrd_producer_member*)region_alloc(xfrd->region,
1021			sizeof(struct xfrd_producer_member));
1022	to_delete->member_id = cmz->member_id; cmz->member_id = NULL;
1023	cmz->node = *RBTREE_NULL;
1024	to_delete->member_zone_name = member_zone_name;
1025	to_delete->group_name = cmz->options.pattern->pname;
1026	to_delete->next = producer_zone->to_delete;
1027	producer_zone->to_delete = to_delete;
1028	return 1;
1029}
1030
1031static int
1032try_buffer_write_SOA(buffer_type* packet, const dname_type* owner,
1033		uint32_t serial)
1034{
1035	size_t mark = buffer_position(packet);
1036
1037	if(try_buffer_write(packet, dname_name(owner), owner->name_size)
1038	&& try_buffer_write_u16(packet, TYPE_SOA)
1039	&& try_buffer_write_u16(packet, CLASS_IN)
1040	&& try_buffer_write_u32(packet, 0) /* TTL*/
1041	&& try_buffer_write_u16(packet, 9 + 9 + 5 * sizeof(uint32_t))
1042	&& try_buffer_write(packet, "\007invalid\000", 9) /* primary */
1043	&& try_buffer_write(packet, "\007invalid\000", 9) /* mailbox */
1044	&& try_buffer_write_u32(packet,     serial)       /* serial */
1045	&& try_buffer_write_u32(packet,       3600)       /* refresh*/
1046	&& try_buffer_write_u32(packet,        600)       /* retry */
1047	&& try_buffer_write_u32(packet, 2147483646)       /* expire */
1048	&& try_buffer_write_u32(packet,          0)       /* minimum */) {
1049		ANCOUNT_SET(packet, ANCOUNT(packet) + 1);
1050		return 1;
1051	}
1052	buffer_set_position(packet, mark);
1053	return 0;
1054}
1055
1056static int
1057try_buffer_write_RR(buffer_type* packet, const dname_type* owner,
1058		uint16_t rr_type, uint16_t rdata_len, const void* rdata)
1059{
1060	size_t mark = buffer_position(packet);
1061
1062	if(try_buffer_write(packet, dname_name(owner), owner->name_size)
1063	&& try_buffer_write_u16(packet, rr_type)
1064	&& try_buffer_write_u16(packet, CLASS_IN)
1065	&& try_buffer_write_u32(packet, 0) /* TTL*/
1066	&& try_buffer_write_u16(packet, rdata_len)
1067	&& try_buffer_write(packet, rdata, rdata_len)) {
1068		ANCOUNT_SET(packet, ANCOUNT(packet) + 1);
1069		return 1;
1070	}
1071	buffer_set_position(packet, mark);
1072	return 0;
1073}
1074
1075static inline int
1076try_buffer_write_PTR(buffer_type* packet, const dname_type* owner,
1077		const dname_type* name)
1078{
1079	return try_buffer_write_RR(packet, owner, TYPE_PTR,
1080			name->name_size, dname_name(name));
1081}
1082
1083static int
1084try_buffer_write_TXT(buffer_type* packet, const dname_type* name,
1085		const char *txt)
1086{
1087	size_t mark = buffer_position(packet);
1088	size_t len = strlen(txt);
1089
1090	if(len > 255) {
1091		log_msg(LOG_ERR, "cannot make '%s 0 IN TXT \"%s\"': rdata "
1092			"field too long", dname_to_string(name, NULL), txt);
1093		return 1;
1094	}
1095	if(try_buffer_write(packet, dname_name(name), name->name_size)
1096	&& try_buffer_write_u16(packet, TYPE_TXT)
1097	&& try_buffer_write_u16(packet, CLASS_IN)
1098	&& try_buffer_write_u32(packet, 0) /* TTL*/
1099	&& try_buffer_write_u16(packet, len + 1)
1100	&& try_buffer_write_u8(packet, len)
1101	&& try_buffer_write_string(packet, txt)) {
1102		ANCOUNT_SET(packet, ANCOUNT(packet) + 1);
1103		return 1;
1104	}
1105	buffer_set_position(packet, mark);
1106	return 0;
1107}
1108
1109static void
1110xfr_writer_init(struct xfrd_xfr_writer* xw,
1111		struct xfrd_catalog_producer_zone* producer_zone)
1112{
1113	xw->producer_zone = producer_zone;
1114	buffer_create_from( &xw->packet, &xw->packet_space
1115	                               , sizeof(xw->packet_space));
1116	buffer_write(&xw->packet, "\000\000\000\000\000\000"
1117	                          "\000\000\000\000\000\000", 12); /* header */
1118	xw->seq_nr = 0;
1119	xw->old_serial = xw->producer_zone->serial;
1120	xw->new_serial = (uint32_t)xfrd_time();
1121	if(xw->new_serial <= xw->old_serial)
1122		xw->new_serial = xw->old_serial + 1;
1123	if(producer_zone->axfr) {
1124		xw->old_serial = 0;
1125		producer_zone->axfr = 0;
1126	}
1127	xw->xfrfilenumber = xfrd->xfrfilenumber++;
1128}
1129
1130static void
1131xfr_writer_write_packet(struct xfrd_xfr_writer* xw)
1132{
1133	const dname_type* producer_name =
1134		(const dname_type*)xw->producer_zone->options->node.key;
1135
1136	/* We want some content at least, so not just a header
1137	 * This can occur when final SOA was already written.
1138	 */
1139	if(buffer_position(&xw->packet) == 12)
1140		return;
1141	buffer_flip(&xw->packet);
1142	diff_write_packet( dname_to_string(producer_name, NULL)
1143			 , xw->producer_zone->options->pattern->pname
1144			 , xw->old_serial, xw->new_serial, xw->seq_nr
1145			 , buffer_begin(&xw->packet), buffer_limit(&xw->packet)
1146			 , xfrd->nsd, xw->xfrfilenumber);
1147	xw->seq_nr += 1;
1148	buffer_clear(&xw->packet);
1149	buffer_write(&xw->packet, "\000\000\000\000\000\000"
1150	                          "\000\000\000\000\000\000", 12); /* header */
1151}
1152
1153
1154static void
1155xfr_writer_commit(struct xfrd_xfr_writer* xw, const char *fmt, ...)
1156{
1157	va_list args;
1158	char msg[1024];
1159	const dname_type* producer_name =
1160		(const dname_type*)xw->producer_zone->options->node.key;
1161
1162	va_start(args, fmt);
1163	if (vsnprintf(msg, sizeof(msg), fmt, args) >= (int)sizeof(msg)) {
1164		log_msg(LOG_WARNING, "truncated diff commit message: '%s'",
1165				msg);
1166	}
1167	xfr_writer_write_packet(xw); /* Write remaining data */
1168	diff_write_commit( dname_to_string(producer_name, NULL)
1169			 , xw->old_serial, xw->new_serial
1170			 , xw->seq_nr /* Number of packets */
1171			 , 1, msg, xfrd->nsd, xw->xfrfilenumber);
1172	task_new_apply_xfr( xfrd->nsd->task[xfrd->nsd->mytask], xfrd->last_task
1173			  , producer_name
1174			  , xw->old_serial, xw->new_serial, xw->xfrfilenumber);
1175	xfrd_set_reload_now(xfrd);
1176}
1177
1178static void
1179xfrd_process_catalog_producer_zone(
1180		struct xfrd_catalog_producer_zone* producer_zone)
1181{
1182	struct xfrd_xfr_writer xw;
1183	dname_type* producer_name;
1184	struct xfrd_producer_xfr* pxfr;
1185
1186	if(!producer_zone->to_add && !producer_zone->to_delete)
1187		return; /* No changes */
1188
1189	producer_name = (dname_type*)producer_zone->node.key;
1190	xfr_writer_init(&xw, producer_zone);
1191	xfr_writer_add_SOA(&xw, producer_name, xw.new_serial);
1192
1193	if(xw.old_serial == 0) {
1194		/* initial deployment */
1195		assert(producer_zone->to_add && !producer_zone->to_delete);
1196
1197		xfr_writer_add_RR (&xw, producer_name
1198		                      , TYPE_NS, 9, "\007invalid\000");
1199		xfr_writer_add_TXT(&xw, label_plus_dname("version"
1200		                                        , producer_name), "2");
1201		goto add_member_zones;
1202	}
1203	/* IXFR */
1204	xfr_writer_add_SOA(&xw, producer_name, xw.old_serial);
1205	while(producer_zone->to_delete) {
1206		struct xfrd_producer_member* to_delete =
1207			producer_zone->to_delete;
1208
1209		/* Pop to_delete from stack */
1210		producer_zone->to_delete = to_delete->next;
1211		to_delete->next = NULL;
1212
1213		/* Write <member_id> PTR <member_name> */
1214		xfr_writer_add_PTR(&xw, to_delete->member_id
1215				      , to_delete->member_zone_name);
1216
1217		/* Write group.<member_id> TXT <pattern> */
1218		xfr_writer_add_TXT( &xw
1219				  , label_plus_dname("group"
1220						    , to_delete->member_id)
1221				  , to_delete->group_name);
1222
1223		region_recycle( xfrd->nsd->options->region
1224		              , (void *)to_delete->member_id
1225			      , dname_total_size(to_delete->member_id));
1226		region_recycle( xfrd->region /* allocated in perform_delzone */
1227		              , (void *)to_delete->member_zone_name
1228			      , dname_total_size(to_delete->member_zone_name));
1229		/* Don't recycle to_delete->group_name it's pattern->pname */
1230		region_recycle( xfrd->region, to_delete, sizeof(*to_delete));
1231	}
1232	xfr_writer_add_SOA(&xw, producer_name, xw.new_serial);
1233
1234add_member_zones:
1235	while(producer_zone->to_add) {
1236		struct xfrd_producer_member* to_add = producer_zone->to_add;
1237
1238		/* Pop to_add from stack */
1239		producer_zone->to_add = to_add->next;
1240		to_add->next = NULL;
1241
1242		/* Write <member_id> PTR <member_name> */
1243		xfr_writer_add_PTR(&xw, to_add->member_id,
1244				to_add->member_zone_name);
1245
1246		/* Write group.<member_id> TXT <pattern> */
1247		xfr_writer_add_TXT( &xw
1248				  , label_plus_dname("group"
1249						    , to_add->member_id)
1250				  , to_add->group_name);
1251
1252		/* Don't recycle any of the struct attributes as they come
1253		 * from zone_option's that are in use
1254		 */
1255		region_recycle(xfrd->region, to_add, sizeof(*to_add));
1256	}
1257	xfr_writer_add_SOA(&xw, producer_name, xw.new_serial);
1258	xfr_writer_commit(&xw, "xfr for catalog producer zone "
1259			"'%s' with %d members from %u to %u",
1260			dname_to_string(producer_name, NULL),
1261			producer_zone->member_ids.count,
1262			xw.old_serial, xw.new_serial);
1263	producer_zone->serial = xw.new_serial;
1264
1265	/* Hook up an xfrd_producer_xfr, to delete the xfr file when applied */
1266	pxfr = (struct xfrd_producer_xfr*)region_alloc(xfrd->region,
1267			sizeof(struct xfrd_producer_xfr));
1268	pxfr->serial = xw.new_serial;
1269	pxfr->xfrfilenumber = xw.xfrfilenumber;
1270	if((pxfr->next = producer_zone->latest_pxfr))
1271		pxfr->next->prev_next_ptr = &pxfr->next;
1272	pxfr->prev_next_ptr = &producer_zone->latest_pxfr;
1273	producer_zone->latest_pxfr = pxfr;
1274}
1275
1276void xfrd_process_catalog_producer_zones()
1277{
1278	struct xfrd_catalog_producer_zone* producer_zone;
1279
1280	RBTREE_FOR(producer_zone, struct xfrd_catalog_producer_zone*,
1281			xfrd->catalog_producer_zones) {
1282		xfrd_process_catalog_producer_zone(producer_zone);
1283	}
1284}
1285
1286