Retry only for https protocol
[elinks.git] / src / network / dns.c
blobf409dda7e23aa8040ca33bc27c1e6477b0f679c6
1 /* Domain Name System Resolver Department */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/types.h>
10 #ifdef HAVE_NETDB_H
11 #include <netdb.h> /* OS/2 needs this after sys/types.h */
12 #endif
13 #ifdef HAVE_SYS_SOCKET_H
14 #include <sys/socket.h> /* OS/2 needs this after sys/types.h */
15 #endif
16 #ifdef HAVE_FCNTL_H
17 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
18 #endif
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif
23 /* Go and say 'thanks' to BSD. */
24 #ifdef HAVE_NETINET_IN_H
25 #include <netinet/in.h>
26 #endif
27 #ifdef HAVE_ARPA_INET_H
28 #include <arpa/inet.h>
29 #endif
31 #include "elinks.h"
33 #include "config/options.h"
34 #include "main/select.h"
35 #include "network/dns.h"
36 #include "osdep/osdep.h"
37 #include "protocol/uri.h"
38 #include "util/error.h"
39 #include "util/memory.h"
40 #include "util/time.h"
43 struct dnsentry {
44 LIST_HEAD(struct dnsentry);
46 struct sockaddr_storage *addr; /* Pointer to array of addresses. */
47 int addrno; /* Adress array length. */
48 timeval_T creation_time; /* Creation time; let us do timeouts. */
49 unsigned char name[1]; /* Associated host; XXX: Must be last. */
52 struct dnsquery {
53 #ifdef THREAD_SAFE_LOOKUP
54 struct dnsquery *next_in_queue; /* Got queued? */
55 #endif
56 dns_callback_T done; /* Used for reporting back DNS result. */
57 void *data; /* Private callback data. */
59 /* The @done callback is called with these members. Thus, when
60 * free()ing, *always* set pointer to NULL ! */
61 struct sockaddr_storage *addr; /* Reference to array of addresses. */
62 int addrno; /* Reference to array len. */
64 /* As with the two members above, when stopping a DNS query *always* set
65 * this pointer to NULL. */
66 struct dnsquery **queryref; /* Reference to callers DNS member. */
68 #ifndef NO_ASYNC_LOOKUP
69 int h; /* One end of the async thread pipe. */
70 #endif
71 unsigned char name[1]; /* Associated host; XXX: Must be last. */
75 #ifdef THREAD_SAFE_LOOKUP
76 static struct dnsquery *dns_queue = NULL;
77 #endif
79 static INIT_LIST_OF(struct dnsentry, dns_cache);
81 static void done_dns_lookup(struct dnsquery *query, enum dns_result res);
84 /* DNS cache management: */
86 static struct dnsentry *
87 find_in_dns_cache(unsigned char *name)
89 struct dnsentry *dnsentry;
91 foreach (dnsentry, dns_cache)
92 if (!c_strcasecmp(dnsentry->name, name)) {
93 move_to_top_of_list(dns_cache, dnsentry);
94 return dnsentry;
97 return NULL;
100 static void
101 add_to_dns_cache(unsigned char *name, struct sockaddr_storage *addr, int addrno)
103 int namelen = strlen(name);
104 struct dnsentry *dnsentry;
105 int size;
107 assert(addrno > 0);
109 dnsentry = mem_calloc(1, sizeof(*dnsentry) + namelen);
110 if (!dnsentry) return;
112 size = addrno * sizeof(*dnsentry->addr);
113 dnsentry->addr = mem_alloc(size);
114 if (!dnsentry->addr) {
115 mem_free(dnsentry);
116 return;
119 /* calloc() sets NUL char for us. */
120 memcpy(dnsentry->name, name, namelen);
121 memcpy(dnsentry->addr, addr, size);;
123 dnsentry->addrno = addrno;
125 timeval_now(&dnsentry->creation_time);
126 add_to_list(dns_cache, dnsentry);
129 static void
130 del_dns_cache_entry(struct dnsentry *dnsentry)
132 del_from_list(dnsentry);
133 mem_free_if(dnsentry->addr);
134 mem_free(dnsentry);
138 /* Synchronous DNS lookup management: */
140 enum dns_result
141 do_real_lookup(unsigned char *name, struct sockaddr_storage **addrs, int *addrno,
142 int in_thread)
144 #ifdef CONFIG_IPV6
145 struct addrinfo hint, *ai, *ai_cur;
146 #else
147 struct hostent *hostent = NULL;
148 #endif
149 int i;
151 if (!name || !addrs || !addrno)
152 return DNS_ERROR;
154 #ifdef CONFIG_IPV6
155 /* I had a strong preference for the following, but the glibc is really
156 * obsolete so I had to rather use much more complicated getaddrinfo().
157 * But we duplicate the code terribly here :|. */
158 /* hostent = getipnodebyname(name, AF_INET6, AI_ALL | AI_ADDRCONFIG, NULL); */
159 memset(&hint, 0, sizeof(hint));
160 hint.ai_family = AF_UNSPEC;
161 hint.ai_socktype = SOCK_STREAM;
162 if (getaddrinfo(name, NULL, &hint, &ai) != 0) return DNS_ERROR;
164 #else
165 /* Seems there are problems on Mac, so we first need to try
166 * gethostbyaddr(), but there are problems with gethostbyaddr on Cygwin,
167 * so we do not use gethostbyaddr there. */
168 #if defined(HAVE_GETHOSTBYADDR) && !defined(HAVE_SYS_CYGWIN_H)
170 struct in_addr inp;
172 if (is_ip_address(name, strlen(name)) && inet_aton(name, &inp))
173 hostent = gethostbyaddr(&inp, sizeof(inp), AF_INET);
175 if (!hostent)
176 #endif
178 hostent = gethostbyname(name);
179 if (!hostent) return DNS_ERROR;
181 #endif
183 #ifdef CONFIG_IPV6
184 for (i = 0, ai_cur = ai; ai_cur; i++, ai_cur = ai_cur->ai_next);
185 #else
186 for (i = 0; hostent->h_addr_list[i] != NULL; i++);
187 #endif
189 /* We cannot use mem_*() in thread ("It will chew memory on OS/2 and
190 * BeOS because there are no locks around the memory debugging code."
191 * -- Mikulas). So we don't if in_thread != 0. */
192 *addrs = in_thread ? calloc(i, sizeof(**addrs))
193 : mem_calloc(i, sizeof(**addrs));
194 if (!*addrs) return DNS_ERROR;
195 *addrno = i;
197 #ifdef CONFIG_IPV6
198 for (i = 0, ai_cur = ai; ai_cur; i++, ai_cur = ai_cur->ai_next) {
199 /* Don't use struct sockaddr_in6 here: because we
200 * called getaddrinfo with AF_UNSPEC, the address
201 * might not be for IP at all. */
202 struct sockaddr_storage *addr = &(*addrs)[i];
204 /* RFC 3493 says struct sockaddr_storage is supposed
205 * to be "Large enough to accommodate all supported
206 * protocol-specific address structures." So if
207 * getaddrinfo supports an address that does not fit
208 * in struct sockaddr_storage, then it is a bug in the
209 * library. In this case, fail the whole lookup, to
210 * make the bug more likely to be noticed. */
211 assert(ai_cur->ai_addrlen <= sizeof(*addr));
212 if_assert_failed {
213 freeaddrinfo(ai);
214 if (in_thread)
215 free(*addrs);
216 else
217 mem_free(*addrs);
218 *addrs = NULL;
219 *addrno = 0;
220 return DNS_ERROR;
223 memcpy(addr, ai_cur->ai_addr, ai_cur->ai_addrlen);
226 freeaddrinfo(ai);
228 #else
229 for (i = 0; hostent->h_addr_list[i] != NULL; i++) {
230 struct sockaddr_in *addr = (struct sockaddr_in *) &(*addrs)[i];
232 addr->sin_family = hostent->h_addrtype;
233 memcpy(&addr->sin_addr.s_addr, hostent->h_addr_list[i], hostent->h_length);
235 #endif
237 return DNS_SUCCESS;
241 /* Asynchronous DNS lookup management: */
243 #ifndef NO_ASYNC_LOOKUP
244 static enum dns_result
245 write_dns_data(int h, void *data, size_t datalen)
247 size_t done = 0;
249 do {
250 int w = safe_write(h, data + done, datalen - done);
252 if (w < 0) return DNS_ERROR;
253 done += w;
254 } while (done < datalen);
256 assert(done == datalen);
258 return DNS_SUCCESS;
261 static void
262 async_dns_writer(void *data, int h)
264 unsigned char *name = (unsigned char *) data;
265 struct sockaddr_storage *addrs;
266 int addrno, i;
268 if (do_real_lookup(name, &addrs, &addrno, 1) == DNS_ERROR)
269 return;
271 /* We will do blocking I/O here, however it's only local communication
272 * and it's supposed to be just a flash talk, so it shouldn't matter.
273 * And it would be incredibly more complicated and messy (and mainly
274 * useless) to do this in non-blocking way. */
275 if (set_blocking_fd(h) < 0) return;
277 if (write_dns_data(h, &addrno, sizeof(addrno)) == DNS_ERROR)
278 return;
280 for (i = 0; i < addrno; i++) {
281 struct sockaddr_storage *addr = &addrs[i];
283 if (write_dns_data(h, addr, sizeof(*addr)) == DNS_ERROR)
284 return;
287 /* We're in thread, thus we must do plain free(). */
288 free(addrs);
291 static enum dns_result
292 read_dns_data(int h, void *data, size_t datalen)
294 size_t done = 0;
296 do {
297 ssize_t r = safe_read(h, data + done, datalen - done);
299 if (r <= 0) return DNS_ERROR;
300 done += r;
301 } while (done < datalen);
303 assert(done == datalen);
305 return DNS_SUCCESS;
308 static void
309 async_dns_reader(struct dnsquery *query)
311 enum dns_result result = DNS_ERROR;
312 int i;
314 /* We will do blocking I/O here, however it's only local communication
315 * and it's supposed to be just a flash talk, so it shouldn't matter.
316 * And it would be incredibly more complicated and messy (and mainly
317 * useless) to do this in non-blocking way. */
318 if (set_blocking_fd(query->h) < 0) goto done;
320 if (read_dns_data(query->h, &query->addrno, sizeof(query->addrno)) == DNS_ERROR)
321 goto done;
323 query->addr = mem_calloc(query->addrno, sizeof(*query->addr));
324 if (!query->addr) goto done;
326 for (i = 0; i < query->addrno; i++) {
327 struct sockaddr_storage *addr = &query->addr[i];
329 if (read_dns_data(query->h, addr, sizeof(*addr)) == DNS_ERROR)
330 goto done;
333 result = DNS_SUCCESS;
335 done:
336 if (result == DNS_ERROR)
337 mem_free_set(&query->addr, NULL);
339 done_dns_lookup(query, result);
342 static void
343 async_dns_error(struct dnsquery *query)
345 done_dns_lookup(query, DNS_ERROR);
348 static int
349 init_async_dns_lookup(struct dnsquery *dnsquery, int force_async)
351 if (!force_async && !get_opt_bool("connection.async_dns", NULL)) {
352 dnsquery->h = -1;
353 return 0;
356 dnsquery->h = start_thread(async_dns_writer, dnsquery->name,
357 strlen(dnsquery->name) + 1);
358 if (dnsquery->h == -1)
359 return 0;
361 set_handlers(dnsquery->h, (select_handler_T) async_dns_reader, NULL,
362 (select_handler_T) async_dns_error, dnsquery);
364 return 1;
367 static void
368 done_async_dns_lookup(struct dnsquery *dnsquery)
370 if (dnsquery->h == -1) return;
372 clear_handlers(dnsquery->h);
373 close(dnsquery->h);
374 dnsquery->h = -1;
376 #else
377 #define init_async_dns_lookup(dnsquery, force) (0)
378 #define done_async_dns_lookup(dnsquery) /* Nada. */
379 #endif /* NO_ASYNC_LOOKUP */
382 static enum dns_result
383 do_lookup(struct dnsquery *query, int force_async)
385 enum dns_result result;
387 /* DBG("starting lookup for %s", query->name); */
389 /* Async lookup */
390 if (init_async_dns_lookup(query, force_async))
391 return DNS_ASYNC;
393 /* Sync lookup */
394 result = do_real_lookup(query->name, &query->addr, &query->addrno, 0);
395 done_dns_lookup(query, result);
397 return result;
400 static enum dns_result
401 do_queued_lookup(struct dnsquery *query)
403 #ifdef THREAD_SAFE_LOOKUP
404 query->next_in_queue = NULL;
406 if (dns_queue) {
407 /* DBG("queuing lookup for %s", q->name); */
408 assertm(!dns_queue->next_in_queue, "DNS queue corrupted");
409 dns_queue->next_in_queue = query;
410 dns_queue = query;
411 return DNS_ERROR;
414 dns_queue = query;
415 #endif
416 /* DBG("direct lookup"); */
417 return do_lookup(query, 0);
421 static void
422 done_dns_lookup(struct dnsquery *query, enum dns_result result)
424 struct dnsentry *dnsentry;
426 /* DBG("end lookup %s (%d)", query->name, res); */
428 /* do_lookup() might start a new async thread */
429 done_async_dns_lookup(query);
431 #ifdef THREAD_SAFE_LOOKUP
432 if (query->next_in_queue) {
433 /* DBG("processing next in queue: %s", query->next_in_queue->name); */
434 do_lookup(query->next_in_queue, 1);
435 } else {
436 dns_queue = NULL;
438 #endif
440 /* Make sure the query is unregister _before_ calling any callbacks. */
441 *query->queryref = NULL;
443 /* If the callback was cleared skip to the freeing part. */
444 if (!query->done)
445 goto done;
447 dnsentry = find_in_dns_cache(query->name);
448 if (dnsentry) {
449 /* If the query failed, use the existing DNS cache entry even if
450 * it is too old. */
451 if (result == DNS_ERROR) {
452 query->done(query->data, dnsentry->addr, dnsentry->addrno);
453 goto done;
456 del_dns_cache_entry(dnsentry);
459 if (result == DNS_SUCCESS)
460 add_to_dns_cache(query->name, query->addr, query->addrno);
462 query->done(query->data, query->addr, query->addrno);
464 done:
465 mem_free_set(&query->addr, NULL);
466 mem_free(query);
469 static enum dns_result
470 init_dns_lookup(unsigned char *name, void **queryref,
471 dns_callback_T done, void *data)
473 struct dnsquery *query;
474 int namelen = strlen(name);
476 query = mem_calloc(1, sizeof(*query) + namelen);
477 if (!query) {
478 done(data, NULL, 0);
479 return DNS_ERROR;
482 query->done = done;
483 query->data = data;
485 /* calloc() sets NUL char for us. */
486 memcpy(query->name, name, namelen);
488 query->queryref = (struct dnsquery **) queryref;
489 *(query->queryref) = query;
491 return do_queued_lookup(query);
495 enum dns_result
496 find_host(unsigned char *name, void **queryref,
497 dns_callback_T done, void *data, int no_cache)
499 struct dnsentry *dnsentry;
501 assert(queryref);
502 *queryref = NULL;
504 if (no_cache)
505 return init_dns_lookup(name, queryref, done, data);
507 /* Check if the DNS name is in the cache. If the cache entry is too old
508 * do a new lookup. However, old cache entries will be used as a
509 * fallback if the new lookup fails. */
510 dnsentry = find_in_dns_cache(name);
511 if (dnsentry) {
512 timeval_T age, now, max_age;
514 assert(dnsentry && dnsentry->addrno > 0);
516 timeval_from_seconds(&max_age, DNS_CACHE_TIMEOUT);
517 timeval_now(&now);
518 timeval_sub(&age, &dnsentry->creation_time, &now);
520 if (timeval_cmp(&age, &max_age) <= 0) {
521 done(data, dnsentry->addr, dnsentry->addrno);
522 return DNS_SUCCESS;
526 return init_dns_lookup(name, queryref, done, data);
529 void
530 kill_dns_request(void **queryref)
532 struct dnsquery *query = *queryref;
534 assert(query);
536 query->done = NULL;
537 done_dns_lookup(query, DNS_ERROR);
540 void
541 shrink_dns_cache(int whole)
543 struct dnsentry *dnsentry, *next;
545 if (whole) {
546 foreachsafe (dnsentry, next, dns_cache)
547 del_dns_cache_entry(dnsentry);
549 } else {
550 timeval_T now, max_age;
552 timeval_from_seconds(&max_age, DNS_CACHE_TIMEOUT);
553 timeval_now(&now);
555 foreachsafe (dnsentry, next, dns_cache) {
556 timeval_T age;
558 timeval_sub(&age, &dnsentry->creation_time, &now);
560 if (timeval_cmp(&age, &max_age) > 0)
561 del_dns_cache_entry(dnsentry);