xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/dyndb/driver/syncptr.c (revision dd75ac5b443e967e26b4d18cc8cd5eb98512bfbf)
1 /*	$NetBSD: syncptr.c,v 1.6 2022/09/23 12:15:25 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0 AND ISC
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*
17  * Copyright (C) Red Hat
18  *
19  * Permission to use, copy, modify, and/or distribute this software for any
20  * purpose with or without fee is hereby granted, provided that the above
21  * copyright notice and this permission notice appear in all copies.
22  *
23  * THE SOFTWARE IS PROVIDED "AS IS" AND AUTHORS DISCLAIMS ALL WARRANTIES WITH
24  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
25  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
26  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
27  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
28  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
29  * PERFORMANCE OF THIS SOFTWARE.
30  */
31 
32 /*
33  * Automatic A/AAAA/PTR record synchronization.
34  */
35 
36 #include "syncptr.h"
37 
38 #include <isc/event.h>
39 #include <isc/eventclass.h>
40 #include <isc/netaddr.h>
41 #include <isc/task.h>
42 #include <isc/util.h>
43 
44 #include <dns/byaddr.h>
45 #include <dns/db.h>
46 #include <dns/name.h>
47 #include <dns/view.h>
48 #include <dns/zone.h>
49 
50 #include "instance.h"
51 #include "util.h"
52 
53 /* Almost random value. See eventclass.h */
54 #define SYNCPTR_WRITE_EVENT (ISC_EVENTCLASS(1025) + 1)
55 
56 /*
57  * Event used for making changes to reverse zones.
58  */
59 typedef struct syncptrevent syncptrevent_t;
60 struct syncptrevent {
61 	ISC_EVENT_COMMON(syncptrevent_t);
62 	isc_mem_t *mctx;
63 	dns_zone_t *zone;
64 	dns_diff_t diff;
65 	dns_fixedname_t ptr_target_name; /* referenced by owner name in
66 					  * tuple */
67 	isc_buffer_t b; /* referenced by target name in tuple */
68 	unsigned char buf[DNS_NAME_MAXWIRE];
69 };
70 
71 /*
72  * Write diff generated in syncptr() to reverse zone.
73  *
74  * This function will be called asynchronously and syncptr() will not get
75  * any result from it.
76  *
77  */
78 static void
79 syncptr_write(isc_task_t *task, isc_event_t *event) {
80 	syncptrevent_t *pevent = (syncptrevent_t *)event;
81 	dns_dbversion_t *version = NULL;
82 	dns_db_t *db = NULL;
83 	isc_result_t result;
84 
85 	REQUIRE(event->ev_type == SYNCPTR_WRITE_EVENT);
86 
87 	UNUSED(task);
88 
89 	log_write(ISC_LOG_INFO, "ENTER: syncptr_write");
90 
91 	result = dns_zone_getdb(pevent->zone, &db);
92 	if (result != ISC_R_SUCCESS) {
93 		log_write(ISC_LOG_ERROR,
94 			  "syncptr_write: dns_zone_getdb -> %s\n",
95 			  isc_result_totext(result));
96 		goto cleanup;
97 	}
98 
99 	result = dns_db_newversion(db, &version);
100 	if (result != ISC_R_SUCCESS) {
101 		log_write(ISC_LOG_ERROR,
102 			  "syncptr_write: dns_db_newversion -> %s\n",
103 			  isc_result_totext(result));
104 		goto cleanup;
105 	}
106 	result = dns_diff_apply(&pevent->diff, db, version);
107 	if (result != ISC_R_SUCCESS) {
108 		log_write(ISC_LOG_ERROR,
109 			  "syncptr_write: dns_diff_apply -> %s\n",
110 			  isc_result_totext(result));
111 		goto cleanup;
112 	}
113 
114 cleanup:
115 	if (db != NULL) {
116 		if (version != NULL) {
117 			dns_db_closeversion(db, &version, true);
118 		}
119 		dns_db_detach(&db);
120 	}
121 	dns_zone_detach(&pevent->zone);
122 	dns_diff_clear(&pevent->diff);
123 	isc_event_free(&event);
124 }
125 
126 /*
127  * Find a reverse zone for given IP address.
128  *
129  * @param[in]  rdata      IP address as A/AAAA record
130  * @param[out] name       Owner name for the PTR record
131  * @param[out] zone       DNS zone for reverse record matching the IP address
132  *
133  * @retval ISC_R_SUCCESS  DNS name derived from given IP address belongs to an
134  * 			  reverse zone managed by this driver instance.
135  * 			  PTR record synchronization can continue.
136  * @retval ISC_R_NOTFOUND Suitable reverse zone was not found because it
137  * 			  does not exist or is not managed by this driver.
138  */
139 static isc_result_t
140 syncptr_find_zone(sample_instance_t *inst, dns_rdata_t *rdata, dns_name_t *name,
141 		  dns_zone_t **zone) {
142 	isc_result_t result;
143 	isc_netaddr_t isc_ip; /* internal net address representation */
144 	dns_rdata_in_a_t ipv4;
145 	dns_rdata_in_aaaa_t ipv6;
146 
147 	REQUIRE(inst != NULL);
148 	REQUIRE(zone != NULL && *zone == NULL);
149 
150 	switch (rdata->type) {
151 	case dns_rdatatype_a:
152 		CHECK(dns_rdata_tostruct(rdata, &ipv4, inst->mctx));
153 		isc_netaddr_fromin(&isc_ip, &ipv4.in_addr);
154 		break;
155 
156 	case dns_rdatatype_aaaa:
157 		CHECK(dns_rdata_tostruct(rdata, &ipv6, inst->mctx));
158 		isc_netaddr_fromin6(&isc_ip, &ipv6.in6_addr);
159 		break;
160 
161 	default:
162 		fatal_error("unsupported address type 0x%x", rdata->type);
163 		break;
164 	}
165 
166 	/*
167 	 * Convert IP address to PTR owner name.
168 	 *
169 	 * @example
170 	 * 192.168.0.1 -> 1.0.168.192.in-addr.arpa
171 	 */
172 	result = dns_byaddr_createptrname(&isc_ip, 0, name);
173 	if (result != ISC_R_SUCCESS) {
174 		log_write(ISC_LOG_ERROR,
175 			  "syncptr_find_zone: dns_byaddr_createptrname -> %s\n",
176 			  isc_result_totext(result));
177 		goto cleanup;
178 	}
179 
180 	/* Find a zone containing owner name of the PTR record. */
181 	result = dns_zt_find(inst->view->zonetable, name, 0, NULL, zone);
182 	if (result == DNS_R_PARTIALMATCH) {
183 		result = ISC_R_SUCCESS;
184 	} else if (result != ISC_R_SUCCESS) {
185 		log_write(ISC_LOG_ERROR,
186 			  "syncptr_find_zone: dns_zt_find -> %s\n",
187 			  isc_result_totext(result));
188 		goto cleanup;
189 	}
190 
191 	/* Make sure that the zone is managed by this driver. */
192 	if (*zone != inst->zone1 && *zone != inst->zone2) {
193 		dns_zone_detach(zone);
194 		log_write(ISC_LOG_INFO, "syncptr_find_zone: zone not managed");
195 		result = ISC_R_NOTFOUND;
196 	}
197 
198 cleanup:
199 	if (rdata->type == dns_rdatatype_a) {
200 		dns_rdata_freestruct(&ipv4);
201 	} else {
202 		dns_rdata_freestruct(&ipv6);
203 	}
204 
205 	return (result);
206 }
207 
208 /*
209  * Generate update event for PTR record to reflect change in A/AAAA record.
210  *
211  * @pre Reverse zone is managed by this driver.
212  *
213  * @param[in]  a_name  DNS domain of modified A/AAAA record
214  * @param[in]  af      Address family
215  * @param[in]  ip_str  IP address as a string (IPv4 or IPv6)
216  * @param[in]  mod_op  LDAP_MOD_DELETE if A/AAAA record is being deleted
217  *                     or LDAP_MOD_ADD if A/AAAA record is being added.
218  *
219  * @retval ISC_R_SUCCESS Event for PTR record update was generated and send.
220  *                       Change to reverse zone will be done asynchronously.
221  * @retval other	 Synchronization failed - reverse doesn't exist,
222  * 			 is not managed by this driver instance,
223  * 			 memory allocation error, etc.
224  */
225 static isc_result_t
226 syncptr(sample_instance_t *inst, dns_name_t *name, dns_rdata_t *addr_rdata,
227 	dns_ttl_t ttl, dns_diffop_t op) {
228 	isc_result_t result;
229 	isc_mem_t *mctx = inst->mctx;
230 	dns_fixedname_t ptr_name;
231 	dns_zone_t *ptr_zone = NULL;
232 	dns_rdata_ptr_t ptr_struct;
233 	dns_rdata_t ptr_rdata = DNS_RDATA_INIT;
234 	dns_difftuple_t *tp = NULL;
235 	isc_task_t *task = NULL;
236 	syncptrevent_t *pevent = NULL;
237 
238 	dns_fixedname_init(&ptr_name);
239 	DNS_RDATACOMMON_INIT(&ptr_struct, dns_rdatatype_ptr, dns_rdataclass_in);
240 	dns_name_init(&ptr_struct.ptr, NULL);
241 
242 	pevent = (syncptrevent_t *)isc_event_allocate(
243 		inst->mctx, inst, SYNCPTR_WRITE_EVENT, syncptr_write, NULL,
244 		sizeof(syncptrevent_t));
245 	isc_buffer_init(&pevent->b, pevent->buf, sizeof(pevent->buf));
246 	dns_fixedname_init(&pevent->ptr_target_name);
247 
248 	/* Check if reverse zone is managed by this driver */
249 	result = syncptr_find_zone(inst, addr_rdata,
250 				   dns_fixedname_name(&ptr_name), &ptr_zone);
251 	if (result != ISC_R_SUCCESS) {
252 		log_error_r("PTR record synchronization skipped: reverse zone "
253 			    "is not managed by driver instance '%s'",
254 			    inst->db_name);
255 		goto cleanup;
256 	}
257 
258 	/* Reverse zone is managed by this driver, prepare PTR record */
259 	pevent->zone = NULL;
260 	dns_zone_attach(ptr_zone, &pevent->zone);
261 	dns_name_copynf(name, dns_fixedname_name(&pevent->ptr_target_name));
262 	dns_name_clone(dns_fixedname_name(&pevent->ptr_target_name),
263 		       &ptr_struct.ptr);
264 	dns_diff_init(inst->mctx, &pevent->diff);
265 	result = dns_rdata_fromstruct(&ptr_rdata, dns_rdataclass_in,
266 				      dns_rdatatype_ptr, &ptr_struct,
267 				      &pevent->b);
268 	if (result != ISC_R_SUCCESS) {
269 		log_write(ISC_LOG_ERROR,
270 			  "syncptr: dns_rdata_fromstruct -> %s\n",
271 			  isc_result_totext(result));
272 		goto cleanup;
273 	}
274 
275 	/* Create diff */
276 	result = dns_difftuple_create(mctx, op, dns_fixedname_name(&ptr_name),
277 				      ttl, &ptr_rdata, &tp);
278 	if (result != ISC_R_SUCCESS) {
279 		log_write(ISC_LOG_ERROR,
280 			  "syncptr: dns_difftuple_create -> %s\n",
281 			  isc_result_totext(result));
282 		goto cleanup;
283 	}
284 	dns_diff_append(&pevent->diff, &tp);
285 
286 	/*
287 	 * Send update event to the reverse zone.
288 	 * It will be processed asynchronously.
289 	 */
290 	dns_zone_gettask(ptr_zone, &task);
291 	isc_task_send(task, (isc_event_t **)&pevent);
292 
293 cleanup:
294 	if (ptr_zone != NULL) {
295 		dns_zone_detach(&ptr_zone);
296 	}
297 	if (tp != NULL) {
298 		dns_difftuple_free(&tp);
299 	}
300 	if (task != NULL) {
301 		isc_task_detach(&task);
302 	}
303 	if (pevent != NULL) {
304 		isc_event_free((isc_event_t **)&pevent);
305 	}
306 
307 	return (result);
308 }
309 
310 /*
311  * Generate update event for every rdata in rdataset.
312  *
313  * @param[in]  name      Owner name for A/AAAA records in rdataset.
314  * @param[in]  rdataset  A/AAAA records.
315  * @param[in]  op	 DNS_DIFFOP_ADD / DNS_DIFFOP_DEL for adding / deleting
316  * 			 the rdata
317  */
318 isc_result_t
319 syncptrs(sample_instance_t *inst, dns_name_t *name, dns_rdataset_t *rdataset,
320 	 dns_diffop_t op) {
321 	isc_result_t result;
322 	dns_rdata_t rdata = DNS_RDATA_INIT;
323 
324 	for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
325 	     result = dns_rdataset_next(rdataset))
326 	{
327 		dns_rdataset_current(rdataset, &rdata);
328 		result = syncptr(inst, name, &rdata, rdataset->ttl, op);
329 		if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
330 			goto cleanup;
331 		}
332 	}
333 	if (result == ISC_R_NOMORE) {
334 		result = ISC_R_SUCCESS;
335 	}
336 
337 cleanup:
338 	return (result);
339 }
340