Coverage Report

Created: 2026-03-07 07:03

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