Coverage Report

Created: 2026-09-01 06:58

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
5.52k
#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
138
{
105
138
  const size_t hostlen = strlen(host);
106
138
  unsigned char *orig = dnsp;
107
138
  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
138
  size_t expected_len;
132
138
  DEBUGASSERT(hostlen);
133
138
  expected_len = 12 + 1 + hostlen + 4;
134
138
  if(host[hostlen - 1] != '.')
135
120
    expected_len++;
136
137
138
  if(expected_len > DOH_MAX_DNSREQ_SIZE)
138
36
    return DOH_DNS_NAME_TOO_LONG;
139
140
102
  if(len < expected_len)
141
0
    return DOH_TOO_SMALL_BUFFER;
142
143
102
  *dnsp++ = 0; /* 16-bit id */
144
102
  *dnsp++ = 0;
145
102
  *dnsp++ = 0x01; /* |QR|   Opcode  |AA|TC|RD| Set the RD bit */
146
102
  *dnsp++ = '\0'; /* |RA|   Z    |   RCODE   |                */
147
102
  *dnsp++ = '\0';
148
102
  *dnsp++ = 1;    /* QDCOUNT (number of entries in the question section) */
149
102
  *dnsp++ = '\0';
150
102
  *dnsp++ = '\0'; /* ANCOUNT */
151
102
  *dnsp++ = '\0';
152
102
  *dnsp++ = '\0'; /* NSCOUNT */
153
102
  *dnsp++ = '\0';
154
102
  *dnsp++ = '\0'; /* ARCOUNT */
155
156
  /* encode each label and store it in the QNAME */
157
551
  while(*hostp) {
158
479
    size_t labellen;
159
479
    const char *dot = strchr(hostp, '.');
160
479
    if(dot)
161
396
      labellen = dot - hostp;
162
83
    else
163
83
      labellen = strlen(hostp);
164
479
    if((labellen > 63) || (!labellen)) {
165
      /* label is too long or too short, error out */
166
30
      *olen = 0;
167
30
      return DOH_DNS_BAD_LABEL;
168
30
    }
169
    /* label is non-empty, process it */
170
449
    *dnsp++ = (unsigned char)labellen;
171
449
    memcpy(dnsp, hostp, labellen);
172
449
    dnsp += labellen;
173
449
    hostp += labellen;
174
    /* advance past dot, but only if there is one */
175
449
    if(dot)
176
385
      hostp++;
177
449
  } /* next label */
178
179
72
  *dnsp++ = 0; /* append zero-length label for root */
180
181
  /* There are assigned TYPE codes beyond 255: use range [1..65535] */
182
72
  *dnsp++ = (unsigned char)(255 & (dnstype >> 8)); /* upper 8-bit TYPE */
183
72
  *dnsp++ = (unsigned char)(255 & dnstype);        /* lower 8-bit TYPE */
184
185
72
  *dnsp++ = '\0'; /* upper 8-bit CLASS */
186
72
  *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
187
188
72
  *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
72
  DEBUGASSERT(*olen == expected_len);
193
72
  return DOH_OK;
194
72
}
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(CURL_EASY_STR(data, STRING_SSL_CAFILE)) {
340
0
      ERROR_CHECK_SETOPT(CURLOPT_CAINFO,
341
0
                         CURL_EASY_STR(data, STRING_SSL_CAFILE));
342
0
    }
343
0
    if(data->set.blobs[BLOB_CAINFO]) {
344
0
      ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]);
345
0
    }
346
0
    if(CURL_EASY_STR(data, STRING_SSL_CAPATH)) {
347
0
      ERROR_CHECK_SETOPT(CURLOPT_CAPATH,
348
0
                         CURL_EASY_STR(data, STRING_SSL_CAPATH));
349
0
    }
350
0
    if(CURL_EASY_STR(data, STRING_SSL_CRLFILE)) {
351
0
      ERROR_CHECK_SETOPT(CURLOPT_CRLFILE,
352
0
                         CURL_EASY_STR(data, STRING_SSL_CRLFILE));
353
0
    }
354
0
    if(data->set.ssl.certinfo)
355
0
      ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L);
356
0
    if(data->set.ssl.fsslctx)
357
0
      ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx);
358
0
    if(data->set.ssl.fsslctxp)
359
0
      ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp);
360
0
    if(CURL_EASY_STR(data, STRING_SSL_EC_CURVES)) {
361
0
      ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES,
362
0
                         CURL_EASY_STR(data, STRING_SSL_EC_CURVES));
363
0
    }
364
365
0
    (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS,
366
0
                           ((long)data->set.ssl.primary.ssl_options &
367
0
                            ~CURLSSLOPT_AUTO_CLIENT_CERT));
368
0
  }
369
370
0
  doh->state.internal = TRUE;
371
0
  doh->master_mid = data->mid; /* master transfer of this one */
372
0
  doh->sub_xfer_done = doh_probe_done;
373
374
0
  result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor);
375
0
  doh_req = NULL; /* call took ownership */
376
0
  if(result)
377
0
    goto error;
378
379
  /* DoH handles must not inherit private_data. The handles may be passed to
380
     the user via callbacks and the user will be able to identify them as
381
     internal handles because private data is not set. The user can then set
382
     private_data via CURLOPT_PRIVATE if they so choose. */
383
0
  DEBUGASSERT(!doh->set.private_data);
384
385
0
  if(Curl_multi_add_handle(multi, doh))
386
0
    goto error;
387
388
0
  *pmid = doh->mid;
389
0
  return CURLE_OK;
390
391
0
error:
392
0
  Curl_close(&doh);
393
0
  if(doh_req)
394
0
    doh_probe_dtor(NULL, 0, doh_req);
395
0
  return result;
396
0
}
397
398
/*
399
 * Curl_doh() starts a name resolve using DoH. It resolves a name and returns
400
 * a 'Curl_addrinfo *' with the address information.
401
 */
402
403
CURLcode Curl_doh(struct Curl_easy *data,
404
                  struct Curl_resolv_async *async)
405
0
{
406
0
  CURLcode result = CURLE_OK;
407
0
  struct doh_probes *dohp = NULL;
408
0
  size_t i;
409
410
0
  DEBUGASSERT(!async->doh);
411
0
  DEBUGASSERT(async->peer->hostname[0]);
412
0
  if(async->doh) {
413
0
    DEBUGASSERT(0); /* should not happen */
414
0
    Curl_doh_cleanup(data, async);
415
0
  }
416
417
0
  if(!async->dns_queries)
418
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
419
#ifdef USE_HTTPSRR
420
  if(CURL_DNSQ_IS_ADDR(async->dns_queries) &&
421
     (async->dns_queries & CURL_DNSQ_HTTPS)) {
422
    /* Can't mix those in the same async resolve */
423
    DEBUGASSERT(0);
424
    return CURLE_BAD_FUNCTION_ARGUMENT;
425
  }
426
#else
427
0
  if(async->dns_queries & CURL_DNSQ_HTTPS) {
428
0
    DEBUGASSERT(0);
429
0
    return CURLE_NOT_BUILT_IN;
430
0
  }
431
0
#endif
432
433
  /* start clean, consider allocating this struct on demand */
434
0
  async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
435
0
  if(!dohp)
436
0
    return CURLE_OUT_OF_MEMORY;
437
438
0
  for(i = 0; i < DOH_SLOT_COUNT; ++i) {
439
0
    dohp->probe_rc[i] = DOH_OK;
440
0
    dohp->probe_mid[i] = UINT32_MAX;
441
0
  }
442
443
0
#ifdef USE_IPV6
444
  /* AAAA results have preference in happy eyeballing, trigger first */
445
0
  if(async->dns_queries & CURL_DNSQ_AAAA) {
446
    /* create IPv6 DoH request */
447
0
    result = doh_probe_run(data, CURL_DNS_TYPE_AAAA,
448
0
                           async->peer->hostname,
449
0
                           CURL_EASY_STR(data, STRING_DOH),
450
0
                           data->multi, async->id,
451
0
                           &dohp->probe_mid[DOH_SLOT_IPV6]);
452
0
    if(result)
453
0
      goto error;
454
0
    async->queries_ongoing++;
455
0
  }
456
0
#endif
457
458
  /* create IPv4 DoH request */
459
0
  if(async->dns_queries & CURL_DNSQ_A) {
460
0
    result = doh_probe_run(data, CURL_DNS_TYPE_A,
461
0
                           async->peer->hostname,
462
0
                           CURL_EASY_STR(data, STRING_DOH),
463
0
                           data->multi, async->id,
464
0
                           &dohp->probe_mid[DOH_SLOT_IPV4]);
465
0
    if(result)
466
0
      goto error;
467
0
    async->queries_ongoing++;
468
0
  }
469
470
#ifdef USE_HTTPSRR
471
  if(async->dns_queries & CURL_DNSQ_HTTPS) {
472
    char *qname = NULL;
473
    if(async->peer->port != PORT_HTTPS) {
474
      qname = curl_maprintf("_%u._https.%s",
475
                            async->peer->port, async->peer->hostname);
476
      if(!qname)
477
        goto error;
478
    }
479
    result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS,
480
                           qname ? qname : async->peer->hostname,
481
                           CURL_EASY_STR(data, STRING_DOH), data->multi,
482
                           async->id,
483
                           &dohp->probe_mid[DOH_SLOT_HTTPS_RR]);
484
    curlx_free(qname);
485
    if(result)
486
      goto error;
487
    async->queries_ongoing++;
488
  }
489
#endif
490
0
  return CURLE_OK;
491
492
0
error:
493
0
  Curl_doh_cleanup(data, async);
494
0
  return result;
495
0
}
496
497
static DOHcode doh_skipqname(const unsigned char *doh, size_t dohlen,
498
                             unsigned int *indexp)
499
231k
{
500
231k
  unsigned char length;
501
1.25M
  do {
502
1.25M
    if(dohlen < (*indexp + 1))
503
227
      return DOH_DNS_OUT_OF_RANGE;
504
1.25M
    length = doh[*indexp];
505
1.25M
    if((length & 0xc0) == 0xc0) {
506
      /* name pointer, advance over it and be done */
507
99.0k
      if(dohlen < (*indexp + 2))
508
37
        return DOH_DNS_OUT_OF_RANGE;
509
98.9k
      *indexp += 2;
510
98.9k
      break;
511
99.0k
    }
512
1.15M
    if(length & 0xc0)
513
52
      return DOH_DNS_BAD_LABEL;
514
1.15M
    if(dohlen < (*indexp + 1 + length))
515
85
      return DOH_DNS_OUT_OF_RANGE;
516
1.15M
    *indexp += (unsigned int)(1 + length);
517
1.15M
  } while(length);
518
231k
  return DOH_OK;
519
231k
}
520
521
static unsigned short doh_get16bit(const unsigned char *doh,
522
                                   unsigned int index)
523
48.2k
{
524
48.2k
  return (unsigned short)((doh[index] << 8) | doh[index + 1]);
525
48.2k
}
526
527
static unsigned int doh_get32bit(const unsigned char *doh, unsigned int index)
528
5.41k
{
529
  /* make clang and gcc optimize this to bswap by incrementing
530
     the pointer first. */
531
5.41k
  doh += index;
532
533
  /* avoid undefined behavior by casting to unsigned before shifting
534
     24 bits, possibly into the sign bit. codegen is same, but
535
     ub sanitizer will not be upset */
536
5.41k
  return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) |
537
5.41k
         ((unsigned)doh[2] << 8) | doh[3];
538
5.41k
}
539
540
static void doh_store_a(const unsigned char *doh, int index,
541
                        struct dohentry *d)
542
880
{
543
  /* silently ignore addresses over the limit */
544
880
  if(d->numaddr < DOH_MAX_ADDR) {
545
491
    struct dohaddr *a = &d->addr[d->numaddr];
546
491
    a->type = CURL_DNS_TYPE_A;
547
491
    memcpy(&a->ip.v4, &doh[index], 4);
548
491
    d->numaddr++;
549
491
  }
550
880
}
551
552
static void doh_store_aaaa(const unsigned char *doh, int index,
553
                           struct dohentry *d)
554
600
{
555
  /* silently ignore addresses over the limit */
556
600
  if(d->numaddr < DOH_MAX_ADDR) {
557
406
    struct dohaddr *a = &d->addr[d->numaddr];
558
406
    a->type = CURL_DNS_TYPE_AAAA;
559
406
    memcpy(&a->ip.v6, &doh[index], 16);
560
406
    d->numaddr++;
561
406
  }
562
600
}
563
564
#ifdef USE_HTTPSRR
565
static DOHcode doh_store_https(const unsigned char *doh, int index,
566
                               struct dohentry *d, uint16_t len)
567
{
568
  /* silently ignore RRs over the limit */
569
  if(d->numhttps_rrs < DOH_MAX_HTTPS) {
570
    struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs];
571
    h->val = curlx_memdup(&doh[index], len);
572
    if(!h->val)
573
      return DOH_OUT_OF_MEM;
574
    h->len = len;
575
    d->numhttps_rrs++;
576
  }
577
  return DOH_OK;
578
}
579
#endif
580
581
static DOHcode doh_rdata(const unsigned char *doh,
582
                         unsigned short rdlength,
583
                         unsigned short type,
584
                         int index,
585
                         struct dohentry *d)
586
5.25k
{
587
  /* RDATA
588
     - A (TYPE 1): 4 bytes
589
     - AAAA (TYPE 28): 16 bytes
590
     - HTTPS (TYPE 65): N bytes */
591
5.25k
  switch(type) {
592
912
  case CURL_DNS_TYPE_A:
593
912
    if(rdlength != 4)
594
32
      return DOH_DNS_RDATA_LEN;
595
880
    doh_store_a(doh, index, d);
596
880
    break;
597
633
  case CURL_DNS_TYPE_AAAA:
598
633
    if(rdlength != 16)
599
33
      return DOH_DNS_RDATA_LEN;
600
600
    doh_store_aaaa(doh, index, d);
601
600
    break;
602
#ifdef USE_HTTPSRR
603
  case CURL_DNS_TYPE_HTTPS: {
604
    DOHcode rc = doh_store_https(doh, index, d, rdlength);
605
    if(rc)
606
      return rc;
607
    break;
608
  }
609
#endif
610
3.71k
  default:
611
    /* unsupported type, or type we do not store, skip it */
612
3.71k
    break;
613
5.25k
  }
614
5.19k
  return DOH_OK;
615
5.25k
}
616
617
/* @unittest 1655 */
618
UNITTEST void de_init(struct dohentry *de);
619
UNITTEST void de_init(struct dohentry *de)
620
1.22k
{
621
1.22k
  memset(de, 0, sizeof(*de));
622
1.22k
  de->ttl = INT_MAX;
623
1.22k
}
624
625
/* TTL value cap */
626
8.60k
#define MAX_DNS_TTL 86400U /* 24 hours */
627
/* @unittest 1650 */
628
UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
629
                                 size_t dohlen,
630
                                 DNStype dnstype,
631
                                 struct dohentry *d);
632
UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
633
                                 size_t dohlen,
634
                                 DNStype dnstype,
635
                                 struct dohentry *d)
636
1.22k
{
637
1.22k
  unsigned char rcode;
638
1.22k
  unsigned short qdcount;
639
1.22k
  unsigned short ancount;
640
1.22k
  unsigned short type = 0;
641
1.22k
  unsigned short rdlength;
642
1.22k
  unsigned short nscount;
643
1.22k
  unsigned short arcount;
644
1.22k
  unsigned int index = 12;
645
1.22k
  DOHcode rc;
646
647
1.22k
  if(dohlen < 12)
648
9
    return DOH_TOO_SMALL_BUFFER; /* too small */
649
1.21k
  if(!doh || doh[0] || doh[1])
650
17
    return DOH_DNS_BAD_ID; /* bad ID */
651
1.19k
  rcode = doh[3] & 0x0f;
652
1.19k
  if(rcode == 3)
653
1
    return DOH_DNS_NXDOMAIN; /* name does not exist */
654
1.19k
  if(rcode)
655
6
    return DOH_DNS_BAD_RCODE; /* bad rcode */
656
657
1.19k
  qdcount = doh_get16bit(doh, 4);
658
197k
  while(qdcount) {
659
196k
    rc = doh_skipqname(doh, dohlen, &index);
660
196k
    if(rc)
661
125
      return rc; /* bad qname */
662
196k
    if(dohlen < (index + 4))
663
50
      return DOH_DNS_OUT_OF_RANGE;
664
196k
    index += 4; /* skip question's type and class */
665
196k
    qdcount--;
666
196k
  }
667
668
1.01k
  ancount = doh_get16bit(doh, 6);
669
6.21k
  while(ancount) {
670
5.77k
    unsigned short dnsclass;
671
5.77k
    unsigned int ttl;
672
673
5.77k
    rc = doh_skipqname(doh, dohlen, &index);
674
5.77k
    if(rc)
675
135
      return rc; /* bad qname */
676
677
5.64k
    if(dohlen < (index + 2))
678
37
      return DOH_DNS_OUT_OF_RANGE;
679
680
5.60k
    type = doh_get16bit(doh, index);
681
5.60k
    if((type != CURL_DNS_TYPE_CNAME) &&  /* may be synthesized from DNAME */
682
2.71k
       (type != CURL_DNS_TYPE_DNAME) &&  /* if present, accept and ignore */
683
1.99k
       (type != dnstype))
684
      /* Not the same type as was asked for, nor CNAME nor DNAME */
685
114
      return DOH_DNS_UNEXPECTED_TYPE;
686
5.49k
    index += 2;
687
688
5.49k
    if(dohlen < (index + 2))
689
35
      return DOH_DNS_OUT_OF_RANGE;
690
5.45k
    dnsclass = doh_get16bit(doh, index);
691
5.45k
    if(DNS_CLASS_IN != dnsclass)
692
32
      return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
693
5.42k
    index += 2;
694
695
5.42k
    if(dohlen < (index + 4))
696
15
      return DOH_DNS_OUT_OF_RANGE;
697
698
5.41k
    ttl = doh_get32bit(doh, index);
699
5.41k
    if(ttl > MAX_DNS_TTL)
700
3.19k
      ttl = MAX_DNS_TTL;
701
5.41k
    if(ttl < d->ttl)
702
520
      d->ttl = ttl;
703
5.41k
    index += 4;
704
705
5.41k
    if(dohlen < (index + 2))
706
115
      return DOH_DNS_OUT_OF_RANGE;
707
708
5.29k
    rdlength = doh_get16bit(doh, index);
709
5.29k
    index += 2;
710
5.29k
    if(dohlen < (index + rdlength))
711
36
      return DOH_DNS_OUT_OF_RANGE;
712
713
5.25k
    rc = doh_rdata(doh, rdlength, type, (int)index, d);
714
5.25k
    if(rc)
715
65
      return rc;
716
5.19k
    index += rdlength;
717
5.19k
    ancount--;
718
5.19k
  }
719
720
433
  nscount = doh_get16bit(doh, 8);
721
19.5k
  while(nscount) {
722
19.3k
    rc = doh_skipqname(doh, dohlen, &index);
723
19.3k
    if(rc)
724
81
      return rc; /* bad qname */
725
726
19.2k
    if(dohlen < (index + 8))
727
46
      return DOH_DNS_OUT_OF_RANGE;
728
729
19.1k
    index += 2 + 2 + 4; /* type, dnsclass and ttl */
730
731
19.1k
    if(dohlen < (index + 2))
732
22
      return DOH_DNS_OUT_OF_RANGE;
733
734
19.1k
    rdlength = doh_get16bit(doh, index);
735
19.1k
    index += 2;
736
19.1k
    if(dohlen < (index + rdlength))
737
52
      return DOH_DNS_OUT_OF_RANGE;
738
19.1k
    index += rdlength;
739
19.1k
    nscount--;
740
19.1k
  }
741
742
232
  arcount = doh_get16bit(doh, 10);
743
9.99k
  while(arcount) {
744
9.94k
    rc = doh_skipqname(doh, dohlen, &index);
745
9.94k
    if(rc)
746
60
      return rc; /* bad qname */
747
748
9.88k
    if(dohlen < (index + 8))
749
44
      return DOH_DNS_OUT_OF_RANGE;
750
751
9.83k
    index += 2 + 2 + 4; /* type, dnsclass and ttl */
752
753
9.83k
    if(dohlen < (index + 2))
754
22
      return DOH_DNS_OUT_OF_RANGE;
755
756
9.81k
    rdlength = doh_get16bit(doh, index);
757
9.81k
    index += 2;
758
9.81k
    if(dohlen < (index + rdlength))
759
51
      return DOH_DNS_OUT_OF_RANGE;
760
9.76k
    index += rdlength;
761
9.76k
    arcount--;
762
9.76k
  }
763
764
55
  if(index != dohlen)
765
53
    return DOH_DNS_MALFORMAT; /* something is wrong */
766
767
2
  return DOH_OK; /* ok */
768
55
}
769
770
/*
771
 * This function returns a pointer to the first element of a newly allocated
772
 * Curl_addrinfo struct linked list filled with the data from a set of DoH
773
 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
774
 * an IPv6 stack, but usable also for IPv4, all hosts and environments.
775
 *
776
 * The memory allocated by this function *MUST* be free'd later on calling
777
 * Curl_freeaddrinfo(). For each successful call to this function there
778
 * must be an associated call later to Curl_freeaddrinfo().
779
 */
780
static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
781
                       int port, struct Curl_addrinfo **aip)
782
0
{
783
0
  struct Curl_addrinfo *ai;
784
0
  struct Curl_addrinfo *prevai = NULL;
785
0
  struct Curl_addrinfo *firstai = NULL;
786
0
  struct sockaddr_in *addr;
787
0
#ifdef USE_IPV6
788
0
  struct sockaddr_in6 *addr6;
789
0
#endif
790
0
  size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
791
0
  CURLcode result = CURLE_OK;
792
0
  int i;
793
794
0
  for(i = 0; i < de->numaddr; i++) {
795
0
    size_t ss_size;
796
0
    CURL_SA_FAMILY_T addrtype;
797
0
    if(de->addr[i].type == CURL_DNS_TYPE_AAAA) {
798
#ifndef USE_IPV6
799
      /* we cannot handle IPv6 addresses */
800
      continue;
801
#else
802
0
      ss_size = sizeof(struct sockaddr_in6);
803
0
      addrtype = AF_INET6;
804
0
#endif
805
0
    }
806
0
    else {
807
0
      ss_size = sizeof(struct sockaddr_in);
808
0
      addrtype = AF_INET;
809
0
    }
810
811
0
    ai = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
812
0
    if(!ai) {
813
0
      result = CURLE_OUT_OF_MEMORY;
814
0
      break;
815
0
    }
816
0
    ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
817
0
    ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
818
0
    memcpy(ai->ai_canonname, hostname, hostlen);
819
820
0
    if(!firstai)
821
      /* store the pointer we want to return from this function */
822
0
      firstai = ai;
823
824
0
    if(prevai)
825
      /* make the previous entry point to this */
826
0
      prevai->ai_next = ai;
827
828
0
    ai->ai_family = addrtype;
829
830
    /* we return all names as STREAM, so when using this address for TFTP
831
       the type must be ignored and conn->socktype be used instead! */
832
0
    ai->ai_socktype = SOCK_STREAM;
833
834
0
    ai->ai_addrlen = (curl_socklen_t)ss_size;
835
836
    /* leave the rest of the struct filled with zero */
837
838
0
    switch(ai->ai_family) {
839
0
    case AF_INET:
840
0
      addr = (void *)ai->ai_addr; /* storage area for this info */
841
0
      DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
842
0
      memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
843
0
      addr->sin_family = addrtype;
844
0
      addr->sin_port = htons((unsigned short)port);
845
0
      break;
846
847
0
#ifdef USE_IPV6
848
0
    case AF_INET6:
849
0
      addr6 = (void *)ai->ai_addr; /* storage area for this info */
850
0
      DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
851
0
      memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
852
0
      addr6->sin6_family = addrtype;
853
0
      addr6->sin6_port = htons((unsigned short)port);
854
0
      break;
855
0
#endif
856
0
    }
857
858
0
    prevai = ai;
859
0
  }
860
861
0
  if(result) {
862
0
    Curl_freeaddrinfo(firstai);
863
0
    firstai = NULL;
864
0
  }
865
0
  *aip = firstai;
866
867
0
  return result;
868
0
}
869
870
/* @unittest 1655 */
871
UNITTEST void de_cleanup(struct dohentry *d);
872
UNITTEST void de_cleanup(struct dohentry *d)
873
1.22k
{
874
#ifdef USE_HTTPSRR
875
  int i = 0;
876
  for(i = 0; i < d->numhttps_rrs; i++)
877
    curlx_safefree(d->https_rrs[i].val);
878
#else
879
1.22k
  (void)d;
880
1.22k
#endif
881
1.22k
}
882
883
#ifdef USE_HTTPSRR
884
885
/*
886
 * @brief decode the DNS name in a binary RRData
887
 * @param buf points to the buffer (in/out)
888
 * @param remaining points to the remaining buffer length (in/out)
889
 * @param dnsname returns the string form name on success
890
 * @return is 1 for success, error otherwise
891
 *
892
 * The encoding here is defined in
893
 * https://datatracker.ietf.org/doc/html/rfc1035#section-3.1
894
 *
895
 * The input buffer pointer will be modified so it points to after the end of
896
 * the DNS name encoding on output. (that is why it is an "unsigned char
897
 * **" :-)
898
 */
899
static CURLcode doh_decode_rdata_name(const unsigned char **buf,
900
                                      size_t *remaining, char **dnsname)
901
{
902
  const unsigned char *cp = NULL;
903
  size_t rem = 0;
904
  unsigned char clen = 0; /* chunk len */
905
  struct dynbuf thename;
906
907
  DEBUGASSERT(buf && remaining && dnsname);
908
  if(!buf || !remaining || !dnsname || !*remaining)
909
    return CURLE_OUT_OF_MEMORY;
910
  curlx_dyn_init(&thename, CURL_MAXLEN_HOST_NAME);
911
  rem = *remaining;
912
  cp = *buf;
913
  clen = *cp++;
914
  /* RFC 9460 says it must be uncompressed */
915
  if(clen > 63)
916
    return CURLE_WEIRD_SERVER_REPLY;
917
918
  if(clen == 0) {
919
    /* special case - return "." as name */
920
    if(curlx_dyn_addn(&thename, ".", 1))
921
      return CURLE_OUT_OF_MEMORY;
922
  }
923
  while(clen) {
924
    if(clen >= rem) {
925
      curlx_dyn_free(&thename);
926
      return CURLE_OUT_OF_MEMORY;
927
    }
928
    if(curlx_dyn_addn(&thename, cp, clen) ||
929
       curlx_dyn_addn(&thename, ".", 1))
930
      return CURLE_TOO_LARGE;
931
932
    cp += clen;
933
    rem -= (clen + 1);
934
    if(rem <= 0) {
935
      curlx_dyn_free(&thename);
936
      return CURLE_OUT_OF_MEMORY;
937
    }
938
    clen = *cp++;
939
    if(clen > 63) {
940
      /* invalid format */
941
      curlx_dyn_free(&thename);
942
      return CURLE_WEIRD_SERVER_REPLY;
943
    }
944
  }
945
  *buf = cp;
946
  *remaining = rem - 1;
947
  *dnsname = curlx_dyn_ptr(&thename);
948
  return CURLE_OK;
949
}
950
951
/* @unittest 1658 */
952
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
953
                                          const unsigned char *cp, size_t len,
954
                                          struct Curl_https_rrinfo **hrr);
955
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
956
                                          const unsigned char *cp, size_t len,
957
                                          struct Curl_https_rrinfo **hrr)
958
{
959
  uint16_t pcode = 0, plen = 0;
960
  uint32_t expected_min_pcode = 0;
961
  struct Curl_https_rrinfo *lhrr = NULL;
962
  char *dnsname = NULL;
963
  CURLcode result = CURLE_OUT_OF_MEMORY;
964
  size_t olen;
965
966
  (void)data;
967
  *hrr = NULL;
968
  if(len <= 2)
969
    return CURLE_BAD_FUNCTION_ARGUMENT;
970
  lhrr = curlx_calloc(1, sizeof(struct Curl_https_rrinfo));
971
  if(!lhrr)
972
    return CURLE_OUT_OF_MEMORY;
973
  lhrr->priority = doh_get16bit(cp, 0);
974
  cp += 2;
975
  len -= 2;
976
  if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK)
977
    goto err;
978
  lhrr->target = dnsname;
979
  if(Curl_junkscan(dnsname, &olen, FALSE)) {
980
    /* unacceptable hostname content */
981
    result = CURLE_WEIRD_SERVER_REPLY;
982
    goto err;
983
  }
984
  while(len >= 4) {
985
    pcode = doh_get16bit(cp, 0);
986
    plen = doh_get16bit(cp, 2);
987
    cp += 4;
988
    len -= 4;
989
    if(pcode < expected_min_pcode || plen > len) {
990
      result = CURLE_WEIRD_SERVER_REPLY;
991
      goto err;
992
    }
993
    result = Curl_httpsrr_set(lhrr, pcode, cp, plen);
994
    if(result)
995
      goto err;
996
    Curl_httpsrr_trace(data, lhrr);
997
    cp += plen;
998
    len -= plen;
999
    expected_min_pcode = pcode + 1;
1000
  }
1001
  DEBUGASSERT(!len);
1002
  *hrr = lhrr;
1003
  return CURLE_OK;
1004
err:
1005
  Curl_httpsrr_destroy(lhrr);
1006
  return result;
1007
}
1008
1009
#endif /* USE_HTTPSRR */
1010
1011
/* called from multi when a sub transfer, e.g. doh probe, is done.
1012
 * Parse the response and set the results in the `async` context
1013
 * of master, using the id from the probe's CURL_EZM_DOH_PROBE
1014
 * meta data. */
1015
static void doh_probe_done(struct Curl_easy *doh,
1016
                           struct Curl_easy *master, CURLcode result)
1017
0
{
1018
0
  struct Curl_resolv_async *async = NULL;
1019
0
  struct doh_probes *dohp = NULL;
1020
0
  struct doh_request *doh_req = NULL;
1021
0
  struct Curl_addrinfo **pdest_ai;
1022
0
  struct dohentry de;
1023
0
  int slot, httpcode;
1024
1025
0
  de_init(&de);
1026
0
  doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
1027
0
  if(!doh_req) {
1028
    /* transfer `doh` is not a DoH probe. */
1029
0
    DEBUGASSERT(0);
1030
0
    goto out;
1031
0
  }
1032
1033
0
  async = Curl_async_get(master, doh_req->resolv_id);
1034
0
  if(!async) {
1035
0
    CURL_TRC_DNS(master, "[%u] ignoring outdated DoH response",
1036
0
                 doh_req->resolv_id);
1037
0
    goto out;
1038
0
  }
1039
0
  dohp = async->doh;
1040
1041
0
  for(slot = 0; slot < DOH_SLOT_COUNT; ++slot) {
1042
0
    if(dohp->probe_mid[slot] == doh->mid)
1043
0
      break;
1044
0
  }
1045
  /* We really should have found the slot where to store the response */
1046
0
  if(slot >= DOH_SLOT_COUNT) {
1047
0
    failf(master, "DoH: unknown sub request done");
1048
0
    DEBUGASSERT(0);
1049
0
    goto out;
1050
0
  }
1051
1052
0
  async->queries_ongoing--;
1053
0
  dohp = async->doh;
1054
0
  httpcode = doh->info.httpcode;
1055
0
  switch(slot) {
1056
0
  case DOH_SLOT_IPV4:
1057
0
    async->dns_responses |= CURL_DNSQ_A;
1058
0
    break;
1059
0
#ifdef USE_IPV6
1060
0
  case DOH_SLOT_IPV6:
1061
0
    async->dns_responses |= CURL_DNSQ_AAAA;
1062
0
    break;
1063
0
#endif
1064
#ifdef USE_HTTPSRR
1065
  case DOH_SLOT_HTTPS_RR:
1066
    async->dns_responses |= CURL_DNSQ_HTTPS;
1067
    break;
1068
#endif
1069
0
  default:
1070
0
    DEBUGASSERT(0);
1071
0
    break;
1072
0
  }
1073
1074
0
  if(result) {
1075
0
    dohp->probe_rc[slot] = DOH_HTTP_FAILED;
1076
0
    infof(doh, "[DoH] [%s] error: %s",
1077
0
          doh_type2name(doh_req->dnstype), curl_easy_strerror(result));
1078
0
    goto out;
1079
0
  }
1080
0
  else if((httpcode < 200) || (httpcode >= 300)) {
1081
0
    dohp->probe_rc[slot] = DOH_HTTP_FAILED;
1082
0
    infof(doh, "[DoH] [%s] error: HTTP status %d",
1083
0
          doh_type2name(doh_req->dnstype), httpcode);
1084
0
    goto out;
1085
0
  }
1086
1087
0
  dohp->probe_rc[slot] = doh_resp_decode(curlx_dyn_uptr(&doh_req->resp_body),
1088
0
                                         curlx_dyn_len(&doh_req->resp_body),
1089
0
                                         doh_req->dnstype, &de);
1090
0
  if(dohp->probe_rc[slot]) {
1091
#ifdef USE_HTTPSRR
1092
    if((dohp->probe_rc[slot] == DOH_NO_CONTENT) &&
1093
       (doh_req->dnstype == CURL_DNS_TYPE_HTTPS)) {
1094
      dohp->probe_rc[slot] = DOH_DNS_NXDOMAIN;
1095
    }
1096
#endif
1097
0
    infof(doh, "[DoH] [%s] error decoding response: %s",
1098
0
          doh_type2name(doh_req->dnstype),
1099
0
          doh_strerror(dohp->probe_rc[slot]));
1100
0
    goto out;
1101
0
  }
1102
1103
0
  if(doh_req->dnstype == CURL_DNS_TYPE_A)
1104
0
    pdest_ai = &async->ai_A;
1105
0
  else if(doh_req->dnstype == CURL_DNS_TYPE_AAAA)
1106
0
    pdest_ai = &async->ai_AAAA;
1107
0
  else
1108
0
    pdest_ai = NULL;
1109
1110
0
  if(pdest_ai && de.numaddr) {
1111
0
    if(*pdest_ai) {
1112
0
      Curl_freeaddrinfo(*pdest_ai);
1113
0
      *pdest_ai = NULL;
1114
0
    }
1115
0
    result = doh2ai(&de, async->peer->hostname, async->peer->port, pdest_ai);
1116
0
    if(result) { /* hard failure on our side, fail completely */
1117
0
      infof(doh, "[DoH] [%s] error creating addrinfo: %s",
1118
0
            doh_type2name(doh_req->dnstype), curl_easy_strerror(result));
1119
0
      dohp->probe_rc[slot] = DOH_OOM;
1120
0
      async->result = result;
1121
0
    }
1122
0
  }
1123
#ifdef USE_HTTPSRR
1124
  else if((doh_req->dnstype == CURL_DNS_TYPE_HTTPS) && de.numhttps_rrs) {
1125
    CURL_TRC_DNS(doh, "[HTTPS] got %d records", de.numhttps_rrs);
1126
    result = doh_resp_decode_httpsrr(doh, de.https_rrs->val,
1127
                                     de.https_rrs->len, &async->httpsrr);
1128
    if(result) {
1129
      dohp->probe_rc[slot] = DOH_HTTP_FAILED;
1130
      infof(doh, "[DoH] error decoding HTTPS RR: %s",
1131
            curl_easy_strerror(result));
1132
      goto out;
1133
    }
1134
  }
1135
#endif /* USE_HTTPSRR */
1136
1137
  /* DoH request complete, run master to act on results */
1138
0
  infof(doh, "DoH request complete, %u to go", async->queries_ongoing);
1139
1140
0
out:
1141
0
  Curl_multi_mark_dirty(master);
1142
0
  de_cleanup(&de);
1143
0
  Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
1144
0
}
1145
1146
CURLcode Curl_doh_take_result(struct Curl_easy *data,
1147
                              struct Curl_resolv_async *async,
1148
                              struct Curl_dns_entry **pdns)
1149
0
{
1150
0
  struct doh_probes *dohp = async->doh;
1151
0
  CURLcode result = CURLE_OK;
1152
1153
0
  *pdns = NULL; /* defaults to no response */
1154
0
  if(!dohp)
1155
0
    return CURLE_OUT_OF_MEMORY;
1156
1157
0
  async->negative_answer = FALSE;
1158
0
  if(async->result) {
1159
0
    result = async->result;
1160
0
    goto out;
1161
0
  }
1162
1163
0
  if(CURL_DNSQ_IS_ADDR(async->dns_queries) &&
1164
0
     dohp->probe_mid[DOH_SLOT_IPV4] == UINT32_MAX &&
1165
0
     dohp->probe_mid[DOH_SLOT_IPV6] == UINT32_MAX) {
1166
0
    failf(data, "Could not DoH-resolve: %s", async->peer->hostname);
1167
0
    return async->for_proxy ?
1168
0
      CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
1169
0
  }
1170
0
  else if(!async->queries_ongoing) {
1171
0
    struct Curl_dns_entry *dns = NULL;
1172
0
    bool negative = TRUE;
1173
0
    int slot;
1174
1175
    /* remove DoH handles from multi handle and close them */
1176
0
    doh_close(data, async);
1177
    /* parse the responses, create the struct and return it! */
1178
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1179
      /* Failing without an NXDOMAIN answer - a SERVFAIL-class rcode or
1180
         an undecodable response - says nothing about the name. Such a
1181
         failure must not be cached as a negative entry. */
1182
0
      if(dohp->probe_rc[slot] && (dohp->probe_rc[slot] != DOH_DNS_NXDOMAIN))
1183
0
        negative = FALSE;
1184
0
    } /* next slot */
1185
1186
0
    if(async->ai_A || async->ai_AAAA) {
1187
0
      dns = Curl_dnsc_mk_addr2(
1188
0
        data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer);
1189
0
      if(!dns) {
1190
0
        result = CURLE_OUT_OF_MEMORY;
1191
0
        goto out;
1192
0
      }
1193
0
    }
1194
#ifdef USE_HTTPSRR
1195
    else if((async->dns_queries & CURL_DNSQ_HTTPS) &&
1196
            !dohp->probe_rc[DOH_SLOT_HTTPS_RR]) {
1197
      Curl_httpsrr_trace(data, async->httpsrr);
1198
      dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer);
1199
      if(!dns) {
1200
        result = CURLE_OUT_OF_MEMORY;
1201
        goto out;
1202
      }
1203
    }
1204
#endif /* USE_HTTPSRR */
1205
0
    else {
1206
      /* every query failed. Only NXDOMAIN answers for all of them
1207
         make this a negative answer, eligible for caching. */
1208
0
      async->negative_answer = negative;
1209
0
      result = async->for_proxy ?
1210
0
        CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
1211
0
    }
1212
1213
    /* and add the entry to the cache */
1214
0
    if(dns)
1215
0
      result = Curl_dnscache_add(data, dns);
1216
0
    *pdns = dns;
1217
0
  } /* !async->queries_ongoing */
1218
0
  else
1219
    /* wait for pending DoH transactions to complete */
1220
0
    return CURLE_AGAIN;
1221
1222
0
out:
1223
0
  Curl_doh_cleanup(data, async);
1224
0
  return result;
1225
0
}
1226
1227
static void doh_close(struct Curl_easy *data,
1228
                      struct Curl_resolv_async *async)
1229
0
{
1230
0
  struct doh_probes *doh = async ? async->doh : NULL;
1231
0
  if(doh && data->multi) {
1232
0
    struct Curl_easy *probe_data;
1233
0
    uint32_t mid;
1234
0
    size_t slot;
1235
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1236
0
      mid = doh->probe_mid[slot];
1237
0
      if(mid == UINT32_MAX)
1238
0
        continue;
1239
0
      doh->probe_mid[slot] = UINT32_MAX;
1240
      /* should have been called before data is removed from multi handle */
1241
0
      DEBUGASSERT(data->multi);
1242
0
      probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) : NULL;
1243
0
      if(!probe_data) {
1244
0
        DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!", mid));
1245
0
        continue;
1246
0
      }
1247
0
      probe_data->sub_xfer_done = NULL; /* No longer interested in result */
1248
      /* data->multi might already be reset at this time */
1249
0
      Curl_multi_remove_handle(data->multi, probe_data);
1250
0
      Curl_close(&probe_data);
1251
0
    }
1252
0
    CURL_TRC_DNS(data, "[DoH] probe done");
1253
0
  }
1254
0
}
1255
1256
void Curl_doh_cleanup(struct Curl_easy *data,
1257
                      struct Curl_resolv_async *async)
1258
0
{
1259
0
  struct doh_probes *dohp = async->doh;
1260
0
  if(dohp) {
1261
0
    doh_close(data, async);
1262
    curlx_safefree(async->doh);
1263
0
  }
1264
0
}
1265
1266
#endif /* CURL_DISABLE_DOH */