Coverage Report

Created: 2026-01-25 06:18

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