Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Internet Systems Consortium, Inc. ("ISC") |
3 | | * |
4 | | * SPDX-License-Identifier: MPL-2.0 |
5 | | * |
6 | | * This Source Code Form is subject to the terms of the Mozilla Public |
7 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
8 | | * file, you can obtain one at https://mozilla.org/MPL/2.0/. |
9 | | * |
10 | | * See the COPYRIGHT file distributed with this work for additional |
11 | | * information regarding copyright ownership. |
12 | | */ |
13 | | |
14 | | /*! \file */ |
15 | | |
16 | | #include <isc/lex.h> |
17 | | #include <isc/log.h> |
18 | | |
19 | | #include <dns/callbacks.h> |
20 | | #include <dns/fixedname.h> |
21 | | #include <dns/rdata.h> |
22 | | #include <dns/rdataclass.h> |
23 | | #include <dns/rdatatype.h> |
24 | | #include <dns/skr.h> |
25 | | #include <dns/time.h> |
26 | | #include <dns/ttl.h> |
27 | | |
28 | | #define READLINE(lex, opt, token) |
29 | | |
30 | 0 | #define NEXTTOKEN(lex, opt, token) CHECK(isc_lex_gettoken(lex, opt, token)) |
31 | | |
32 | 0 | #define BADTOKEN() CLEANUP(ISC_R_UNEXPECTEDTOKEN) |
33 | | |
34 | 0 | #define TOKENSIZ (8 * 1024) |
35 | 0 | #define STR(t) ((t).value.as_textregion.base) |
36 | | |
37 | | static isc_result_t |
38 | | parse_rr(isc_lex_t *lex, isc_mem_t *mctx, char *owner, dns_name_t *origin, |
39 | | dns_rdataclass_t rdclass, isc_buffer_t *buf, dns_ttl_t *ttl, |
40 | 0 | dns_rdatatype_t *rdtype, dns_rdata_t **rdata) { |
41 | 0 | dns_rdatacallbacks_t callbacks; |
42 | 0 | dns_fixedname_t dfname; |
43 | 0 | dns_name_t *dname = NULL; |
44 | 0 | dns_rdataclass_t clas; |
45 | 0 | isc_buffer_t b; |
46 | 0 | isc_token_t token; |
47 | 0 | unsigned int opt = ISC_LEXOPT_EOL; |
48 | 0 | isc_result_t result = ISC_R_SUCCESS; |
49 | |
|
50 | 0 | isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE); |
51 | | |
52 | | /* Read the domain name */ |
53 | 0 | if (!strcmp(owner, "@")) { |
54 | 0 | BADTOKEN(); |
55 | 0 | } |
56 | 0 | dname = dns_fixedname_initname(&dfname); |
57 | 0 | isc_buffer_init(&b, owner, strlen(owner)); |
58 | 0 | isc_buffer_add(&b, strlen(owner)); |
59 | 0 | CHECK(dns_name_fromtext(dname, &b, dns_rootname, 0)); |
60 | 0 | if (dns_name_compare(dname, origin) != 0) { |
61 | 0 | CLEANUP(DNS_R_BADOWNERNAME); |
62 | 0 | } |
63 | 0 | isc_buffer_clear(&b); |
64 | | |
65 | | /* Read the next word: either TTL, class, or type */ |
66 | 0 | NEXTTOKEN(lex, opt, &token); |
67 | 0 | if (token.type != isc_tokentype_string) { |
68 | 0 | BADTOKEN(); |
69 | 0 | } |
70 | | |
71 | | /* If it's a TTL, read the next one */ |
72 | 0 | result = dns_ttl_fromtext(&token.value.as_textregion, ttl); |
73 | 0 | if (result == ISC_R_SUCCESS) { |
74 | 0 | NEXTTOKEN(lex, opt, &token); |
75 | 0 | } |
76 | 0 | if (token.type != isc_tokentype_string) { |
77 | 0 | BADTOKEN(); |
78 | 0 | } |
79 | | |
80 | | /* If it's a class, read the next one */ |
81 | 0 | result = dns_rdataclass_fromtext(&clas, &token.value.as_textregion); |
82 | 0 | if (result == ISC_R_SUCCESS) { |
83 | 0 | if (clas != rdclass) { |
84 | 0 | BADTOKEN(); |
85 | 0 | } |
86 | 0 | NEXTTOKEN(lex, opt, &token); |
87 | 0 | } |
88 | 0 | if (token.type != isc_tokentype_string) { |
89 | 0 | BADTOKEN(); |
90 | 0 | } |
91 | | |
92 | | /* Must be the record type */ |
93 | 0 | result = dns_rdatatype_fromtext(rdtype, &token.value.as_textregion); |
94 | 0 | if (result != ISC_R_SUCCESS) { |
95 | 0 | BADTOKEN(); |
96 | 0 | } |
97 | 0 | switch (*rdtype) { |
98 | 0 | case dns_rdatatype_dnskey: |
99 | 0 | case dns_rdatatype_cdnskey: |
100 | 0 | case dns_rdatatype_cds: |
101 | 0 | case dns_rdatatype_rrsig: |
102 | | /* Allowed record types */ |
103 | 0 | break; |
104 | 0 | default: |
105 | 0 | BADTOKEN(); |
106 | 0 | } |
107 | | |
108 | 0 | dns_rdatacallbacks_init(&callbacks); |
109 | 0 | result = dns_rdata_fromtext(*rdata, rdclass, *rdtype, lex, dname, 0, |
110 | 0 | mctx, buf, &callbacks); |
111 | 0 | cleanup: |
112 | 0 | isc_lex_setcomments(lex, 0); |
113 | 0 | return result; |
114 | 0 | } |
115 | | |
116 | | static void |
117 | | skrbundle_create(isc_mem_t *mctx, isc_stdtime_t inception, |
118 | 0 | dns_skrbundle_t **bp) { |
119 | 0 | dns_skrbundle_t *b; |
120 | |
|
121 | 0 | REQUIRE(bp != NULL && *bp == NULL); |
122 | |
|
123 | 0 | b = isc_mem_get(mctx, sizeof(*b)); |
124 | 0 | b->magic = DNS_SKRBUNDLE_MAGIC; |
125 | 0 | b->inception = inception; |
126 | 0 | dns_diff_init(mctx, &b->diff); |
127 | |
|
128 | 0 | ISC_LINK_INIT(b, link); |
129 | |
|
130 | 0 | *bp = b; |
131 | 0 | } |
132 | | |
133 | | static void |
134 | 0 | skrbundle_addtuple(dns_skrbundle_t *bundle, dns_difftuple_t **tuple) { |
135 | 0 | REQUIRE(DNS_DIFFTUPLE_VALID(*tuple)); |
136 | 0 | REQUIRE(DNS_SKRBUNDLE_VALID(bundle)); |
137 | 0 | REQUIRE(DNS_DIFF_VALID(&bundle->diff)); |
138 | |
|
139 | 0 | dns_diff_append(&bundle->diff, tuple); |
140 | 0 | } |
141 | | |
142 | | isc_result_t |
143 | | dns_skrbundle_getsig(dns_skrbundle_t *bundle, dst_key_t *key, |
144 | 0 | dns_rdatatype_t covering_type, dns_rdata_t *sigrdata) { |
145 | 0 | REQUIRE(DNS_SKRBUNDLE_VALID(bundle)); |
146 | 0 | REQUIRE(DNS_DIFF_VALID(&bundle->diff)); |
147 | |
|
148 | 0 | ISC_LIST_FOREACH(bundle->diff.tuples, tuple, link) { |
149 | 0 | dns_rdata_rrsig_t rrsig; |
150 | |
|
151 | 0 | if (tuple->op != DNS_DIFFOP_ADDRESIGN) { |
152 | 0 | continue; |
153 | 0 | } |
154 | 0 | INSIST(tuple->rdata.type == dns_rdatatype_rrsig); |
155 | |
|
156 | 0 | RETERR(dns_rdata_tostruct(&tuple->rdata, &rrsig, NULL)); |
157 | | |
158 | | /* |
159 | | * Check if covering type matches, and if the signature is |
160 | | * generated by 'key'. |
161 | | */ |
162 | 0 | if (rrsig.covered == covering_type && |
163 | 0 | rrsig.keyid == dst_key_id(key)) |
164 | 0 | { |
165 | 0 | dns_rdata_clone(&tuple->rdata, sigrdata); |
166 | 0 | return ISC_R_SUCCESS; |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | 0 | return ISC_R_NOTFOUND; |
171 | 0 | } |
172 | | |
173 | | void |
174 | | dns_skr_create(isc_mem_t *mctx, const char *filename, dns_name_t *origin, |
175 | 0 | dns_rdataclass_t rdclass, dns_skr_t **skrp) { |
176 | 0 | isc_time_t now; |
177 | 0 | dns_skr_t *skr = NULL; |
178 | |
|
179 | 0 | REQUIRE(skrp != NULL && *skrp == NULL); |
180 | 0 | REQUIRE(mctx != NULL); |
181 | |
|
182 | 0 | UNUSED(origin); |
183 | 0 | UNUSED(rdclass); |
184 | |
|
185 | 0 | now = isc_time_now(); |
186 | 0 | skr = isc_mem_get(mctx, sizeof(*skr)); |
187 | 0 | *skr = (dns_skr_t){ |
188 | 0 | .magic = DNS_SKR_MAGIC, |
189 | 0 | .filename = isc_mem_strdup(mctx, filename), |
190 | 0 | .loadtime = now, |
191 | 0 | }; |
192 | | /* |
193 | | * A list is not the best structure to store bundles that |
194 | | * we need to look up, but we don't expect many bundles |
195 | | * per SKR so it is acceptable for now. |
196 | | */ |
197 | 0 | ISC_LIST_INIT(skr->bundles); |
198 | |
|
199 | 0 | isc_mem_attach(mctx, &skr->mctx); |
200 | 0 | isc_refcount_init(&skr->references, 1); |
201 | 0 | *skrp = skr; |
202 | 0 | } |
203 | | |
204 | | static void |
205 | 0 | addbundle(dns_skr_t *skr, dns_skrbundle_t **bundlep) { |
206 | 0 | REQUIRE(DNS_SKR_VALID(skr)); |
207 | 0 | REQUIRE(DNS_SKRBUNDLE_VALID(*bundlep)); |
208 | |
|
209 | 0 | ISC_LIST_APPEND(skr->bundles, *bundlep, link); |
210 | 0 | *bundlep = NULL; |
211 | 0 | } |
212 | | |
213 | | isc_result_t |
214 | | dns_skr_read(isc_mem_t *mctx, const char *filename, dns_name_t *origin, |
215 | 0 | dns_rdataclass_t rdclass, dns_ttl_t dnskeyttl, dns_skr_t **skrp) { |
216 | 0 | isc_result_t result; |
217 | 0 | dns_skrbundle_t *bundle = NULL; |
218 | 0 | char bundlebuf[1024]; |
219 | 0 | uint32_t bundle_id; |
220 | 0 | isc_lex_t *lex = NULL; |
221 | 0 | isc_lexspecials_t specials; |
222 | 0 | isc_token_t token; |
223 | 0 | unsigned int opt = ISC_LEXOPT_EOL; |
224 | |
|
225 | 0 | REQUIRE(DNS_SKR_VALID(*skrp)); |
226 | |
|
227 | 0 | isc_lex_create(mctx, TOKENSIZ, &lex); |
228 | 0 | memset(specials, 0, sizeof(specials)); |
229 | 0 | specials['('] = 1; |
230 | 0 | specials[')'] = 1; |
231 | 0 | specials['"'] = 1; |
232 | 0 | isc_lex_setspecials(lex, specials); |
233 | 0 | result = isc_lex_openfile(lex, filename); |
234 | 0 | if (result != ISC_R_SUCCESS) { |
235 | 0 | isc_log_write(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, |
236 | 0 | ISC_LOG_ERROR, "unable to open ksr file %s: %s", |
237 | 0 | filename, isc_result_totext(result)); |
238 | 0 | isc_lex_destroy(&lex); |
239 | 0 | return result; |
240 | 0 | } |
241 | | |
242 | 0 | for (result = isc_lex_gettoken(lex, opt, &token); |
243 | 0 | result == ISC_R_SUCCESS; |
244 | 0 | result = isc_lex_gettoken(lex, opt, &token)) |
245 | 0 | { |
246 | 0 | if (token.type == isc_tokentype_eol) { |
247 | 0 | continue; |
248 | 0 | } |
249 | | |
250 | 0 | if (token.type != isc_tokentype_string) { |
251 | 0 | CLEANUP(DNS_R_SYNTAX); |
252 | 0 | } |
253 | | |
254 | 0 | if (strcmp(STR(token), ";;") == 0) { |
255 | | /* New bundle */ |
256 | 0 | CHECK(isc_lex_gettoken(lex, opt, &token)); |
257 | 0 | if (token.type != isc_tokentype_string || |
258 | 0 | strcmp(STR(token), "SignedKeyResponse") != 0) |
259 | 0 | { |
260 | 0 | CLEANUP(DNS_R_SYNTAX); |
261 | 0 | } |
262 | | |
263 | | /* Version */ |
264 | 0 | CHECK(isc_lex_gettoken(lex, opt, &token)); |
265 | 0 | if (token.type != isc_tokentype_string || |
266 | 0 | strcmp(STR(token), "1.0") != 0) |
267 | 0 | { |
268 | 0 | CLEANUP(DNS_R_SYNTAX); |
269 | 0 | } |
270 | | |
271 | | /* Date and time of bundle */ |
272 | 0 | CHECK(isc_lex_gettoken(lex, opt, &token)); |
273 | 0 | if (token.type != isc_tokentype_string) { |
274 | 0 | CLEANUP(DNS_R_SYNTAX); |
275 | 0 | } |
276 | 0 | if (strcmp(STR(token), "generated") == 0) { |
277 | | /* Final bundle */ |
278 | 0 | goto readline; |
279 | 0 | } |
280 | 0 | if (token.type != isc_tokentype_string) { |
281 | 0 | CLEANUP(DNS_R_SYNTAX); |
282 | 0 | } |
283 | | |
284 | | /* Add previous bundle */ |
285 | 0 | if (bundle != NULL) { |
286 | 0 | addbundle(*skrp, &bundle); |
287 | 0 | } |
288 | | |
289 | | /* Create new bundle */ |
290 | 0 | sscanf(STR(token), "%s", bundlebuf); |
291 | 0 | CHECK(dns_time32_fromtext(bundlebuf, &bundle_id)); |
292 | 0 | bundle = NULL; |
293 | 0 | skrbundle_create(mctx, (isc_stdtime_t)bundle_id, |
294 | 0 | &bundle); |
295 | |
|
296 | 0 | readline: |
297 | | /* Read remainder of header line */ |
298 | 0 | do { |
299 | 0 | CHECK(isc_lex_gettoken(lex, opt, &token)); |
300 | 0 | } while (token.type != isc_tokentype_eol); |
301 | 0 | } else { |
302 | 0 | isc_buffer_t buf; |
303 | 0 | dns_rdata_t *rdata = NULL; |
304 | 0 | u_char rdatabuf[DST_KEY_MAXSIZE]; |
305 | 0 | dns_rdatatype_t rdtype; |
306 | | |
307 | | /* Parse record */ |
308 | 0 | rdata = isc_mem_get(mctx, sizeof(*rdata)); |
309 | 0 | dns_rdata_init(rdata); |
310 | 0 | isc_buffer_init(&buf, rdatabuf, sizeof(rdatabuf)); |
311 | 0 | result = parse_rr(lex, mctx, STR(token), origin, |
312 | 0 | rdclass, &buf, &dnskeyttl, &rdtype, |
313 | 0 | &rdata); |
314 | 0 | if (result != ISC_R_SUCCESS) { |
315 | 0 | isc_log_write( |
316 | 0 | DNS_LOGCATEGORY_GENERAL, |
317 | 0 | DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(1), |
318 | 0 | "read skr file %s(%lu) parse rr " |
319 | 0 | "failed: %s", |
320 | 0 | filename, isc_lex_getsourceline(lex), |
321 | 0 | isc_result_totext(result)); |
322 | 0 | isc_mem_put(mctx, rdata, sizeof(*rdata)); |
323 | 0 | goto cleanup; |
324 | 0 | } |
325 | | |
326 | | /* Create new diff tuple */ |
327 | 0 | dns_diffop_t op = (rdtype == dns_rdatatype_rrsig) |
328 | 0 | ? DNS_DIFFOP_ADDRESIGN |
329 | 0 | : DNS_DIFFOP_ADD; |
330 | 0 | dns_difftuple_t *tuple = NULL; |
331 | |
|
332 | 0 | dns_difftuple_create((*skrp)->mctx, op, origin, |
333 | 0 | dnskeyttl, rdata, &tuple); |
334 | |
|
335 | 0 | skrbundle_addtuple(bundle, &tuple); |
336 | 0 | INSIST(tuple == NULL); |
337 | |
|
338 | 0 | isc_mem_put(mctx, rdata, sizeof(*rdata)); |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | 0 | if (result != ISC_R_EOF) { |
343 | 0 | CLEANUP(DNS_R_SYNTAX); |
344 | 0 | } |
345 | 0 | result = ISC_R_SUCCESS; |
346 | | |
347 | | /* Add final bundle */ |
348 | 0 | if (bundle != NULL) { |
349 | 0 | addbundle(*skrp, &bundle); |
350 | 0 | } |
351 | |
|
352 | 0 | cleanup: |
353 | 0 | if (result != ISC_R_SUCCESS) { |
354 | 0 | isc_log_write(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, |
355 | 0 | ISC_LOG_DEBUG(1), |
356 | 0 | "read skr file %s(%lu) failed: %s", filename, |
357 | 0 | isc_lex_getsourceline(lex), |
358 | 0 | isc_result_totext(result)); |
359 | 0 | } |
360 | | |
361 | | /* Clean up */ |
362 | 0 | isc_lex_destroy(&lex); |
363 | 0 | return result; |
364 | 0 | } |
365 | | |
366 | | dns_skrbundle_t * |
367 | 0 | dns_skr_lookup(dns_skr_t *skr, isc_stdtime_t time, uint32_t sigval) { |
368 | 0 | REQUIRE(DNS_SKR_VALID(skr)); |
369 | |
|
370 | 0 | ISC_LIST_FOREACH(skr->bundles, b, link) { |
371 | 0 | dns_skrbundle_t *next = ISC_LIST_NEXT(b, link); |
372 | 0 | isc_stdtime_t expired = (next != NULL) |
373 | 0 | ? next->inception |
374 | 0 | : (b->inception + sigval); |
375 | 0 | if (b->inception <= time && time < expired) { |
376 | 0 | return b; |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | 0 | return NULL; |
381 | 0 | } |
382 | | |
383 | | void |
384 | 0 | dns_skr_attach(dns_skr_t *source, dns_skr_t **targetp) { |
385 | 0 | REQUIRE(DNS_SKR_VALID(source)); |
386 | 0 | REQUIRE(targetp != NULL && *targetp == NULL); |
387 | |
|
388 | 0 | isc_refcount_increment(&source->references); |
389 | 0 | *targetp = source; |
390 | 0 | } |
391 | | |
392 | | void |
393 | 0 | dns_skr_detach(dns_skr_t **skrp) { |
394 | 0 | REQUIRE(skrp != NULL && DNS_SKR_VALID(*skrp)); |
395 | |
|
396 | 0 | dns_skr_t *skr = *skrp; |
397 | 0 | *skrp = NULL; |
398 | |
|
399 | 0 | if (isc_refcount_decrement(&skr->references) == 1) { |
400 | 0 | dns_skr_destroy(skr); |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | | void |
405 | 0 | dns_skr_destroy(dns_skr_t *skr) { |
406 | 0 | REQUIRE(DNS_SKR_VALID(skr)); |
407 | |
|
408 | 0 | ISC_LIST_FOREACH(skr->bundles, b, link) { |
409 | 0 | ISC_LIST_UNLINK(skr->bundles, b, link); |
410 | 0 | dns_diff_clear(&b->diff); |
411 | 0 | isc_mem_put(skr->mctx, b, sizeof(*b)); |
412 | 0 | } |
413 | 0 | INSIST(ISC_LIST_EMPTY(skr->bundles)); |
414 | |
|
415 | 0 | isc_mem_free(skr->mctx, skr->filename); |
416 | | isc_mem_putanddetach(&skr->mctx, skr, sizeof(*skr)); |
417 | 0 | } |