Coverage Report

Created: 2026-06-30 08:33

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