xref: /netbsd-src/external/mpl/bind/dist/lib/dns/rdata/generic/opt_41.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: opt_41.c,v 1.13 2025/01/26 16:25:33 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
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 /* RFC2671 */
17 
18 #ifndef RDATA_GENERIC_OPT_41_C
19 #define RDATA_GENERIC_OPT_41_C
20 
21 #define RRTYPE_OPT_ATTRIBUTES                                   \
22 	(DNS_RDATATYPEATTR_SINGLETON | DNS_RDATATYPEATTR_META | \
23 	 DNS_RDATATYPEATTR_NOTQUESTION)
24 
25 #include <isc/utf8.h>
26 
27 static isc_result_t
28 fromtext_opt(ARGS_FROMTEXT) {
29 	/*
30 	 * OPT records do not have a text format.
31 	 */
32 
33 	REQUIRE(type == dns_rdatatype_opt);
34 
35 	UNUSED(type);
36 	UNUSED(rdclass);
37 	UNUSED(lexer);
38 	UNUSED(origin);
39 	UNUSED(options);
40 	UNUSED(target);
41 	UNUSED(callbacks);
42 
43 	return ISC_R_NOTIMPLEMENTED;
44 }
45 
46 static isc_result_t
47 totext_opt(ARGS_TOTEXT) {
48 	isc_region_t r;
49 	isc_region_t or ;
50 	uint16_t option;
51 	uint16_t length;
52 	char buf[sizeof("64000 64000")];
53 
54 	/*
55 	 * OPT records do not have a text format.
56 	 */
57 
58 	REQUIRE(rdata->type == dns_rdatatype_opt);
59 
60 	dns_rdata_toregion(rdata, &r);
61 	while (r.length > 0) {
62 		option = uint16_fromregion(&r);
63 		isc_region_consume(&r, 2);
64 		length = uint16_fromregion(&r);
65 		isc_region_consume(&r, 2);
66 		snprintf(buf, sizeof(buf), "%u %u", option, length);
67 		RETERR(str_totext(buf, target));
68 		INSIST(r.length >= length);
69 		if (length > 0) {
70 			if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
71 				RETERR(str_totext(" (", target));
72 			}
73 			RETERR(str_totext(tctx->linebreak, target));
74 			or = r;
75 			or.length = length;
76 			if (tctx->width == 0) { /* No splitting */
77 				RETERR(isc_base64_totext(&or, 60, "", target));
78 			} else {
79 				RETERR(isc_base64_totext(&or, tctx->width - 2,
80 							 tctx->linebreak,
81 							 target));
82 			}
83 			isc_region_consume(&r, length);
84 			if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0) {
85 				RETERR(str_totext(" )", target));
86 			}
87 		}
88 		if (r.length > 0) {
89 			RETERR(str_totext(" ", target));
90 		}
91 	}
92 
93 	return ISC_R_SUCCESS;
94 }
95 
96 static isc_result_t
97 fromwire_opt(ARGS_FROMWIRE) {
98 	isc_region_t sregion;
99 	isc_region_t tregion;
100 	uint16_t opt;
101 	uint16_t length;
102 	unsigned int total;
103 
104 	REQUIRE(type == dns_rdatatype_opt);
105 
106 	UNUSED(type);
107 	UNUSED(rdclass);
108 	UNUSED(dctx);
109 
110 	isc_buffer_activeregion(source, &sregion);
111 	if (sregion.length == 0) {
112 		return ISC_R_SUCCESS;
113 	}
114 	total = 0;
115 	while (sregion.length != 0) {
116 		if (sregion.length < 4) {
117 			return ISC_R_UNEXPECTEDEND;
118 		}
119 		opt = uint16_fromregion(&sregion);
120 		isc_region_consume(&sregion, 2);
121 		length = uint16_fromregion(&sregion);
122 		isc_region_consume(&sregion, 2);
123 		total += 4;
124 		if (sregion.length < length) {
125 			return ISC_R_UNEXPECTEDEND;
126 		}
127 		switch (opt) {
128 		case DNS_OPT_LLQ:
129 			if (length != 18U) {
130 				return DNS_R_OPTERR;
131 			}
132 			isc_region_consume(&sregion, length);
133 			break;
134 		case DNS_OPT_UL:
135 			if (length != 4U && length != 8U) {
136 				return DNS_R_OPTERR;
137 			}
138 			isc_region_consume(&sregion, length);
139 			break;
140 		case DNS_OPT_CLIENT_SUBNET: {
141 			uint16_t family;
142 			uint8_t addrlen;
143 			uint8_t scope;
144 			uint8_t addrbytes;
145 
146 			if (length < 4) {
147 				return DNS_R_OPTERR;
148 			}
149 			family = uint16_fromregion(&sregion);
150 			isc_region_consume(&sregion, 2);
151 			addrlen = uint8_fromregion(&sregion);
152 			isc_region_consume(&sregion, 1);
153 			scope = uint8_fromregion(&sregion);
154 			isc_region_consume(&sregion, 1);
155 
156 			switch (family) {
157 			case 0:
158 				/*
159 				 * XXXMUKS: In queries and replies, if
160 				 * FAMILY is set to 0, SOURCE
161 				 * PREFIX-LENGTH and SCOPE PREFIX-LENGTH
162 				 * must be 0 and ADDRESS should not be
163 				 * present as the address and prefix
164 				 * lengths don't make sense because the
165 				 * family is unknown.
166 				 */
167 				if (addrlen != 0U || scope != 0U) {
168 					return DNS_R_OPTERR;
169 				}
170 				break;
171 			case 1:
172 				if (addrlen > 32U || scope > 32U) {
173 					return DNS_R_OPTERR;
174 				}
175 				break;
176 			case 2:
177 				if (addrlen > 128U || scope > 128U) {
178 					return DNS_R_OPTERR;
179 				}
180 				break;
181 			default:
182 				return DNS_R_OPTERR;
183 			}
184 			addrbytes = (addrlen + 7) / 8;
185 			if (addrbytes + 4 != length) {
186 				return DNS_R_OPTERR;
187 			}
188 
189 			if (addrbytes != 0U && (addrlen % 8) != 0) {
190 				uint8_t bits = ~0U << (8 - (addrlen % 8));
191 				bits &= sregion.base[addrbytes - 1];
192 				if (bits != sregion.base[addrbytes - 1]) {
193 					return DNS_R_OPTERR;
194 				}
195 			}
196 			isc_region_consume(&sregion, addrbytes);
197 			break;
198 		}
199 		case DNS_OPT_EXPIRE:
200 			/*
201 			 * Request has zero length.  Response is 32 bits.
202 			 */
203 			if (length != 0 && length != 4) {
204 				return DNS_R_OPTERR;
205 			}
206 			isc_region_consume(&sregion, length);
207 			break;
208 		case DNS_OPT_COOKIE:
209 			/*
210 			 * Client cookie alone has length 8.
211 			 * Client + server cookie is 8 + [8..32].
212 			 */
213 			if (length != 8 && (length < 16 || length > 40)) {
214 				return DNS_R_OPTERR;
215 			}
216 			isc_region_consume(&sregion, length);
217 			break;
218 		case DNS_OPT_KEY_TAG:
219 			if (length == 0 || (length % 2) != 0) {
220 				return DNS_R_OPTERR;
221 			}
222 			isc_region_consume(&sregion, length);
223 			break;
224 		case DNS_OPT_EDE:
225 			if (length < 2) {
226 				return DNS_R_OPTERR;
227 			}
228 			/* UTF-8 Byte Order Mark is not permitted. RFC 5198 */
229 			if (isc_utf8_bom(sregion.base + 2, length - 2)) {
230 				return DNS_R_OPTERR;
231 			}
232 			/*
233 			 * The EXTRA-TEXT field is specified as UTF-8, and
234 			 * therefore must be validated for correctness
235 			 * according to RFC 3269 security considerations.
236 			 */
237 			if (!isc_utf8_valid(sregion.base + 2, length - 2)) {
238 				return DNS_R_OPTERR;
239 			}
240 			isc_region_consume(&sregion, length);
241 			break;
242 		case DNS_OPT_CLIENT_TAG:
243 			FALLTHROUGH;
244 		case DNS_OPT_SERVER_TAG:
245 			if (length != 2) {
246 				return DNS_R_OPTERR;
247 			}
248 			isc_region_consume(&sregion, length);
249 			break;
250 		default:
251 			isc_region_consume(&sregion, length);
252 			break;
253 		}
254 		total += length;
255 	}
256 
257 	isc_buffer_activeregion(source, &sregion);
258 	isc_buffer_availableregion(target, &tregion);
259 	if (tregion.length < total) {
260 		return ISC_R_NOSPACE;
261 	}
262 	memmove(tregion.base, sregion.base, total);
263 	isc_buffer_forward(source, total);
264 	isc_buffer_add(target, total);
265 
266 	return ISC_R_SUCCESS;
267 }
268 
269 static isc_result_t
270 towire_opt(ARGS_TOWIRE) {
271 	REQUIRE(rdata->type == dns_rdatatype_opt);
272 
273 	UNUSED(cctx);
274 
275 	return mem_tobuffer(target, rdata->data, rdata->length);
276 }
277 
278 static int
279 compare_opt(ARGS_COMPARE) {
280 	isc_region_t r1;
281 	isc_region_t r2;
282 
283 	REQUIRE(rdata1->type == rdata2->type);
284 	REQUIRE(rdata1->rdclass == rdata2->rdclass);
285 	REQUIRE(rdata1->type == dns_rdatatype_opt);
286 
287 	dns_rdata_toregion(rdata1, &r1);
288 	dns_rdata_toregion(rdata2, &r2);
289 	return isc_region_compare(&r1, &r2);
290 }
291 
292 static isc_result_t
293 fromstruct_opt(ARGS_FROMSTRUCT) {
294 	dns_rdata_opt_t *opt = source;
295 	isc_region_t region;
296 	uint16_t length;
297 
298 	REQUIRE(type == dns_rdatatype_opt);
299 	REQUIRE(opt != NULL);
300 	REQUIRE(opt->common.rdtype == type);
301 	REQUIRE(opt->common.rdclass == rdclass);
302 	REQUIRE(opt->options != NULL || opt->length == 0);
303 
304 	UNUSED(type);
305 	UNUSED(rdclass);
306 
307 	region.base = opt->options;
308 	region.length = opt->length;
309 	while (region.length >= 4) {
310 		isc_region_consume(&region, 2); /* opt */
311 		length = uint16_fromregion(&region);
312 		isc_region_consume(&region, 2);
313 		if (region.length < length) {
314 			return ISC_R_UNEXPECTEDEND;
315 		}
316 		isc_region_consume(&region, length);
317 	}
318 	if (region.length != 0) {
319 		return ISC_R_UNEXPECTEDEND;
320 	}
321 
322 	return mem_tobuffer(target, opt->options, opt->length);
323 }
324 
325 static isc_result_t
326 tostruct_opt(ARGS_TOSTRUCT) {
327 	dns_rdata_opt_t *opt = target;
328 	isc_region_t r;
329 
330 	REQUIRE(rdata->type == dns_rdatatype_opt);
331 	REQUIRE(opt != NULL);
332 
333 	opt->common.rdclass = rdata->rdclass;
334 	opt->common.rdtype = rdata->type;
335 	ISC_LINK_INIT(&opt->common, link);
336 
337 	dns_rdata_toregion(rdata, &r);
338 	opt->length = r.length;
339 	opt->options = mem_maybedup(mctx, r.base, r.length);
340 	opt->offset = 0;
341 	opt->mctx = mctx;
342 	return ISC_R_SUCCESS;
343 }
344 
345 static void
346 freestruct_opt(ARGS_FREESTRUCT) {
347 	dns_rdata_opt_t *opt = source;
348 
349 	REQUIRE(opt != NULL);
350 	REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
351 
352 	if (opt->mctx == NULL) {
353 		return;
354 	}
355 
356 	if (opt->options != NULL) {
357 		isc_mem_free(opt->mctx, opt->options);
358 	}
359 	opt->mctx = NULL;
360 }
361 
362 static isc_result_t
363 additionaldata_opt(ARGS_ADDLDATA) {
364 	REQUIRE(rdata->type == dns_rdatatype_opt);
365 
366 	UNUSED(rdata);
367 	UNUSED(owner);
368 	UNUSED(add);
369 	UNUSED(arg);
370 
371 	return ISC_R_SUCCESS;
372 }
373 
374 static isc_result_t
375 digest_opt(ARGS_DIGEST) {
376 	/*
377 	 * OPT records are not digested.
378 	 */
379 
380 	REQUIRE(rdata->type == dns_rdatatype_opt);
381 
382 	UNUSED(rdata);
383 	UNUSED(digest);
384 	UNUSED(arg);
385 
386 	return ISC_R_NOTIMPLEMENTED;
387 }
388 
389 static bool
390 checkowner_opt(ARGS_CHECKOWNER) {
391 	REQUIRE(type == dns_rdatatype_opt);
392 
393 	UNUSED(type);
394 	UNUSED(rdclass);
395 	UNUSED(wildcard);
396 
397 	return dns_name_equal(name, dns_rootname);
398 }
399 
400 static bool
401 checknames_opt(ARGS_CHECKNAMES) {
402 	REQUIRE(rdata->type == dns_rdatatype_opt);
403 
404 	UNUSED(rdata);
405 	UNUSED(owner);
406 	UNUSED(bad);
407 
408 	return true;
409 }
410 
411 static int
412 casecompare_opt(ARGS_COMPARE) {
413 	return compare_opt(rdata1, rdata2);
414 }
415 
416 isc_result_t
417 dns_rdata_opt_first(dns_rdata_opt_t *opt) {
418 	REQUIRE(opt != NULL);
419 	REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
420 	REQUIRE(opt->options != NULL || opt->length == 0);
421 
422 	if (opt->length == 0) {
423 		return ISC_R_NOMORE;
424 	}
425 
426 	opt->offset = 0;
427 	return ISC_R_SUCCESS;
428 }
429 
430 isc_result_t
431 dns_rdata_opt_next(dns_rdata_opt_t *opt) {
432 	isc_region_t r;
433 	uint16_t length;
434 
435 	REQUIRE(opt != NULL);
436 	REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
437 	REQUIRE(opt->options != NULL && opt->length != 0);
438 	REQUIRE(opt->offset < opt->length);
439 
440 	INSIST(opt->offset + 4 <= opt->length);
441 	r.base = opt->options + opt->offset + 2;
442 	r.length = opt->length - opt->offset - 2;
443 	length = uint16_fromregion(&r);
444 	INSIST(opt->offset + 4 + length <= opt->length);
445 	opt->offset = opt->offset + 4 + length;
446 	if (opt->offset == opt->length) {
447 		return ISC_R_NOMORE;
448 	}
449 	return ISC_R_SUCCESS;
450 }
451 
452 isc_result_t
453 dns_rdata_opt_current(dns_rdata_opt_t *opt, dns_rdata_opt_opcode_t *opcode) {
454 	isc_region_t r;
455 
456 	REQUIRE(opt != NULL);
457 	REQUIRE(opcode != NULL);
458 	REQUIRE(opt->common.rdtype == dns_rdatatype_opt);
459 	REQUIRE(opt->options != NULL);
460 	REQUIRE(opt->offset < opt->length);
461 
462 	INSIST(opt->offset + 4 <= opt->length);
463 	r.base = opt->options + opt->offset;
464 	r.length = opt->length - opt->offset;
465 
466 	opcode->opcode = uint16_fromregion(&r);
467 	isc_region_consume(&r, 2);
468 	opcode->length = uint16_fromregion(&r);
469 	isc_region_consume(&r, 2);
470 	opcode->data = r.base;
471 	INSIST(opt->offset + 4 + opcode->length <= opt->length);
472 
473 	return ISC_R_SUCCESS;
474 }
475 
476 #endif /* RDATA_GENERIC_OPT_41_C */
477