Coverage Report

Created: 2026-07-16 06:34

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