Change the SoupURI properties to SoupAddress properties.
[platform/upstream/libsoup.git] / libsoup / soup-dns.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-dns.c: Async DNS code
4  *
5  * Copyright (C) 2000-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <errno.h>
13 #include <stdlib.h>
14 #include <signal.h>
15 #include <string.h>
16 #include <time.h>
17 #include <unistd.h>
18 #include <sys/types.h>
19
20 #include "soup-dns.h"
21 #include "soup-misc.h"
22 #include "soup-status.h"
23
24 #ifndef INET_ADDRSTRLEN
25 #  define INET_ADDRSTRLEN 16
26 #  define INET6_ADDRSTRLEN 46
27 #endif
28
29 #ifndef INADDR_NONE
30 #define INADDR_NONE -1
31 #endif
32
33 #ifdef HAVE_IPV6
34 #define SOUP_DNS_SOCKADDR_LEN(sa) \
35         (sa->sa_family == AF_INET ? sizeof (struct sockaddr_in) : \
36                                     sizeof (struct sockaddr_in6))
37 #else
38 #define SOUP_DNS_SOCKADDR_LEN(sa) sizeof (struct sockaddr_in)
39 #endif
40
41 #ifdef G_OS_WIN32
42
43 static int
44 inet_pton(int af, const char* src, void* dst)
45 {
46         int address_length;
47         struct sockaddr_storage sa;
48         struct sockaddr_in *sin = (struct sockaddr_in *)&sa;
49         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa;
50
51         switch (af) {
52         case AF_INET:
53                 address_length = sizeof (struct sockaddr_in);
54                 break;
55                 
56         case AF_INET6:
57                 address_length = sizeof (struct sockaddr_in6);
58                 break;
59                 
60         default:
61                 return -1;
62         }
63         
64         if (WSAStringToAddress ((LPTSTR) src, af, NULL, (LPSOCKADDR) &sa, &address_length) == 0) {
65                 switch (af) {
66                 case AF_INET:
67                         memcpy (dst, &sin->sin_addr, sizeof (struct in_addr));
68                         break;
69
70                 case AF_INET6:
71                         memcpy (dst, &sin6->sin6_addr, sizeof (struct in6_addr));
72                         break;
73                 }
74                 return 1;
75         }
76         
77         return 0;
78 }
79
80 static const char* 
81 inet_ntop(int af, const void* src, char* dst, size_t size)
82 {
83         int address_length;
84         DWORD string_length = size;
85         struct sockaddr_storage sa;
86         struct sockaddr_in *sin = (struct sockaddr_in *)&sa;
87         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa;
88         
89         memset (&sa, 0, sizeof (sa));
90         switch (af) {
91         case AF_INET:
92                 address_length = sizeof (struct sockaddr_in);
93                 sin->sin_family = af;
94                 memcpy (&sin->sin_addr, src, sizeof (struct in_addr));
95                 break;
96                 
97         case AF_INET6:
98                 address_length = sizeof (struct sockaddr_in6);
99                 sin6->sin6_family = af;
100                 memcpy (&sin6->sin6_addr, src, sizeof (struct in6_addr));
101                 break;
102                 
103         default:
104                 return NULL;
105         }
106         
107         if (WSAAddressToString ((LPSOCKADDR) &sa, address_length, NULL,
108                                 dst, &string_length) == 0)
109                 return dst;
110         
111         return NULL;
112 }
113
114 #endif
115
116 typedef struct {
117         char *entry_name;
118         guint ref_count;
119         time_t expires;
120
121         char *hostname;
122         struct sockaddr *sockaddr;
123
124         gboolean resolved;
125         GThread *resolver_thread;
126         GSList *async_lookups;
127 } SoupDNSCacheEntry;
128
129 static GHashTable *soup_dns_cache;
130 #define SOUP_DNS_CACHE_MAX 20
131
132 struct SoupDNSLookup {
133         SoupDNSCacheEntry *entry;
134
135         GMainContext *async_context;
136         GCancellable *cancellable;
137         SoupDNSCallback callback;
138         gpointer user_data;
139 };
140
141 static GMutex *soup_dns_lock;
142 static GCond *soup_dns_cond;
143
144 #if !defined (HAVE_GETADDRINFO) || !defined (HAVE_GETNAMEINFO)
145 static GMutex *soup_gethost_lock;
146 #endif
147
148 /**
149  * soup_dns_init:
150  *
151  * Initializes the libsoup DNS system. Must be called before any other
152  * soup_dns method is called. Normally this gets called automatically
153  * by #SoupAddress's class_init function.
154  **/
155 void
156 soup_dns_init (void)
157 {
158         static volatile gsize inited_dns = 0;
159
160         if (g_once_init_enter (&inited_dns)) {
161                 soup_dns_cache = g_hash_table_new (soup_str_case_hash, soup_str_case_equal);
162                 soup_dns_lock = g_mutex_new ();
163                 soup_dns_cond = g_cond_new ();
164 #if !defined (HAVE_GETADDRINFO) || !defined (HAVE_GETNAMEINFO)
165                 soup_gethost_lock = g_mutex_new ();
166 #endif
167
168                 g_once_init_leave (&inited_dns, TRUE);
169         }
170 }
171
172 static void
173 prune_cache_cb (gpointer key, gpointer value, gpointer data)
174 {
175         SoupDNSCacheEntry *entry = value, **prune_entry = data; 
176
177         if (!*prune_entry || (*prune_entry)->expires > entry->expires)
178                 *prune_entry = entry;
179 }
180
181 static void
182 soup_dns_cache_entry_set_from_phys (SoupDNSCacheEntry *entry)
183 {
184         struct sockaddr_in sin;
185 #ifdef HAVE_IPV6
186         struct sockaddr_in6 sin6;
187 #endif
188
189 #ifdef HAVE_IPV6
190         memset (&sin6, 0, sizeof (struct sockaddr_in6));
191         if (inet_pton (AF_INET6, entry->entry_name, &sin6.sin6_addr) != 0) {
192                 entry->sockaddr = g_memdup (&sin6, sizeof (struct sockaddr_in6));
193                 entry->sockaddr->sa_family = AF_INET6;
194                 return;
195         }
196 #endif /* HAVE_IPV6 */
197
198         memset (&sin, 0, sizeof (struct sockaddr_in));
199         if (
200 #if defined(HAVE_INET_PTON)
201                 inet_pton (AF_INET, entry->entry_name, &sin.sin_addr) != 0
202 #elif defined(HAVE_INET_ATON)
203                 inet_aton (entry->entry_name, &sin.sin_addr) != 0
204 #else
205                 (sin.sin_addr.s_addr = inet_addr (entry->entry_name)) &&
206                 (sin.sin_addr.s_addr != INADDR_NONE)
207 #endif
208                 ) {
209                 entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in));
210                 entry->sockaddr->sa_family = AF_INET;
211                 return;
212         }
213 }
214
215 static void
216 soup_dns_cache_entry_ref (SoupDNSCacheEntry *entry)
217 {
218         entry->ref_count++;
219 }
220
221 static void
222 soup_dns_cache_entry_unref (SoupDNSCacheEntry *entry)
223 {
224         if (--entry->ref_count == 0) {
225                 g_free (entry->entry_name);
226                 g_free (entry->hostname);
227                 g_free (entry->sockaddr);
228
229                 /* If there were lookups pending, ref_count couldn't
230                  * have reached zero. So no cleanup needed there.
231                  */
232
233                 g_slice_free (SoupDNSCacheEntry, entry);
234         }
235 }
236
237 static SoupDNSCacheEntry *
238 soup_dns_cache_entry_new (const char *name)
239 {
240         SoupDNSCacheEntry *entry;
241
242         entry = g_slice_new0 (SoupDNSCacheEntry);
243         entry->entry_name = g_strdup (name);
244         entry->ref_count = 2; /* One for the caller, one for the cache */
245         soup_dns_cache_entry_set_from_phys (entry);
246
247         if (g_hash_table_size (soup_dns_cache) == SOUP_DNS_CACHE_MAX) {
248                 SoupDNSCacheEntry *prune_entry = NULL;
249
250                 g_hash_table_foreach (soup_dns_cache, prune_cache_cb, &prune_entry);
251                 if (prune_entry) {
252                         g_hash_table_remove (soup_dns_cache, prune_entry->entry_name);
253                         soup_dns_cache_entry_unref (prune_entry);
254                 }
255         }
256
257         entry->expires = time (0) + 60 * 60;
258         g_hash_table_insert (soup_dns_cache, entry->entry_name, entry);
259
260         return entry;
261 }
262
263 /**
264  * soup_dns_ntop:
265  * @sa: pointer to a #sockaddr
266  *
267  * Converts @sa's address into textual form (eg, "141.213.8.59"), like
268  * the standard library function inet_ntop(), except that the returned
269  * string must be freed.
270  *
271  * Return value: the text form or @sa, which must be freed.
272  **/
273 char *
274 soup_dns_ntop (struct sockaddr *sa)
275 {
276         switch (sa->sa_family) {
277         case AF_INET:
278         {
279                 struct sockaddr_in *sin = (struct sockaddr_in *)sa;
280 #ifdef HAVE_INET_NTOP
281                 char buffer[INET_ADDRSTRLEN];
282
283                 inet_ntop (AF_INET, &sin->sin_addr, buffer, sizeof (buffer));
284                 return g_strdup (buffer);
285 #else
286                 return g_strdup (inet_ntoa (sin->sin_addr));
287 #endif
288         }
289
290 #ifdef HAVE_IPV6
291         case AF_INET6:
292         {
293                 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
294                 char buffer[INET6_ADDRSTRLEN];
295
296                 inet_ntop (AF_INET6, &sin6->sin6_addr, buffer, sizeof (buffer));
297                 return g_strdup (buffer);
298         }
299 #endif
300
301         default:
302                 return NULL;
303         }
304 }
305
306 gboolean
307 soup_dns_is_ip_address (const char *name)
308 {
309         struct sockaddr_in sin;
310 #ifdef HAVE_IPV6
311         struct sockaddr_in6 sin6;
312
313         if (inet_pton (AF_INET, name, &sin.sin_addr) > 0 ||
314             inet_pton (AF_INET6, name, &sin6.sin6_addr) > 0)
315                 return TRUE;
316 #else /* !HAVE_IPV6 */
317 #if defined(HAVE_INET_PTON)
318         if (inet_pton (AF_INET, name, &sin.sin_addr) > 0)
319                 return TRUE;
320 #elif defined(HAVE_INET_ATON)
321         if (inet_aton (name, &sin.sin_addr) != 0)
322                 return TRUE;
323 #else
324         if (inet_addr (name) != INADDR_NONE)
325                 return TRUE;
326 #endif
327 #endif /* HAVE_IPV6 */
328         return FALSE;
329 }
330
331 static void
332 resolve_address (SoupDNSCacheEntry *entry)
333 {
334 #if defined (HAVE_GETADDRINFO)
335
336         struct addrinfo hints, *res;
337         int retval;
338
339         memset (&hints, 0, sizeof (struct addrinfo));
340 #  ifdef AI_ADDRCONFIG
341         hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
342 #  else
343         hints.ai_flags = AI_CANONNAME;
344 #  endif
345         hints.ai_family = PF_UNSPEC;
346         hints.ai_socktype = SOCK_STREAM;
347         hints.ai_protocol = IPPROTO_TCP;
348
349         retval = getaddrinfo (entry->hostname, NULL, &hints, &res);
350         if (retval == 0) {
351                 entry->sockaddr = g_memdup (res->ai_addr, res->ai_addrlen);
352                 entry->resolved = TRUE;
353                 freeaddrinfo (res);
354         }
355
356 #else /* !HAVE_GETADDRINFO */
357
358         struct hostent *h;
359
360         g_mutex_lock (soup_gethost_lock);
361
362         h = gethostbyname (entry->hostname);
363         if (h && h->h_addrtype == AF_INET) {
364                 struct sockaddr_in sin;
365                 memset (&sin, 0, sizeof (struct sockaddr_in));
366                 sin.sin_family = AF_INET;
367                 memcpy (&sin.sin_addr, h->h_addr_list[0], sizeof (struct in_addr));
368                 entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in));
369                 entry->resolved = TRUE;
370         }
371
372         g_mutex_unlock (soup_gethost_lock);
373
374 #endif
375 }
376
377 static void
378 resolve_name (SoupDNSCacheEntry *entry)
379 {
380 #ifdef HAVE_GETNAMEINFO
381         int retval, len = 0;
382         char *name = NULL;
383
384         do {
385                 len += 128;
386                 name = g_realloc (name, len);
387                 retval = getnameinfo (entry->sockaddr, SOUP_DNS_SOCKADDR_LEN (entry->sockaddr),
388                                       name, len, NULL, 0, NI_NAMEREQD);
389         } while (
390 #ifdef EAI_OVERFLOW
391                 retval == EAI_OVERFLOW
392 #else
393                 strlen (name) == len - 1
394 #endif
395                 );
396
397         if (retval == 0) {
398                 entry->hostname = name;
399                 entry->resolved = TRUE;
400         } else
401                 g_free (name);
402
403 #else /* !HAVE_GETNAMEINFO */
404
405         struct sockaddr_in *sin = (struct sockaddr_in *)entry->sockaddr;
406         struct hostent *h;
407
408         g_mutex_lock (soup_gethost_lock);
409
410         if (sin->sin_family == AF_INET) {
411                 h = gethostbyaddr (&sin->sin_addr, sizeof (sin->sin_addr), AF_INET);
412                 if (h) {
413                         entry->hostname = g_strdup (h->h_name);
414                         entry->resolved = TRUE;
415                 }
416         }
417
418         g_mutex_unlock (soup_gethost_lock);
419
420 #endif /* HAVE_GETNAMEINFO */
421 }
422
423 /* Assumes soup_dns_lock is held */
424 static SoupDNSCacheEntry *
425 soup_dns_cache_entry_lookup (const char *name)
426 {
427         SoupDNSCacheEntry *entry;
428
429         entry = g_hash_table_lookup (soup_dns_cache, name);
430         if (entry)
431                 soup_dns_cache_entry_ref (entry);
432         return entry;
433 }
434
435 /**
436  * soup_dns_lookup_name:
437  * @name: a hostname (eg, "www.gnome.org") or physical address
438  * (eg, "12.107.209.247").
439  *
440  * Creates a #SoupDNSLookup for @name. This should be passed to
441  * soup_dns_lookup_resolve() or soup_dns_lookup_resolve_async().
442  *
443  * Returns: a #SoupDNSLookup, which should eventually be freed with
444  * soup_dns_lookup_free().
445  **/
446 SoupDNSLookup *
447 soup_dns_lookup_name (const char *name)
448 {
449         SoupDNSCacheEntry *entry;
450         SoupDNSLookup *lookup;
451
452         g_mutex_lock (soup_dns_lock);
453
454         entry = soup_dns_cache_entry_lookup (name);
455         if (!entry) {
456                 entry = soup_dns_cache_entry_new (name);
457                 entry->hostname = g_strdup (name);
458                 if (entry->sockaddr)
459                         entry->resolved = TRUE;
460         }
461
462         lookup = g_slice_new0 (SoupDNSLookup);
463         lookup->entry = entry;
464         g_mutex_unlock (soup_dns_lock);
465
466         return lookup;
467 }
468
469 /**
470  * soup_dns_lookup_address:
471  * @sockaddr: pointer to a #sockaddr
472  *
473  * Creates a #SoupDNSLookup for @sockaddr. This should be passed to
474  * soup_dns_lookup_resolve() or soup_dns_lookup_resolve_async().
475  *
476  * Returns: a #SoupDNSLookup, which should eventually be freed with
477  * soup_dns_lookup_free()
478  **/
479 SoupDNSLookup *
480 soup_dns_lookup_address (struct sockaddr *sockaddr)
481 {
482         SoupDNSCacheEntry *entry;
483         SoupDNSLookup *lookup;
484         char *name;
485
486         name = soup_dns_ntop (sockaddr);
487         g_return_val_if_fail (name != NULL, NULL);
488
489         g_mutex_lock (soup_dns_lock);
490
491         entry = soup_dns_cache_entry_lookup (name);
492         if (!entry)
493                 entry = soup_dns_cache_entry_new (name); /* FIXME */
494         g_free (name);
495
496         lookup = g_slice_new0 (SoupDNSLookup);
497         lookup->entry = entry;
498         g_mutex_unlock (soup_dns_lock);
499
500         return lookup;
501 }
502
503 static inline guint
504 resolve_status (SoupDNSCacheEntry *entry, GCancellable *cancellable)
505 {
506         if (entry->hostname && entry->sockaddr)
507                 return SOUP_STATUS_OK;
508         else if (g_cancellable_is_cancelled (cancellable))
509                 return SOUP_STATUS_CANCELLED;
510         else
511                 return SOUP_STATUS_CANT_RESOLVE;
512 }
513
514 static void async_cancel (GCancellable *cancellable, gpointer user_data);
515
516 static gboolean
517 do_async_callback (gpointer user_data)
518 {
519         SoupDNSLookup *lookup = user_data;
520         SoupDNSCacheEntry *entry = lookup->entry;
521         GCancellable *cancellable = lookup->cancellable;
522
523         if (cancellable)
524                 g_signal_handlers_disconnect_by_func (cancellable, async_cancel, lookup);
525         lookup->callback (lookup, resolve_status (entry, cancellable),
526                           lookup->user_data);
527
528         return FALSE;
529 }
530
531 static gpointer
532 resolver_thread (gpointer user_data)
533 {
534         SoupDNSCacheEntry *entry = user_data;
535         GSList *async_lookups;
536         SoupDNSLookup *lookup;
537
538         if (entry->hostname == NULL)
539                 resolve_name (entry);
540         else if (entry->sockaddr == NULL)
541                 resolve_address (entry);
542
543         g_mutex_lock (soup_dns_lock);
544         entry->resolver_thread = NULL;
545
546         async_lookups = entry->async_lookups;
547         entry->async_lookups = NULL;
548         g_mutex_unlock (soup_dns_lock);
549
550         g_cond_broadcast (soup_dns_cond);
551
552         while (async_lookups) {
553                 lookup = async_lookups->data;
554                 async_lookups = g_slist_remove (async_lookups, lookup);
555
556                 soup_add_completion (lookup->async_context, do_async_callback, lookup);
557         }
558
559         soup_dns_cache_entry_unref (entry);
560         return NULL;
561 }
562
563 static void
564 sync_cancel (GCancellable *cancellable, gpointer user_data)
565 {
566         /* We can't actually cancel the resolver thread. So we just
567          * wake up the blocking thread, which will see that
568          * @cancellable has been cancelled and then stop waiting for
569          * the result. If the resolver thread eventually finishes,
570          * its result will make it to the cache.
571          */
572         g_cond_broadcast (soup_dns_cond);
573 }
574
575 /**
576  * soup_dns_lookup_resolve:
577  * @lookup: a #SoupDNSLookup
578  * @cancellable: a #GCancellable, or %NULL
579  *
580  * Synchronously resolves @lookup.
581  *
582  * Return value: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or
583  * %SOUP_STATUS_CANCELLED
584  **/
585 guint
586 soup_dns_lookup_resolve (SoupDNSLookup *lookup, GCancellable *cancellable)
587 {
588         SoupDNSCacheEntry *entry = lookup->entry;
589         guint cancel_id = 0;
590
591         g_mutex_lock (soup_dns_lock);
592
593         if (!entry->resolved) {
594                 if (!entry->resolver_thread) {
595                         soup_dns_cache_entry_ref (entry);
596                         entry->resolver_thread =
597                                 g_thread_create (resolver_thread, entry,
598                                                  FALSE, NULL);
599                 }
600
601                 if (cancellable) {
602                         cancel_id = g_signal_connect (cancellable, "cancelled",
603                                                       G_CALLBACK (sync_cancel),
604                                                       NULL);
605                 }
606         }
607
608         while (entry->resolver_thread &&
609                !g_cancellable_is_cancelled (cancellable))
610                 g_cond_wait (soup_dns_cond, soup_dns_lock);
611
612         if (cancel_id)
613                 g_signal_handler_disconnect (cancellable, cancel_id);
614
615         g_mutex_unlock (soup_dns_lock);
616
617         return resolve_status (entry, cancellable);
618 }
619
620 static void
621 async_cancel (GCancellable *cancellable, gpointer user_data)
622 {
623         SoupDNSLookup *lookup = user_data;
624         SoupDNSCacheEntry *entry = lookup->entry;
625
626         /* We can't actually cancel the resolver thread. So we just
627          * remove @lookup from the list of pending async lookups and
628          * invoke its callback now. If the resolver thread eventually
629          * finishes, its result will make it to the cache.
630          */
631         g_mutex_lock (soup_dns_lock);
632
633         if (g_slist_find (entry->async_lookups, lookup)) {
634                 entry->async_lookups = g_slist_remove (entry->async_lookups,
635                                                        lookup);
636                 soup_add_completion (lookup->async_context, do_async_callback, lookup);
637         }
638
639         g_mutex_unlock (soup_dns_lock);
640 }
641
642 /**
643  * soup_dns_lookup_resolve_async:
644  * @lookup: a #SoupDNSLookup
645  * @async_context: #GMainContext to call @callback in
646  * @cancellable: a #GCancellable, or %NULL
647  * @callback: callback to call when @lookup is resolved
648  * @user_data: data to pass to @callback;
649  *
650  * Tries to asynchronously resolve @lookup. Invokes @callback when it
651  * has succeeded or failed.
652  **/
653 void
654 soup_dns_lookup_resolve_async (SoupDNSLookup *lookup,
655                                GMainContext *async_context,
656                                GCancellable *cancellable,
657                                SoupDNSCallback callback, gpointer user_data)
658 {
659         SoupDNSCacheEntry *entry = lookup->entry;
660
661         g_mutex_lock (soup_dns_lock);
662
663         lookup->async_context = async_context;
664         lookup->cancellable = cancellable;
665         lookup->callback = callback;
666         lookup->user_data = user_data;
667
668         if (!entry->resolved) {
669                 entry->async_lookups = g_slist_prepend (entry->async_lookups,
670                                                         lookup);
671                 if (cancellable) {
672                         g_signal_connect (cancellable, "cancelled",
673                                           G_CALLBACK (async_cancel), lookup);
674                 }
675
676                 if (!entry->resolver_thread) {
677                         soup_dns_cache_entry_ref (entry);
678                         entry->resolver_thread =
679                                 g_thread_create (resolver_thread, entry,
680                                                  FALSE, NULL);
681                 }
682         } else
683                 soup_add_completion (lookup->async_context, do_async_callback, lookup);
684
685         g_mutex_unlock (soup_dns_lock);
686 }
687
688 /**
689  * soup_dns_lookup_get_hostname:
690  * @lookup: a #SoupDNSLookup
691  *
692  * Gets the hostname of @lookup.
693  *
694  * Return value: the hostname, which the caller owns and must free, or
695  * %NULL if @lookup has not been completely resolved.
696  **/
697 char *
698 soup_dns_lookup_get_hostname (SoupDNSLookup *lookup)
699 {
700         return g_strdup (lookup->entry->hostname);
701 }
702
703 /**
704  * soup_dns_lookup_get_address:
705  * @lookup: a #SoupDNSLookup
706  *
707  * Gets the address of @lookup.
708  *
709  * Return value: the address, which the caller owns and must free, or
710  * %NULL if @lookup has not been completely resolved.
711  **/
712 struct sockaddr *
713 soup_dns_lookup_get_address (SoupDNSLookup *lookup)
714 {
715         return g_memdup (lookup->entry->sockaddr,
716                          SOUP_DNS_SOCKADDR_LEN (lookup->entry->sockaddr));
717 }
718
719 /**
720  * soup_dns_lookup_free:
721  * @lookup: a #SoupDNSLookup
722  *
723  * Frees @lookup. It is an error to cancel a lookup while it is
724  * running.
725  **/
726 void
727 soup_dns_lookup_free (SoupDNSLookup *lookup)
728 {
729         soup_dns_cache_entry_unref (lookup->entry);
730         g_slice_free (SoupDNSLookup, lookup);
731 }