Coverage Report

Created: 2026-08-13 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/vdns/doh.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifndef CURL_DISABLE_DOH
27
28
#include "urldata.h"
29
#include "curl_addrinfo.h"
30
#include "curl_trc.h"
31
#include "multiif.h"
32
#include "url.h"
33
#include "connect.h"
34
#include "vdns/doh.h"
35
#include "vdns/httpsrr.h"
36
#include "curlx/strdup.h"
37
#include "curlx/dynbuf.h"
38
#include "escape.h"  /* for Curl_hexencode() */
39
#include "urlapi-int.h"
40
41
0
#define DNS_CLASS_IN 0x01
42
43
static void doh_close(struct Curl_easy *data,
44
                      struct Curl_resolv_async *async);
45
46
#ifdef CURLVERBOSE
47
static const char * const doh_code_str[] = {
48
  "",
49
  "Bad label",
50
  "Out of range",
51
  "Label loop",
52
  "Too small",
53
  "Out of memory",
54
  "RDATA length",
55
  "Malformat",
56
  "Bad RCODE",
57
  "Unexpected TYPE",
58
  "Unexpected CLASS",
59
  "No content",
60
  "Bad ID",
61
  "Name too long",
62
  "No such name",
63
  "Transport failed",
64
  "Out Of Memory"
65
};
66
67
static const char *doh_strerror(DOHcode code)
68
0
{
69
0
  if((size_t)code < CURL_ARRAYSIZE(doh_code_str))
70
0
    return doh_code_str[code];
71
0
  return "bad error code";
72
0
}
73
74
static const char *doh_type2name(DNStype dnstype)
75
0
{
76
0
  switch(dnstype) {
77
0
  case CURL_DNS_TYPE_A:
78
0
    return "A";
79
0
  case CURL_DNS_TYPE_AAAA:
80
0
    return "AAAA";
81
#ifdef USE_HTTPSRR
82
  case CURL_DNS_TYPE_HTTPS:
83
    return "HTTPS";
84
#endif
85
0
  default:
86
0
    return "unknown";
87
0
  }
88
0
}
89
90
#endif /* CURLVERBOSE */
91
92
/* @unittest 1655
93
 */
94
UNITTEST DOHcode doh_req_encode(const char *host,
95
                                DNStype dnstype,
96
                                unsigned char *dnsp,  /* buffer */
97
                                size_t len,  /* buffer size */
98
                                size_t *olen);  /* output length */
99
UNITTEST DOHcode doh_req_encode(const char *host,
100
                                DNStype dnstype,
101
                                unsigned char *dnsp, /* buffer */
102
                                size_t len,   /* buffer size */
103
                                size_t *olen) /* output length */
104
0
{
105
0
  const size_t hostlen = strlen(host);
106
0
  unsigned char *orig = dnsp;
107
0
  const char *hostp = host;
108
109
  /* The expected output length is 16 bytes more than the length of
110
   * the QNAME-encoding of the hostname.
111
   *
112
   * A valid DNS name may not contain a zero-length label, except at
113
   * the end. For this reason, a name beginning with a dot, or
114
   * containing a sequence of two or more consecutive dots, is invalid
115
   * and cannot be encoded as a QNAME.
116
   *
117
   * If the hostname ends with a trailing dot, the corresponding
118
   * QNAME-encoding is one byte longer than the hostname. If (as is
119
   * also valid) the hostname is shortened by the omission of the
120
   * trailing dot, then its QNAME-encoding will be two bytes longer
121
   * than the hostname.
122
   *
123
   * Each [ label, dot ] pair is encoded as [ length, label ],
124
   * preserving overall length. A final [ label ] without a dot is
125
   * also encoded as [ length, label ], increasing overall length
126
   * by one. The encoding is completed by appending a zero byte,
127
   * representing the zero-length root label, again increasing
128
   * the overall length by one.
129
   */
130
131
0
  size_t expected_len;
132
0
  DEBUGASSERT(hostlen);
133
0
  expected_len = 12 + 1 + hostlen + 4;
134
0
  if(host[hostlen - 1] != '.')
135
0
    expected_len++;
136
137
0
  if(expected_len > DOH_MAX_DNSREQ_SIZE)
138
0
    return DOH_DNS_NAME_TOO_LONG;
139
140
0
  if(len < expected_len)
141
0
    return DOH_TOO_SMALL_BUFFER;
142
143
0
  *dnsp++ = 0; /* 16-bit id */
144
0
  *dnsp++ = 0;
145
0
  *dnsp++ = 0x01; /* |QR|   Opcode  |AA|TC|RD| Set the RD bit */
146
0
  *dnsp++ = '\0'; /* |RA|   Z    |   RCODE   |                */
147
0
  *dnsp++ = '\0';
148
0
  *dnsp++ = 1;    /* QDCOUNT (number of entries in the question section) */
149
0
  *dnsp++ = '\0';
150
0
  *dnsp++ = '\0'; /* ANCOUNT */
151
0
  *dnsp++ = '\0';
152
0
  *dnsp++ = '\0'; /* NSCOUNT */
153
0
  *dnsp++ = '\0';
154
0
  *dnsp++ = '\0'; /* ARCOUNT */
155
156
  /* encode each label and store it in the QNAME */
157
0
  while(*hostp) {
158
0
    size_t labellen;
159
0
    const char *dot = strchr(hostp, '.');
160
0
    if(dot)
161
0
      labellen = dot - hostp;
162
0
    else
163
0
      labellen = strlen(hostp);
164
0
    if((labellen > 63) || (!labellen)) {
165
      /* label is too long or too short, error out */
166
0
      *olen = 0;
167
0
      return DOH_DNS_BAD_LABEL;
168
0
    }
169
    /* label is non-empty, process it */
170
0
    *dnsp++ = (unsigned char)labellen;
171
0
    memcpy(dnsp, hostp, labellen);
172
0
    dnsp += labellen;
173
0
    hostp += labellen;
174
    /* advance past dot, but only if there is one */
175
0
    if(dot)
176
0
      hostp++;
177
0
  } /* next label */
178
179
0
  *dnsp++ = 0; /* append zero-length label for root */
180
181
  /* There are assigned TYPE codes beyond 255: use range [1..65535] */
182
0
  *dnsp++ = (unsigned char)(255 & (dnstype >> 8)); /* upper 8-bit TYPE */
183
0
  *dnsp++ = (unsigned char)(255 & dnstype);        /* lower 8-bit TYPE */
184
185
0
  *dnsp++ = '\0'; /* upper 8-bit CLASS */
186
0
  *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
187
188
0
  *olen = dnsp - orig;
189
190
  /* verify that our estimation of length is valid, since
191
   * this has led to buffer overflows in this function */
192
0
  DEBUGASSERT(*olen == expected_len);
193
0
  return DOH_OK;
194
0
}
195
196
static size_t doh_probe_write_cb(char *contents, size_t size, size_t nmemb,
197
                                 void *userp)
198
0
{
199
0
  size_t realsize = size * nmemb;
200
0
  struct Curl_easy *data = userp;
201
0
  struct doh_request *doh_req = Curl_meta_get(data, CURL_EZM_DOH_PROBE);
202
0
  if(!doh_req)
203
0
    return CURL_WRITEFUNC_ERROR;
204
205
0
  if(curlx_dyn_addn(&doh_req->resp_body, contents, realsize))
206
0
    return 0;
207
208
0
  return realsize;
209
0
}
210
211
static void doh_probe_done(struct Curl_easy *doh,
212
                           struct Curl_easy *master, CURLcode result);
213
static void doh_probe_dtor(void *key, size_t klen, void *e)
214
0
{
215
0
  (void)key;
216
0
  (void)klen;
217
0
  if(e) {
218
0
    struct doh_request *doh_req = e;
219
0
    curl_slist_free_all(doh_req->req_hds);
220
0
    curlx_dyn_free(&doh_req->resp_body);
221
0
    curlx_free(e);
222
0
  }
223
0
}
224
225
#define ERROR_CHECK_SETOPT(x, y)                        \
226
0
  do {                                                  \
227
0
    result = curl_easy_setopt((CURL *)doh, x, y);       \
228
0
    if(result &&                                        \
229
0
       result != CURLE_NOT_BUILT_IN &&                  \
230
0
       result != CURLE_UNKNOWN_OPTION)                  \
231
0
      goto error;                                       \
232
0
  } while(0)
233
234
static CURLcode doh_probe_run(struct Curl_easy *data,
235
                              DNStype dnstype,
236
                              const char *host,
237
                              const char *url, CURLM *multi,
238
                              uint32_t resolv_id,
239
                              uint32_t *pmid)
240
0
{
241
0
  struct Curl_easy *doh = NULL;
242
0
  CURLcode result = CURLE_OK;
243
0
  timediff_t timeout_ms;
244
0
  struct doh_request *doh_req;
245
0
  DOHcode d;
246
0
  bool maybe_https = !curl_strnequal(url, STRCONST("http:"));
247
248
0
  *pmid = UINT32_MAX;
249
250
0
  doh_req = curlx_calloc(1, sizeof(*doh_req));
251
0
  if(!doh_req)
252
0
    return CURLE_OUT_OF_MEMORY;
253
0
  doh_req->resolv_id = resolv_id;
254
0
  doh_req->dnstype = dnstype;
255
0
  curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE);
256
257
0
  d = doh_req_encode(host, dnstype, doh_req->req_body,
258
0
                     sizeof(doh_req->req_body),
259
0
                     &doh_req->req_body_len);
260
0
  if(d) {
261
0
    failf(data, "Failed to encode DoH packet [%d]", (int)d);
262
0
    result = CURLE_OUT_OF_MEMORY;
263
0
    goto error;
264
0
  }
265
266
0
  timeout_ms = Curl_timeleft_ms(data);
267
0
  if(timeout_ms < 0) {
268
0
    result = CURLE_OPERATION_TIMEDOUT;
269
0
    goto error;
270
0
  }
271
272
0
  doh_req->req_hds =
273
0
    curl_slist_append(NULL, "Content-Type: application/dns-message");
274
0
  if(!doh_req->req_hds) {
275
0
    result = CURLE_OUT_OF_MEMORY;
276
0
    goto error;
277
0
  }
278
279
  /* Curl_open() is the internal version of curl_easy_init() */
280
0
  result = Curl_open(&doh);
281
0
  if(result)
282
0
    goto error;
283
284
  /* pass in the struct pointer via a local variable to please coverity and
285
     the gcc typecheck helpers */
286
0
  VERBOSE(doh->state.feat = &Curl_trc_feat_doh);
287
0
  ERROR_CHECK_SETOPT(CURLOPT_URL, url);
288
0
  ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
289
0
  ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb);
290
0
  ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, doh);
291
0
  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, doh_req->req_body);
292
0
  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len);
293
0
  ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds);
294
0
#ifdef USE_HTTP2
295
0
  if(maybe_https) {
296
0
    ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
297
0
    ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L);
298
0
  }
299
0
#endif
300
#ifndef DEBUGBUILD
301
  /* enforce HTTPS if not debug */
302
  ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
303
#else
304
  /* in debug mode, also allow http */
305
0
  ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
306
0
#endif
307
0
  ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
308
0
  ERROR_CHECK_SETOPT(CURLOPT_SHARE, (CURLSH *)data->share);
309
0
  if(data->set.err && data->set.err != stderr)
310
0
    ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err);
311
0
  if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_doh))
312
0
    ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
313
0
  if(data->set.no_signal)
314
0
    ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L);
315
0
  if(data->set.fdebug)
316
0
    ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug);
317
0
  if(data->set.debugdata)
318
0
    ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata);
319
320
0
  if(maybe_https) {
321
0
    ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST,
322
0
                       data->set.doh_verifyhost ? 2L : 0L);
323
0
    ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER,
324
0
                       data->set.doh_verifypeer ? 1L : 0L);
325
0
    ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS,
326
0
                       data->set.doh_verifystatus ? 1L : 0L);
327
328
    /* Inherit *some* SSL options from the user's transfer. This is a
329
       best-guess as to which options are needed for compatibility. #3661
330
331
       Note DoH does not inherit the user's proxy server so proxy SSL settings
332
       have no effect and are not inherited. If that changes then two new
333
       options should be added to check doh proxy insecure separately,
334
       CURLOPT_DOH_PROXY_SSL_VERIFYHOST and CURLOPT_DOH_PROXY_SSL_VERIFYPEER.
335
       */
336
0
    doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile;
337
0
    doh->set.ssl.custom_capath = data->set.ssl.custom_capath;
338
0
    doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob;
339
0
    if(data->set.str[STRING_SSL_CAFILE]) {
340
0
      ERROR_CHECK_SETOPT(CURLOPT_CAINFO, data->set.str[STRING_SSL_CAFILE]);
341
0
    }
342
0
    if(data->set.blobs[BLOB_CAINFO]) {
343
0
      ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]);
344
0
    }
345
0
    if(data->set.str[STRING_SSL_CAPATH]) {
346
0
      ERROR_CHECK_SETOPT(CURLOPT_CAPATH, data->set.str[STRING_SSL_CAPATH]);
347
0
    }
348
0
    if(data->set.str[STRING_SSL_CRLFILE]) {
349
0
      ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, data->set.str[STRING_SSL_CRLFILE]);
350
0
    }
351
0
    if(data->set.ssl.certinfo)
352
0
      ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L);
353
0
    if(data->set.ssl.fsslctx)
354
0
      ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx);
355
0
    if(data->set.ssl.fsslctxp)
356
0
      ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp);
357
0
    if(data->set.str[STRING_SSL_EC_CURVES]) {
358
0
      ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES,
359
0
                         data->set.str[STRING_SSL_EC_CURVES]);
360
0
    }
361
362
0
    (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS,
363
0
                           ((long)data->set.ssl.primary.ssl_options &
364
0
                            ~CURLSSLOPT_AUTO_CLIENT_CERT));
365
0
  }
366
367
0
  doh->state.internal = TRUE;
368
0
  doh->master_mid = data->mid; /* master transfer of this one */
369
0
  doh->sub_xfer_done = doh_probe_done;
370
371
0
  result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor);
372
0
  doh_req = NULL; /* call took ownership */
373
0
  if(result)
374
0
    goto error;
375
376
  /* DoH handles must not inherit private_data. The handles may be passed to
377
     the user via callbacks and the user will be able to identify them as
378
     internal handles because private data is not set. The user can then set
379
     private_data via CURLOPT_PRIVATE if they so choose. */
380
0
  DEBUGASSERT(!doh->set.private_data);
381
382
0
  if(Curl_multi_add_handle(multi, doh))
383
0
    goto error;
384
385
0
  *pmid = doh->mid;
386
0
  return CURLE_OK;
387
388
0
error:
389
0
  Curl_close(&doh);
390
0
  if(doh_req)
391
0
    doh_probe_dtor(NULL, 0, doh_req);
392
0
  return result;
393
0
}
394
395
/*
396
 * Curl_doh() starts a name resolve using DoH. It resolves a name and returns
397
 * a 'Curl_addrinfo *' with the address information.
398
 */
399
400
CURLcode Curl_doh(struct Curl_easy *data,
401
                  struct Curl_resolv_async *async)
402
0
{
403
0
  CURLcode result = CURLE_OK;
404
0
  struct doh_probes *dohp = NULL;
405
0
  size_t i;
406
407
0
  DEBUGASSERT(!async->doh);
408
0
  DEBUGASSERT(async->peer->hostname[0]);
409
0
  if(async->doh) {
410
0
    DEBUGASSERT(0); /* should not happen */
411
0
    Curl_doh_cleanup(data, async);
412
0
  }
413
414
0
  if(!async->dns_queries)
415
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
416
#ifdef USE_HTTPSRR
417
  if(CURL_DNSQ_IS_ADDR(async->dns_queries) &&
418
     (async->dns_queries & CURL_DNSQ_HTTPS)) {
419
    /* Can't mix those in the same async resolve */
420
    DEBUGASSERT(0);
421
    return CURLE_BAD_FUNCTION_ARGUMENT;
422
  }
423
#else
424
0
  if(async->dns_queries & CURL_DNSQ_HTTPS) {
425
0
    DEBUGASSERT(0);
426
0
    return CURLE_NOT_BUILT_IN;
427
0
  }
428
0
#endif
429
430
  /* start clean, consider allocating this struct on demand */
431
0
  async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
432
0
  if(!dohp)
433
0
    return CURLE_OUT_OF_MEMORY;
434
435
0
  for(i = 0; i < DOH_SLOT_COUNT; ++i) {
436
0
    dohp->probe_rc[i] = DOH_OK;
437
0
    dohp->probe_mid[i] = UINT32_MAX;
438
0
  }
439
440
0
#ifdef USE_IPV6
441
  /* AAAA results have preference in happy eyeballing, trigger first */
442
0
  if(async->dns_queries & CURL_DNSQ_AAAA) {
443
    /* create IPv6 DoH request */
444
0
    result = doh_probe_run(data, CURL_DNS_TYPE_AAAA,
445
0
                           async->peer->hostname, data->set.str[STRING_DOH],
446
0
                           data->multi, async->id,
447
0
                           &dohp->probe_mid[DOH_SLOT_IPV6]);
448
0
    if(result)
449
0
      goto error;
450
0
    async->queries_ongoing++;
451
0
  }
452
0
#endif
453
454
  /* create IPv4 DoH request */
455
0
  if(async->dns_queries & CURL_DNSQ_A) {
456
0
    result = doh_probe_run(data, CURL_DNS_TYPE_A,
457
0
                           async->peer->hostname, data->set.str[STRING_DOH],
458
0
                           data->multi, async->id,
459
0
                           &dohp->probe_mid[DOH_SLOT_IPV4]);
460
0
    if(result)
461
0
      goto error;
462
0
    async->queries_ongoing++;
463
0
  }
464
465
#ifdef USE_HTTPSRR
466
  if(async->dns_queries & CURL_DNSQ_HTTPS) {
467
    char *qname = NULL;
468
    if(async->peer->port != PORT_HTTPS) {
469
      qname = curl_maprintf("_%u._https.%s",
470
                            async->peer->port, async->peer->hostname);
471
      if(!qname)
472
        goto error;
473
    }
474
    result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS,
475
                           qname ? qname : async->peer->hostname,
476
                           data->set.str[STRING_DOH], data->multi,
477
                           async->id,
478
                           &dohp->probe_mid[DOH_SLOT_HTTPS_RR]);
479
    curlx_free(qname);
480
    if(result)
481
      goto error;
482
    async->queries_ongoing++;
483
  }
484
#endif
485
0
  return CURLE_OK;
486
487
0
error:
488
0
  Curl_doh_cleanup(data, async);
489
0
  return result;
490
0
}
491
492
static DOHcode doh_skipqname(const unsigned char *doh, size_t dohlen,
493
                             unsigned int *indexp)
494
0
{
495
0
  unsigned char length;
496
0
  do {
497
0
    if(dohlen < (*indexp + 1))
498
0
      return DOH_DNS_OUT_OF_RANGE;
499
0
    length = doh[*indexp];
500
0
    if((length & 0xc0) == 0xc0) {
501
      /* name pointer, advance over it and be done */
502
0
      if(dohlen < (*indexp + 2))
503
0
        return DOH_DNS_OUT_OF_RANGE;
504
0
      *indexp += 2;
505
0
      break;
506
0
    }
507
0
    if(length & 0xc0)
508
0
      return DOH_DNS_BAD_LABEL;
509
0
    if(dohlen < (*indexp + 1 + length))
510
0
      return DOH_DNS_OUT_OF_RANGE;
511
0
    *indexp += (unsigned int)(1 + length);
512
0
  } while(length);
513
0
  return DOH_OK;
514
0
}
515
516
static unsigned short doh_get16bit(const unsigned char *doh,
517
                                   unsigned int index)
518
0
{
519
0
  return (unsigned short)((doh[index] << 8) | doh[index + 1]);
520
0
}
521
522
static unsigned int doh_get32bit(const unsigned char *doh, unsigned int index)
523
0
{
524
  /* make clang and gcc optimize this to bswap by incrementing
525
     the pointer first. */
526
0
  doh += index;
527
528
  /* avoid undefined behavior by casting to unsigned before shifting
529
     24 bits, possibly into the sign bit. codegen is same, but
530
     ub sanitizer will not be upset */
531
0
  return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) |
532
0
         ((unsigned)doh[2] << 8) | doh[3];
533
0
}
534
535
static void doh_store_a(const unsigned char *doh, int index,
536
                        struct dohentry *d)
537
0
{
538
  /* silently ignore addresses over the limit */
539
0
  if(d->numaddr < DOH_MAX_ADDR) {
540
0
    struct dohaddr *a = &d->addr[d->numaddr];
541
0
    a->type = CURL_DNS_TYPE_A;
542
0
    memcpy(&a->ip.v4, &doh[index], 4);
543
0
    d->numaddr++;
544
0
  }
545
0
}
546
547
static void doh_store_aaaa(const unsigned char *doh, int index,
548
                           struct dohentry *d)
549
0
{
550
  /* silently ignore addresses over the limit */
551
0
  if(d->numaddr < DOH_MAX_ADDR) {
552
0
    struct dohaddr *a = &d->addr[d->numaddr];
553
0
    a->type = CURL_DNS_TYPE_AAAA;
554
0
    memcpy(&a->ip.v6, &doh[index], 16);
555
0
    d->numaddr++;
556
0
  }
557
0
}
558
559
#ifdef USE_HTTPSRR
560
static DOHcode doh_store_https(const unsigned char *doh, int index,
561
                               struct dohentry *d, uint16_t len)
562
{
563
  /* silently ignore RRs over the limit */
564
  if(d->numhttps_rrs < DOH_MAX_HTTPS) {
565
    struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs];
566
    h->val = curlx_memdup(&doh[index], len);
567
    if(!h->val)
568
      return DOH_OUT_OF_MEM;
569
    h->len = len;
570
    d->numhttps_rrs++;
571
  }
572
  return DOH_OK;
573
}
574
#endif
575
576
static DOHcode doh_rdata(const unsigned char *doh,
577
                         unsigned short rdlength,
578
                         unsigned short type,
579
                         int index,
580
                         struct dohentry *d)
581
0
{
582
  /* RDATA
583
     - A (TYPE 1): 4 bytes
584
     - AAAA (TYPE 28): 16 bytes
585
     - HTTPS (TYPE 65): N bytes */
586
0
  switch(type) {
587
0
  case CURL_DNS_TYPE_A:
588
0
    if(rdlength != 4)
589
0
      return DOH_DNS_RDATA_LEN;
590
0
    doh_store_a(doh, index, d);
591
0
    break;
592
0
  case CURL_DNS_TYPE_AAAA:
593
0
    if(rdlength != 16)
594
0
      return DOH_DNS_RDATA_LEN;
595
0
    doh_store_aaaa(doh, index, d);
596
0
    break;
597
#ifdef USE_HTTPSRR
598
  case CURL_DNS_TYPE_HTTPS: {
599
    DOHcode rc = doh_store_https(doh, index, d, rdlength);
600
    if(rc)
601
      return rc;
602
    break;
603
  }
604
#endif
605
0
  default:
606
    /* unsupported type, or type we do not store, skip it */
607
0
    break;
608
0
  }
609
0
  return DOH_OK;
610
0
}
611
612
/* @unittest 1655 */
613
UNITTEST void de_init(struct dohentry *de);
614
UNITTEST void de_init(struct dohentry *de)
615
0
{
616
0
  memset(de, 0, sizeof(*de));
617
0
  de->ttl = INT_MAX;
618
0
}
619
620
/* TTL value cap */
621
0
#define MAX_DNS_TTL 86400U /* 24 hours */
622
/* @unittest 1650 */
623
UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
624
                                 size_t dohlen,
625
                                 DNStype dnstype,
626
                                 struct dohentry *d);
627
UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
628
                                 size_t dohlen,
629
                                 DNStype dnstype,
630
                                 struct dohentry *d)
631
0
{
632
0
  unsigned char rcode;
633
0
  unsigned short qdcount;
634
0
  unsigned short ancount;
635
0
  unsigned short type = 0;
636
0
  unsigned short rdlength;
637
0
  unsigned short nscount;
638
0
  unsigned short arcount;
639
0
  unsigned int index = 12;
640
0
  DOHcode rc;
641
642
0
  if(dohlen < 12)
643
0
    return DOH_TOO_SMALL_BUFFER; /* too small */
644
0
  if(!doh || doh[0] || doh[1])
645
0
    return DOH_DNS_BAD_ID; /* bad ID */
646
0
  rcode = doh[3] & 0x0f;
647
0
  if(rcode == 3)
648
0
    return DOH_DNS_NXDOMAIN; /* name does not exist */
649
0
  if(rcode)
650
0
    return DOH_DNS_BAD_RCODE; /* bad rcode */
651
652
0
  qdcount = doh_get16bit(doh, 4);
653
0
  while(qdcount) {
654
0
    rc = doh_skipqname(doh, dohlen, &index);
655
0
    if(rc)
656
0
      return rc; /* bad qname */
657
0
    if(dohlen < (index + 4))
658
0
      return DOH_DNS_OUT_OF_RANGE;
659
0
    index += 4; /* skip question's type and class */
660
0
    qdcount--;
661
0
  }
662
663
0
  ancount = doh_get16bit(doh, 6);
664
0
  while(ancount) {
665
0
    unsigned short dnsclass;
666
0
    unsigned int ttl;
667
668
0
    rc = doh_skipqname(doh, dohlen, &index);
669
0
    if(rc)
670
0
      return rc; /* bad qname */
671
672
0
    if(dohlen < (index + 2))
673
0
      return DOH_DNS_OUT_OF_RANGE;
674
675
0
    type = doh_get16bit(doh, index);
676
0
    if((type != CURL_DNS_TYPE_CNAME) &&  /* may be synthesized from DNAME */
677
0
       (type != CURL_DNS_TYPE_DNAME) &&  /* if present, accept and ignore */
678
0
       (type != dnstype))
679
      /* Not the same type as was asked for, nor CNAME nor DNAME */
680
0
      return DOH_DNS_UNEXPECTED_TYPE;
681
0
    index += 2;
682
683
0
    if(dohlen < (index + 2))
684
0
      return DOH_DNS_OUT_OF_RANGE;
685
0
    dnsclass = doh_get16bit(doh, index);
686
0
    if(DNS_CLASS_IN != dnsclass)
687
0
      return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
688
0
    index += 2;
689
690
0
    if(dohlen < (index + 4))
691
0
      return DOH_DNS_OUT_OF_RANGE;
692
693
0
    ttl = doh_get32bit(doh, index);
694
0
    if(ttl > MAX_DNS_TTL)
695
0
      ttl = MAX_DNS_TTL;
696
0
    if(ttl < d->ttl)
697
0
      d->ttl = ttl;
698
0
    index += 4;
699
700
0
    if(dohlen < (index + 2))
701
0
      return DOH_DNS_OUT_OF_RANGE;
702
703
0
    rdlength = doh_get16bit(doh, index);
704
0
    index += 2;
705
0
    if(dohlen < (index + rdlength))
706
0
      return DOH_DNS_OUT_OF_RANGE;
707
708
0
    rc = doh_rdata(doh, rdlength, type, (int)index, d);
709
0
    if(rc)
710
0
      return rc;
711
0
    index += rdlength;
712
0
    ancount--;
713
0
  }
714
715
0
  nscount = doh_get16bit(doh, 8);
716
0
  while(nscount) {
717
0
    rc = doh_skipqname(doh, dohlen, &index);
718
0
    if(rc)
719
0
      return rc; /* bad qname */
720
721
0
    if(dohlen < (index + 8))
722
0
      return DOH_DNS_OUT_OF_RANGE;
723
724
0
    index += 2 + 2 + 4; /* type, dnsclass and ttl */
725
726
0
    if(dohlen < (index + 2))
727
0
      return DOH_DNS_OUT_OF_RANGE;
728
729
0
    rdlength = doh_get16bit(doh, index);
730
0
    index += 2;
731
0
    if(dohlen < (index + rdlength))
732
0
      return DOH_DNS_OUT_OF_RANGE;
733
0
    index += rdlength;
734
0
    nscount--;
735
0
  }
736
737
0
  arcount = doh_get16bit(doh, 10);
738
0
  while(arcount) {
739
0
    rc = doh_skipqname(doh, dohlen, &index);
740
0
    if(rc)
741
0
      return rc; /* bad qname */
742
743
0
    if(dohlen < (index + 8))
744
0
      return DOH_DNS_OUT_OF_RANGE;
745
746
0
    index += 2 + 2 + 4; /* type, dnsclass and ttl */
747
748
0
    if(dohlen < (index + 2))
749
0
      return DOH_DNS_OUT_OF_RANGE;
750
751
0
    rdlength = doh_get16bit(doh, index);
752
0
    index += 2;
753
0
    if(dohlen < (index + rdlength))
754
0
      return DOH_DNS_OUT_OF_RANGE;
755
0
    index += rdlength;
756
0
    arcount--;
757
0
  }
758
759
0
  if(index != dohlen)
760
0
    return DOH_DNS_MALFORMAT; /* something is wrong */
761
762
0
  return DOH_OK; /* ok */
763
0
}
764
765
/*
766
 * This function returns a pointer to the first element of a newly allocated
767
 * Curl_addrinfo struct linked list filled with the data from a set of DoH
768
 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
769
 * an IPv6 stack, but usable also for IPv4, all hosts and environments.
770
 *
771
 * The memory allocated by this function *MUST* be free'd later on calling
772
 * Curl_freeaddrinfo(). For each successful call to this function there
773
 * must be an associated call later to Curl_freeaddrinfo().
774
 */
775
static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
776
                       int port, struct Curl_addrinfo **aip)
777
0
{
778
0
  struct Curl_addrinfo *ai;
779
0
  struct Curl_addrinfo *prevai = NULL;
780
0
  struct Curl_addrinfo *firstai = NULL;
781
0
  struct sockaddr_in *addr;
782
0
#ifdef USE_IPV6
783
0
  struct sockaddr_in6 *addr6;
784
0
#endif
785
0
  size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
786
0
  CURLcode result = CURLE_OK;
787
0
  int i;
788
789
0
  for(i = 0; i < de->numaddr; i++) {
790
0
    size_t ss_size;
791
0
    CURL_SA_FAMILY_T addrtype;
792
0
    if(de->addr[i].type == CURL_DNS_TYPE_AAAA) {
793
#ifndef USE_IPV6
794
      /* we cannot handle IPv6 addresses */
795
      continue;
796
#else
797
0
      ss_size = sizeof(struct sockaddr_in6);
798
0
      addrtype = AF_INET6;
799
0
#endif
800
0
    }
801
0
    else {
802
0
      ss_size = sizeof(struct sockaddr_in);
803
0
      addrtype = AF_INET;
804
0
    }
805
806
0
    ai = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
807
0
    if(!ai) {
808
0
      result = CURLE_OUT_OF_MEMORY;
809
0
      break;
810
0
    }
811
0
    ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
812
0
    ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
813
0
    memcpy(ai->ai_canonname, hostname, hostlen);
814
815
0
    if(!firstai)
816
      /* store the pointer we want to return from this function */
817
0
      firstai = ai;
818
819
0
    if(prevai)
820
      /* make the previous entry point to this */
821
0
      prevai->ai_next = ai;
822
823
0
    ai->ai_family = addrtype;
824
825
    /* we return all names as STREAM, so when using this address for TFTP
826
       the type must be ignored and conn->socktype be used instead! */
827
0
    ai->ai_socktype = SOCK_STREAM;
828
829
0
    ai->ai_addrlen = (curl_socklen_t)ss_size;
830
831
    /* leave the rest of the struct filled with zero */
832
833
0
    switch(ai->ai_family) {
834
0
    case AF_INET:
835
0
      addr = (void *)ai->ai_addr; /* storage area for this info */
836
0
      DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
837
0
      memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
838
0
      addr->sin_family = addrtype;
839
0
      addr->sin_port = htons((unsigned short)port);
840
0
      break;
841
842
0
#ifdef USE_IPV6
843
0
    case AF_INET6:
844
0
      addr6 = (void *)ai->ai_addr; /* storage area for this info */
845
0
      DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
846
0
      memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
847
0
      addr6->sin6_family = addrtype;
848
0
      addr6->sin6_port = htons((unsigned short)port);
849
0
      break;
850
0
#endif
851
0
    }
852
853
0
    prevai = ai;
854
0
  }
855
856
0
  if(result) {
857
0
    Curl_freeaddrinfo(firstai);
858
0
    firstai = NULL;
859
0
  }
860
0
  *aip = firstai;
861
862
0
  return result;
863
0
}
864
865
/* @unittest 1655 */
866
UNITTEST void de_cleanup(struct dohentry *d);
867
UNITTEST void de_cleanup(struct dohentry *d)
868
0
{
869
#ifdef USE_HTTPSRR
870
  int i = 0;
871
  for(i = 0; i < d->numhttps_rrs; i++)
872
    curlx_safefree(d->https_rrs[i].val);
873
#else
874
0
  (void)d;
875
0
#endif
876
0
}
877
878
#ifdef USE_HTTPSRR
879
880
/*
881
 * @brief decode the DNS name in a binary RRData
882
 * @param buf points to the buffer (in/out)
883
 * @param remaining points to the remaining buffer length (in/out)
884
 * @param dnsname returns the string form name on success
885
 * @return is 1 for success, error otherwise
886
 *
887
 * The encoding here is defined in
888
 * https://datatracker.ietf.org/doc/html/rfc1035#section-3.1
889
 *
890
 * The input buffer pointer will be modified so it points to after the end of
891
 * the DNS name encoding on output. (that is why it is an "unsigned char
892
 * **" :-)
893
 */
894
static CURLcode doh_decode_rdata_name(const unsigned char **buf,
895
                                      size_t *remaining, char **dnsname)
896
{
897
  const unsigned char *cp = NULL;
898
  size_t rem = 0;
899
  unsigned char clen = 0; /* chunk len */
900
  struct dynbuf thename;
901
902
  DEBUGASSERT(buf && remaining && dnsname);
903
  if(!buf || !remaining || !dnsname || !*remaining)
904
    return CURLE_OUT_OF_MEMORY;
905
  curlx_dyn_init(&thename, CURL_MAXLEN_HOST_NAME);
906
  rem = *remaining;
907
  cp = *buf;
908
  clen = *cp++;
909
  /* RFC 9460 says it must be uncompressed */
910
  if(clen > 63)
911
    return CURLE_WEIRD_SERVER_REPLY;
912
913
  if(clen == 0) {
914
    /* special case - return "." as name */
915
    if(curlx_dyn_addn(&thename, ".", 1))
916
      return CURLE_OUT_OF_MEMORY;
917
  }
918
  while(clen) {
919
    if(clen >= rem) {
920
      curlx_dyn_free(&thename);
921
      return CURLE_OUT_OF_MEMORY;
922
    }
923
    if(curlx_dyn_addn(&thename, cp, clen) ||
924
       curlx_dyn_addn(&thename, ".", 1))
925
      return CURLE_TOO_LARGE;
926
927
    cp += clen;
928
    rem -= (clen + 1);
929
    if(rem <= 0) {
930
      curlx_dyn_free(&thename);
931
      return CURLE_OUT_OF_MEMORY;
932
    }
933
    clen = *cp++;
934
    if(clen > 63) {
935
      /* invalid format */
936
      curlx_dyn_free(&thename);
937
      return CURLE_WEIRD_SERVER_REPLY;
938
    }
939
  }
940
  *buf = cp;
941
  *remaining = rem - 1;
942
  *dnsname = curlx_dyn_ptr(&thename);
943
  return CURLE_OK;
944
}
945
946
/* @unittest 1658 */
947
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
948
                                          const unsigned char *cp, size_t len,
949
                                          struct Curl_https_rrinfo **hrr);
950
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
951
                                          const unsigned char *cp, size_t len,
952
                                          struct Curl_https_rrinfo **hrr)
953
{
954
  uint16_t pcode = 0, plen = 0;
955
  uint32_t expected_min_pcode = 0;
956
  struct Curl_https_rrinfo *lhrr = NULL;
957
  char *dnsname = NULL;
958
  CURLcode result = CURLE_OUT_OF_MEMORY;
959
  size_t olen;
960
961
  (void)data;
962
  *hrr = NULL;
963
  if(len <= 2)
964
    return CURLE_BAD_FUNCTION_ARGUMENT;
965
  lhrr = curlx_calloc(1, sizeof(struct Curl_https_rrinfo));
966
  if(!lhrr)
967
    return CURLE_OUT_OF_MEMORY;
968
  lhrr->priority = doh_get16bit(cp, 0);
969
  cp += 2;
970
  len -= 2;
971
  if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK)
972
    goto err;
973
  lhrr->target = dnsname;
974
  if(Curl_junkscan(dnsname, &olen, FALSE)) {
975
    /* unacceptable hostname content */
976
    result = CURLE_WEIRD_SERVER_REPLY;
977
    goto err;
978
  }
979
  while(len >= 4) {
980
    pcode = doh_get16bit(cp, 0);
981
    plen = doh_get16bit(cp, 2);
982
    cp += 4;
983
    len -= 4;
984
    if(pcode < expected_min_pcode || plen > len) {
985
      result = CURLE_WEIRD_SERVER_REPLY;
986
      goto err;
987
    }
988
    result = Curl_httpsrr_set(lhrr, pcode, cp, plen);
989
    if(result)
990
      goto err;
991
    Curl_httpsrr_trace(data, lhrr);
992
    cp += plen;
993
    len -= plen;
994
    expected_min_pcode = pcode + 1;
995
  }
996
  DEBUGASSERT(!len);
997
  *hrr = lhrr;
998
  return CURLE_OK;
999
err:
1000
  Curl_httpsrr_destroy(lhrr);
1001
  return result;
1002
}
1003
1004
#endif /* USE_HTTPSRR */
1005
1006
/* called from multi when a sub transfer, e.g. doh probe, is done.
1007
 * Parse the response and set the results in the `async` context
1008
 * of master, using the id from the probe's CURL_EZM_DOH_PROBE
1009
 * meta data. */
1010
static void doh_probe_done(struct Curl_easy *doh,
1011
                           struct Curl_easy *master, CURLcode result)
1012
0
{
1013
0
  struct Curl_resolv_async *async = NULL;
1014
0
  struct doh_probes *dohp = NULL;
1015
0
  struct doh_request *doh_req = NULL;
1016
0
  struct Curl_addrinfo **pdest_ai;
1017
0
  struct dohentry de;
1018
0
  int slot, httpcode;
1019
1020
0
  de_init(&de);
1021
0
  doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
1022
0
  if(!doh_req) {
1023
    /* transfer `doh` is not a DoH probe. */
1024
0
    DEBUGASSERT(0);
1025
0
    goto out;
1026
0
  }
1027
1028
0
  async = Curl_async_get(master, doh_req->resolv_id);
1029
0
  if(!async) {
1030
0
    CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response",
1031
0
                 doh_req->resolv_id);
1032
0
    goto out;
1033
0
  }
1034
0
  dohp = async->doh;
1035
1036
0
  for(slot = 0; slot < DOH_SLOT_COUNT; ++slot) {
1037
0
    if(dohp->probe_mid[slot] == doh->mid)
1038
0
      break;
1039
0
  }
1040
  /* We really should have found the slot where to store the response */
1041
0
  if(slot >= DOH_SLOT_COUNT) {
1042
0
    failf(master, "DoH: unknown sub request done");
1043
0
    DEBUGASSERT(0);
1044
0
    goto out;
1045
0
  }
1046
1047
0
  async->queries_ongoing--;
1048
0
  dohp = async->doh;
1049
0
  httpcode = doh->info.httpcode;
1050
0
  switch(slot) {
1051
0
  case DOH_SLOT_IPV4:
1052
0
    async->dns_responses |= CURL_DNSQ_A;
1053
0
    break;
1054
0
#ifdef USE_IPV6
1055
0
  case DOH_SLOT_IPV6:
1056
0
    async->dns_responses |= CURL_DNSQ_AAAA;
1057
0
    break;
1058
0
#endif
1059
#ifdef USE_HTTPSRR
1060
  case DOH_SLOT_HTTPS_RR:
1061
    async->dns_responses |= CURL_DNSQ_HTTPS;
1062
    break;
1063
#endif
1064
0
  default:
1065
0
    DEBUGASSERT(0);
1066
0
    break;
1067
0
  }
1068
1069
0
  if(result) {
1070
0
    dohp->probe_rc[slot] = DOH_HTTP_FAILED;
1071
0
    infof(doh, "[DoH] [%s] error: %s",
1072
0
          doh_type2name(doh_req->dnstype), curl_easy_strerror(result));
1073
0
    goto out;
1074
0
  }
1075
0
  else if((httpcode < 200) || (httpcode >= 300)) {
1076
0
    dohp->probe_rc[slot] = DOH_HTTP_FAILED;
1077
0
    infof(doh, "[DoH] [%s] error: HTTP status %d",
1078
0
          doh_type2name(doh_req->dnstype), httpcode);
1079
0
    goto out;
1080
0
  }
1081
1082
0
  dohp->probe_rc[slot] = doh_resp_decode(curlx_dyn_uptr(&doh_req->resp_body),
1083
0
                                         curlx_dyn_len(&doh_req->resp_body),
1084
0
                                         doh_req->dnstype, &de);
1085
0
  if(dohp->probe_rc[slot]) {
1086
#ifdef USE_HTTPSRR
1087
    if((dohp->probe_rc[slot] == DOH_NO_CONTENT) &&
1088
       (doh_req->dnstype == CURL_DNS_TYPE_HTTPS)) {
1089
      dohp->probe_rc[slot] = DOH_DNS_NXDOMAIN;
1090
    }
1091
#endif
1092
0
    infof(doh, "[DoH] [%s] error decoding response: %s",
1093
0
          doh_type2name(doh_req->dnstype),
1094
0
          doh_strerror(dohp->probe_rc[slot]));
1095
0
    goto out;
1096
0
  }
1097
1098
0
  if(doh_req->dnstype == CURL_DNS_TYPE_A)
1099
0
    pdest_ai = &async->ai_A;
1100
0
  else if(doh_req->dnstype == CURL_DNS_TYPE_AAAA)
1101
0
    pdest_ai = &async->ai_AAAA;
1102
0
  else
1103
0
    pdest_ai = NULL;
1104
1105
0
  if(pdest_ai && de.numaddr) {
1106
0
    if(*pdest_ai) {
1107
0
      Curl_freeaddrinfo(*pdest_ai);
1108
0
      *pdest_ai = NULL;
1109
0
    }
1110
0
    result = doh2ai(&de, async->peer->hostname, async->peer->port, pdest_ai);
1111
0
    if(result) { /* hard failure on our side, fail completely */
1112
0
      infof(doh, "[DoH] [%s] error creating addrinfo: %s",
1113
0
            doh_type2name(doh_req->dnstype), curl_easy_strerror(result));
1114
0
      dohp->probe_rc[slot] = DOH_OOM;
1115
0
      async->result = result;
1116
0
    }
1117
0
  }
1118
#ifdef USE_HTTPSRR
1119
  else if((doh_req->dnstype == CURL_DNS_TYPE_HTTPS) && de.numhttps_rrs) {
1120
    CURL_TRC_DNS(doh, "[HTTPS] got %d records", de.numhttps_rrs);
1121
    result = doh_resp_decode_httpsrr(doh, de.https_rrs->val,
1122
                                     de.https_rrs->len, &async->httpsrr);
1123
    if(result) {
1124
      dohp->probe_rc[slot] = DOH_HTTP_FAILED;
1125
      infof(doh, "[DoH] error decoding HTTPS RR: %s",
1126
            curl_easy_strerror(result));
1127
      goto out;
1128
    }
1129
  }
1130
#endif /* USE_HTTPSRR */
1131
1132
  /* DoH request complete, run master to act on results */
1133
0
  infof(doh, "DoH request complete, %u to go", async->queries_ongoing);
1134
1135
0
out:
1136
0
  Curl_multi_mark_dirty(master);
1137
0
  de_cleanup(&de);
1138
0
  Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
1139
0
}
1140
1141
CURLcode Curl_doh_take_result(struct Curl_easy *data,
1142
                              struct Curl_resolv_async *async,
1143
                              struct Curl_dns_entry **pdns)
1144
0
{
1145
0
  struct doh_probes *dohp = async->doh;
1146
0
  CURLcode result = CURLE_OK;
1147
1148
0
  *pdns = NULL; /* defaults to no response */
1149
0
  if(!dohp)
1150
0
    return CURLE_OUT_OF_MEMORY;
1151
1152
0
  async->negative_answer = FALSE;
1153
0
  if(async->result) {
1154
0
    result = async->result;
1155
0
    goto out;
1156
0
  }
1157
1158
0
  if(CURL_DNSQ_IS_ADDR(async->dns_queries) &&
1159
0
     dohp->probe_mid[DOH_SLOT_IPV4] == UINT32_MAX &&
1160
0
     dohp->probe_mid[DOH_SLOT_IPV6] == UINT32_MAX) {
1161
0
    failf(data, "Could not DoH-resolve: %s", async->peer->hostname);
1162
0
    return async->for_proxy ?
1163
0
      CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
1164
0
  }
1165
0
  else if(!async->queries_ongoing) {
1166
0
    struct Curl_dns_entry *dns = NULL;
1167
0
    bool negative = TRUE;
1168
0
    int slot;
1169
1170
    /* remove DoH handles from multi handle and close them */
1171
0
    doh_close(data, async);
1172
    /* parse the responses, create the struct and return it! */
1173
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1174
      /* Failing without an NXDOMAIN answer - a SERVFAIL-class rcode or
1175
         an undecodable response - says nothing about the name. Such a
1176
         failure must not be cached as a negative entry. */
1177
0
      if(dohp->probe_rc[slot] && (dohp->probe_rc[slot] != DOH_DNS_NXDOMAIN))
1178
0
        negative = FALSE;
1179
0
    } /* next slot */
1180
1181
0
    if(async->ai_A || async->ai_AAAA) {
1182
0
      dns = Curl_dnsc_mk_addr2(
1183
0
        data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer);
1184
0
      if(!dns) {
1185
0
        result = CURLE_OUT_OF_MEMORY;
1186
0
        goto out;
1187
0
      }
1188
0
    }
1189
#ifdef USE_HTTPSRR
1190
    else if((async->dns_queries & CURL_DNSQ_HTTPS) &&
1191
            !dohp->probe_rc[DOH_SLOT_HTTPS_RR]) {
1192
      Curl_httpsrr_trace(data, async->httpsrr);
1193
      dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer);
1194
      if(!dns) {
1195
        result = CURLE_OUT_OF_MEMORY;
1196
        goto out;
1197
      }
1198
    }
1199
#endif /* USE_HTTPSRR */
1200
0
    else {
1201
      /* every query failed. Only NXDOMAIN answers for all of them
1202
         make this a negative answer, eligible for caching. */
1203
0
      async->negative_answer = negative;
1204
0
      result = async->for_proxy ?
1205
0
        CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
1206
0
    }
1207
1208
    /* and add the entry to the cache */
1209
0
    if(dns)
1210
0
      result = Curl_dnscache_add(data, dns);
1211
0
    *pdns = dns;
1212
0
  } /* !async->queries_ongoing */
1213
0
  else
1214
    /* wait for pending DoH transactions to complete */
1215
0
    return CURLE_AGAIN;
1216
1217
0
out:
1218
0
  Curl_doh_cleanup(data, async);
1219
0
  return result;
1220
0
}
1221
1222
static void doh_close(struct Curl_easy *data,
1223
                      struct Curl_resolv_async *async)
1224
0
{
1225
0
  struct doh_probes *doh = async ? async->doh : NULL;
1226
0
  if(doh && data->multi) {
1227
0
    struct Curl_easy *probe_data;
1228
0
    uint32_t mid;
1229
0
    size_t slot;
1230
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1231
0
      mid = doh->probe_mid[slot];
1232
0
      if(mid == UINT32_MAX)
1233
0
        continue;
1234
0
      doh->probe_mid[slot] = UINT32_MAX;
1235
      /* should have been called before data is removed from multi handle */
1236
0
      DEBUGASSERT(data->multi);
1237
0
      probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) : NULL;
1238
0
      if(!probe_data) {
1239
0
        DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", mid));
1240
0
        continue;
1241
0
      }
1242
0
      probe_data->sub_xfer_done = NULL; /* No longer interested in result */
1243
      /* data->multi might already be reset at this time */
1244
0
      Curl_multi_remove_handle(data->multi, probe_data);
1245
0
      Curl_close(&probe_data);
1246
0
    }
1247
0
    CURL_TRC_DNS(data, "[DoH] probe done");
1248
0
  }
1249
0
}
1250
1251
void Curl_doh_cleanup(struct Curl_easy *data,
1252
                      struct Curl_resolv_async *async)
1253
0
{
1254
0
  struct doh_probes *dohp = async->doh;
1255
0
  if(dohp) {
1256
0
    doh_close(data, async);
1257
    curlx_safefree(async->doh);
1258
0
  }
1259
0
}
1260
1261
#endif /* CURL_DISABLE_DOH */