Coverage Report

Created: 2025-12-14 06:23

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