Coverage Report

Created: 2025-08-26 07:08

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