Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmcurl/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
0
#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
0
{
78
0
  const size_t hostlen = strlen(host);
79
0
  unsigned char *orig = dnsp;
80
0
  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
0
  size_t expected_len;
105
0
  DEBUGASSERT(hostlen);
106
0
  expected_len = 12 + 1 + hostlen + 4;
107
0
  if(host[hostlen - 1] != '.')
108
0
    expected_len++;
109
110
0
  if(expected_len > DOH_MAX_DNSREQ_SIZE)
111
0
    return DOH_DNS_NAME_TOO_LONG;
112
113
0
  if(len < expected_len)
114
0
    return DOH_TOO_SMALL_BUFFER;
115
116
0
  *dnsp++ = 0; /* 16-bit id */
117
0
  *dnsp++ = 0;
118
0
  *dnsp++ = 0x01; /* |QR|   Opcode  |AA|TC|RD| Set the RD bit */
119
0
  *dnsp++ = '\0'; /* |RA|   Z    |   RCODE   |                */
120
0
  *dnsp++ = '\0';
121
0
  *dnsp++ = 1;    /* QDCOUNT (number of entries in the question section) */
122
0
  *dnsp++ = '\0';
123
0
  *dnsp++ = '\0'; /* ANCOUNT */
124
0
  *dnsp++ = '\0';
125
0
  *dnsp++ = '\0'; /* NSCOUNT */
126
0
  *dnsp++ = '\0';
127
0
  *dnsp++ = '\0'; /* ARCOUNT */
128
129
  /* encode each label and store it in the QNAME */
130
0
  while(*hostp) {
131
0
    size_t labellen;
132
0
    const char *dot = strchr(hostp, '.');
133
0
    if(dot)
134
0
      labellen = dot - hostp;
135
0
    else
136
0
      labellen = strlen(hostp);
137
0
    if((labellen > 63) || (!labellen)) {
138
      /* label is too long or too short, error out */
139
0
      *olen = 0;
140
0
      return DOH_DNS_BAD_LABEL;
141
0
    }
142
    /* label is non-empty, process it */
143
0
    *dnsp++ = (unsigned char)labellen;
144
0
    memcpy(dnsp, hostp, labellen);
145
0
    dnsp += labellen;
146
0
    hostp += labellen;
147
    /* advance past dot, but only if there is one */
148
0
    if(dot)
149
0
      hostp++;
150
0
  } /* next label */
151
152
0
  *dnsp++ = 0; /* append zero-length label for root */
153
154
  /* There are assigned TYPE codes beyond 255: use range [1..65535] */
155
0
  *dnsp++ = (unsigned char)(255 & (dnstype >> 8)); /* upper 8-bit TYPE */
156
0
  *dnsp++ = (unsigned char)(255 & dnstype);        /* lower 8-bit TYPE */
157
158
0
  *dnsp++ = '\0'; /* upper 8-bit CLASS */
159
0
  *dnsp++ = DNS_CLASS_IN; /* IN - "the Internet" */
160
161
0
  *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
0
  DEBUGASSERT(*olen == expected_len);
166
0
  return DOH_OK;
167
0
}
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
0
{
214
0
  struct doh_probes *dohp = data->state.async.doh;
215
0
  DEBUGASSERT(dohp);
216
0
  if(dohp) {
217
0
    struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
218
0
    int i;
219
220
0
    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
221
0
      if(dohp->probe_resp[i].probe_mid == doh->mid)
222
0
        break;
223
0
    }
224
0
    if(i >= DOH_SLOT_COUNT) {
225
0
      failf(data, "unknown sub request done");
226
0
      return;
227
0
    }
228
229
0
    dohp->pending--;
230
0
    infof(doh, "a DoH request is completed, %u to go", dohp->pending);
231
0
    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
0
    DEBUGASSERT(doh_req || result);
235
0
    if(doh_req) {
236
0
      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
0
      Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
244
0
    }
245
246
0
    if(result)
247
0
      infof(doh, "DoH request %s", curl_easy_strerror(result));
248
249
0
    if(!dohp->pending) {
250
      /* DoH completed, run the transfer picking up the results */
251
0
      Curl_multi_mark_dirty(data);
252
0
    }
253
0
  }
254
0
}
255
256
static void doh_probe_dtor(void *key, size_t klen, void *e)
257
0
{
258
0
  (void)key;
259
0
  (void)klen;
260
0
  if(e) {
261
0
    struct doh_request *doh_req = e;
262
0
    curl_slist_free_all(doh_req->req_hds);
263
0
    curlx_dyn_free(&doh_req->resp_body);
264
0
    curlx_free(e);
265
0
  }
266
0
}
267
268
#define ERROR_CHECK_SETOPT(x, y)                        \
269
0
  do {                                                  \
270
0
    result = curl_easy_setopt((CURL *)doh, x, y);       \
271
0
    if(result &&                                        \
272
0
       result != CURLE_NOT_BUILT_IN &&                  \
273
0
       result != CURLE_UNKNOWN_OPTION)                  \
274
0
      goto error;                                       \
275
0
  } 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
0
{
283
0
  struct Curl_easy *doh = NULL;
284
0
  CURLcode result = CURLE_OK;
285
0
  timediff_t timeout_ms;
286
0
  struct doh_request *doh_req;
287
0
  DOHcode d;
288
289
0
  *pmid = UINT32_MAX;
290
291
0
  doh_req = curlx_calloc(1, sizeof(*doh_req));
292
0
  if(!doh_req)
293
0
    return CURLE_OUT_OF_MEMORY;
294
0
  doh_req->dnstype = dnstype;
295
0
  curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE);
296
297
0
  d = doh_req_encode(host, dnstype, doh_req->req_body,
298
0
                     sizeof(doh_req->req_body),
299
0
                     &doh_req->req_body_len);
300
0
  if(d) {
301
0
    failf(data, "Failed to encode DoH packet [%d]", d);
302
0
    result = CURLE_OUT_OF_MEMORY;
303
0
    goto error;
304
0
  }
305
306
0
  timeout_ms = Curl_timeleft_ms(data);
307
0
  if(timeout_ms < 0) {
308
0
    result = CURLE_OPERATION_TIMEDOUT;
309
0
    goto error;
310
0
  }
311
312
0
  doh_req->req_hds =
313
0
    curl_slist_append(NULL, "Content-Type: application/dns-message");
314
0
  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
0
  result = Curl_open(&doh);
321
0
  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
0
  VERBOSE(doh->state.feat = &Curl_trc_feat_dns);
327
0
  ERROR_CHECK_SETOPT(CURLOPT_URL, url);
328
0
  ERROR_CHECK_SETOPT(CURLOPT_DEFAULT_PROTOCOL, "https");
329
0
  ERROR_CHECK_SETOPT(CURLOPT_WRITEFUNCTION, doh_probe_write_cb);
330
0
  ERROR_CHECK_SETOPT(CURLOPT_WRITEDATA, doh);
331
0
  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, doh_req->req_body);
332
0
  ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)doh_req->req_body_len);
333
0
  ERROR_CHECK_SETOPT(CURLOPT_HTTPHEADER, doh_req->req_hds);
334
0
#ifdef USE_HTTP2
335
0
  ERROR_CHECK_SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
336
0
  ERROR_CHECK_SETOPT(CURLOPT_PIPEWAIT, 1L);
337
0
#endif
338
0
#ifndef DEBUGBUILD
339
  /* enforce HTTPS if not debug */
340
0
  ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
341
#else
342
  /* in debug mode, also allow http */
343
  ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
344
#endif
345
0
  ERROR_CHECK_SETOPT(CURLOPT_TIMEOUT_MS, (long)timeout_ms);
346
0
  ERROR_CHECK_SETOPT(CURLOPT_SHARE, (CURLSH *)data->share);
347
0
  if(data->set.err && data->set.err != stderr)
348
0
    ERROR_CHECK_SETOPT(CURLOPT_STDERR, data->set.err);
349
0
  if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns))
350
0
    ERROR_CHECK_SETOPT(CURLOPT_VERBOSE, 1L);
351
0
  if(data->set.no_signal)
352
0
    ERROR_CHECK_SETOPT(CURLOPT_NOSIGNAL, 1L);
353
354
0
  ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYHOST,
355
0
                     data->set.doh_verifyhost ? 2L : 0L);
356
0
  ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYPEER,
357
0
                     data->set.doh_verifypeer ? 1L : 0L);
358
0
  ERROR_CHECK_SETOPT(CURLOPT_SSL_VERIFYSTATUS,
359
0
                     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
0
  doh->set.ssl.custom_cafile = data->set.ssl.custom_cafile;
370
0
  doh->set.ssl.custom_capath = data->set.ssl.custom_capath;
371
0
  doh->set.ssl.custom_cablob = data->set.ssl.custom_cablob;
372
0
  if(data->set.str[STRING_SSL_CAFILE]) {
373
0
    ERROR_CHECK_SETOPT(CURLOPT_CAINFO, data->set.str[STRING_SSL_CAFILE]);
374
0
  }
375
0
  if(data->set.blobs[BLOB_CAINFO]) {
376
0
    ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB, data->set.blobs[BLOB_CAINFO]);
377
0
  }
378
0
  if(data->set.str[STRING_SSL_CAPATH]) {
379
0
    ERROR_CHECK_SETOPT(CURLOPT_CAPATH, data->set.str[STRING_SSL_CAPATH]);
380
0
  }
381
0
  if(data->set.str[STRING_SSL_CRLFILE]) {
382
0
    ERROR_CHECK_SETOPT(CURLOPT_CRLFILE, data->set.str[STRING_SSL_CRLFILE]);
383
0
  }
384
0
  if(data->set.ssl.certinfo)
385
0
    ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L);
386
0
  if(data->set.ssl.fsslctx)
387
0
    ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx);
388
0
  if(data->set.ssl.fsslctxp)
389
0
    ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp);
390
0
  if(data->set.fdebug)
391
0
    ERROR_CHECK_SETOPT(CURLOPT_DEBUGFUNCTION, data->set.fdebug);
392
0
  if(data->set.debugdata)
393
0
    ERROR_CHECK_SETOPT(CURLOPT_DEBUGDATA, data->set.debugdata);
394
0
  if(data->set.str[STRING_SSL_EC_CURVES]) {
395
0
    ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES,
396
0
                       data->set.str[STRING_SSL_EC_CURVES]);
397
0
  }
398
399
0
  (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS,
400
0
                         (long)data->set.ssl.primary.ssl_options);
401
402
0
  doh->state.internal = TRUE;
403
0
  doh->master_mid = data->mid; /* master transfer of this one */
404
405
0
  result = Curl_meta_set(doh, CURL_EZM_DOH_PROBE, doh_req, doh_probe_dtor);
406
0
  doh_req = NULL; /* call took ownership */
407
0
  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
0
  DEBUGASSERT(!doh->set.private_data);
415
416
0
  if(curl_multi_add_handle(multi, doh))
417
0
    goto error;
418
419
0
  *pmid = doh->mid;
420
0
  return CURLE_OK;
421
422
0
error:
423
0
  Curl_close(&doh);
424
0
  if(doh_req)
425
0
    doh_probe_dtor(NULL, 0, doh_req);
426
0
  return result;
427
0
}
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
0
{
437
0
  CURLcode result = CURLE_OK;
438
0
  struct doh_probes *dohp = NULL;
439
0
  struct connectdata *conn = data->conn;
440
0
  size_t i;
441
442
0
  DEBUGASSERT(conn);
443
0
  DEBUGASSERT(!data->state.async.doh);
444
0
  DEBUGASSERT(hostname && hostname[0]);
445
0
  if(data->state.async.doh)
446
0
    Curl_doh_cleanup(data);
447
448
0
  data->state.async.done = FALSE;
449
0
  data->state.async.port = port;
450
0
  data->state.async.ip_version = ip_version;
451
0
  data->state.async.hostname = curlx_strdup(hostname);
452
0
  if(!data->state.async.hostname)
453
0
    return CURLE_OUT_OF_MEMORY;
454
455
  /* start clean, consider allocating this struct on demand */
456
0
  data->state.async.doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
457
0
  if(!dohp)
458
0
    return CURLE_OUT_OF_MEMORY;
459
460
0
  for(i = 0; i < DOH_SLOT_COUNT; ++i) {
461
0
    dohp->probe_resp[i].probe_mid = UINT32_MAX;
462
0
    curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE);
463
0
  }
464
465
0
  conn->bits.doh = TRUE;
466
0
  dohp->host = data->state.async.hostname;
467
0
  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
0
  data->sub_xfer_done = doh_probe_done;
471
472
  /* create IPv4 DoH request */
473
0
  result = doh_probe_run(data, CURL_DNS_TYPE_A,
474
0
                         hostname, data->set.str[STRING_DOH],
475
0
                         data->multi,
476
0
                         &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
477
0
  if(result)
478
0
    goto error;
479
0
  dohp->pending++;
480
481
0
#ifdef USE_IPV6
482
0
  if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
483
    /* create IPv6 DoH request */
484
0
    result = doh_probe_run(data, CURL_DNS_TYPE_AAAA,
485
0
                           hostname, data->set.str[STRING_DOH],
486
0
                           data->multi,
487
0
                           &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid);
488
0
    if(result)
489
0
      goto error;
490
0
    dohp->pending++;
491
0
  }
492
0
#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
0
  return CURLE_OK;
514
515
0
error:
516
0
  Curl_doh_cleanup(data);
517
0
  return result;
518
0
}
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
0
{
703
0
  int i;
704
0
  memset(de, 0, sizeof(*de));
705
0
  de->ttl = INT_MAX;
706
0
  for(i = 0; i < DOH_MAX_CNAME; i++)
707
0
    curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME);
708
0
}
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
0
{
917
0
  struct Curl_addrinfo *ai;
918
0
  struct Curl_addrinfo *prevai = NULL;
919
0
  struct Curl_addrinfo *firstai = NULL;
920
0
  struct sockaddr_in *addr;
921
0
#ifdef USE_IPV6
922
0
  struct sockaddr_in6 *addr6;
923
0
#endif
924
0
  CURLcode result = CURLE_OK;
925
0
  int i;
926
0
  size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
927
928
0
  DEBUGASSERT(de);
929
930
0
  if(!de->numaddr)
931
0
    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
0
{
1029
0
  int i = 0;
1030
0
  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
0
}
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
0
{
1200
0
  CURLcode result = CURLE_OK;
1201
0
  struct doh_probes *dohp = data->state.async.doh;
1202
0
  struct dohentry de;
1203
0
  *dnsp = NULL; /* defaults to no response */
1204
0
  if(!dohp)
1205
0
    return CURLE_OUT_OF_MEMORY;
1206
1207
0
  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
0
  else if(!dohp->pending) {
1214
0
    DOHcode rc[DOH_SLOT_COUNT];
1215
0
    int slot;
1216
1217
    /* Clear any result the might still be there */
1218
0
    Curl_resolv_unlink(data, &data->state.async.dns);
1219
1220
0
    memset(rc, 0, sizeof(rc));
1221
    /* remove DoH handles from multi handle and close them */
1222
0
    Curl_doh_close(data);
1223
    /* parse the responses, create the struct and return it! */
1224
0
    de_init(&de);
1225
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1226
0
      struct doh_response *p = &dohp->probe_resp[slot];
1227
0
      if(!p->dnstype)
1228
0
        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
0
    result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
1239
0
    if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
1240
      /* we have an address, of one kind or other */
1241
0
      struct Curl_dns_entry *dns;
1242
0
      struct Curl_addrinfo *ai;
1243
1244
0
      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
0
      result = doh2ai(&de, dohp->host, dohp->port, &ai);
1250
0
      if(result)
1251
0
        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
0
  else
1286
    /* wait for pending DoH transactions to complete */
1287
0
    return CURLE_OK;
1288
1289
0
error:
1290
0
  de_cleanup(&de);
1291
0
  Curl_doh_cleanup(data);
1292
0
  return result;
1293
0
}
1294
1295
void Curl_doh_close(struct Curl_easy *data)
1296
0
{
1297
0
  struct doh_probes *doh = data->state.async.doh;
1298
0
  if(doh && data->multi) {
1299
0
    struct Curl_easy *probe_data;
1300
0
    uint32_t mid;
1301
0
    size_t slot;
1302
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1303
0
      mid = doh->probe_resp[slot].probe_mid;
1304
0
      if(mid == UINT32_MAX)
1305
0
        continue;
1306
0
      doh->probe_resp[slot].probe_mid = UINT32_MAX;
1307
      /* should have been called before data is removed from multi handle */
1308
0
      DEBUGASSERT(data->multi);
1309
0
      probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) : NULL;
1310
0
      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
0
      curl_multi_remove_handle(data->multi, probe_data);
1317
0
      Curl_close(&probe_data);
1318
0
    }
1319
0
    data->sub_xfer_done = NULL;
1320
0
  }
1321
0
}
1322
1323
void Curl_doh_cleanup(struct Curl_easy *data)
1324
0
{
1325
0
  struct doh_probes *dohp = data->state.async.doh;
1326
0
  if(dohp) {
1327
0
    int i;
1328
0
    Curl_doh_close(data);
1329
0
    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
1330
0
      curlx_dyn_free(&dohp->probe_resp[i].body);
1331
0
    }
1332
    Curl_safefree(data->state.async.doh);
1333
0
  }
1334
0
}
1335
1336
#endif /* CURL_DISABLE_DOH */