Coverage Report

Created: 2025-10-30 06:17

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
  DEBUGASSERT(hostname && hostname[0]);
462
0
  if(data->state.async.doh)
463
0
    Curl_doh_cleanup(data);
464
465
0
  data->state.async.done = FALSE;
466
0
  data->state.async.port = port;
467
0
  data->state.async.ip_version = ip_version;
468
0
  data->state.async.hostname = strdup(hostname);
469
0
  if(!data->state.async.hostname)
470
0
    return NULL;
471
472
  /* start clean, consider allocating this struct on demand */
473
0
  data->state.async.doh = dohp = calloc(1, sizeof(struct doh_probes));
474
0
  if(!dohp)
475
0
    return NULL;
476
477
0
  for(i = 0; i < DOH_SLOT_COUNT; ++i) {
478
0
    dohp->probe_resp[i].probe_mid = UINT_MAX;
479
0
    curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE);
480
0
  }
481
482
0
  conn->bits.doh = TRUE;
483
0
  dohp->host = data->state.async.hostname;
484
0
  dohp->port = data->state.async.port;
485
  /* We are making sub easy handles and want to be called back when
486
   * one is done. */
487
0
  data->sub_xfer_done = doh_probe_done;
488
489
  /* create IPv4 DoH request */
490
0
  result = doh_probe_run(data, CURL_DNS_TYPE_A,
491
0
                         hostname, data->set.str[STRING_DOH],
492
0
                         data->multi,
493
0
                         &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
494
0
  if(result)
495
0
    goto error;
496
0
  dohp->pending++;
497
498
0
#ifdef USE_IPV6
499
0
  if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
500
    /* create IPv6 DoH request */
501
0
    result = doh_probe_run(data, CURL_DNS_TYPE_AAAA,
502
0
                           hostname, data->set.str[STRING_DOH],
503
0
                           data->multi,
504
0
                           &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid);
505
0
    if(result)
506
0
      goto error;
507
0
    dohp->pending++;
508
0
  }
509
0
#endif
510
511
#ifdef USE_HTTPSRR
512
  if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
513
    /* Only use HTTPS RR for HTTP(S) transfers */
514
    char *qname = NULL;
515
    if(port != PORT_HTTPS) {
516
      qname = curl_maprintf("_%d._https.%s", port, hostname);
517
      if(!qname)
518
        goto error;
519
    }
520
    result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS,
521
                           qname ? qname : hostname, data->set.str[STRING_DOH],
522
                           data->multi,
523
                           &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid);
524
    free(qname);
525
    if(result)
526
      goto error;
527
    dohp->pending++;
528
  }
529
#endif
530
0
  *waitp = TRUE; /* this never returns synchronously */
531
0
  return NULL;
532
533
0
error:
534
0
  Curl_doh_cleanup(data);
535
0
  return NULL;
536
0
}
537
538
static DOHcode doh_skipqname(const unsigned char *doh, size_t dohlen,
539
                             unsigned int *indexp)
540
0
{
541
0
  unsigned char length;
542
0
  do {
543
0
    if(dohlen < (*indexp + 1))
544
0
      return DOH_DNS_OUT_OF_RANGE;
545
0
    length = doh[*indexp];
546
0
    if((length & 0xc0) == 0xc0) {
547
      /* name pointer, advance over it and be done */
548
0
      if(dohlen < (*indexp + 2))
549
0
        return DOH_DNS_OUT_OF_RANGE;
550
0
      *indexp += 2;
551
0
      break;
552
0
    }
553
0
    if(length & 0xc0)
554
0
      return DOH_DNS_BAD_LABEL;
555
0
    if(dohlen < (*indexp + 1 + length))
556
0
      return DOH_DNS_OUT_OF_RANGE;
557
0
    *indexp += (unsigned int)(1 + length);
558
0
  } while(length);
559
0
  return DOH_OK;
560
0
}
561
562
static unsigned short doh_get16bit(const unsigned char *doh,
563
                                   unsigned int index)
564
0
{
565
0
  return (unsigned short)((doh[index] << 8) | doh[index + 1]);
566
0
}
567
568
static unsigned int doh_get32bit(const unsigned char *doh, unsigned int index)
569
0
{
570
  /* make clang and gcc optimize this to bswap by incrementing
571
     the pointer first. */
572
0
  doh += index;
573
574
  /* avoid undefined behavior by casting to unsigned before shifting
575
     24 bits, possibly into the sign bit. codegen is same, but
576
     ub sanitizer will not be upset */
577
0
  return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) |
578
0
         ((unsigned)doh[2] << 8) | doh[3];
579
0
}
580
581
static void doh_store_a(const unsigned char *doh, int index,
582
                        struct dohentry *d)
583
0
{
584
  /* silently ignore addresses over the limit */
585
0
  if(d->numaddr < DOH_MAX_ADDR) {
586
0
    struct dohaddr *a = &d->addr[d->numaddr];
587
0
    a->type = CURL_DNS_TYPE_A;
588
0
    memcpy(&a->ip.v4, &doh[index], 4);
589
0
    d->numaddr++;
590
0
  }
591
0
}
592
593
static void doh_store_aaaa(const unsigned char *doh, int index,
594
                           struct dohentry *d)
595
0
{
596
  /* silently ignore addresses over the limit */
597
0
  if(d->numaddr < DOH_MAX_ADDR) {
598
0
    struct dohaddr *a = &d->addr[d->numaddr];
599
0
    a->type = CURL_DNS_TYPE_AAAA;
600
0
    memcpy(&a->ip.v6, &doh[index], 16);
601
0
    d->numaddr++;
602
0
  }
603
0
}
604
605
#ifdef USE_HTTPSRR
606
static DOHcode doh_store_https(const unsigned char *doh, int index,
607
                               struct dohentry *d, uint16_t len)
608
{
609
  /* silently ignore RRs over the limit */
610
  if(d->numhttps_rrs < DOH_MAX_HTTPS) {
611
    struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs];
612
    h->val = Curl_memdup(&doh[index], len);
613
    if(!h->val)
614
      return DOH_OUT_OF_MEM;
615
    h->len = len;
616
    d->numhttps_rrs++;
617
  }
618
  return DOH_OK;
619
}
620
#endif
621
622
static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen,
623
                               unsigned int index, struct dohentry *d)
624
0
{
625
0
  struct dynbuf *c;
626
0
  unsigned int loop = 128; /* a valid DNS name can never loop this much */
627
0
  unsigned char length;
628
629
0
  if(d->numcname == DOH_MAX_CNAME)
630
0
    return DOH_OK; /* skip! */
631
632
0
  c = &d->cname[d->numcname++];
633
0
  do {
634
0
    if(index >= dohlen)
635
0
      return DOH_DNS_OUT_OF_RANGE;
636
0
    length = doh[index];
637
0
    if((length & 0xc0) == 0xc0) {
638
0
      int newpos;
639
      /* name pointer, get the new offset (14 bits) */
640
0
      if((index + 1) >= dohlen)
641
0
        return DOH_DNS_OUT_OF_RANGE;
642
643
      /* move to the new index */
644
0
      newpos = (length & 0x3f) << 8 | doh[index + 1];
645
0
      index = (unsigned int)newpos;
646
0
      continue;
647
0
    }
648
0
    else if(length & 0xc0)
649
0
      return DOH_DNS_BAD_LABEL; /* bad input */
650
0
    else
651
0
      index++;
652
653
0
    if(length) {
654
0
      if(curlx_dyn_len(c)) {
655
0
        if(curlx_dyn_addn(c, STRCONST(".")))
656
0
          return DOH_OUT_OF_MEM;
657
0
      }
658
0
      if((index + length) > dohlen)
659
0
        return DOH_DNS_BAD_LABEL;
660
661
0
      if(curlx_dyn_addn(c, &doh[index], length))
662
0
        return DOH_OUT_OF_MEM;
663
0
      index += length;
664
0
    }
665
0
  } while(length && --loop);
666
667
0
  if(!loop)
668
0
    return DOH_DNS_LABEL_LOOP;
669
0
  return DOH_OK;
670
0
}
671
672
static DOHcode doh_rdata(const unsigned char *doh,
673
                         size_t dohlen,
674
                         unsigned short rdlength,
675
                         unsigned short type,
676
                         int index,
677
                         struct dohentry *d)
678
0
{
679
  /* RDATA
680
     - A (TYPE 1):  4 bytes
681
     - AAAA (TYPE 28): 16 bytes
682
     - NS (TYPE 2): N bytes
683
     - HTTPS (TYPE 65): N bytes */
684
0
  DOHcode rc;
685
686
0
  switch(type) {
687
0
  case CURL_DNS_TYPE_A:
688
0
    if(rdlength != 4)
689
0
      return DOH_DNS_RDATA_LEN;
690
0
    doh_store_a(doh, index, d);
691
0
    break;
692
0
  case CURL_DNS_TYPE_AAAA:
693
0
    if(rdlength != 16)
694
0
      return DOH_DNS_RDATA_LEN;
695
0
    doh_store_aaaa(doh, index, d);
696
0
    break;
697
#ifdef USE_HTTPSRR
698
  case CURL_DNS_TYPE_HTTPS:
699
    rc = doh_store_https(doh, index, d, rdlength);
700
    if(rc)
701
      return rc;
702
    break;
703
#endif
704
0
  case CURL_DNS_TYPE_CNAME:
705
0
    rc = doh_store_cname(doh, dohlen, (unsigned int)index, d);
706
0
    if(rc)
707
0
      return rc;
708
0
    break;
709
0
  case CURL_DNS_TYPE_DNAME:
710
    /* explicit for clarity; just skip; rely on synthesized CNAME  */
711
0
    break;
712
0
  default:
713
    /* unsupported type, just skip it */
714
0
    break;
715
0
  }
716
0
  return DOH_OK;
717
0
}
718
719
UNITTEST void de_init(struct dohentry *de)
720
0
{
721
0
  int i;
722
0
  memset(de, 0, sizeof(*de));
723
0
  de->ttl = INT_MAX;
724
0
  for(i = 0; i < DOH_MAX_CNAME; i++)
725
0
    curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME);
726
0
}
727
728
729
UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
730
                                 size_t dohlen,
731
                                 DNStype dnstype,
732
                                 struct dohentry *d)
733
0
{
734
0
  unsigned char rcode;
735
0
  unsigned short qdcount;
736
0
  unsigned short ancount;
737
0
  unsigned short type = 0;
738
0
  unsigned short rdlength;
739
0
  unsigned short nscount;
740
0
  unsigned short arcount;
741
0
  unsigned int index = 12;
742
0
  DOHcode rc;
743
744
0
  if(dohlen < 12)
745
0
    return DOH_TOO_SMALL_BUFFER; /* too small */
746
0
  if(!doh || doh[0] || doh[1])
747
0
    return DOH_DNS_BAD_ID; /* bad ID */
748
0
  rcode = doh[3] & 0x0f;
749
0
  if(rcode)
750
0
    return DOH_DNS_BAD_RCODE; /* bad rcode */
751
752
0
  qdcount = doh_get16bit(doh, 4);
753
0
  while(qdcount) {
754
0
    rc = doh_skipqname(doh, dohlen, &index);
755
0
    if(rc)
756
0
      return rc; /* bad qname */
757
0
    if(dohlen < (index + 4))
758
0
      return DOH_DNS_OUT_OF_RANGE;
759
0
    index += 4; /* skip question's type and class */
760
0
    qdcount--;
761
0
  }
762
763
0
  ancount = doh_get16bit(doh, 6);
764
0
  while(ancount) {
765
0
    unsigned short dnsclass;
766
0
    unsigned int ttl;
767
768
0
    rc = doh_skipqname(doh, dohlen, &index);
769
0
    if(rc)
770
0
      return rc; /* bad qname */
771
772
0
    if(dohlen < (index + 2))
773
0
      return DOH_DNS_OUT_OF_RANGE;
774
775
0
    type = doh_get16bit(doh, index);
776
0
    if((type != CURL_DNS_TYPE_CNAME)    /* may be synthesized from DNAME */
777
0
       && (type != CURL_DNS_TYPE_DNAME) /* if present, accept and ignore */
778
0
       && (type != dnstype))
779
      /* Not the same type as was asked for nor CNAME nor DNAME */
780
0
      return DOH_DNS_UNEXPECTED_TYPE;
781
0
    index += 2;
782
783
0
    if(dohlen < (index + 2))
784
0
      return DOH_DNS_OUT_OF_RANGE;
785
0
    dnsclass = doh_get16bit(doh, index);
786
0
    if(DNS_CLASS_IN != dnsclass)
787
0
      return DOH_DNS_UNEXPECTED_CLASS; /* unsupported */
788
0
    index += 2;
789
790
0
    if(dohlen < (index + 4))
791
0
      return DOH_DNS_OUT_OF_RANGE;
792
793
0
    ttl = doh_get32bit(doh, index);
794
0
    if(ttl < d->ttl)
795
0
      d->ttl = ttl;
796
0
    index += 4;
797
798
0
    if(dohlen < (index + 2))
799
0
      return DOH_DNS_OUT_OF_RANGE;
800
801
0
    rdlength = doh_get16bit(doh, index);
802
0
    index += 2;
803
0
    if(dohlen < (index + rdlength))
804
0
      return DOH_DNS_OUT_OF_RANGE;
805
806
0
    rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d);
807
0
    if(rc)
808
0
      return rc; /* bad doh_rdata */
809
0
    index += rdlength;
810
0
    ancount--;
811
0
  }
812
813
0
  nscount = doh_get16bit(doh, 8);
814
0
  while(nscount) {
815
0
    rc = doh_skipqname(doh, dohlen, &index);
816
0
    if(rc)
817
0
      return rc; /* bad qname */
818
819
0
    if(dohlen < (index + 8))
820
0
      return DOH_DNS_OUT_OF_RANGE;
821
822
0
    index += 2 + 2 + 4; /* type, dnsclass and ttl */
823
824
0
    if(dohlen < (index + 2))
825
0
      return DOH_DNS_OUT_OF_RANGE;
826
827
0
    rdlength = doh_get16bit(doh, index);
828
0
    index += 2;
829
0
    if(dohlen < (index + rdlength))
830
0
      return DOH_DNS_OUT_OF_RANGE;
831
0
    index += rdlength;
832
0
    nscount--;
833
0
  }
834
835
0
  arcount = doh_get16bit(doh, 10);
836
0
  while(arcount) {
837
0
    rc = doh_skipqname(doh, dohlen, &index);
838
0
    if(rc)
839
0
      return rc; /* bad qname */
840
841
0
    if(dohlen < (index + 8))
842
0
      return DOH_DNS_OUT_OF_RANGE;
843
844
0
    index += 2 + 2 + 4; /* type, dnsclass and ttl */
845
846
0
    if(dohlen < (index + 2))
847
0
      return DOH_DNS_OUT_OF_RANGE;
848
849
0
    rdlength = doh_get16bit(doh, index);
850
0
    index += 2;
851
0
    if(dohlen < (index + rdlength))
852
0
      return DOH_DNS_OUT_OF_RANGE;
853
0
    index += rdlength;
854
0
    arcount--;
855
0
  }
856
857
0
  if(index != dohlen)
858
0
    return DOH_DNS_MALFORMAT; /* something is wrong */
859
860
#ifdef USE_HTTTPS
861
  if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr &&
862
      !d->numhttps_rrs)
863
#else
864
0
  if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr)
865
0
#endif
866
    /* nothing stored! */
867
0
    return DOH_NO_CONTENT;
868
869
0
  return DOH_OK; /* ok */
870
0
}
871
872
#ifndef CURL_DISABLE_VERBOSE_STRINGS
873
static void doh_show(struct Curl_easy *data,
874
                     const struct dohentry *d)
875
0
{
876
0
  int i;
877
0
  infof(data, "[DoH] TTL: %u seconds", d->ttl);
878
0
  for(i = 0; i < d->numaddr; i++) {
879
0
    const struct dohaddr *a = &d->addr[i];
880
0
    if(a->type == CURL_DNS_TYPE_A) {
881
0
      infof(data, "[DoH] A: %u.%u.%u.%u",
882
0
            a->ip.v4[0], a->ip.v4[1],
883
0
            a->ip.v4[2], a->ip.v4[3]);
884
0
    }
885
0
    else if(a->type == CURL_DNS_TYPE_AAAA) {
886
0
      int j;
887
0
      char buffer[128] = "[DoH] AAAA: ";
888
0
      size_t len = strlen(buffer);
889
0
      char *ptr = &buffer[len];
890
0
      len = sizeof(buffer) - len;
891
0
      for(j = 0; j < 16; j += 2) {
892
0
        size_t l;
893
0
        curl_msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "",
894
0
                       d->addr[i].ip.v6[j],
895
0
                       d->addr[i].ip.v6[j + 1]);
896
0
        l = strlen(ptr);
897
0
        len -= l;
898
0
        ptr += l;
899
0
      }
900
0
      infof(data, "%s", buffer);
901
0
    }
902
0
  }
903
#ifdef USE_HTTPSRR
904
  for(i = 0; i < d->numhttps_rrs; i++) {
905
# ifdef DEBUGBUILD
906
    doh_print_buf(data, "DoH HTTPS",
907
                  d->https_rrs[i].val, d->https_rrs[i].len);
908
# else
909
    infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len);
910
# endif
911
  }
912
#endif
913
0
  for(i = 0; i < d->numcname; i++) {
914
0
    infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i]));
915
0
  }
916
0
}
917
#else
918
#define doh_show(x,y)
919
#endif
920
921
/*
922
 * doh2ai()
923
 *
924
 * This function returns a pointer to the first element of a newly allocated
925
 * Curl_addrinfo struct linked list filled with the data from a set of DoH
926
 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
927
 * an IPv6 stack, but usable also for IPv4, all hosts and environments.
928
 *
929
 * The memory allocated by this function *MUST* be free'd later on calling
930
 * Curl_freeaddrinfo(). For each successful call to this function there
931
 * must be an associated call later to Curl_freeaddrinfo().
932
 */
933
934
static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
935
                       int port, struct Curl_addrinfo **aip)
936
0
{
937
0
  struct Curl_addrinfo *ai;
938
0
  struct Curl_addrinfo *prevai = NULL;
939
0
  struct Curl_addrinfo *firstai = NULL;
940
0
  struct sockaddr_in *addr;
941
0
#ifdef USE_IPV6
942
0
  struct sockaddr_in6 *addr6;
943
0
#endif
944
0
  CURLcode result = CURLE_OK;
945
0
  int i;
946
0
  size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
947
948
0
  DEBUGASSERT(de);
949
950
0
  if(!de->numaddr)
951
0
    return CURLE_COULDNT_RESOLVE_HOST;
952
953
0
  for(i = 0; i < de->numaddr; i++) {
954
0
    size_t ss_size;
955
0
    CURL_SA_FAMILY_T addrtype;
956
0
    if(de->addr[i].type == CURL_DNS_TYPE_AAAA) {
957
#ifndef USE_IPV6
958
      /* we cannot handle IPv6 addresses */
959
      continue;
960
#else
961
0
      ss_size = sizeof(struct sockaddr_in6);
962
0
      addrtype = AF_INET6;
963
0
#endif
964
0
    }
965
0
    else {
966
0
      ss_size = sizeof(struct sockaddr_in);
967
0
      addrtype = AF_INET;
968
0
    }
969
970
0
    ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
971
0
    if(!ai) {
972
0
      result = CURLE_OUT_OF_MEMORY;
973
0
      break;
974
0
    }
975
0
    ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
976
0
    ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
977
0
    memcpy(ai->ai_canonname, hostname, hostlen);
978
979
0
    if(!firstai)
980
      /* store the pointer we want to return from this function */
981
0
      firstai = ai;
982
983
0
    if(prevai)
984
      /* make the previous entry point to this */
985
0
      prevai->ai_next = ai;
986
987
0
    ai->ai_family = addrtype;
988
989
    /* we return all names as STREAM, so when using this address for TFTP
990
       the type must be ignored and conn->socktype be used instead! */
991
0
    ai->ai_socktype = SOCK_STREAM;
992
993
0
    ai->ai_addrlen = (curl_socklen_t)ss_size;
994
995
    /* leave the rest of the struct filled with zero */
996
997
0
    switch(ai->ai_family) {
998
0
    case AF_INET:
999
0
      addr = (void *)ai->ai_addr; /* storage area for this info */
1000
0
      DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
1001
0
      memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
1002
0
      addr->sin_family = (CURL_SA_FAMILY_T)addrtype;
1003
0
      addr->sin_port = htons((unsigned short)port);
1004
0
      break;
1005
1006
0
#ifdef USE_IPV6
1007
0
    case AF_INET6:
1008
0
      addr6 = (void *)ai->ai_addr; /* storage area for this info */
1009
0
      DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
1010
0
      memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
1011
0
      addr6->sin6_family = (CURL_SA_FAMILY_T)addrtype;
1012
0
      addr6->sin6_port = htons((unsigned short)port);
1013
0
      break;
1014
0
#endif
1015
0
    }
1016
1017
0
    prevai = ai;
1018
0
  }
1019
1020
0
  if(result) {
1021
0
    Curl_freeaddrinfo(firstai);
1022
0
    firstai = NULL;
1023
0
  }
1024
0
  *aip = firstai;
1025
1026
0
  return result;
1027
0
}
1028
1029
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1030
static const char *doh_type2name(DNStype dnstype)
1031
0
{
1032
0
  switch(dnstype) {
1033
0
    case CURL_DNS_TYPE_A:
1034
0
      return "A";
1035
0
    case CURL_DNS_TYPE_AAAA:
1036
0
      return "AAAA";
1037
#ifdef USE_HTTPSRR
1038
    case CURL_DNS_TYPE_HTTPS:
1039
      return "HTTPS";
1040
#endif
1041
0
    default:
1042
0
       return "unknown";
1043
0
  }
1044
0
}
1045
#endif
1046
1047
UNITTEST void de_cleanup(struct dohentry *d)
1048
0
{
1049
0
  int i = 0;
1050
0
  for(i = 0; i < d->numcname; i++) {
1051
0
    curlx_dyn_free(&d->cname[i]);
1052
0
  }
1053
#ifdef USE_HTTPSRR
1054
  for(i = 0; i < d->numhttps_rrs; i++)
1055
    Curl_safefree(d->https_rrs[i].val);
1056
#endif
1057
0
}
1058
1059
#ifdef USE_HTTPSRR
1060
1061
/*
1062
 * @brief decode the DNS name in a binary RRData
1063
 * @param buf points to the buffer (in/out)
1064
 * @param remaining points to the remaining buffer length (in/out)
1065
 * @param dnsname returns the string form name on success
1066
 * @return is 1 for success, error otherwise
1067
 *
1068
 * The encoding here is defined in
1069
 * https://datatracker.ietf.org/doc/html/rfc1035#section-3.1
1070
 *
1071
 * The input buffer pointer will be modified so it points to
1072
 * just after the end of the DNS name encoding on output. (And
1073
 * that is why it is an "unsigned char **" :-)
1074
 */
1075
static CURLcode doh_decode_rdata_name(const unsigned char **buf,
1076
                                      size_t *remaining, char **dnsname)
1077
{
1078
  const unsigned char *cp = NULL;
1079
  size_t rem = 0;
1080
  unsigned char clen = 0; /* chunk len */
1081
  struct dynbuf thename;
1082
1083
  DEBUGASSERT(buf && remaining && dnsname);
1084
  if(!buf || !remaining || !dnsname || !*remaining)
1085
    return CURLE_OUT_OF_MEMORY;
1086
  curlx_dyn_init(&thename, CURL_MAXLEN_host_name);
1087
  rem = *remaining;
1088
  cp = *buf;
1089
  clen = *cp++;
1090
  if(clen == 0) {
1091
    /* special case - return "." as name */
1092
    if(curlx_dyn_addn(&thename, ".", 1))
1093
      return CURLE_OUT_OF_MEMORY;
1094
  }
1095
  while(clen) {
1096
    if(clen >= rem) {
1097
      curlx_dyn_free(&thename);
1098
      return CURLE_OUT_OF_MEMORY;
1099
    }
1100
    if(curlx_dyn_addn(&thename, cp, clen) ||
1101
       curlx_dyn_addn(&thename, ".", 1))
1102
      return CURLE_TOO_LARGE;
1103
1104
    cp += clen;
1105
    rem -= (clen + 1);
1106
    if(rem <= 0) {
1107
      curlx_dyn_free(&thename);
1108
      return CURLE_OUT_OF_MEMORY;
1109
    }
1110
    clen = *cp++;
1111
  }
1112
  *buf = cp;
1113
  *remaining = rem - 1;
1114
  *dnsname = curlx_dyn_ptr(&thename);
1115
  return CURLE_OK;
1116
}
1117
1118
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
1119
                                          const unsigned char *cp, size_t len,
1120
                                          struct Curl_https_rrinfo **hrr);
1121
1122
/* @unittest 1658 */
1123
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
1124
                                          const unsigned char *cp, size_t len,
1125
                                          struct Curl_https_rrinfo **hrr)
1126
{
1127
  uint16_t pcode = 0, plen = 0;
1128
  uint32_t expected_min_pcode = 0;
1129
  struct Curl_https_rrinfo *lhrr = NULL;
1130
  char *dnsname = NULL;
1131
  CURLcode result = CURLE_OUT_OF_MEMORY;
1132
  size_t olen;
1133
1134
  *hrr = NULL;
1135
  if(len <= 2)
1136
    return CURLE_BAD_FUNCTION_ARGUMENT;
1137
  lhrr = calloc(1, sizeof(struct Curl_https_rrinfo));
1138
  if(!lhrr)
1139
    return CURLE_OUT_OF_MEMORY;
1140
  lhrr->priority = doh_get16bit(cp, 0);
1141
  cp += 2;
1142
  len -= 2;
1143
  if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK)
1144
    goto err;
1145
  lhrr->target = dnsname;
1146
  if(Curl_junkscan(dnsname, &olen, FALSE)) {
1147
    /* unacceptable hostname content */
1148
    result = CURLE_WEIRD_SERVER_REPLY;
1149
    goto err;
1150
  }
1151
  lhrr->port = -1; /* until set */
1152
  while(len >= 4) {
1153
    pcode = doh_get16bit(cp, 0);
1154
    plen = doh_get16bit(cp, 2);
1155
    cp += 4;
1156
    len -= 4;
1157
    if(pcode < expected_min_pcode || plen > len) {
1158
      result = CURLE_WEIRD_SERVER_REPLY;
1159
      goto err;
1160
    }
1161
    result = Curl_httpsrr_set(data, lhrr, pcode, cp, plen);
1162
    if(result)
1163
      goto err;
1164
    cp += plen;
1165
    len -= plen;
1166
    expected_min_pcode = pcode + 1;
1167
  }
1168
  DEBUGASSERT(!len);
1169
  *hrr = lhrr;
1170
  return CURLE_OK;
1171
err:
1172
  Curl_httpsrr_cleanup(lhrr);
1173
  Curl_safefree(lhrr);
1174
  return result;
1175
}
1176
1177
#ifdef DEBUGBUILD
1178
UNITTEST void doh_print_httpsrr(struct Curl_easy *data,
1179
                                struct Curl_https_rrinfo *hrr);
1180
1181
UNITTEST void doh_print_httpsrr(struct Curl_easy *data,
1182
                                struct Curl_https_rrinfo *hrr)
1183
{
1184
  DEBUGASSERT(hrr);
1185
  infof(data, "HTTPS RR: priority %d, target: %s",
1186
        hrr->priority, hrr->target);
1187
  if(hrr->alpns[0] != ALPN_none)
1188
    infof(data, "HTTPS RR: alpns %u %u %u %u",
1189
          hrr->alpns[0], hrr->alpns[1], hrr->alpns[2], hrr->alpns[3]);
1190
  else
1191
    infof(data, "HTTPS RR: no alpns");
1192
  if(hrr->no_def_alpn)
1193
    infof(data, "HTTPS RR: no_def_alpn set");
1194
  else
1195
    infof(data, "HTTPS RR: no_def_alpn not set");
1196
  if(hrr->ipv4hints) {
1197
    doh_print_buf(data, "HTTPS RR: ipv4hints",
1198
                  hrr->ipv4hints, hrr->ipv4hints_len);
1199
  }
1200
  else
1201
    infof(data, "HTTPS RR: no ipv4hints");
1202
  if(hrr->echconfiglist) {
1203
    doh_print_buf(data, "HTTPS RR: ECHConfigList",
1204
                  hrr->echconfiglist, hrr->echconfiglist_len);
1205
  }
1206
  else
1207
    infof(data, "HTTPS RR: no ECHConfigList");
1208
  if(hrr->ipv6hints) {
1209
    doh_print_buf(data, "HTTPS RR: ipv6hint",
1210
                  hrr->ipv6hints, hrr->ipv6hints_len);
1211
  }
1212
  else
1213
    infof(data, "HTTPS RR: no ipv6hints");
1214
  return;
1215
}
1216
# endif
1217
#endif
1218
1219
CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
1220
                              struct Curl_dns_entry **dnsp)
1221
0
{
1222
0
  CURLcode result;
1223
0
  struct doh_probes *dohp = data->state.async.doh;
1224
0
  *dnsp = NULL; /* defaults to no response */
1225
0
  if(!dohp)
1226
0
    return CURLE_OUT_OF_MEMORY;
1227
1228
0
  if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT_MAX &&
1229
0
     dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT_MAX) {
1230
0
    failf(data, "Could not DoH-resolve: %s", dohp->host);
1231
0
    return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
1232
0
      CURLE_COULDNT_RESOLVE_HOST;
1233
0
  }
1234
0
  else if(!dohp->pending) {
1235
0
    DOHcode rc[DOH_SLOT_COUNT];
1236
0
    struct dohentry de;
1237
0
    int slot;
1238
1239
    /* Clear any result the might still be there */
1240
0
    Curl_resolv_unlink(data, &data->state.async.dns);
1241
1242
0
    memset(rc, 0, sizeof(rc));
1243
    /* remove DoH handles from multi handle and close them */
1244
0
    Curl_doh_close(data);
1245
    /* parse the responses, create the struct and return it! */
1246
0
    de_init(&de);
1247
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1248
0
      struct doh_response *p = &dohp->probe_resp[slot];
1249
0
      if(!p->dnstype)
1250
0
        continue;
1251
0
      rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body),
1252
0
                                 curlx_dyn_len(&p->body),
1253
0
                                 p->dnstype, &de);
1254
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1255
0
      if(rc[slot]) {
1256
0
        CURL_TRC_DNS(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
1257
0
                     doh_type2name(p->dnstype), dohp->host);
1258
0
      }
1259
0
#endif
1260
0
    } /* next slot */
1261
1262
0
    result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
1263
0
    if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
1264
      /* we have an address, of one kind or other */
1265
0
      struct Curl_dns_entry *dns;
1266
0
      struct Curl_addrinfo *ai;
1267
1268
1269
0
      if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) {
1270
0
        CURL_TRC_DNS(data, "hostname: %s", dohp->host);
1271
0
        doh_show(data, &de);
1272
0
      }
1273
1274
0
      result = doh2ai(&de, dohp->host, dohp->port, &ai);
1275
0
      if(result) {
1276
0
        de_cleanup(&de);
1277
0
        return result;
1278
0
      }
1279
1280
      /* we got a response, create a dns entry. */
1281
0
      dns = Curl_dnscache_mk_entry(data, ai, dohp->host, 0, dohp->port, FALSE);
1282
0
      if(dns) {
1283
        /* Now add and HTTPSRR information if we have */
1284
#ifdef USE_HTTPSRR
1285
        if(de.numhttps_rrs > 0 && result == CURLE_OK) {
1286
          struct Curl_https_rrinfo *hrr = NULL;
1287
          result = doh_resp_decode_httpsrr(data, de.https_rrs->val,
1288
                                           de.https_rrs->len, &hrr);
1289
          if(result) {
1290
            infof(data, "Failed to decode HTTPS RR");
1291
            return result;
1292
          }
1293
          infof(data, "Some HTTPS RR to process");
1294
# ifdef DEBUGBUILD
1295
          doh_print_httpsrr(data, hrr);
1296
# endif
1297
          dns->hinfo = hrr;
1298
       }
1299
#endif
1300
        /* and add the entry to the cache */
1301
0
        data->state.async.dns = dns;
1302
0
        result = Curl_dnscache_add(data, dns);
1303
0
        *dnsp = data->state.async.dns;
1304
0
      }
1305
0
    } /* address processing done */
1306
1307
    /* All done */
1308
0
    data->state.async.done = TRUE;
1309
0
    de_cleanup(&de);
1310
0
    Curl_doh_cleanup(data);
1311
0
    return result;
1312
1313
0
  } /* !dohp->pending */
1314
1315
  /* else wait for pending DoH transactions to complete */
1316
0
  return CURLE_OK;
1317
0
}
1318
1319
void Curl_doh_close(struct Curl_easy *data)
1320
0
{
1321
0
  struct doh_probes *doh = data->state.async.doh;
1322
0
  if(doh && data->multi) {
1323
0
    struct Curl_easy *probe_data;
1324
0
    unsigned int mid;
1325
0
    size_t slot;
1326
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1327
0
      mid = doh->probe_resp[slot].probe_mid;
1328
0
      if(mid == UINT_MAX)
1329
0
        continue;
1330
0
      doh->probe_resp[slot].probe_mid = UINT_MAX;
1331
      /* should have been called before data is removed from multi handle */
1332
0
      DEBUGASSERT(data->multi);
1333
0
      probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) :
1334
0
        NULL;
1335
0
      if(!probe_data) {
1336
0
        DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!",
1337
0
                     doh->probe_resp[slot].probe_mid));
1338
0
        continue;
1339
0
      }
1340
      /* data->multi might already be reset at this time */
1341
0
      curl_multi_remove_handle(data->multi, probe_data);
1342
0
      Curl_close(&probe_data);
1343
0
    }
1344
0
    data->sub_xfer_done = NULL;
1345
0
  }
1346
0
}
1347
1348
void Curl_doh_cleanup(struct Curl_easy *data)
1349
0
{
1350
0
  struct doh_probes *dohp = data->state.async.doh;
1351
0
  if(dohp) {
1352
0
    int i;
1353
0
    Curl_doh_close(data);
1354
0
    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
1355
0
      curlx_dyn_free(&dohp->probe_resp[i].body);
1356
0
    }
1357
    Curl_safefree(data->state.async.doh);
1358
0
  }
1359
0
}
1360
1361
#endif /* CURL_DISABLE_DOH */