Coverage Report

Created: 2025-12-04 06:52

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