Coverage Report

Created: 2026-09-14 07:12

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