]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/dnssec_v2/dnssec_v2_client.c
mDNSResponder-1310.40.42.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / dnssec_v2 / dnssec_v2_client.c
1 //
2 // dnssec_v2_client.c
3 // mDNSResponder
4 //
5 // Copyright (c) 2020 Apple Inc. All rights reserved.
6 //
7
8 #include "mDNSEmbeddedAPI.h"
9 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
10 #include "udns.h"
11 #include "dnssec_v2.h"
12 #include "dnssec_v2_client.h"
13 #include "dnssec_v2_retrieval.h"
14 #include "dnssec_v2_helper.h"
15
16 //======================================================================================================================
17 // Local function prototype
18 //======================================================================================================================
19
20 // When calling remove_records_if_necessary/add_records_if_necessary, always check the boolean value stored in
21 // out_stop_immediately. If the value is true, then the caller should stop all the work immediately and return, because
22 // the functions might call the callback function which may cancel the current request and the question we are working
23 // on.
24
25 mDNSlocal mDNSBool
26 remove_records_if_necessary(
27 dnssec_context_t * const _Nonnull dnssec_context,
28 const dnssec_result_t dnssec_result,
29 mDNS * const mdnsresponder_context,
30 DNSQuestion * const _Nonnull question,
31 mDNSBool * _Nonnull out_stop_immediately);
32
33 mDNSlocal mDNSBool
34 add_records_if_necessary(
35 dnssec_context_t * const _Nonnull dnssec_context,
36 const dnssec_result_t dnssec_result,
37 const DNSServiceErrorType dns_error_from_core,
38 mDNS * const mdnsresponder_context,
39 DNSQuestion * const _Nonnull question,
40 mDNSBool * _Nonnull out_stop_immediately);
41
42 mDNSlocal mStatus
43 handle_cname_response(dnssec_context_t * const _Nonnull context);
44
45 // Always check the return value of return_answer_to_user. If the value is true, then the caller should stop all the
46 // work immediately and return, because the functions may call the callback function which may cancel the current
47 // request and the question we are working on.
48 mDNSlocal mDNSBool
49 return_answer_to_user(
50 QueryRecordResultHandler user_handler,
51 void * const user_context,
52 dnssec_result_t result,
53 ResourceRecord * const _Nonnull rr,
54 const QC_result add_or_remove,
55 const DNSServiceErrorType dns_error,
56 mDNS * const _Nonnull m,
57 DNSQuestion * const _Nonnull primary_question,
58 const DNSQuestion * const _Nonnull current_question);
59
60 mDNSlocal mStatus
61 add_record_to_be_returned_to_returned_answers_t(returned_answers_t * const returned_asnwers, const ResourceRecord * const rr);
62
63 mDNSlocal mDNSBool
64 contains_rr_in_returned_rrs(const ResourceRecord * const rr, const list_t * const returned_rrs/* list_t<ResourceRecord *> */);
65
66 mDNSlocal DNSServiceErrorType
67 get_dnsservice_error_type_from_originals(const original_t * const _Nonnull original, DNSServiceErrorType type_from_mDNSCore);
68
69 #pragma mark - handle_validation_result
70
71 mDNSlocal void
72 handle_cname_retrieval_error(dnssec_context_t * const _Nonnull dnssec_context,
73 mDNS * const _Nonnull mdnsresponder_context);
74
75 mDNSexport mDNSBool
76 handle_retrieval_result(
77 DNSQuestion * const _Nonnull question,
78 dnssec_context_t * _Nonnull original_dnssec_context,
79 dnssec_retrieval_result_t retrieval_result,
80 const DNSServiceErrorType dnssec_error,
81 mDNS * const _Nonnull mdnsresponder_context) {
82
83 mDNSBool stop = mDNSfalse;
84 // We use a new variable here so that current_dnssec_context can be set to NULL when it is freed, to avoid corrupted
85 // memory access.
86 dnssec_context_t * current_dnssec_context = original_dnssec_context;
87 mDNSu32 request_id = current_dnssec_context->original.original_parameters.request_id;
88 mDNSu16 question_id = mDNSVal16(question->TargetQID);
89 dnssec_result_t dnssec_result;
90 mDNSBool stop_immediately = mDNSfalse;
91
92 switch (retrieval_result) {
93 case dnssec_retrieval_no_error:
94 case dnssec_retrieval_waiting_for_records: // We are waiting for the response for some active queries.
95 case dnssec_retrieval_validate_again: // We have a trust anchor for the current root, and will use it to validate immediately.
96 stop = mDNSfalse;
97 break;
98
99 case dnssec_retrieval_no_new_change:
100 stop = mDNStrue;
101 break;
102
103 case dnssec_retrieval_no_rrsig: { // The returned answer is missing some necessary records to finish DNSSEC, it usually means the domain is not signed, thus it is an insecure zone.
104 mDNSu16 question_type = current_dnssec_context->original.original_parameters.question_type;
105 response_type_t response_type = current_dnssec_context->original.original_result_with_rrsig.type;
106
107 dnssec_result = dnssec_insecure;
108 stop = mDNStrue;
109
110 if (response_type == cname_response && question_type != kDNSServiceType_CNAME) {
111 // After calling handle_cname_retrieval_error, dnssec_context will be freed, and should never be used.
112 handle_cname_retrieval_error(current_dnssec_context, mdnsresponder_context);
113 // Set current_dnssec_context to NULL to avoid any further access.
114 current_dnssec_context = NULL;
115 goto exit;
116 }
117
118 remove_records_if_necessary(current_dnssec_context, dnssec_result, mdnsresponder_context, question,
119 &stop_immediately);
120 require_quiet(!stop_immediately, exit);
121
122 add_records_if_necessary(current_dnssec_context, dnssec_result, dnssec_error, mdnsresponder_context,
123 question, &stop_immediately);
124 require_quiet(!stop_immediately, exit);
125 }
126 break;
127 case dnssec_retrieval_suppressed: {
128 // Suppressed answers are generated by mDNSResponder itself, and we do not have a way to validate this
129 // generated record, thus the DNSSEC result should be indeterminate.
130 dnssec_result = dnssec_indeterminate;
131 stop = mDNStrue;
132
133 remove_records_if_necessary(current_dnssec_context, dnssec_result, mdnsresponder_context, question,
134 &stop_immediately);
135 require_quiet(!stop_immediately, exit);
136 add_records_if_necessary(current_dnssec_context, dnssec_result, dnssec_error, mdnsresponder_context,
137 question, &stop_immediately);
138 require_quiet(!stop_immediately, exit);
139 }
140 break;
141 case dnssec_retrieval_cname_removed: {
142 // if the CNAME is removed, then the sub request that follows this removed CNAME is no longer valid, and
143 // we need to stop it. If the CNAME record does not exist anymore, there is nothing we could do to validate
144 // the record(Maybe later we would get NSEC/NSEC3 records that denies the existence of this CNAME),
145 // therefore, it is marked as indeterminate.
146 dnssec_result = dnssec_indeterminate;
147 stop = mDNStrue;
148
149 remove_records_if_necessary(current_dnssec_context, dnssec_result, mdnsresponder_context, question,
150 &stop_immediately);
151 require_quiet(!stop_immediately, exit);
152
153 require_action_quiet(current_dnssec_context->subtask_dnssec_context != mDNSNULL, exit, stop = mDNStrue;
154 log_error("[R%u->Q%u] Get dnssec_retrieval_cname_removed error while the context of CNAME request is NULL",
155 request_id, question_id));
156
157 stop_immediately = stop_sub_cname_request_and_dnssec(question, current_dnssec_context, mDNStrue, mdnsresponder_context);
158 require_quiet(!stop_immediately, exit);
159 // Since the previous CNAME we rely on is removed, there is nothing we can do next, so we should stop the
160 // current processing.
161
162 }
163 break;
164 default:
165 stop = mDNStrue;
166 log_error("[R%u->Q%u] handle_retrieval_result not handling this type of error; retrieval_result=%d",
167 request_id, question_id, retrieval_result);
168 break;
169 }
170
171 exit:
172 return stop || stop_immediately;
173 }
174
175 //======================================================================================================================
176
177 mDNSlocal void
178 handle_cname_retrieval_error(dnssec_context_t * const _Nonnull dnssec_context,
179 mDNS * const _Nonnull mdnsresponder_context)
180 {
181 dnssec_context_t * const primary_dnssec_context = GET_PRIMARY_DNSSEC_CONTEXT(dnssec_context);
182 QueryRecordClientRequest * const query_request = GET_PRIMARY_REQUEST(dnssec_context);
183 DNSQuestion * const q = &query_request->op.q;
184 QueryRecordResultHandler user_handler = primary_dnssec_context->original.original_parameters.user_handler;
185 void * const user_context = primary_dnssec_context->original.original_parameters.user_context;
186 cnames_with_rrsig_t *cname_ptr;
187 list_node_t *cname_node;
188 dnssec_cname_t * dnssec_cname;
189 ResourceRecord * rr_ptr;
190
191 // Get the CNAME that has no RRSIG
192 cname_ptr = &dnssec_context->original.original_result_with_rrsig.u.cname_with_rrsig;
193 cname_node = list_get_first(&cname_ptr->cname_records);
194 // Should never be NULL, but add checking here to avoid invalid memory access.
195 verify_action(cname_node != mDNSNULL, return);
196 dnssec_cname = (dnssec_cname_t *)cname_node->data;
197 rr_ptr = dnssec_cname->dnssec_rr.rr; // this pointer points to the cached resource record in mDNSCore.
198 // Should never be NULL, since when dnssec_cname_t is initialized in initialize_dnssec_cname_t, the rr has to be non-NULL.
199 verify_action(rr_ptr != mDNSNULL, return);
200
201 // After calling this function, dnssec_context has been freed, and should never be used.
202 stop_dnssec(query_request);
203
204 query_request->op.resultHandler = user_handler; // change the user handler back to original one
205 query_request->op.resultContext = user_context; // change the user context back to original one
206
207 // Have to grab the lock to avoid any call when we are restarting the query.
208 mDNS_Lock(mdnsresponder_context);
209 AnswerQuestionByFollowingCNAME(mdnsresponder_context, q, rr_ptr);
210 mDNS_Unlock(mdnsresponder_context);
211
212 // Disable DNSSEC for the current request.
213 q->DNSSECStatus.enable_dnssec = mDNSfalse;
214 q->DNSSECStatus.tried_dnssec_but_unsigned = mDNStrue;
215 q->DNSSECStatus.context = mDNSNULL;
216 }
217
218 //======================================================================================================================
219 // handle_validation_result
220 // When error happens or validation succeeds while we are validating records, handle it such as return the answer to user
221 //======================================================================================================================
222
223 #pragma mark - handle_validation_result
224
225 mDNSlocal mStatus
226 handle_cname_response(dnssec_context_t * const _Nonnull context);
227
228 mDNSexport mDNSBool
229 handle_validation_result(
230 DNSQuestion * const _Nonnull question,
231 dnssec_context_t * const _Nonnull dnssec_context,
232 dnssec_validation_result_t validation_result,
233 const DNSServiceErrorType dnssec_error,
234 mDNS * const _Nonnull mdnsresponder_context) {
235
236 mStatus error = mStatus_NoError;
237 mDNSBool stop = mDNSfalse;
238 mDNSBool stop_immediately = mDNSfalse;
239 dnssec_result_t dnssec_result;
240
241 switch (validation_result) {
242 case dnssec_validation_trusted: {
243 // return the secure answer to the user or continue the DNSSEC validation process if the verified answer is
244 // CNAME and user is not querying for CNAME record
245 mDNSu16 question_type = dnssec_context->original.original_parameters.question_type;
246 response_type_t response_type = dnssec_context->original.original_result_with_rrsig.type;
247 dnssec_context_t * const cname_dnssec_context = dnssec_context->subtask_dnssec_context;
248 dnssec_result = dnssec_secure;
249
250 if (response_type == cname_response && question_type != kDNSType_CNAME) {
251 if (cname_dnssec_context != mDNSNULL) {
252 // stop the old CNAME request, if the reference has changed
253 const list_t * cnames = &dnssec_context->original.original_result_with_rrsig.u.cname_with_rrsig.cname_records;
254 const mDNSu8 * const new_cname = ((dnssec_cname_t *)list_get_first(cnames)->data)->cname;
255 const mDNSu8 * const old_cname = cname_dnssec_context->original.original_parameters.question_name.c;
256
257 if (DOMAIN_NAME_EQUALS(new_cname, old_cname)) {
258 // CNAME reference does not change
259 stop = mDNStrue;
260 goto exit;
261 }
262
263 // stop CNAME request and also deliver RMV event for those records that are returned to the client.
264 stop_immediately = stop_sub_cname_request_and_dnssec(question, dnssec_context, mDNStrue, mdnsresponder_context);
265 require_quiet(!stop_immediately, exit);
266 }
267 error = handle_cname_response(dnssec_context);
268 if (error != mStatus_NoError) {
269 stop = mDNStrue;
270 goto exit;
271 }
272 stop = mDNStrue;
273 // if the user requries CNAME refrence to be returned
274 mDNSBool return_intermediates = ((dnssec_context->original.original_parameters.flags & kDNSServiceFlagsReturnIntermediates) != 0);
275 if (!return_intermediates) {
276 goto exit;
277 }
278 // fall outside of it intentionally
279 }
280 stop = mDNStrue;
281 remove_records_if_necessary(dnssec_context, dnssec_result, mdnsresponder_context, question,
282 &stop_immediately);
283 require_quiet(!stop_immediately, exit);
284 add_records_if_necessary(dnssec_context, dnssec_result, dnssec_error, mdnsresponder_context, question,
285 &stop_immediately);
286 require_quiet(!stop_immediately, exit);
287 goto exit;
288 }
289 case dnssec_validation_trust_anchor_does_not_macth: {
290 const dnssec_zone_t * const zone = list_empty(&dnssec_context->zone_chain) ? mDNSNULL : (dnssec_zone_t *)list_get_last(&dnssec_context->zone_chain)->data;
291 if (zone != mDNSNULL && is_root_domain(zone->domain_name.c) && !trust_anchor_contains_dnskey(zone->trust_anchor)) {
292 // root DS trust anchor failed to validate the record, maybe update our trust anchor
293 stop = mDNStrue;
294 log_error("root trust anchor does not verifies the validation tree");
295 break;
296 } else {
297 // tries to fetch records from the DNS server instead of using local trust anchor
298 goto exit;
299 }
300 }
301 break;
302 default:
303 stop = mDNStrue;
304 log_error("handle_validation_result not hanlding this type of error; validation_result=%d", validation_result);
305 break;
306 }
307
308 exit:
309 return stop || stop_immediately;
310 }
311
312
313 #pragma mark handle_cname_response
314 // follow the CNAME reference chain, create another DNSSEC request to finish the CNAME query
315 mDNSlocal mStatus
316 handle_cname_response(dnssec_context_t * const _Nonnull context) {
317 response_type_t type = context->original.original_result_with_rrsig.type;
318 domainname * old_question_name;
319 original_request_parameters_t * parameters;
320 domainname new_question_name;
321 dnssec_context_t * new_context = mDNSNULL;
322 mStatus error = mStatus_NoError;
323
324 // handle the cname referencing
325 list_t *cnames = &context->original.original_result_with_rrsig.u.cname_with_rrsig.cname_records;
326 verify(type == cname_response && list_count_node(cnames) == 1);
327 old_question_name = (domainname *)(((dnssec_cname_t *)list_get_first(cnames)->data)->cname);
328 AssignDomainName(&new_question_name, old_question_name);
329
330 // Create new dnssec_context_t for the CNAME
331 parameters = &context->original.original_parameters;
332 error = create_dnssec_context_t(mDNSNULL, parameters->request_id, &new_question_name, parameters->question_type,
333 parameters->question_class, parameters->interface_id, parameters->service_id, parameters->flags,
334 parameters->append_search_domains, parameters->pid, parameters->uuid, parameters->uid,
335 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
336 parameters->has_peer_audit_token ? &parameters->peer_audit_token : mDNSNULL,
337 parameters->has_delegate_audit_token ? &parameters->delegate_audit_token : mDNSNULL,
338 #endif
339 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
340 parameters->resolver_uuid, parameters->need_encryption, parameters->custom_id,
341 #endif
342 parameters->user_handler, parameters->user_context,
343 context->primary_dnssec_context ? context->primary_dnssec_context : context,
344 &new_context);
345 require_action(error == mStatus_NoError, exit,
346 log_error("create_dnssec_context_t failed; error_description='%s'", mStatusDescription(error)));
347
348 new_context->me = &context->request_to_follow_cname;
349
350 // start a new dnssec request with new_question_name
351 error = QueryRecordOpStartForClientRequest(&new_context->me->op, parameters->request_id, &new_question_name,
352 parameters->question_type, parameters->question_class, parameters->interface_id, parameters->service_id,
353 parameters->flags, parameters->append_search_domains, parameters->pid, parameters->uuid, parameters->uid,
354 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
355 parameters->has_peer_audit_token ? &parameters->peer_audit_token : mDNSNULL,
356 parameters->has_delegate_audit_token ? &parameters->delegate_audit_token : mDNSNULL,
357 #endif
358 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
359 parameters->resolver_uuid, parameters->need_encryption, parameters->custom_id,
360 #endif
361 query_record_result_reply_with_dnssec, new_context);
362 require_action(error == mStatus_NoError, exit,
363 log_error("QueryRecordOpStart failed; error_description='%s'", mStatusDescription(error)));
364
365 context->subtask_dnssec_context = new_context;
366
367 exit:
368 if (error != mStatus_NoError) {
369 if (new_context != mDNSNULL) {
370 destroy_dnssec_context_t(new_context);
371 }
372 }
373 return error;
374 }
375
376 #pragma mark remove_records_if_necessary
377
378 mDNSlocal mDNSBool
379 deliver_remove_for_returned_records(
380 dnssec_context_t * const _Nonnull dnssec_context,
381 const dnssec_result_t dnssec_result,
382 mDNS * const _Nonnull mdnsresponder_context,
383 DNSQuestion * const _Nonnull question,
384 const response_type_t type,
385 mDNSBool * _Nonnull out_stop_immediately);
386
387 mDNSlocal mDNSBool
388 remove_records_if_necessary(
389 dnssec_context_t * const _Nonnull dnssec_context,
390 const dnssec_result_t dnssec_result,
391 mDNS * const _Nonnull mdnsresponder_context,
392 DNSQuestion * const _Nonnull question,
393 mDNSBool * _Nonnull out_stop_immediately) {
394
395 returned_answers_t * const returned_answers = &dnssec_context->returned_answers;
396 const original_t * const original = &dnssec_context->original;
397 mDNSBool delivered_remove_all = mDNSfalse;
398 mDNSBool delivered_remove_some = mDNSfalse;
399
400 // check if we ever returned answers back to the user
401 if (returned_answers->error == kDNSServiceErr_Invalid) {
402 // there is no previous returned anwers(which means this is our first time to return the answer to the user)
403 goto exit;
404 }
405
406 // if we return No Error answer to the user
407 if (returned_answers->error == kDNSServiceErr_NoError) {
408 require_action_quiet(returned_answers->type == original_response || returned_answers->type == cname_response,
409 exit, log_error("kDNSServiceErr_NoError must be matched with original_response or cname_response"));
410 if (original->original_result_with_rrsig.type == returned_answers->type && dnssec_result == returned_answers->dnssec_result) {
411 // No Error response with no DNSSEC result change
412 delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
413 mdnsresponder_context, question, returned_answers->type, out_stop_immediately);
414 require_quiet(!(*out_stop_immediately), exit);
415 } else {
416 // either the response is no longer the original response or the dnssec validation result has changed
417 delivered_remove_all = mDNStrue;
418 }
419 goto exit;
420 }
421
422 // if we return No Such Name or No Such Record to deny the existence of some records
423 if (returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord) {
424 if (returned_answers->type == nsec_response) {
425 if (original->original_result_with_rrsig.type == nsec_response && dnssec_result == dnssec_secure) {
426 // unchanged NSEC response with no DNSSEC result change
427 delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
428 mdnsresponder_context, question, nsec_response, out_stop_immediately);
429 require_quiet(!(*out_stop_immediately), exit);
430 } else {
431 delivered_remove_all = mDNStrue;
432 }
433 } else if (returned_answers->type == nsec3_response) {
434 if (original->original_result_with_rrsig.type == nsec3_response && dnssec_result == dnssec_secure) {
435 // unchanged NSEC3 response with no DNSSEC result change
436 delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
437 mdnsresponder_context, question, nsec3_response, out_stop_immediately);
438 require_quiet(!(*out_stop_immediately), exit);
439 } else {
440 delivered_remove_all = mDNStrue;
441 }
442 } else if (returned_answers->type == original_response) {
443 // The zone is unsigned
444 if (original->original_result_with_rrsig.type == original_response && dnssec_result == dnssec_insecure) {
445 // The zone is unsigned, and returns No Such Name or No Such Record, nothing has changed
446 delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
447 mdnsresponder_context, question, original_response, out_stop_immediately);
448 require_quiet(!(*out_stop_immediately), exit);
449 } else {
450 delivered_remove_all = mDNStrue;
451 }
452 } else {
453 log_error("kDNSServiceErr_NoSuchName/kDNSServiceErr_NoSuchRecord must be matched with nsec_response/nsec3_response");
454 goto exit;
455 }
456
457 goto exit;
458 }
459
460 exit:
461 if (delivered_remove_all) {
462 *out_stop_immediately = deliver_remove_to_callback_with_all_returned_answers(dnssec_context, returned_answers,
463 mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
464 // Check if the callback in deliver_remove_to_callback_with_all_returned_answers has already deleted the
465 // current request and question. If so we should not touch anything.
466 if (!(*out_stop_immediately)) {
467 uninitialize_returned_answers_t(returned_answers);
468 initialize_returned_answers_t(returned_answers, dnssec_indeterminate, kDNSServiceErr_Invalid);
469 }
470 }
471 return delivered_remove_all || delivered_remove_some;
472 }
473
474 #pragma mark return_answer_to_user
475 // return_answer_to_user returns whether the caller should stop the DNSSEC-related processing. If it returns true,
476 // it means that the callback has stopped the request and deleted the current question, and all the current work should
477 // be stopped because the callback has stopped the request and deleted the current question. If we continue to process
478 // more, we may access the memory that has already been freed.
479 mDNSlocal mDNSBool
480 return_answer_to_user(
481 QueryRecordResultHandler user_handler,
482 void * const user_context,
483 dnssec_result_t result,
484 ResourceRecord * const _Nonnull rr,
485 const QC_result add_or_remove,
486 const DNSServiceErrorType dns_error,
487 mDNS * const _Nonnull m,
488 DNSQuestion * const _Nonnull primary_question,
489 const DNSQuestion * const _Nonnull current_question) {
490
491 rr->dnssec_result = result;
492 user_handler(m, primary_question, rr, add_or_remove, dns_error, user_context);
493 rr->dnssec_result = dnssec_indeterminate;
494
495 return m->CurrentQuestion != current_question;
496 }
497
498 #pragma mark - add_records_if_necessary
499
500 mDNSlocal mDNSBool
501 add_records_if_necessary(
502 dnssec_context_t * const _Nonnull dnssec_context,
503 const dnssec_result_t dnssec_result,
504 const DNSServiceErrorType dns_error_from_core,
505 mDNS * const mdnsresponder_context,
506 DNSQuestion * const _Nonnull question,
507 mDNSBool * _Nonnull out_stop_immediately) {
508
509 originals_with_rrsig_t * originals_with_rrsig = &dnssec_context->original.original_result_with_rrsig;
510 returned_answers_t * const returned_answers = &dnssec_context->returned_answers;
511 list_t * const returned_rrs = &returned_answers->answers;
512 mDNSBool add_records = mDNSfalse;
513 QueryRecordResultHandler user_handler = dnssec_context->original.original_parameters.user_handler;
514 void * const user_context = dnssec_context->original.original_parameters.user_context;
515 DNSServiceErrorType dns_error = get_dnsservice_error_type_from_originals(&dnssec_context->original, dns_error_from_core);
516 mDNSBool stop_immediately = mDNSfalse;
517 mStatus error;
518
519 if (returned_answers->error == kDNSServiceErr_Invalid) {
520 // It is our first time to return the response back to callback
521 initialize_returned_answers_t(returned_answers, dnssec_result, dns_error);
522 } else {
523 returned_answers->error = dns_error_from_core;
524 }
525
526 switch (originals_with_rrsig->type) {
527 case original_response: {
528 list_t * const dnssec_originals = &originals_with_rrsig->u.original.original_records;
529 if (originals_with_rrsig->u.original.negative_rr != mDNSNULL) {
530 ResourceRecord * const rr = originals_with_rrsig->u.original.negative_rr;
531 mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
532 if (contained_in_returned_answer) {
533 break;
534 }
535
536 error = add_record_to_be_returned_to_returned_answers_t(&dnssec_context->returned_answers, rr);
537 require_quiet(error == mStatus_NoError, exit);
538
539 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add,
540 dns_error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
541 add_records = mDNStrue;
542 require_quiet(!stop_immediately, exit);
543 returned_answers->type = original_response;
544 } else {
545 for (const list_node_t * dnssec_original_node = list_get_first(dnssec_originals);
546 !list_has_ended(dnssec_originals, dnssec_original_node);
547 dnssec_original_node = list_next(dnssec_original_node)) {
548
549 dnssec_original_t * const dnssec_original = (dnssec_original_t *)dnssec_original_node->data;
550 ResourceRecord * const rr = dnssec_original->dnssec_rr.rr;
551 mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
552 if (contained_in_returned_answer) {
553 continue;
554 }
555
556 error = add_record_to_be_returned_to_returned_answers_t(returned_answers, rr);
557 require_quiet(error == mStatus_NoError, exit);
558
559 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add,
560 dns_error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
561 add_records = mDNStrue;
562 require_quiet(!stop_immediately, exit);
563 }
564 }
565 returned_answers->type = original_response;
566 }
567 break;
568 case cname_response: {
569 list_t * const dnssec_cnames = &originals_with_rrsig->u.cname_with_rrsig.cname_records;
570 for (const list_node_t * dnssec_cname_node = list_get_first(dnssec_cnames);
571 !list_has_ended(dnssec_cnames, dnssec_cname_node);
572 dnssec_cname_node = list_next(dnssec_cname_node)) {
573
574 dnssec_cname_t * const dnssec_cname = (dnssec_cname_t *)dnssec_cname_node->data;
575 ResourceRecord * const rr = dnssec_cname->dnssec_rr.rr;
576 mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
577 if (contained_in_returned_answer) {
578 continue;
579 }
580
581 error = add_record_to_be_returned_to_returned_answers_t(returned_answers, rr);
582 require_quiet(error == mStatus_NoError, exit);
583
584 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add,
585 dns_error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
586 add_records = mDNStrue;
587 require_quiet(!stop_immediately, exit);
588 }
589 returned_answers->type = cname_response;
590 }
591 break;
592 case nsec_response: {
593 ResourceRecord * const rr = originals_with_rrsig->u.nsecs_with_rrsig.negative_rr;
594 mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
595 if (contained_in_returned_answer) {
596 break;
597 }
598
599 error = add_record_to_be_returned_to_returned_answers_t(&dnssec_context->returned_answers, rr);
600 require_quiet(error == mStatus_NoError, exit);
601
602 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add, dns_error,
603 mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
604 add_records = mDNStrue;
605 require_quiet(!stop_immediately, exit);
606 returned_answers->type = nsec_response;
607 }
608 break;
609 case nsec3_response: {
610 ResourceRecord * const rr = originals_with_rrsig->u.nsec3s_with_rrsig.negative_rr;
611 mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
612 if (contained_in_returned_answer) {
613 break;
614 }
615
616 error = add_record_to_be_returned_to_returned_answers_t(&dnssec_context->returned_answers, rr);
617 require_quiet(error == mStatus_NoError, exit);
618
619 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add, dns_error,
620 mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
621 add_records = mDNStrue;
622 require_quiet(!stop_immediately, exit);
623 returned_answers->type = nsec3_response;
624 }
625 break;
626 default:
627 goto exit;
628 }
629
630 exit:
631 if (out_stop_immediately != mDNSNULL) {
632 *out_stop_immediately = stop_immediately;
633 }
634 return add_records;
635 }
636
637
638 #pragma mark - returned_answers_t
639
640
641
642 #pragma mark add_record_to_be_returned_to_returned_answers_t
643 mDNSlocal mStatus
644 add_record_to_be_returned_to_returned_answers_t(returned_answers_t * const returned_asnwers, const ResourceRecord * const rr) {
645 const ResourceRecord ** inserted_rr;
646 mStatus error;
647
648 require_action_quiet(rr != mDNSNULL, exit, error = mStatus_BadReferenceErr);
649
650 // No need to remember if we returned negative answer to the user since wo do not need to send RMV for negative record
651 if (rr->RecordType == kDNSRecordTypePacketNegative) {
652 error = mStatus_NoError;
653 goto exit;
654 }
655
656 error = list_append_uinitialized(&returned_asnwers->answers, sizeof(ResourceRecord *), (void **)&inserted_rr);
657 require_quiet(error == mStatus_NoError, exit);
658
659 *inserted_rr = rr;
660
661 exit:
662 return error;
663 }
664
665 #pragma mark get_dnsservice_error_type_from_originals
666 mDNSlocal DNSServiceErrorType
667 get_dnsservice_error_type_from_originals(const original_t * const _Nonnull original, DNSServiceErrorType type_from_mDNSCore) {
668 response_type_t type = original->original_result_with_rrsig.type;
669 DNSServiceErrorType error = kDNSServiceErr_Invalid;
670
671 if (type == original_response) {
672 if (original->original_result_with_rrsig.u.original.negative_rr != mDNSNULL) {
673 error = type_from_mDNSCore;
674 } else {
675 error = kDNSServiceErr_NoError;
676 }
677 } else if (type == cname_response) {
678 error = kDNSServiceErr_NoError;
679 } else if (type == nsec_response) {
680 dnssec_validation_result_t validation_result = original->original_result_with_rrsig.u.nsecs_with_rrsig.nsec_result;
681 switch (validation_result) {
682 case dnssec_validation_nsec_name_error:
683 error = kDNSServiceErr_NoSuchName;
684 break;
685 case dnssec_validation_nsec_no_data:
686 error = kDNSServiceErr_NoSuchRecord;
687 break;
688 case dnssec_validation_nsec_wildcard_answer:
689 case dnssec_validation_nsec_wildcard_no_data:
690 log_error("wildcard not handled");
691 default:
692 break;
693 }
694 } else if (type == nsec3_response) {
695 dnssec_validation_result_t validation_result = original->original_result_with_rrsig.u.nsec3s_with_rrsig.nsec3_result;
696 switch (validation_result) {
697 case dnssec_validation_nsec3_name_error:
698 error = kDNSServiceErr_NoSuchName;
699 break;
700 case dnssec_validation_nsec3_no_data_response:
701 error = kDNSServiceErr_NoSuchRecord;
702 break;
703 case dnssec_validation_nsec3_wildcard_answer_response:
704 case dnssec_validation_nsec3_wildcard_no_data:
705 log_error("wildcard not handled");
706 break;
707 default:
708 break;
709 }
710 } else {
711 log_error("Original response has type other than 'original_response', 'cname_response', 'nsec_response', 'nsec3_response'");
712 }
713
714 return error;
715 }
716
717 #pragma mark deliver_remove_to_callback_with_all_returned_answer
718 // deliver_remove_to_callback_with_all_returned_answers returns whether the caller should stop the DNSSEC-related
719 // processing. If it returns true, it means that the callback has stopped the request and deleted the current question,
720 // and all the current work should be stopped because the callback has stopped the request and deleted the current
721 // question. If we continue to process more, we may access the memory that has already been freed.
722 mDNSexport mDNSBool
723 deliver_remove_to_callback_with_all_returned_answers(
724 const dnssec_context_t * const _Nonnull context,
725 const returned_answers_t * const _Nonnull returned_answers,
726 mDNS * const _Nonnull m,
727 DNSQuestion * const _Nonnull primary_question,
728 const DNSQuestion * const _Nonnull current_question) {
729
730 mDNSBool stop_immediately = mDNSfalse;
731
732 require_quiet(returned_answers->error != kDNSServiceErr_Invalid, exit);
733
734 const list_t * const returned_rrs = &returned_answers->answers;
735 QueryRecordResultHandler user_handler = context->original.original_parameters.user_handler;
736 void * const user_context = context->original.original_parameters.user_context;
737
738 for (const list_node_t * rr_node = list_get_first(returned_rrs); !list_has_ended(returned_rrs, rr_node); rr_node = list_next(rr_node)) {
739 ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)rr_node->data;
740 ResourceRecord * const rr = *rr_ptr;
741 // 1. No need to deliver RMV for negative answer, such as No Such Record.
742 // 2. No need to deliver RMV for CNAME answer.
743 if (rr->RecordType == kDNSRecordTypePacketNegative || rr->rrtype == kDNSType_CNAME) {
744 continue;
745 }
746 stop_immediately = return_answer_to_user(user_handler, user_context, returned_answers->dnssec_result, rr,
747 QC_rmv, returned_answers->error, m, primary_question, current_question);
748 require_quiet(!stop_immediately, exit);
749 }
750
751 exit:
752 return stop_immediately;
753 }
754
755 #pragma mark deliver_remove_for_returned_records
756
757 mDNSlocal mDNSBool
758 contains_rr_in_original_records(const ResourceRecord * const rr, const list_t * const original_records/* list_t<dnssec_original_t> */) {
759 mDNSBool contains = mDNSfalse;
760
761 for (const list_node_t * original_record_node = list_get_first(original_records);
762 !list_has_ended(original_records, original_record_node);
763 original_record_node = list_next(original_record_node)) {
764
765 const dnssec_original_t * const dnssec_original = (dnssec_original_t *)original_record_node->data;
766 if (dnssec_original->dnssec_rr.rr == rr) {
767 contains = mDNStrue;
768 goto exit;
769 }
770 }
771
772 exit:
773 return contains;
774 }
775
776 mDNSlocal mDNSBool
777 deliver_remove_for_returned_records(
778 dnssec_context_t * const _Nonnull dnssec_context,
779 const dnssec_result_t dnssec_result,
780 mDNS * const _Nonnull mdnsresponder_context,
781 DNSQuestion * const _Nonnull question,
782 const response_type_t type,
783 mDNSBool * _Nonnull out_stop_immediately) {
784
785 returned_answers_t * const returned_answers = &dnssec_context->returned_answers;
786 list_t * const returned_rrs = &returned_answers->answers;
787 QueryRecordResultHandler user_handler = dnssec_context->original.original_parameters.user_handler;
788 void * const user_context = dnssec_context->original.original_parameters.user_context;
789 originals_with_rrsig_t * const originals_with_rrsig = &dnssec_context->original.original_result_with_rrsig;
790 mDNSu32 request_id = dnssec_context->original.original_parameters.request_id;
791 mDNSu16 question_id = mDNSVal16(question->TargetQID);
792 mDNSBool remove_some = mDNSfalse;
793 mDNSBool stop_immediately = mDNSfalse;
794
795 if (type == original_response) {
796 for (list_node_t * rr_node = list_get_first(returned_rrs), *next_node; !list_has_ended(returned_rrs, rr_node); rr_node = next_node) {
797 ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)rr_node->data;
798 ResourceRecord *rr = *rr_ptr;
799 next_node = list_next(rr_node);
800 mDNSBool contains = mDNStrue;
801 if (returned_answers->error == kDNSServiceErr_NoError) {
802 contains = contains_rr_in_original_records(rr, &originals_with_rrsig->u.original.original_records);
803 } else if (returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord) {
804 contains = (rr == originals_with_rrsig->u.original.negative_rr);
805 } else {
806 log_error("[R%u->Q%u] when the DNSSEC response is original response, only NoError, NoSuchName and NoSuchRecord are allowed"
807 " - response type: " PUB_S ", kDNSServiceErr type: %u",
808 request_id, question_id, response_type_value_to_string(type), returned_answers->error);
809 goto exit;
810 }
811 if (!contains) {
812 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_rmv,
813 returned_answers->error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
814 remove_some = mDNStrue;
815 require_quiet(!stop_immediately, exit);
816 list_node_delete(rr_node);
817 }
818 }
819 } else if (type == nsec_response) {
820 require_action_quiet(
821 returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord,
822 exit,
823 log_error("[R%u->Q%u] when the NSEC response is original response, only NoSuchName and NoSuchRecord are allowed"
824 " - response type: " PUB_S ", kDNSServiceErr type: %u",
825 request_id, question_id, response_type_value_to_string(type), returned_answers->error)
826 );
827
828 require_action_quiet(list_count_node(returned_rrs) == 1, exit,
829 log_error("[R%u->Q%u] Denail of existence answer returns more than one negative answer with NSEC proof - number_of_records: %u",
830 request_id, question_id, list_count_node(returned_rrs)));
831
832 ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)(list_get_first(returned_rrs)->data);
833 ResourceRecord *rr = *rr_ptr;
834 if (rr != originals_with_rrsig->u.nsecs_with_rrsig.negative_rr) {
835 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_rmv,
836 returned_answers->error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
837 remove_some = mDNStrue;
838 require_quiet(!stop_immediately, exit);
839 // Deletes the removed record.
840 list_node_delete_all(returned_rrs);
841 }
842 } else if (type == nsec3_response) {
843 require_action_quiet(
844 returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord,
845 exit,
846 log_error("[R%u->Q%u] when the NSEC3 response is original response, only NoSuchName and NoSuchRecord are allowed"
847 " - response type: " PUB_S ", kDNSServiceErr type: %u",
848 request_id, question_id, response_type_value_to_string(type), returned_answers->error)
849 );
850
851 require_action_quiet(list_count_node(returned_rrs) == 1, exit,
852 log_error("[R%u->Q%u] Denail of existence answer returns more than one negative answer with NSEC3 proof - number_of_records: %u",
853 request_id, question_id, list_count_node(returned_rrs)));
854
855 ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)(list_get_first(returned_rrs)->data);
856 ResourceRecord *rr = *rr_ptr;
857 if (rr != originals_with_rrsig->u.nsec3s_with_rrsig.negative_rr) {
858 stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_rmv,
859 returned_answers->error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
860 remove_some = mDNStrue;
861 require_quiet(!stop_immediately, exit);
862 // Deletes the removed record.
863 list_node_delete_all(returned_rrs);
864 }
865 } else {
866 // cname_response or other invalid value
867 log_error("[R%u->Q%u] Invalid returned answers response type - response type: " PUB_S, request_id, question_id,
868 response_type_value_to_string(type));
869 goto exit;
870 }
871
872 exit:
873 if (out_stop_immediately != mDNSNULL) {
874 *out_stop_immediately = stop_immediately;
875 }
876 return remove_some;
877 }
878
879 #pragma mark contains_rr_in_returned_rrs
880 mDNSlocal mDNSBool
881 contains_rr_in_returned_rrs(const ResourceRecord * const rr, const list_t * const returned_rrs/* list_t<ResourceRecord *> */) {
882 mDNSBool contains = mDNSfalse;
883
884 for (const list_node_t *rr_ptr_node = list_get_first(returned_rrs);
885 !list_has_ended(returned_rrs, rr_ptr_node);
886 rr_ptr_node = list_next(rr_ptr_node)) {
887
888 const ResourceRecord * const * const rr_to_compare_ptr = (const ResourceRecord * const * const)rr_ptr_node->data;
889 const ResourceRecord * const rr_to_compare = *rr_to_compare_ptr;
890
891 if (rr == rr_to_compare) {
892 contains = mDNStrue;
893 goto exit;
894 }
895 }
896
897 exit:
898 return contains;
899 }
900
901 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)