Coverage Report

Created: 2025-10-10 06:31

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