Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmcurl/lib/hostip.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifdef HAVE_NETINET_IN_H
27
#include <netinet/in.h>
28
#endif
29
#ifdef HAVE_NETINET_IN6_H
30
#include <netinet/in6.h>
31
#endif
32
#ifdef HAVE_NETDB_H
33
#include <netdb.h>
34
#endif
35
#ifdef HAVE_ARPA_INET_H
36
#include <arpa/inet.h>
37
#endif
38
#ifdef __VMS
39
#include <in.h>
40
#include <inet.h>
41
#endif
42
43
#include <setjmp.h>  /* for sigjmp_buf, sigsetjmp() */
44
#include <signal.h>
45
46
#include "urldata.h"
47
#include "curl_trc.h"
48
#include "connect.h"
49
#include "hostip.h"
50
#include "hash.h"
51
#include "rand.h"
52
#include "curl_share.h"
53
#include "url.h"
54
#include "curlx/inet_ntop.h"
55
#include "curlx/inet_pton.h"
56
#include "multiif.h"
57
#include "doh.h"
58
#include "progress.h"
59
#include "select.h"
60
#include "strcase.h"
61
#include "easy_lock.h"
62
#include "curlx/strcopy.h"
63
#include "curlx/strparse.h"
64
65
#if defined(CURLRES_SYNCH) &&                   \
66
  defined(HAVE_ALARM) &&                        \
67
  defined(SIGALRM) &&                           \
68
  defined(HAVE_SIGSETJMP) &&                    \
69
  defined(GLOBAL_INIT_IS_THREADSAFE)
70
/* alarm-based timeouts can only be used with all the dependencies satisfied */
71
#define USE_ALARM_TIMEOUT
72
#endif
73
74
#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
75
76
0
#define MAX_DNS_CACHE_SIZE 29999
77
78
/*
79
 * hostip.c explained
80
 * ==================
81
 *
82
 * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
83
 * source file are these:
84
 *
85
 * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
86
 * that. The host may not be able to resolve IPv6, but we do not really have to
87
 * take that into account. Hosts that are not IPv6-enabled have CURLRES_IPV4
88
 * defined.
89
 *
90
 * CURLRES_ARES - is defined if libcurl is built to use c-ares for
91
 * asynchronous name resolves. This can be Windows or *nix.
92
 *
93
 * CURLRES_THREADED - is defined if libcurl is built to run under (native)
94
 * Windows, and then the name resolve will be done in a new thread, and the
95
 * supported API will be the same as for ares-builds.
96
 *
97
 * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
98
 * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
99
 * defined.
100
 *
101
 * The host*.c sources files are split up like this:
102
 *
103
 * hostip.c   - method-independent resolver functions and utility functions
104
 * hostip4.c  - IPv4 specific functions
105
 * hostip6.c  - IPv6 specific functions
106
 * asyn.h     - common functions for all async resolvers
107
 * The two asynchronous name resolver backends are implemented in:
108
 * asyn-ares.c - async resolver using c-ares
109
 * asyn-thread.c - async resolver using POSIX threads
110
 *
111
 * The hostip.h is the united header file for all this. It defines the
112
 * CURLRES_* defines based on the config*.h and curl_setup.h defines.
113
 */
114
115
static void dnscache_entry_free(struct Curl_dns_entry *dns);
116
117
#ifndef CURL_DISABLE_VERBOSE_STRINGS
118
static void show_resolve_info(struct Curl_easy *data,
119
                              struct Curl_dns_entry *dns);
120
#else
121
#define show_resolve_info(x, y) Curl_nop_stmt
122
#endif
123
124
/*
125
 * Curl_printable_address() stores a printable version of the 1st address
126
 * given in the 'ai' argument. The result will be stored in the buf that is
127
 * bufsize bytes big.
128
 *
129
 * If the conversion fails, the target buffer is empty.
130
 */
131
void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf,
132
                            size_t bufsize)
133
0
{
134
0
  DEBUGASSERT(bufsize);
135
0
  buf[0] = 0;
136
137
0
  switch(ai->ai_family) {
138
0
  case AF_INET: {
139
0
    const struct sockaddr_in *sa4 = (const void *)ai->ai_addr;
140
0
    const struct in_addr *ipaddr4 = &sa4->sin_addr;
141
0
    (void)curlx_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize);
142
0
    break;
143
0
  }
144
0
#ifdef USE_IPV6
145
0
  case AF_INET6: {
146
0
    const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr;
147
0
    const struct in6_addr *ipaddr6 = &sa6->sin6_addr;
148
0
    (void)curlx_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, bufsize);
149
0
    break;
150
0
  }
151
0
#endif
152
0
  default:
153
0
    break;
154
0
  }
155
0
}
156
157
/*
158
 * Create a hostcache id string for the provided host + port, to be used by
159
 * the DNS caching. Without alloc. Return length of the id string.
160
 */
161
static size_t create_dnscache_id(const char *name,
162
                                 size_t nlen, /* 0 or actual name length */
163
                                 int port, char *ptr, size_t buflen)
164
0
{
165
0
  size_t len = nlen ? nlen : strlen(name);
166
0
  DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
167
0
  if(len > (buflen - 7))
168
0
    len = buflen - 7;
169
  /* store and lower case the name */
170
0
  Curl_strntolower(ptr, name, len);
171
0
  return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
172
0
}
173
174
struct dnscache_prune_data {
175
  struct curltime now;
176
  timediff_t oldest_ms; /* oldest time in cache not pruned. */
177
  timediff_t max_age_ms;
178
};
179
180
/*
181
 * This function is set as a callback to be called for every entry in the DNS
182
 * cache when we want to prune old unused entries.
183
 *
184
 * Returning non-zero means remove the entry, return 0 to keep it in the
185
 * cache.
186
 */
187
static int dnscache_entry_is_stale(void *datap, void *hc)
188
0
{
189
0
  struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
190
0
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
191
192
0
  if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
193
    /* get age in milliseconds */
194
0
    timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
195
0
    if(!dns->addr)
196
0
      age *= 2; /* negative entries age twice as fast */
197
0
    if(age >= prune->max_age_ms)
198
0
      return TRUE;
199
0
    if(age > prune->oldest_ms)
200
0
      prune->oldest_ms = age;
201
0
  }
202
0
  return FALSE;
203
0
}
204
205
/*
206
 * Prune the DNS cache. This assumes that a lock has already been taken.
207
 * Returns the 'age' of the oldest still kept entry - in milliseconds.
208
 */
209
static timediff_t dnscache_prune(struct Curl_hash *hostcache,
210
                                 timediff_t cache_timeout_ms,
211
                                 struct curltime now)
212
0
{
213
0
  struct dnscache_prune_data user;
214
215
0
  user.max_age_ms = cache_timeout_ms;
216
0
  user.now = now;
217
0
  user.oldest_ms = 0;
218
219
0
  Curl_hash_clean_with_criterium(hostcache,
220
0
                                 (void *)&user,
221
0
                                 dnscache_entry_is_stale);
222
223
0
  return user.oldest_ms;
224
0
}
225
226
static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
227
0
{
228
0
  if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
229
0
    return &data->share->dnscache;
230
0
  if(data->multi)
231
0
    return &data->multi->dnscache;
232
0
  return NULL;
233
0
}
234
235
static void dnscache_lock(struct Curl_easy *data,
236
                          struct Curl_dnscache *dnscache)
237
0
{
238
0
  if(data->share && dnscache == &data->share->dnscache)
239
0
    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
240
0
}
241
242
static void dnscache_unlock(struct Curl_easy *data,
243
                            struct Curl_dnscache *dnscache)
244
0
{
245
0
  if(data->share && dnscache == &data->share->dnscache)
246
0
    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
247
0
}
248
249
/*
250
 * Library-wide function for pruning the DNS cache. This function takes and
251
 * returns the appropriate locks.
252
 */
253
void Curl_dnscache_prune(struct Curl_easy *data)
254
0
{
255
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
256
  /* the timeout may be set -1 (forever) */
257
0
  timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
258
259
0
  if(!dnscache || (timeout_ms == -1))
260
    /* NULL hostcache means we cannot do it */
261
0
    return;
262
263
0
  dnscache_lock(data, dnscache);
264
265
0
  do {
266
    /* Remove outdated and unused entries from the hostcache */
267
0
    timediff_t oldest_ms =
268
0
      dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
269
270
0
    if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
271
      /* prune the ones over half this age */
272
0
      timeout_ms = oldest_ms / 2;
273
0
    else
274
0
      break;
275
276
    /* if the cache size is still too big, use the oldest age as new prune
277
       limit */
278
0
  } while(timeout_ms);
279
280
0
  dnscache_unlock(data, dnscache);
281
0
}
282
283
void Curl_dnscache_clear(struct Curl_easy *data)
284
0
{
285
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
286
0
  if(dnscache) {
287
0
    dnscache_lock(data, dnscache);
288
0
    Curl_hash_clean(&dnscache->entries);
289
0
    dnscache_unlock(data, dnscache);
290
0
  }
291
0
}
292
293
#ifdef USE_ALARM_TIMEOUT
294
/* Beware this is a global and unique instance. This is used to store the
295
   return address that we can jump back to from inside a signal handler. This
296
   is not thread-safe stuff. */
297
static sigjmp_buf curl_jmpenv;
298
static curl_simple_lock curl_jmpenv_lock;
299
#endif
300
301
/* lookup address, returns entry if found and not stale */
302
static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
303
                                         struct Curl_dnscache *dnscache,
304
                                         const char *hostname,
305
                                         int port,
306
                                         int ip_version)
307
0
{
308
0
  struct Curl_dns_entry *dns = NULL;
309
0
  char entry_id[MAX_HOSTCACHE_LEN];
310
0
  size_t entry_len;
311
312
0
  if(!dnscache)
313
0
    return NULL;
314
315
  /* Create an entry id, based upon the hostname and port */
316
0
  entry_len = create_dnscache_id(hostname, 0, port,
317
0
                                 entry_id, sizeof(entry_id));
318
319
  /* See if it is already in our dns cache */
320
0
  dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
321
322
  /* No entry found in cache, check if we might have a wildcard entry */
323
0
  if(!dns && data->state.wildcard_resolve) {
324
0
    entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
325
326
    /* See if it is already in our dns cache */
327
0
    dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
328
0
  }
329
330
0
  if(dns && (data->set.dns_cache_timeout_ms != -1)) {
331
    /* See whether the returned entry is stale. Done before we release lock */
332
0
    struct dnscache_prune_data user;
333
334
0
    user.now = *Curl_pgrs_now(data);
335
0
    user.max_age_ms = data->set.dns_cache_timeout_ms;
336
0
    user.oldest_ms = 0;
337
338
0
    if(dnscache_entry_is_stale(&user, dns)) {
339
0
      infof(data, "Hostname in DNS cache was stale, zapped");
340
0
      dns = NULL; /* the memory deallocation is being handled by the hash */
341
0
      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
342
0
    }
343
0
  }
344
345
  /* See if the returned entry matches the required resolve mode */
346
0
  if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) {
347
0
    int pf = PF_INET;
348
0
    bool found = FALSE;
349
0
    struct Curl_addrinfo *addr = dns->addr;
350
351
0
#ifdef PF_INET6
352
0
    if(ip_version == CURL_IPRESOLVE_V6)
353
0
      pf = PF_INET6;
354
0
#endif
355
356
0
    while(addr) {
357
0
      if(addr->ai_family == pf) {
358
0
        found = TRUE;
359
0
        break;
360
0
      }
361
0
      addr = addr->ai_next;
362
0
    }
363
364
0
    if(!found) {
365
0
      infof(data, "Hostname in DNS cache does not have needed family, zapped");
366
0
      dns = NULL; /* the memory deallocation is being handled by the hash */
367
0
      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
368
0
    }
369
0
  }
370
0
  return dns;
371
0
}
372
373
/*
374
 * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
375
 *
376
 * Curl_resolv() checks initially and multi_runsingle() checks each time
377
 * it discovers the handle in the state WAITRESOLVE whether the hostname
378
 * has already been resolved and the address has already been stored in
379
 * the DNS cache. This short circuits waiting for a lot of pending
380
 * lookups for the same hostname requested by different handles.
381
 *
382
 * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
383
 *
384
 * The returned data *MUST* be "released" with Curl_resolv_unlink() after
385
 * use, or we will leak memory!
386
 */
387
struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data,
388
                                         const char *hostname,
389
                                         int port,
390
                                         int ip_version)
391
0
{
392
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
393
0
  struct Curl_dns_entry *dns = NULL;
394
395
0
  dnscache_lock(data, dnscache);
396
397
0
  dns = fetch_addr(data, dnscache, hostname, port, ip_version);
398
0
  if(dns)
399
0
    dns->refcount++; /* we use it! */
400
401
0
  dnscache_unlock(data, dnscache);
402
403
0
  return dns;
404
0
}
405
406
#ifndef CURL_DISABLE_SHUFFLE_DNS
407
/*
408
 * Return # of addresses in a Curl_addrinfo struct
409
 */
410
static int num_addresses(const struct Curl_addrinfo *addr)
411
0
{
412
0
  int i = 0;
413
0
  while(addr) {
414
0
    addr = addr->ai_next;
415
0
    i++;
416
0
  }
417
0
  return i;
418
0
}
419
420
UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
421
                                    struct Curl_addrinfo **addr);
422
/*
423
 * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
424
 * struct by re-linking its linked list.
425
 *
426
 * The addr argument should be the address of a pointer to the head node of a
427
 * `Curl_addrinfo` list and it will be modified to point to the new head after
428
 * shuffling.
429
 *
430
 * Not declared static only to make it easy to use in a unit test!
431
 *
432
 * @unittest: 1608
433
 */
434
UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
435
                                    struct Curl_addrinfo **addr)
436
0
{
437
0
  CURLcode result = CURLE_OK;
438
0
  const int num_addrs = num_addresses(*addr);
439
440
0
  if(num_addrs > 1) {
441
0
    struct Curl_addrinfo **nodes;
442
0
    infof(data, "Shuffling %i addresses", num_addrs);
443
444
0
    nodes = curlx_malloc(num_addrs * sizeof(*nodes));
445
0
    if(nodes) {
446
0
      int i;
447
0
      unsigned int *rnd;
448
0
      const size_t rnd_size = num_addrs * sizeof(*rnd);
449
450
      /* build a plain array of Curl_addrinfo pointers */
451
0
      nodes[0] = *addr;
452
0
      for(i = 1; i < num_addrs; i++) {
453
0
        nodes[i] = nodes[i - 1]->ai_next;
454
0
      }
455
456
0
      rnd = curlx_malloc(rnd_size);
457
0
      if(rnd) {
458
        /* Fisher-Yates shuffle */
459
0
        if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
460
0
          struct Curl_addrinfo *swap_tmp;
461
0
          for(i = num_addrs - 1; i > 0; i--) {
462
0
            swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
463
0
            nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
464
0
            nodes[i] = swap_tmp;
465
0
          }
466
467
          /* relink list in the new order */
468
0
          for(i = 1; i < num_addrs; i++) {
469
0
            nodes[i - 1]->ai_next = nodes[i];
470
0
          }
471
472
0
          nodes[num_addrs - 1]->ai_next = NULL;
473
0
          *addr = nodes[0];
474
0
        }
475
0
        curlx_free(rnd);
476
0
      }
477
0
      else
478
0
        result = CURLE_OUT_OF_MEMORY;
479
0
      curlx_free(nodes);
480
0
    }
481
0
    else
482
0
      result = CURLE_OUT_OF_MEMORY;
483
0
  }
484
0
  return result;
485
0
}
486
#endif
487
488
struct Curl_dns_entry *
489
Curl_dnscache_mk_entry(struct Curl_easy *data,
490
                       struct Curl_addrinfo *addr,
491
                       const char *hostname,
492
                       size_t hostlen, /* length or zero */
493
                       int port,
494
                       bool permanent)
495
0
{
496
0
  struct Curl_dns_entry *dns;
497
498
0
#ifndef CURL_DISABLE_SHUFFLE_DNS
499
  /* shuffle addresses if requested */
500
0
  if(data->set.dns_shuffle_addresses) {
501
0
    CURLcode result = Curl_shuffle_addr(data, &addr);
502
0
    if(result)
503
0
      return NULL;
504
0
  }
505
#else
506
  (void)data;
507
#endif
508
0
  if(!hostlen)
509
0
    hostlen = strlen(hostname);
510
511
  /* Create a new cache entry */
512
0
  dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
513
0
  if(!dns)
514
0
    return NULL;
515
516
0
  dns->refcount = 1; /* the cache has the first reference */
517
0
  dns->addr = addr; /* this is the address(es) */
518
0
  if(permanent) {
519
0
    dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
520
0
    dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
521
0
  }
522
0
  else {
523
0
    dns->timestamp = *Curl_pgrs_now(data);
524
0
  }
525
0
  dns->hostport = port;
526
0
  if(hostlen)
527
0
    memcpy(dns->hostname, hostname, hostlen);
528
529
0
  return dns;
530
0
}
531
532
static struct Curl_dns_entry *
533
dnscache_add_addr(struct Curl_easy *data,
534
                  struct Curl_dnscache *dnscache,
535
                  struct Curl_addrinfo *addr,
536
                  const char *hostname,
537
                  size_t hlen, /* length or zero */
538
                  int port,
539
                  bool permanent)
540
0
{
541
0
  char entry_id[MAX_HOSTCACHE_LEN];
542
0
  size_t entry_len;
543
0
  struct Curl_dns_entry *dns;
544
0
  struct Curl_dns_entry *dns2;
545
546
0
  dns = Curl_dnscache_mk_entry(data, addr, hostname, hlen, port, permanent);
547
0
  if(!dns)
548
0
    return NULL;
549
550
  /* Create an entry id, based upon the hostname and port */
551
0
  entry_len = create_dnscache_id(hostname, hlen, port,
552
0
                                 entry_id, sizeof(entry_id));
553
554
  /* Store the resolved data in our DNS cache. */
555
0
  dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
556
0
                       (void *)dns);
557
0
  if(!dns2) {
558
0
    dns->addr = NULL;
559
0
    dnscache_entry_free(dns);
560
0
    return NULL;
561
0
  }
562
563
0
  dns = dns2;
564
0
  dns->refcount++;         /* mark entry as in-use */
565
0
  return dns;
566
0
}
567
568
CURLcode Curl_dnscache_add(struct Curl_easy *data,
569
                           struct Curl_dns_entry *entry)
570
0
{
571
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
572
0
  char id[MAX_HOSTCACHE_LEN];
573
0
  size_t idlen;
574
575
0
  if(!dnscache)
576
0
    return CURLE_FAILED_INIT;
577
  /* Create an entry id, based upon the hostname and port */
578
0
  idlen = create_dnscache_id(entry->hostname, 0, entry->hostport,
579
0
                             id, sizeof(id));
580
581
  /* Store the resolved data in our DNS cache and up ref count */
582
0
  dnscache_lock(data, dnscache);
583
0
  if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
584
0
    dnscache_unlock(data, dnscache);
585
0
    return CURLE_OUT_OF_MEMORY;
586
0
  }
587
0
  entry->refcount++;
588
0
  dnscache_unlock(data, dnscache);
589
0
  return CURLE_OK;
590
0
}
591
592
#ifdef USE_IPV6
593
/* return a static IPv6 ::1 for the name */
594
static struct Curl_addrinfo *get_localhost6(int port, const char *name)
595
0
{
596
0
  struct Curl_addrinfo *ca;
597
0
  const size_t ss_size = sizeof(struct sockaddr_in6);
598
0
  const size_t hostlen = strlen(name);
599
0
  struct sockaddr_in6 sa6;
600
0
  unsigned char ipv6[16];
601
0
  unsigned short port16 = (unsigned short)(port & 0xffff);
602
0
  ca = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1);
603
0
  if(!ca)
604
0
    return NULL;
605
606
0
  sa6.sin6_family = AF_INET6;
607
0
  sa6.sin6_port = htons(port16);
608
0
  sa6.sin6_flowinfo = 0;
609
0
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
610
0
  sa6.sin6_scope_id = 0;
611
0
#endif
612
613
0
  (void)curlx_inet_pton(AF_INET6, "::1", ipv6);
614
0
  memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6));
615
616
0
  ca->ai_flags     = 0;
617
0
  ca->ai_family    = AF_INET6;
618
0
  ca->ai_socktype  = SOCK_STREAM;
619
0
  ca->ai_protocol  = IPPROTO_TCP;
620
0
  ca->ai_addrlen   = (curl_socklen_t)ss_size;
621
0
  ca->ai_next      = NULL;
622
0
  ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
623
0
  memcpy(ca->ai_addr, &sa6, ss_size);
624
0
  ca->ai_canonname = (char *)ca->ai_addr + ss_size;
625
0
  curlx_strcopy(ca->ai_canonname, hostlen + 1, name, hostlen);
626
0
  return ca;
627
0
}
628
#else
629
#define get_localhost6(x, y) NULL
630
#endif
631
632
/* return a static IPv4 127.0.0.1 for the given name */
633
static struct Curl_addrinfo *get_localhost(int port, const char *name)
634
0
{
635
0
  struct Curl_addrinfo *ca;
636
0
  struct Curl_addrinfo *ca6;
637
0
  const size_t ss_size = sizeof(struct sockaddr_in);
638
0
  const size_t hostlen = strlen(name);
639
0
  struct sockaddr_in sa;
640
0
  unsigned int ipv4;
641
0
  unsigned short port16 = (unsigned short)(port & 0xffff);
642
643
  /* memset to clear the sa.sin_zero field */
644
0
  memset(&sa, 0, sizeof(sa));
645
0
  sa.sin_family = AF_INET;
646
0
  sa.sin_port = htons(port16);
647
0
  if(curlx_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1)
648
0
    return NULL;
649
0
  memcpy(&sa.sin_addr, &ipv4, sizeof(ipv4));
650
651
0
  ca = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1);
652
0
  if(!ca)
653
0
    return NULL;
654
0
  ca->ai_flags     = 0;
655
0
  ca->ai_family    = AF_INET;
656
0
  ca->ai_socktype  = SOCK_STREAM;
657
0
  ca->ai_protocol  = IPPROTO_TCP;
658
0
  ca->ai_addrlen   = (curl_socklen_t)ss_size;
659
0
  ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
660
0
  memcpy(ca->ai_addr, &sa, ss_size);
661
0
  ca->ai_canonname = (char *)ca->ai_addr + ss_size;
662
0
  curlx_strcopy(ca->ai_canonname, hostlen + 1, name, hostlen);
663
664
0
  ca6 = get_localhost6(port, name);
665
0
  if(!ca6)
666
0
    return ca;
667
0
  ca6->ai_next = ca;
668
0
  return ca6;
669
0
}
670
671
#ifdef USE_IPV6
672
/*
673
 * Curl_ipv6works() returns TRUE if IPv6 seems to work.
674
 */
675
bool Curl_ipv6works(struct Curl_easy *data)
676
0
{
677
0
  if(data) {
678
    /* the nature of most system is that IPv6 status does not come and go
679
       during a program's lifetime so we only probe the first time and then we
680
       have the info kept for fast reuse */
681
0
    DEBUGASSERT(data);
682
0
    DEBUGASSERT(data->multi);
683
0
    if(data->multi->ipv6_up == IPV6_UNKNOWN) {
684
0
      bool works = Curl_ipv6works(NULL);
685
0
      data->multi->ipv6_up = works ? IPV6_WORKS : IPV6_DEAD;
686
0
    }
687
0
    return data->multi->ipv6_up == IPV6_WORKS;
688
0
  }
689
0
  else {
690
0
    int ipv6_works = -1;
691
    /* probe to see if we have a working IPv6 stack */
692
0
    curl_socket_t s = CURL_SOCKET(PF_INET6, SOCK_DGRAM, 0);
693
0
    if(s == CURL_SOCKET_BAD)
694
      /* an IPv6 address was requested but we cannot get/use one */
695
0
      ipv6_works = 0;
696
0
    else {
697
0
      ipv6_works = 1;
698
0
      sclose(s);
699
0
    }
700
0
    return ipv6_works > 0;
701
0
  }
702
0
}
703
#endif /* USE_IPV6 */
704
705
/*
706
 * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4
707
 * (or IPv6 if supported) address.
708
 */
709
bool Curl_host_is_ipnum(const char *hostname)
710
0
{
711
0
  struct in_addr in;
712
0
#ifdef USE_IPV6
713
0
  struct in6_addr in6;
714
0
#endif
715
0
  if(curlx_inet_pton(AF_INET, hostname, &in) > 0
716
0
#ifdef USE_IPV6
717
0
     || curlx_inet_pton(AF_INET6, hostname, &in6) > 0
718
0
#endif
719
0
    )
720
0
    return TRUE;
721
0
  return FALSE;
722
0
}
723
724
/* return TRUE if 'part' is a case insensitive tail of 'full' */
725
static bool tailmatch(const char *full, size_t flen,
726
                      const char *part, size_t plen)
727
0
{
728
0
  if(plen > flen)
729
0
    return FALSE;
730
0
  return curl_strnequal(part, &full[flen - plen], plen);
731
0
}
732
733
static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version)
734
0
{
735
0
#ifdef CURLRES_IPV6
736
0
  if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data))
737
0
    return FALSE;
738
#elif defined(CURLRES_IPV4)
739
  (void)data;
740
  if(ip_version == CURL_IPRESOLVE_V6)
741
    return FALSE;
742
#else
743
#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined
744
#endif
745
0
  return TRUE;
746
0
}
747
748
static CURLcode store_negative_resolve(struct Curl_easy *data,
749
                                       const char *host,
750
                                       int port)
751
0
{
752
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
753
0
  struct Curl_dns_entry *dns;
754
0
  DEBUGASSERT(dnscache);
755
0
  if(!dnscache)
756
0
    return CURLE_FAILED_INIT;
757
758
  /* put this new host in the cache */
759
0
  dns = dnscache_add_addr(data, dnscache, NULL, host, 0, port, FALSE);
760
0
  if(dns) {
761
    /* release the returned reference; the cache itself will keep the
762
     * entry alive: */
763
0
    dns->refcount--;
764
0
    infof(data, "Store negative name resolve for %s:%d", host, port);
765
0
    return CURLE_OK;
766
0
  }
767
0
  return CURLE_OUT_OF_MEMORY;
768
0
}
769
770
/*
771
 * Curl_resolv() is the main name resolve function within libcurl. It resolves
772
 * a name and returns a pointer to the entry in the 'entry' argument. This
773
 * function might return immediately if we are using asynch resolves. See the
774
 * return codes.
775
 *
776
 * The cache entry we return will get its 'inuse' counter increased when this
777
 * function is used. You MUST call Curl_resolv_unlink() later (when you are
778
 * done using this struct) to decrease the reference counter again.
779
 *
780
 * Return codes:
781
 * CURLE_OK = success, *entry set to non-NULL
782
 * CURLE_AGAIN = resolving in progress, *entry == NULL
783
 * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
784
 * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
785
 */
786
CURLcode Curl_resolv(struct Curl_easy *data,
787
                     const char *hostname,
788
                     int port,
789
                     int ip_version,
790
                     bool allowDOH,
791
                     struct Curl_dns_entry **entry)
792
0
{
793
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
794
0
  struct Curl_dns_entry *dns = NULL;
795
0
  struct Curl_addrinfo *addr = NULL;
796
0
  bool respwait = FALSE;
797
0
  size_t hostname_len;
798
0
  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
799
800
0
  *entry = NULL;
801
802
0
#ifndef CURL_DISABLE_DOH
803
0
  data->conn->bits.doh = FALSE; /* default is not */
804
#else
805
  (void)allowDOH;
806
#endif
807
0
  DEBUGASSERT(dnscache);
808
0
  if(!dnscache) {
809
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
810
0
    goto error;
811
0
  }
812
813
  /* We should intentionally error and not resolve .onion TLDs */
814
0
  hostname_len = strlen(hostname);
815
0
  DEBUGASSERT(hostname_len);
816
0
  if(hostname_len >= 7 &&
817
0
     (curl_strequal(&hostname[hostname_len - 6], ".onion") ||
818
0
      curl_strequal(&hostname[hostname_len - 7], ".onion."))) {
819
0
    failf(data, "Not resolving .onion address (RFC 7686)");
820
0
    goto error;
821
0
  }
822
823
  /* Let's check our DNS cache first */
824
0
  dnscache_lock(data, dnscache);
825
0
  dns = fetch_addr(data, dnscache, hostname, port, ip_version);
826
0
  if(dns)
827
0
    dns->refcount++; /* we pass out the reference. */
828
0
  dnscache_unlock(data, dnscache);
829
0
  if(dns) {
830
0
    infof(data, "Hostname %s was found in DNS cache", hostname);
831
0
    result = CURLE_OK;
832
0
    goto out;
833
0
  }
834
835
  /* No luck, we need to resolve hostname. Notify user callback. */
836
0
  if(data->set.resolver_start) {
837
0
    void *resolver = NULL;
838
0
    int st;
839
#ifdef CURLRES_ASYNCH
840
    result = Curl_async_get_impl(data, &resolver);
841
    if(result)
842
      goto error;
843
#endif
844
0
    Curl_set_in_callback(data, TRUE);
845
0
    st = data->set.resolver_start(resolver, NULL,
846
0
                                  data->set.resolver_start_client);
847
0
    Curl_set_in_callback(data, FALSE);
848
0
    if(st) {
849
0
      result = CURLE_ABORTED_BY_CALLBACK;
850
0
      goto error;
851
0
    }
852
0
  }
853
854
0
  if(Curl_is_ipaddr(hostname)) {
855
0
#ifndef USE_RESOLVE_ON_IPS
856
    /* shortcut literal IP addresses, if we are not told to resolve them. */
857
0
    result = Curl_str2addr(hostname, port, &addr);
858
0
    if(result)
859
0
      goto error;
860
0
    goto out;
861
0
#endif
862
0
  }
863
864
0
  if(curl_strequal(hostname, "localhost") ||
865
0
     curl_strequal(hostname, "localhost.") ||
866
0
     tailmatch(hostname, hostname_len, STRCONST(".localhost")) ||
867
0
     tailmatch(hostname, hostname_len, STRCONST(".localhost."))) {
868
0
    addr = get_localhost(port, hostname);
869
0
    result = addr ? CURLE_OK : CURLE_OUT_OF_MEMORY;
870
0
  }
871
0
#ifndef CURL_DISABLE_DOH
872
0
  else if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) {
873
0
    result = Curl_doh(data, hostname, port, ip_version);
874
0
    respwait = TRUE;
875
0
  }
876
0
#endif
877
0
  else {
878
    /* Can we provide the requested IP specifics in resolving? */
879
0
    if(!can_resolve_ip_version(data, ip_version)) {
880
0
      result = CURLE_COULDNT_RESOLVE_HOST;
881
0
      goto error;
882
0
    }
883
884
#ifdef CURLRES_ASYNCH
885
    result = Curl_async_getaddrinfo(data, hostname, port, ip_version);
886
    respwait = TRUE;
887
#else
888
0
    respwait = FALSE; /* no async waiting here */
889
0
    addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version);
890
0
    if(addr)
891
0
      result = CURLE_OK;
892
0
#endif
893
0
  }
894
895
0
out:
896
  /* We either have found a `dns` or looked up the `addr` or `respwait` is set
897
   * for an async operation. Everything else is a failure to resolve. */
898
0
  if(result)
899
0
    ;
900
0
  else if(dns) {
901
0
    if(!dns->addr) {
902
0
      infof(data, "Negative DNS entry");
903
0
      dns->refcount--;
904
0
      return CURLE_COULDNT_RESOLVE_HOST;
905
0
    }
906
0
    *entry = dns;
907
0
    return CURLE_OK;
908
0
  }
909
0
  else if(addr) {
910
    /* we got a response, create a dns entry, add to cache, return */
911
0
    dns = Curl_dnscache_mk_entry(data, addr, hostname, 0, port, FALSE);
912
0
    if(!dns || Curl_dnscache_add(data, dns)) {
913
      /* this is OOM or similar, do not store such negative resolves */
914
0
      Curl_freeaddrinfo(addr);
915
0
      if(dns)
916
        /* avoid a dangling pointer to addr in the dying dns entry */
917
0
        dns->addr = NULL;
918
0
      result = CURLE_OUT_OF_MEMORY;
919
0
      goto error;
920
0
    }
921
0
    show_resolve_info(data, dns);
922
0
    *entry = dns;
923
0
    return CURLE_OK;
924
0
  }
925
0
  else if(respwait) {
926
0
    if(!Curl_resolv_check(data, &dns)) {
927
0
      *entry = dns;
928
0
      return dns ? CURLE_OK : CURLE_AGAIN;
929
0
    }
930
0
    result = CURLE_COULDNT_RESOLVE_HOST;
931
0
  }
932
0
error:
933
0
  if(dns)
934
0
    Curl_resolv_unlink(data, &dns);
935
0
  Curl_async_shutdown(data);
936
0
  if(result == CURLE_COULDNT_RESOLVE_HOST)
937
0
    store_negative_resolve(data, hostname, port);
938
0
  DEBUGASSERT(result);
939
0
  return result;
940
0
}
941
942
CURLcode Curl_resolv_blocking(struct Curl_easy *data,
943
                              const char *hostname,
944
                              int port,
945
                              int ip_version,
946
                              struct Curl_dns_entry **dnsentry)
947
0
{
948
0
  CURLcode result;
949
0
  DEBUGASSERT(hostname && *hostname);
950
0
  *dnsentry = NULL;
951
0
  result = Curl_resolv(data, hostname, port, ip_version, FALSE, dnsentry);
952
0
  switch(result) {
953
0
  case CURLE_OK:
954
0
    DEBUGASSERT(*dnsentry);
955
0
    return CURLE_OK;
956
0
  case CURLE_AGAIN:
957
0
    DEBUGASSERT(!*dnsentry);
958
0
    result = Curl_async_await(data, dnsentry);
959
0
    if(result || !*dnsentry) {
960
      /* close the connection, since we cannot return failure here without
961
         cleaning up this connection properly. */
962
0
      connclose(data->conn, "async resolve failed");
963
0
    }
964
0
    return result;
965
0
  default:
966
0
    return result;
967
0
  }
968
0
}
969
970
#ifdef USE_ALARM_TIMEOUT
971
/*
972
 * This signal handler jumps back into the main libcurl code and continues
973
 * execution. This effectively causes the remainder of the application to run
974
 * within a signal handler which is nonportable and could lead to problems.
975
 */
976
CURL_NORETURN static void alarmfunc(int sig)
977
{
978
  (void)sig;
979
  siglongjmp(curl_jmpenv, 1);
980
}
981
#endif /* USE_ALARM_TIMEOUT */
982
983
/*
984
 * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
985
 * timeout. This function might return immediately if we are using asynch
986
 * resolves. See the return codes.
987
 *
988
 * The cache entry we return will get its 'inuse' counter increased when this
989
 * function is used. You MUST call Curl_resolv_unlink() later (when you are
990
 * done using this struct) to decrease the reference counter again.
991
 *
992
 * If built with a synchronous resolver and use of signals is not
993
 * disabled by the application, then a nonzero timeout will cause a
994
 * timeout after the specified number of milliseconds. Otherwise, timeout
995
 * is ignored.
996
 *
997
 * Return codes:
998
 * CURLE_OK = success, *entry set to non-NULL
999
 * CURLE_AGAIN = resolving in progress, *entry == NULL
1000
 * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
1001
 * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
1002
 */
1003
1004
CURLcode Curl_resolv_timeout(struct Curl_easy *data,
1005
                             const char *hostname,
1006
                             int port,
1007
                             int ip_version,
1008
                             struct Curl_dns_entry **entry,
1009
                             timediff_t timeoutms)
1010
0
{
1011
#ifdef USE_ALARM_TIMEOUT
1012
#ifdef HAVE_SIGACTION
1013
  struct sigaction keep_sigact; /* store the old struct here */
1014
  volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
1015
  struct sigaction sigact;
1016
#else
1017
#ifdef HAVE_SIGNAL
1018
  void (*keep_sigact)(int);       /* store the old handler here */
1019
#endif /* HAVE_SIGNAL */
1020
#endif /* HAVE_SIGACTION */
1021
  volatile long timeout;
1022
  volatile unsigned int prev_alarm = 0;
1023
#endif /* USE_ALARM_TIMEOUT */
1024
0
  CURLcode result;
1025
1026
0
  DEBUGASSERT(hostname && *hostname);
1027
0
  *entry = NULL;
1028
1029
0
  if(timeoutms < 0)
1030
    /* got an already expired timeout */
1031
0
    return CURLE_OPERATION_TIMEDOUT;
1032
1033
#ifdef USE_ALARM_TIMEOUT
1034
  if(data->set.no_signal)
1035
    /* Ignore the timeout when signals are disabled */
1036
    timeout = 0;
1037
  else
1038
    timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
1039
1040
  if(!timeout
1041
#ifndef CURL_DISABLE_DOH
1042
     || data->set.doh
1043
#endif
1044
    )
1045
    /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve
1046
       done using DoH */
1047
    return Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
1048
1049
  if(timeout < 1000) {
1050
    /* The alarm() function only provides integer second resolution, so if
1051
       we want to wait less than one second we must bail out already now. */
1052
    failf(data,
1053
          "remaining timeout of %ld too small to resolve via SIGALRM method",
1054
          timeout);
1055
    return CURLE_OPERATION_TIMEDOUT;
1056
  }
1057
  /* This allows us to time-out from the name resolver, as the timeout
1058
     will generate a signal and we will siglongjmp() from that here.
1059
     This technique has problems (see alarmfunc).
1060
     This should be the last thing we do before calling Curl_resolv(),
1061
     as otherwise we would have to worry about variables that get modified
1062
     before we invoke Curl_resolv() (and thus use "volatile"). */
1063
  curl_simple_lock_lock(&curl_jmpenv_lock);
1064
1065
  if(sigsetjmp(curl_jmpenv, 1)) {
1066
    /* this is coming from a siglongjmp() after an alarm signal */
1067
    failf(data, "name lookup timed out");
1068
    result = CURLE_OPERATION_TIMEDOUT;
1069
    goto clean_up;
1070
  }
1071
  else {
1072
    /*************************************************************
1073
     * Set signal handler to catch SIGALRM
1074
     * Store the old value to be able to set it back later!
1075
     *************************************************************/
1076
#ifdef HAVE_SIGACTION
1077
    sigaction(SIGALRM, NULL, &sigact);
1078
    keep_sigact = sigact;
1079
    keep_copysig = TRUE; /* yes, we have a copy */
1080
    sigact.sa_handler = alarmfunc;
1081
#ifdef SA_RESTART
1082
    /* HP-UX does not have SA_RESTART but defaults to that behavior! */
1083
    sigact.sa_flags &= ~SA_RESTART;
1084
#endif
1085
    /* now set the new struct */
1086
    sigaction(SIGALRM, &sigact, NULL);
1087
#else /* HAVE_SIGACTION */
1088
    /* no sigaction(), revert to the much lamer signal() */
1089
#ifdef HAVE_SIGNAL
1090
    keep_sigact = signal(SIGALRM, alarmfunc);
1091
#endif
1092
#endif /* HAVE_SIGACTION */
1093
1094
    /* alarm() makes a signal get sent when the timeout fires off, and that
1095
       will abort system calls */
1096
    prev_alarm = alarm(curlx_sltoui(timeout / 1000L));
1097
  }
1098
1099
#else /* !USE_ALARM_TIMEOUT */
1100
0
#ifndef CURLRES_ASYNCH
1101
0
  if(timeoutms)
1102
0
    infof(data, "timeout on name lookup is not supported");
1103
#else
1104
  (void)timeoutms;
1105
#endif
1106
0
#endif /* USE_ALARM_TIMEOUT */
1107
1108
  /* Perform the actual name resolution. This might be interrupted by an
1109
   * alarm if it takes too long.
1110
   */
1111
0
  result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
1112
1113
#ifdef USE_ALARM_TIMEOUT
1114
clean_up:
1115
1116
  if(!prev_alarm)
1117
    /* deactivate a possibly active alarm before uninstalling the handler */
1118
    alarm(0);
1119
1120
#ifdef HAVE_SIGACTION
1121
  if(keep_copysig) {
1122
    /* we got a struct as it looked before, now put that one back nice
1123
       and clean */
1124
    sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
1125
  }
1126
#else
1127
#ifdef HAVE_SIGNAL
1128
  /* restore the previous SIGALRM handler */
1129
  signal(SIGALRM, keep_sigact);
1130
#endif
1131
#endif /* HAVE_SIGACTION */
1132
1133
  curl_simple_lock_unlock(&curl_jmpenv_lock);
1134
1135
  /* switch back the alarm() to either zero or to what it was before minus
1136
     the time we spent until now! */
1137
  if(prev_alarm) {
1138
    /* there was an alarm() set before us, now put it back */
1139
    timediff_t elapsed_secs = curlx_ptimediff_ms(Curl_pgrs_now(data),
1140
                                                 &data->conn->created) / 1000;
1141
1142
    /* the alarm period is counted in even number of seconds */
1143
    unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs);
1144
1145
    if(!alarm_set ||
1146
       ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000))) {
1147
      /* if the alarm time-left reached zero or turned "negative" (counted
1148
         with unsigned values), we should fire off a SIGALRM here, but we
1149
         will not, and zero would be to switch it off so we never set it to
1150
         less than 1! */
1151
      alarm(1);
1152
      result = CURLE_OPERATION_TIMEDOUT;
1153
      failf(data, "Previous alarm fired off");
1154
    }
1155
    else
1156
      alarm((unsigned int)alarm_set);
1157
  }
1158
#endif /* USE_ALARM_TIMEOUT */
1159
1160
0
  return result;
1161
0
}
1162
1163
static void dnscache_entry_free(struct Curl_dns_entry *dns)
1164
0
{
1165
0
  Curl_freeaddrinfo(dns->addr);
1166
#ifdef USE_HTTPSRR
1167
  if(dns->hinfo) {
1168
    Curl_httpsrr_cleanup(dns->hinfo);
1169
    curlx_free(dns->hinfo);
1170
  }
1171
#endif
1172
0
  curlx_free(dns);
1173
0
}
1174
1175
/*
1176
 * Curl_resolv_unlink() releases a reference to the given cached DNS entry.
1177
 * When the reference count reaches 0, the entry is destroyed. It is important
1178
 * that only one unlink is made for each Curl_resolv() call.
1179
 *
1180
 * May be called with 'data' == NULL for global cache.
1181
 */
1182
void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns)
1183
0
{
1184
0
  if(*pdns) {
1185
0
    struct Curl_dnscache *dnscache = dnscache_get(data);
1186
0
    struct Curl_dns_entry *dns = *pdns;
1187
0
    *pdns = NULL;
1188
0
    dnscache_lock(data, dnscache);
1189
0
    dns->refcount--;
1190
0
    if(dns->refcount == 0)
1191
0
      dnscache_entry_free(dns);
1192
0
    dnscache_unlock(data, dnscache);
1193
0
  }
1194
0
}
1195
1196
static void dnscache_entry_dtor(void *entry)
1197
0
{
1198
0
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
1199
0
  DEBUGASSERT(dns && (dns->refcount > 0));
1200
0
  dns->refcount--;
1201
0
  if(dns->refcount == 0)
1202
0
    dnscache_entry_free(dns);
1203
0
}
1204
1205
/*
1206
 * Curl_dnscache_init() inits a new DNS cache.
1207
 */
1208
void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
1209
0
{
1210
0
  Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
1211
0
                 dnscache_entry_dtor);
1212
0
}
1213
1214
void Curl_dnscache_destroy(struct Curl_dnscache *dns)
1215
0
{
1216
0
  Curl_hash_destroy(&dns->entries);
1217
0
}
1218
1219
CURLcode Curl_loadhostpairs(struct Curl_easy *data)
1220
0
{
1221
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
1222
0
  struct curl_slist *hostp;
1223
1224
0
  if(!dnscache)
1225
0
    return CURLE_FAILED_INIT;
1226
1227
  /* Default is no wildcard found */
1228
0
  data->state.wildcard_resolve = FALSE;
1229
1230
0
  for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
1231
0
    char entry_id[MAX_HOSTCACHE_LEN];
1232
0
    const char *host = hostp->data;
1233
0
    struct Curl_str source;
1234
0
    if(!host)
1235
0
      continue;
1236
0
    if(*host == '-') {
1237
0
      curl_off_t num = 0;
1238
0
      size_t entry_len;
1239
0
      host++;
1240
0
      if(!curlx_str_single(&host, '[')) {
1241
0
        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
1242
0
           curlx_str_single(&host, ']') ||
1243
0
           curlx_str_single(&host, ':'))
1244
0
          continue;
1245
0
      }
1246
0
      else {
1247
0
        if(curlx_str_until(&host, &source, 4096, ':') ||
1248
0
           curlx_str_single(&host, ':')) {
1249
0
          continue;
1250
0
        }
1251
0
      }
1252
1253
0
      if(!curlx_str_number(&host, &num, 0xffff)) {
1254
        /* Create an entry id, based upon the hostname and port */
1255
0
        entry_len = create_dnscache_id(curlx_str(&source),
1256
0
                                       curlx_strlen(&source), (int)num,
1257
0
                                       entry_id, sizeof(entry_id));
1258
0
        dnscache_lock(data, dnscache);
1259
        /* delete entry, ignore if it did not exist */
1260
0
        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
1261
0
        dnscache_unlock(data, dnscache);
1262
0
      }
1263
0
    }
1264
0
    else {
1265
0
      struct Curl_dns_entry *dns;
1266
0
      struct Curl_addrinfo *head = NULL, *tail = NULL;
1267
0
      size_t entry_len;
1268
0
      char address[64];
1269
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1270
0
      const char *addresses = NULL;
1271
0
#endif
1272
0
      curl_off_t port = 0;
1273
0
      bool permanent = TRUE;
1274
0
      bool error = TRUE;
1275
1276
0
      if(*host == '+') {
1277
0
        host++;
1278
0
        permanent = FALSE;
1279
0
      }
1280
0
      if(!curlx_str_single(&host, '[')) {
1281
0
        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
1282
0
           curlx_str_single(&host, ']'))
1283
0
          continue;
1284
0
      }
1285
0
      else {
1286
0
        if(curlx_str_until(&host, &source, 4096, ':'))
1287
0
          continue;
1288
0
      }
1289
0
      if(curlx_str_single(&host, ':') ||
1290
0
         curlx_str_number(&host, &port, 0xffff) ||
1291
0
         curlx_str_single(&host, ':'))
1292
0
        goto err;
1293
1294
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1295
0
      addresses = host;
1296
0
#endif
1297
1298
      /* start the address section */
1299
0
      while(*host) {
1300
0
        struct Curl_str target;
1301
0
        struct Curl_addrinfo *ai;
1302
0
        CURLcode result;
1303
1304
0
        if(!curlx_str_single(&host, '[')) {
1305
0
          if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
1306
0
             curlx_str_single(&host, ']'))
1307
0
            goto err;
1308
0
        }
1309
0
        else {
1310
0
          if(curlx_str_until(&host, &target, 4096, ',')) {
1311
0
            if(curlx_str_single(&host, ','))
1312
0
              goto err;
1313
            /* survive nothing but just a comma */
1314
0
            continue;
1315
0
          }
1316
0
        }
1317
#ifndef USE_IPV6
1318
        if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
1319
          infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
1320
                (int)curlx_strlen(&target), curlx_str(&target));
1321
          if(curlx_str_single(&host, ','))
1322
            goto err;
1323
          continue;
1324
        }
1325
#endif
1326
1327
0
        if(curlx_strlen(&target) >= sizeof(address))
1328
0
          goto err;
1329
1330
0
        memcpy(address, curlx_str(&target), curlx_strlen(&target));
1331
0
        address[curlx_strlen(&target)] = '\0';
1332
1333
0
        result = Curl_str2addr(address, (int)port, &ai);
1334
0
        if(result) {
1335
0
          infof(data, "Resolve address '%s' found illegal", address);
1336
0
          goto err;
1337
0
        }
1338
1339
0
        if(tail) {
1340
0
          tail->ai_next = ai;
1341
0
          tail = tail->ai_next;
1342
0
        }
1343
0
        else {
1344
0
          head = tail = ai;
1345
0
        }
1346
0
        if(curlx_str_single(&host, ','))
1347
0
          break;
1348
0
      }
1349
1350
0
      if(!head)
1351
0
        goto err;
1352
1353
0
      error = FALSE;
1354
0
err:
1355
0
      if(error) {
1356
0
        failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
1357
0
        Curl_freeaddrinfo(head);
1358
0
        return CURLE_SETOPT_OPTION_SYNTAX;
1359
0
      }
1360
1361
      /* Create an entry id, based upon the hostname and port */
1362
0
      entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
1363
0
                                     (int)port,
1364
0
                                     entry_id, sizeof(entry_id));
1365
1366
0
      dnscache_lock(data, dnscache);
1367
1368
      /* See if it is already in our dns cache */
1369
0
      dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
1370
1371
0
      if(dns) {
1372
0
        infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T
1373
0
              " - old addresses discarded", (int)curlx_strlen(&source),
1374
0
              curlx_str(&source), port);
1375
        /* delete old entry, there are two reasons for this
1376
         1. old entry may have different addresses.
1377
         2. even if entry with correct addresses is already in the cache,
1378
            but if it is close to expire, then by the time next http
1379
            request is made, it can get expired and pruned because old
1380
            entry is not necessarily marked as permanent.
1381
         3. when adding a non-permanent entry, we want it to remove and
1382
            replace an existing permanent entry.
1383
         4. when adding a non-permanent entry, we want it to get a "fresh"
1384
            timeout that starts _now_. */
1385
1386
0
        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
1387
0
      }
1388
1389
      /* put this new host in the cache */
1390
0
      dns = dnscache_add_addr(data, dnscache, head, curlx_str(&source),
1391
0
                              curlx_strlen(&source), (int)port, permanent);
1392
0
      if(dns)
1393
        /* release the returned reference; the cache itself will keep the
1394
         * entry alive: */
1395
0
        dns->refcount--;
1396
0
      else
1397
0
        Curl_freeaddrinfo(head);
1398
1399
0
      dnscache_unlock(data, dnscache);
1400
1401
0
      if(!dns)
1402
0
        return CURLE_OUT_OF_MEMORY;
1403
1404
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1405
0
      infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s",
1406
0
            (int)curlx_strlen(&source), curlx_str(&source), port, addresses,
1407
0
            permanent ? "" : " (non-permanent)");
1408
0
#endif
1409
1410
      /* Wildcard hostname */
1411
0
      if(curlx_str_casecompare(&source, "*")) {
1412
0
        infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard",
1413
0
              port);
1414
0
        data->state.wildcard_resolve = TRUE;
1415
0
      }
1416
0
    }
1417
0
  }
1418
0
  data->state.resolve = NULL; /* dealt with now */
1419
1420
0
  return CURLE_OK;
1421
0
}
1422
1423
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1424
static void show_resolve_info(struct Curl_easy *data,
1425
                              struct Curl_dns_entry *dns)
1426
0
{
1427
0
  struct Curl_addrinfo *a;
1428
0
  CURLcode result = CURLE_OK;
1429
0
#ifdef CURLRES_IPV6
1430
0
  struct dynbuf out[2];
1431
#else
1432
  struct dynbuf out[1];
1433
#endif
1434
0
  DEBUGASSERT(data);
1435
0
  DEBUGASSERT(dns);
1436
1437
0
  if(!data->set.verbose ||
1438
     /* ignore no name or numerical IP addresses */
1439
0
     !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname))
1440
0
    return;
1441
1442
0
  a = dns->addr;
1443
1444
0
  infof(data, "Host %s:%d was resolved.",
1445
0
        (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport);
1446
1447
0
  curlx_dyn_init(&out[0], 1024);
1448
0
#ifdef CURLRES_IPV6
1449
0
  curlx_dyn_init(&out[1], 1024);
1450
0
#endif
1451
1452
0
  while(a) {
1453
0
    if(
1454
0
#ifdef CURLRES_IPV6
1455
0
       a->ai_family == PF_INET6 ||
1456
0
#endif
1457
0
       a->ai_family == PF_INET) {
1458
0
      char buf[MAX_IPADR_LEN];
1459
0
      struct dynbuf *d = &out[(a->ai_family != PF_INET)];
1460
0
      Curl_printable_address(a, buf, sizeof(buf));
1461
0
      if(curlx_dyn_len(d))
1462
0
        result = curlx_dyn_addn(d, ", ", 2);
1463
0
      if(!result)
1464
0
        result = curlx_dyn_add(d, buf);
1465
0
      if(result) {
1466
0
        infof(data, "too many IP, cannot show");
1467
0
        goto fail;
1468
0
      }
1469
0
    }
1470
0
    a = a->ai_next;
1471
0
  }
1472
1473
0
#ifdef CURLRES_IPV6
1474
0
  infof(data, "IPv6: %s",
1475
0
        (curlx_dyn_len(&out[1]) ? curlx_dyn_ptr(&out[1]) : "(none)"));
1476
0
#endif
1477
0
  infof(data, "IPv4: %s",
1478
0
        (curlx_dyn_len(&out[0]) ? curlx_dyn_ptr(&out[0]) : "(none)"));
1479
1480
0
fail:
1481
0
  curlx_dyn_free(&out[0]);
1482
0
#ifdef CURLRES_IPV6
1483
0
  curlx_dyn_free(&out[1]);
1484
0
#endif
1485
0
}
1486
#endif
1487
1488
#ifdef USE_CURL_ASYNC
1489
CURLcode Curl_resolv_check(struct Curl_easy *data,
1490
                           struct Curl_dns_entry **dns)
1491
0
{
1492
0
  CURLcode result;
1493
1494
  /* If async resolving is ongoing, this must be set */
1495
0
  if(!data->state.async.hostname)
1496
0
    return CURLE_FAILED_INIT;
1497
1498
  /* check if we have the name resolved by now (from someone else) */
1499
0
  *dns = Curl_dnscache_get(data, data->state.async.hostname,
1500
0
                           data->state.async.port,
1501
0
                           data->state.async.ip_version);
1502
0
  if(*dns) {
1503
    /* Tell a possibly async resolver we no longer need the results. */
1504
0
    infof(data, "Hostname '%s' was found in DNS cache",
1505
0
          data->state.async.hostname);
1506
0
    Curl_async_shutdown(data);
1507
0
    data->state.async.dns = *dns;
1508
0
    data->state.async.done = TRUE;
1509
0
    return CURLE_OK;
1510
0
  }
1511
1512
0
#ifndef CURL_DISABLE_DOH
1513
0
  if(data->conn->bits.doh) {
1514
0
    result = Curl_doh_is_resolved(data, dns);
1515
0
    if(result)
1516
0
      Curl_resolver_error(data, NULL);
1517
0
  }
1518
0
  else
1519
0
#endif
1520
0
  result = Curl_async_is_resolved(data, dns);
1521
0
  if(*dns)
1522
0
    show_resolve_info(data, *dns);
1523
0
  if((result == CURLE_COULDNT_RESOLVE_HOST) ||
1524
0
     (result == CURLE_COULDNT_RESOLVE_PROXY))
1525
0
    store_negative_resolve(data, data->state.async.hostname,
1526
0
                           data->state.async.port);
1527
0
  return result;
1528
0
}
1529
#endif
1530
1531
CURLcode Curl_resolv_pollset(struct Curl_easy *data,
1532
                             struct easy_pollset *ps)
1533
0
{
1534
#ifdef CURLRES_ASYNCH
1535
#ifndef CURL_DISABLE_DOH
1536
  if(data->conn->bits.doh)
1537
    /* nothing to wait for during DoH resolve, those handles have their own
1538
       sockets */
1539
    return CURLE_OK;
1540
#endif
1541
  return Curl_async_pollset(data, ps);
1542
#else
1543
0
  (void)data;
1544
0
  (void)ps;
1545
0
  return CURLE_OK;
1546
0
#endif
1547
0
}
1548
1549
/* Call this function after Curl_connect() has returned async=TRUE and
1550
   then a successful name resolve has been received.
1551
1552
   Note: this function disconnects and frees the conn data in case of
1553
   resolve failure */
1554
CURLcode Curl_once_resolved(struct Curl_easy *data,
1555
                            struct Curl_dns_entry *dns,
1556
                            bool *protocol_done)
1557
0
{
1558
0
  CURLcode result;
1559
0
  struct connectdata *conn = data->conn;
1560
1561
0
#ifdef USE_CURL_ASYNC
1562
0
  if(data->state.async.dns) {
1563
0
    DEBUGASSERT(data->state.async.dns == dns);
1564
0
    data->state.async.dns = NULL;
1565
0
  }
1566
0
#endif
1567
1568
0
  result = Curl_setup_conn(data, dns, protocol_done);
1569
1570
0
  if(result) {
1571
0
    Curl_detach_connection(data);
1572
0
    Curl_conn_terminate(data, conn, TRUE);
1573
0
  }
1574
0
  return result;
1575
0
}
1576
1577
/*
1578
 * Curl_resolver_error() calls failf() with the appropriate message after a
1579
 * resolve error
1580
 */
1581
1582
#ifdef USE_CURL_ASYNC
1583
CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail)
1584
0
{
1585
0
  struct connectdata *conn = data->conn;
1586
0
  const char *host_or_proxy = "host";
1587
0
  const char *name = conn->host.dispname;
1588
0
  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
1589
1590
0
#ifndef CURL_DISABLE_PROXY
1591
0
  if(conn->bits.proxy) {
1592
0
    host_or_proxy = "proxy";
1593
0
    result = CURLE_COULDNT_RESOLVE_PROXY;
1594
0
    name = conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname :
1595
0
      conn->http_proxy.host.dispname;
1596
0
  }
1597
0
#endif
1598
1599
0
  failf(data, "Could not resolve %s: %s%s%s%s", host_or_proxy, name,
1600
0
        detail ? " (" : "", detail ? detail : "", detail ? ")" : "");
1601
0
  return result;
1602
0
}
1603
#endif /* USE_CURL_ASYNC */