Coverage Report

Created: 2025-07-11 06:33

/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, (long)CURLPROTO_HTTPS);
352
#else
353
  /* in debug mode, also allow http */
354
  ERROR_CHECK_SETOPT(CURLOPT_PROTOCOLS, (long)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, 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, 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, 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 = 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 = 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 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 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 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 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 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 != DNS_TYPE_CNAME)    /* may be synthesized from DNAME */
774
0
       && (type != 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 != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs)
859
#else
860
0
  if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
861
0
#endif
862
    /* nothing stored! */
863
0
    return DOH_NO_CONTENT;
864
865
0
  return DOH_OK; /* ok */
866
0
}
867
868
#ifndef CURL_DISABLE_VERBOSE_STRINGS
869
static void doh_show(struct Curl_easy *data,
870
                     const struct dohentry *d)
871
0
{
872
0
  int i;
873
0
  infof(data, "[DoH] TTL: %u seconds", d->ttl);
874
0
  for(i = 0; i < d->numaddr; i++) {
875
0
    const struct dohaddr *a = &d->addr[i];
876
0
    if(a->type == DNS_TYPE_A) {
877
0
      infof(data, "[DoH] A: %u.%u.%u.%u",
878
0
            a->ip.v4[0], a->ip.v4[1],
879
0
            a->ip.v4[2], a->ip.v4[3]);
880
0
    }
881
0
    else if(a->type == DNS_TYPE_AAAA) {
882
0
      int j;
883
0
      char buffer[128] = "[DoH] AAAA: ";
884
0
      size_t len = strlen(buffer);
885
0
      char *ptr = &buffer[len];
886
0
      len = sizeof(buffer) - len;
887
0
      for(j = 0; j < 16; j += 2) {
888
0
        size_t l;
889
0
        msnprintf(ptr, len, "%s%02x%02x", j ? ":" : "", d->addr[i].ip.v6[j],
890
0
                  d->addr[i].ip.v6[j + 1]);
891
0
        l = strlen(ptr);
892
0
        len -= l;
893
0
        ptr += l;
894
0
      }
895
0
      infof(data, "%s", buffer);
896
0
    }
897
0
  }
898
#ifdef USE_HTTPSRR
899
  for(i = 0; i < d->numhttps_rrs; i++) {
900
# ifdef DEBUGBUILD
901
    doh_print_buf(data, "DoH HTTPS",
902
                  d->https_rrs[i].val, d->https_rrs[i].len);
903
# else
904
    infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len);
905
# endif
906
  }
907
#endif
908
0
  for(i = 0; i < d->numcname; i++) {
909
0
    infof(data, "CNAME: %s", curlx_dyn_ptr(&d->cname[i]));
910
0
  }
911
0
}
912
#else
913
#define doh_show(x,y)
914
#endif
915
916
/*
917
 * doh2ai()
918
 *
919
 * This function returns a pointer to the first element of a newly allocated
920
 * Curl_addrinfo struct linked list filled with the data from a set of DoH
921
 * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for
922
 * an IPv6 stack, but usable also for IPv4, all hosts and environments.
923
 *
924
 * The memory allocated by this function *MUST* be free'd later on calling
925
 * Curl_freeaddrinfo(). For each successful call to this function there
926
 * must be an associated call later to Curl_freeaddrinfo().
927
 */
928
929
static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
930
                       int port, struct Curl_addrinfo **aip)
931
0
{
932
0
  struct Curl_addrinfo *ai;
933
0
  struct Curl_addrinfo *prevai = NULL;
934
0
  struct Curl_addrinfo *firstai = NULL;
935
0
  struct sockaddr_in *addr;
936
0
#ifdef USE_IPV6
937
0
  struct sockaddr_in6 *addr6;
938
0
#endif
939
0
  CURLcode result = CURLE_OK;
940
0
  int i;
941
0
  size_t hostlen = strlen(hostname) + 1; /* include null-terminator */
942
943
0
  DEBUGASSERT(de);
944
945
0
  if(!de->numaddr)
946
0
    return CURLE_COULDNT_RESOLVE_HOST;
947
948
0
  for(i = 0; i < de->numaddr; i++) {
949
0
    size_t ss_size;
950
0
    CURL_SA_FAMILY_T addrtype;
951
0
    if(de->addr[i].type == DNS_TYPE_AAAA) {
952
#ifndef USE_IPV6
953
      /* we cannot handle IPv6 addresses */
954
      continue;
955
#else
956
0
      ss_size = sizeof(struct sockaddr_in6);
957
0
      addrtype = AF_INET6;
958
0
#endif
959
0
    }
960
0
    else {
961
0
      ss_size = sizeof(struct sockaddr_in);
962
0
      addrtype = AF_INET;
963
0
    }
964
965
0
    ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen);
966
0
    if(!ai) {
967
0
      result = CURLE_OUT_OF_MEMORY;
968
0
      break;
969
0
    }
970
0
    ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
971
0
    ai->ai_canonname = (void *)((char *)ai->ai_addr + ss_size);
972
0
    memcpy(ai->ai_canonname, hostname, hostlen);
973
974
0
    if(!firstai)
975
      /* store the pointer we want to return from this function */
976
0
      firstai = ai;
977
978
0
    if(prevai)
979
      /* make the previous entry point to this */
980
0
      prevai->ai_next = ai;
981
982
0
    ai->ai_family = addrtype;
983
984
    /* we return all names as STREAM, so when using this address for TFTP
985
       the type must be ignored and conn->socktype be used instead! */
986
0
    ai->ai_socktype = SOCK_STREAM;
987
988
0
    ai->ai_addrlen = (curl_socklen_t)ss_size;
989
990
    /* leave the rest of the struct filled with zero */
991
992
0
    switch(ai->ai_family) {
993
0
    case AF_INET:
994
0
      addr = (void *)ai->ai_addr; /* storage area for this info */
995
0
      DEBUGASSERT(sizeof(struct in_addr) == sizeof(de->addr[i].ip.v4));
996
0
      memcpy(&addr->sin_addr, &de->addr[i].ip.v4, sizeof(struct in_addr));
997
0
      addr->sin_family = (CURL_SA_FAMILY_T)addrtype;
998
0
      addr->sin_port = htons((unsigned short)port);
999
0
      break;
1000
1001
0
#ifdef USE_IPV6
1002
0
    case AF_INET6:
1003
0
      addr6 = (void *)ai->ai_addr; /* storage area for this info */
1004
0
      DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6));
1005
0
      memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr));
1006
0
      addr6->sin6_family = (CURL_SA_FAMILY_T)addrtype;
1007
0
      addr6->sin6_port = htons((unsigned short)port);
1008
0
      break;
1009
0
#endif
1010
0
    }
1011
1012
0
    prevai = ai;
1013
0
  }
1014
1015
0
  if(result) {
1016
0
    Curl_freeaddrinfo(firstai);
1017
0
    firstai = NULL;
1018
0
  }
1019
0
  *aip = firstai;
1020
1021
0
  return result;
1022
0
}
1023
1024
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1025
static const char *doh_type2name(DNStype dnstype)
1026
0
{
1027
0
  switch(dnstype) {
1028
0
    case DNS_TYPE_A:
1029
0
      return "A";
1030
0
    case DNS_TYPE_AAAA:
1031
0
      return "AAAA";
1032
#ifdef USE_HTTPSRR
1033
    case DNS_TYPE_HTTPS:
1034
      return "HTTPS";
1035
#endif
1036
0
    default:
1037
0
       return "unknown";
1038
0
  }
1039
0
}
1040
#endif
1041
1042
UNITTEST void de_cleanup(struct dohentry *d)
1043
0
{
1044
0
  int i = 0;
1045
0
  for(i = 0; i < d->numcname; i++) {
1046
0
    curlx_dyn_free(&d->cname[i]);
1047
0
  }
1048
#ifdef USE_HTTPSRR
1049
  for(i = 0; i < d->numhttps_rrs; i++)
1050
    Curl_safefree(d->https_rrs[i].val);
1051
#endif
1052
0
}
1053
1054
#ifdef USE_HTTPSRR
1055
1056
/*
1057
 * @brief decode the DNS name in a binary RRData
1058
 * @param buf points to the buffer (in/out)
1059
 * @param remaining points to the remaining buffer length (in/out)
1060
 * @param dnsname returns the string form name on success
1061
 * @return is 1 for success, error otherwise
1062
 *
1063
 * The encoding here is defined in
1064
 * https://tools.ietf.org/html/rfc1035#section-3.1
1065
 *
1066
 * The input buffer pointer will be modified so it points to
1067
 * just after the end of the DNS name encoding on output. (And
1068
 * that is why it is an "unsigned char **" :-)
1069
 */
1070
static CURLcode doh_decode_rdata_name(const unsigned char **buf,
1071
                                      size_t *remaining, char **dnsname)
1072
{
1073
  const unsigned char *cp = NULL;
1074
  size_t rem = 0;
1075
  unsigned char clen = 0; /* chunk len */
1076
  struct dynbuf thename;
1077
1078
  DEBUGASSERT(buf && remaining && dnsname);
1079
  if(!buf || !remaining || !dnsname || !*remaining)
1080
    return CURLE_OUT_OF_MEMORY;
1081
  curlx_dyn_init(&thename, CURL_MAXLEN_host_name);
1082
  rem = *remaining;
1083
  cp = *buf;
1084
  clen = *cp++;
1085
  if(clen == 0) {
1086
    /* special case - return "." as name */
1087
    if(curlx_dyn_addn(&thename, ".", 1))
1088
      return CURLE_OUT_OF_MEMORY;
1089
  }
1090
  while(clen) {
1091
    if(clen >= rem) {
1092
      curlx_dyn_free(&thename);
1093
      return CURLE_OUT_OF_MEMORY;
1094
    }
1095
    if(curlx_dyn_addn(&thename, cp, clen) ||
1096
       curlx_dyn_addn(&thename, ".", 1))
1097
      return CURLE_TOO_LARGE;
1098
1099
    cp += clen;
1100
    rem -= (clen + 1);
1101
    if(rem <= 0) {
1102
      curlx_dyn_free(&thename);
1103
      return CURLE_OUT_OF_MEMORY;
1104
    }
1105
    clen = *cp++;
1106
  }
1107
  *buf = cp;
1108
  *remaining = rem - 1;
1109
  *dnsname = curlx_dyn_ptr(&thename);
1110
  return CURLE_OK;
1111
}
1112
1113
UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
1114
                                          const unsigned char *cp, size_t len,
1115
                                          struct Curl_https_rrinfo **hrr);
1116
1117
/* @unittest 1658 */
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
  uint16_t pcode = 0, plen = 0;
1123
  uint32_t expected_min_pcode = 0;
1124
  struct Curl_https_rrinfo *lhrr = NULL;
1125
  char *dnsname = NULL;
1126
  CURLcode result = CURLE_OUT_OF_MEMORY;
1127
  size_t olen;
1128
1129
  *hrr = NULL;
1130
  if(len <= 2)
1131
    return CURLE_BAD_FUNCTION_ARGUMENT;
1132
  lhrr = calloc(1, sizeof(struct Curl_https_rrinfo));
1133
  if(!lhrr)
1134
    return CURLE_OUT_OF_MEMORY;
1135
  lhrr->priority = doh_get16bit(cp, 0);
1136
  cp += 2;
1137
  len -= 2;
1138
  if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK)
1139
    goto err;
1140
  lhrr->target = dnsname;
1141
  if(Curl_junkscan(dnsname, &olen, FALSE)) {
1142
    /* unacceptable hostname content */
1143
    result = CURLE_WEIRD_SERVER_REPLY;
1144
    goto err;
1145
  }
1146
  lhrr->port = -1; /* until set */
1147
  while(len >= 4) {
1148
    pcode = doh_get16bit(cp, 0);
1149
    plen = doh_get16bit(cp, 2);
1150
    cp += 4;
1151
    len -= 4;
1152
    if(pcode < expected_min_pcode || plen > len) {
1153
      result = CURLE_WEIRD_SERVER_REPLY;
1154
      goto err;
1155
    }
1156
    result = Curl_httpsrr_set(data, lhrr, pcode, cp, plen);
1157
    if(result)
1158
      goto err;
1159
    cp += plen;
1160
    len -= plen;
1161
    expected_min_pcode = pcode + 1;
1162
  }
1163
  DEBUGASSERT(!len);
1164
  *hrr = lhrr;
1165
  return CURLE_OK;
1166
err:
1167
  Curl_httpsrr_cleanup(lhrr);
1168
  Curl_safefree(lhrr);
1169
  return result;
1170
}
1171
1172
#ifdef DEBUGBUILD
1173
UNITTEST void doh_print_httpsrr(struct Curl_easy *data,
1174
                                struct Curl_https_rrinfo *hrr);
1175
1176
UNITTEST void doh_print_httpsrr(struct Curl_easy *data,
1177
                                struct Curl_https_rrinfo *hrr)
1178
{
1179
  DEBUGASSERT(hrr);
1180
  infof(data, "HTTPS RR: priority %d, target: %s",
1181
        hrr->priority, hrr->target);
1182
  if(hrr->alpns[0] != ALPN_none)
1183
    infof(data, "HTTPS RR: alpns %u %u %u %u",
1184
          hrr->alpns[0], hrr->alpns[1], hrr->alpns[2], hrr->alpns[3]);
1185
  else
1186
    infof(data, "HTTPS RR: no alpns");
1187
  if(hrr->no_def_alpn)
1188
    infof(data, "HTTPS RR: no_def_alpn set");
1189
  else
1190
    infof(data, "HTTPS RR: no_def_alpn not set");
1191
  if(hrr->ipv4hints) {
1192
    doh_print_buf(data, "HTTPS RR: ipv4hints",
1193
                  hrr->ipv4hints, hrr->ipv4hints_len);
1194
  }
1195
  else
1196
    infof(data, "HTTPS RR: no ipv4hints");
1197
  if(hrr->echconfiglist) {
1198
    doh_print_buf(data, "HTTPS RR: ECHConfigList",
1199
                  hrr->echconfiglist, hrr->echconfiglist_len);
1200
  }
1201
  else
1202
    infof(data, "HTTPS RR: no ECHConfigList");
1203
  if(hrr->ipv6hints) {
1204
    doh_print_buf(data, "HTTPS RR: ipv6hint",
1205
                  hrr->ipv6hints, hrr->ipv6hints_len);
1206
  }
1207
  else
1208
    infof(data, "HTTPS RR: no ipv6hints");
1209
  return;
1210
}
1211
# endif
1212
#endif
1213
1214
CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
1215
                              struct Curl_dns_entry **dnsp)
1216
0
{
1217
0
  CURLcode result;
1218
0
  struct doh_probes *dohp = data->state.async.doh;
1219
0
  *dnsp = NULL; /* defaults to no response */
1220
0
  if(!dohp)
1221
0
    return CURLE_OUT_OF_MEMORY;
1222
1223
0
  if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT_MAX &&
1224
0
     dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT_MAX) {
1225
0
    failf(data, "Could not DoH-resolve: %s", dohp->host);
1226
0
    return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
1227
0
      CURLE_COULDNT_RESOLVE_HOST;
1228
0
  }
1229
0
  else if(!dohp->pending) {
1230
0
    DOHcode rc[DOH_SLOT_COUNT];
1231
0
    struct dohentry de;
1232
0
    int slot;
1233
1234
    /* Clear any result the might still be there */
1235
0
    Curl_resolv_unlink(data, &data->state.async.dns);
1236
1237
0
    memset(rc, 0, sizeof(rc));
1238
    /* remove DoH handles from multi handle and close them */
1239
0
    Curl_doh_close(data);
1240
    /* parse the responses, create the struct and return it! */
1241
0
    de_init(&de);
1242
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1243
0
      struct doh_response *p = &dohp->probe_resp[slot];
1244
0
      if(!p->dnstype)
1245
0
        continue;
1246
0
      rc[slot] = doh_resp_decode(curlx_dyn_uptr(&p->body),
1247
0
                                 curlx_dyn_len(&p->body),
1248
0
                                 p->dnstype, &de);
1249
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1250
0
      if(rc[slot]) {
1251
0
        infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]),
1252
0
              doh_type2name(p->dnstype), dohp->host);
1253
0
      }
1254
0
#endif
1255
0
    } /* next slot */
1256
1257
0
    result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
1258
0
    if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
1259
      /* we have an address, of one kind or other */
1260
0
      struct Curl_dns_entry *dns;
1261
0
      struct Curl_addrinfo *ai;
1262
1263
1264
0
      if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_dns)) {
1265
0
        CURL_TRC_DNS(data, "hostname: %s", dohp->host);
1266
0
        doh_show(data, &de);
1267
0
      }
1268
1269
0
      result = doh2ai(&de, dohp->host, dohp->port, &ai);
1270
0
      if(result) {
1271
0
        de_cleanup(&de);
1272
0
        return result;
1273
0
      }
1274
1275
      /* we got a response, create a dns entry. */
1276
0
      dns = Curl_dnscache_mk_entry(data, ai, dohp->host, 0, dohp->port, FALSE);
1277
0
      if(dns) {
1278
        /* Now add and HTTPSRR information if we have */
1279
#ifdef USE_HTTPSRR
1280
        if(de.numhttps_rrs > 0 && result == CURLE_OK) {
1281
          struct Curl_https_rrinfo *hrr = NULL;
1282
          result = doh_resp_decode_httpsrr(data, de.https_rrs->val,
1283
                                           de.https_rrs->len, &hrr);
1284
          if(result) {
1285
            infof(data, "Failed to decode HTTPS RR");
1286
            return result;
1287
          }
1288
          infof(data, "Some HTTPS RR to process");
1289
# ifdef DEBUGBUILD
1290
          doh_print_httpsrr(data, hrr);
1291
# endif
1292
          dns->hinfo = hrr;
1293
       }
1294
#endif
1295
        /* and add the entry to the cache */
1296
0
        data->state.async.dns = dns;
1297
0
        result = Curl_dnscache_add(data, dns);
1298
0
        *dnsp = data->state.async.dns;
1299
0
      }
1300
0
    } /* address processing done */
1301
1302
    /* All done */
1303
0
    data->state.async.done = TRUE;
1304
0
    de_cleanup(&de);
1305
0
    Curl_doh_cleanup(data);
1306
0
    return result;
1307
1308
0
  } /* !dohp->pending */
1309
1310
  /* else wait for pending DoH transactions to complete */
1311
0
  return CURLE_OK;
1312
0
}
1313
1314
void Curl_doh_close(struct Curl_easy *data)
1315
0
{
1316
0
  struct doh_probes *doh = data->state.async.doh;
1317
0
  if(doh && data->multi) {
1318
0
    struct Curl_easy *probe_data;
1319
0
    unsigned int mid;
1320
0
    size_t slot;
1321
0
    for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
1322
0
      mid = doh->probe_resp[slot].probe_mid;
1323
0
      if(mid == UINT_MAX)
1324
0
        continue;
1325
0
      doh->probe_resp[slot].probe_mid = UINT_MAX;
1326
      /* should have been called before data is removed from multi handle */
1327
0
      DEBUGASSERT(data->multi);
1328
0
      probe_data = data->multi ? Curl_multi_get_easy(data->multi, mid) :
1329
0
        NULL;
1330
0
      if(!probe_data) {
1331
0
        DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%u not found!",
1332
0
                     doh->probe_resp[slot].probe_mid));
1333
0
        continue;
1334
0
      }
1335
      /* data->multi might already be reset at this time */
1336
0
      curl_multi_remove_handle(data->multi, probe_data);
1337
0
      Curl_close(&probe_data);
1338
0
    }
1339
0
    data->sub_xfer_done = NULL;
1340
0
  }
1341
0
}
1342
1343
void Curl_doh_cleanup(struct Curl_easy *data)
1344
0
{
1345
0
  struct doh_probes *dohp = data->state.async.doh;
1346
0
  if(dohp) {
1347
0
    int i;
1348
0
    Curl_doh_close(data);
1349
0
    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
1350
0
      curlx_dyn_free(&dohp->probe_resp[i].body);
1351
0
    }
1352
0
    Curl_safefree(data->state.async.doh);
1353
0
  }
1354
0
}
1355
1356
#endif /* CURL_DISABLE_DOH */