Coverage Report

Created: 2026-01-17 06:25

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