Beginnings of improved synchronous API support
[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/select.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21
22 #include <arpa/inet.h>
23 #include <netinet/in.h>
24
25 #include "soup-dns.h"
26 #include "soup-private.h"
27
28 #ifndef socklen_t
29 #  define socklen_t int
30 #endif
31
32 #ifndef INET_ADDRSTRLEN
33 #  define INET_ADDRSTRLEN 16
34 #  define INET6_ADDRSTRLEN 46
35 #endif
36
37 #ifndef INADDR_NONE
38 #define INADDR_NONE -1
39 #endif
40
41 static struct hostent *
42 new_hostent (const char *name, int type, int length, gpointer addr)
43 {
44         struct hostent *h;
45
46         h = g_new0 (struct hostent, 1);
47         h->h_name = g_strdup (name);
48         h->h_aliases = NULL;
49         h->h_addrtype = type;
50         h->h_length = length;
51         h->h_addr_list = g_new (char *, 2);
52         h->h_addr_list[0] = g_memdup (addr, length);
53         h->h_addr_list[1] = NULL;
54
55         return h;
56 }
57
58 static struct hostent *
59 copy_hostent (struct hostent *h)
60 {
61         return new_hostent (h->h_name, h->h_addrtype,
62                             h->h_length, h->h_addr_list[0]);
63 }
64
65 void
66 soup_dns_free_hostent (struct hostent *h)
67 {
68         g_free (h->h_name);
69         g_free (h->h_addr_list[0]);
70         g_free (h->h_addr_list);
71         g_free (h);
72 }
73
74 static void
75 write_hostent (struct hostent *h, int fd)
76 {
77         guchar namelen = strlen (h->h_name) + 1;
78         guchar addrlen = h->h_length;
79         guchar addrtype = h->h_addrtype;
80         struct iovec iov[5];
81
82         iov[0].iov_base = &namelen;
83         iov[0].iov_len = 1;
84         iov[1].iov_base = h->h_name;
85         iov[1].iov_len = namelen;
86         iov[2].iov_base = &addrtype;
87         iov[2].iov_len = 1;
88         iov[3].iov_base = &addrlen;
89         iov[3].iov_len = 1;
90         iov[4].iov_base = h->h_addr_list[0];
91         iov[4].iov_len = addrlen;
92
93         if (writev (fd, iov, 5) == -1)
94                 g_warning ("Problem writing to pipe");
95 }
96
97 static struct hostent *
98 new_hostent_from_phys (const char *addr)
99 {
100         struct in_addr inaddr;
101 #ifdef HAVE_IPV6
102         struct in6_addr inaddr6;
103 #endif
104
105 #if defined(HAVE_INET_PTON)
106 #ifdef HAVE_IPV6
107         if (inet_pton (AF_INET6, addr, &inaddr6) != 0)
108                 return new_hostent (addr, AF_INET6, sizeof (inaddr6), &inaddr6);
109         else
110 #endif
111         if (inet_pton (AF_INET, addr, &inaddr) != 0)
112                 return new_hostent (addr, AF_INET, sizeof (inaddr), &inaddr);
113 #elif defined(HAVE_INET_ATON)
114         if (inet_aton (addr, &inaddr) != 0)
115                 return new_hostent (addr, AF_INET, sizeof (inaddr), &inaddr);
116 #else
117         inaddr.s_addr = inet_addr (addr);
118         if (inaddr.s_addr != INADDR_NONE)
119                 return new_hostent (addr, AF_INET, sizeof (inaddr), &inaddr);
120 #endif
121
122         return NULL;
123 }
124
125 char *
126 soup_dns_ntop (gconstpointer addr, int family)
127 {
128         switch (family) {
129         case AF_INET:
130         {
131 #ifdef HAVE_INET_NTOP
132                 char buffer[INET_ADDRSTRLEN];
133
134                 inet_ntop (family, addr, buffer, sizeof (buffer));
135                 return g_strdup (buffer);
136 #else
137                 return g_strdup (inet_ntoa (*(struct in_addr *)addr));
138 #endif
139         }
140
141 #ifdef HAVE_IPV6
142         case AF_INET6:
143         {
144                 char buffer[INET6_ADDRSTRLEN];
145
146                 inet_ntop (family, addr, buffer, sizeof (buffer));
147                 return g_strdup (buffer);
148         }
149 #endif
150
151         default:
152                 return NULL;
153         }
154 }
155
156
157 static struct hostent *
158 soup_gethostbyname_internal (const char *hostname)
159 {
160         struct hostent result_buf, *result = &result_buf, *out;
161         char *buf = NULL;
162
163 #if defined(HAVE_GETHOSTBYNAME_R_GLIBC)
164         {
165                 size_t len;
166                 int herr, res;
167
168                 len = 1024;
169                 buf = g_new (char, len);
170
171                 while ((res = gethostbyname_r (hostname,
172                                                &result_buf,
173                                                buf,
174                                                len,
175                                                &result,
176                                                &herr)) == ERANGE) {
177                         len *= 2;
178                         buf = g_renew (char, buf, len);
179                 }
180
181                 if (res || result == NULL || result->h_addr_list [0] == NULL)
182                         result = NULL;
183         }
184 #elif defined(HAVE_GETHOSTBYNAME_R_SOLARIS)
185         {
186                 size_t len;
187                 int herr, res;
188
189                 len = 1024;
190                 buf = g_new (char, len);
191
192                 while ((res = gethostbyname_r (hostname,
193                                                &result_buf,
194                                                buf,
195                                                len,
196                                                &herr)) == ERANGE) {
197                         len *= 2;
198                         buf = g_renew (char, buf, len);
199                 }
200
201                 if (res)
202                         result = NULL;
203         }
204 #elif defined(HAVE_GETHOSTBYNAME_R_HPUX)
205         {
206                 struct hostent_data hdbuf;
207
208                 if (!gethostbyname_r (hostname, &result_buf, &hdbuf))
209                         result = NULL;
210         }
211 #else
212         {
213                 result = gethostbyname (hostname);
214         }
215 #endif
216
217         if (result)
218                 out = copy_hostent (result);
219         else
220                 out = NULL;
221
222         if (buf)
223                 g_free (buf);
224
225         return out;
226 }
227
228 static struct hostent *
229 soup_gethostbyaddr_internal (gconstpointer addr, int family)
230 {
231         struct hostent result_buf, *result = &result_buf, *out;
232         char *buf = NULL;
233         int length;
234
235         switch (family) {
236         case AF_INET:
237                 length = sizeof (struct in_addr);
238                 break;
239 #ifdef HAVE_IPV6
240         case AF_INET6:
241                 length = sizeof (struct in6_addr);
242                 break;
243 #endif
244         default:
245                 return NULL;
246         }
247
248 #if defined(HAVE_GETHOSTBYNAME_R_GLIBC)
249         {
250                 size_t len;
251                 int herr, res;
252
253                 len = 1024;
254                 buf = g_new (char, len);
255
256                 while ((res = gethostbyaddr_r (addr,
257                                                length,
258                                                family,
259                                                &result_buf,
260                                                buf,
261                                                len,
262                                                &result,
263                                                &herr)) == ERANGE) {
264                         len *= 2;
265                         buf = g_renew (char, buf, len);
266                 }
267
268                 if (res || result == NULL || result->h_name == NULL)
269                         result = NULL;
270         }
271 #elif defined(HAVE_GETHOSTBYNAME_R_SOLARIS)
272         {
273                 size_t len;
274                 int herr, res;
275
276                 len = 1024;
277                 buf = g_new (char, len);
278
279                 while ((res = gethostbyaddr_r (addr,
280                                                length,
281                                                family,
282                                                &result_buf,
283                                                buf,
284                                                len,
285                                                &herr)) == ERANGE) {
286                         len *= 2;
287                         buf = g_renew (char, buf, len);
288                 }
289
290                 if (res)
291                         result = NULL;
292         }
293 #elif defined(HAVE_GETHOSTBYNAME_R_HPUX)
294         {
295                 struct hostent_data hdbuf;
296
297                 if (!gethostbyaddr_r (addr, length, family, &result_buf, &hdbuf))
298                         result = NULL;
299         }
300 #else
301         {
302                 result = gethostbyaddr (addr, length, family);
303         }
304 #endif
305
306         if (result)
307                 out = copy_hostent (result);
308         else
309                 out = NULL;
310
311         if (buf)
312                 g_free (buf);
313
314         return out;
315 }
316
317
318 /* Cache */
319
320 struct SoupDNSEntry {
321         char           *name;
322         struct hostent *h;
323         gboolean        resolved;
324
325         time_t          expires;
326         guint           ref_count;
327
328         pid_t           lookup_pid;
329         int             fd;
330 };
331
332 static GHashTable *soup_dns_entries;
333
334 #define SOUP_DNS_ENTRIES_MAX 20
335
336 static GStaticMutex soup_dns_mutex = G_STATIC_MUTEX_INIT;
337 #define soup_dns_lock() g_static_mutex_lock (&soup_dns_mutex)
338 #define soup_dns_unlock() g_static_mutex_unlock (&soup_dns_mutex)
339
340 static void
341 soup_dns_entry_ref (SoupDNSEntry *entry)
342 {
343         entry->ref_count++;
344 }
345
346 static void
347 soup_dns_entry_unref (SoupDNSEntry *entry)
348 {
349         if (!--entry->ref_count) {
350                 g_free (entry->name);
351                 soup_dns_free_hostent (entry->h);
352
353                 if (entry->fd)
354                         close (entry->fd);
355                 if (entry->lookup_pid) {
356                         kill (entry->lookup_pid, SIGKILL);
357                         waitpid (entry->lookup_pid, NULL, 0);
358                 }
359
360                 g_free (entry);
361         }
362 }
363
364 static void
365 uncache_entry (SoupDNSEntry *entry)
366 {
367         g_hash_table_remove (soup_dns_entries, entry->name);
368         soup_dns_entry_unref (entry);
369 }
370
371 static void
372 prune_cache_cb (gpointer key, gpointer value, gpointer data)
373 {
374         SoupDNSEntry *entry = value, **prune_entry = data; 
375
376         if (!*prune_entry || (*prune_entry)->expires > entry->expires)
377                 *prune_entry = entry;
378 }
379
380 static SoupDNSEntry *
381 soup_dns_entry_new (const char *name)
382 {
383         SoupDNSEntry *entry;
384
385         entry = g_new0 (SoupDNSEntry, 1);
386         entry->name = g_strdup (name);
387         entry->ref_count = 2; /* One for the caller, one for the cache */
388
389         if (!soup_dns_entries) {
390                 soup_dns_entries = g_hash_table_new (soup_str_case_hash,
391                                                      soup_str_case_equal);
392         } else if (g_hash_table_size (soup_dns_entries) == SOUP_DNS_ENTRIES_MAX) {
393                 SoupDNSEntry *prune_entry = NULL;
394
395                 g_hash_table_foreach (soup_dns_entries, prune_cache_cb,
396                                       &prune_entry);
397                 if (prune_entry)
398                         uncache_entry (prune_entry);
399         }
400
401         entry->expires = time (0) + 60 * 60;
402         g_hash_table_insert (soup_dns_entries, entry->name, entry);
403
404         return entry;
405 }
406
407 static SoupDNSEntry *
408 soup_dns_lookup_entry (const char *name)
409 {
410         SoupDNSEntry *entry;
411
412         if (!soup_dns_entries)
413                 return NULL;
414
415         entry = g_hash_table_lookup (soup_dns_entries, name);
416         if (entry)
417                 soup_dns_entry_ref (entry);
418         return entry;
419 }
420
421 /**
422  * soup_dns_entry_from_name:
423  * @name: a nice name (eg, mofo.eecs.umich.edu) or a dotted decimal name
424  *   (eg, 141.213.8.59).
425  *
426  * Begins asynchronous resolution of @name. The caller should
427  * periodically call soup_entry_check_lookup() to see if it is done,
428  * and call soup_entry_get_hostent() when soup_entry_check_lookup()
429  * returns %TRUE.
430  *
431  * Currently, this routine forks and does the lookup, which can cause
432  * some problems. In general, this will work ok for most programs most
433  * of the time. It will be slow or even fail when using operating
434  * systems that copy the entire process when forking.
435  *
436  * Returns: a #SoupDNSEntry, which will be freed when you call
437  * soup_entry_get_hostent() or soup_entry_cancel_lookup().
438  **/
439 SoupDNSEntry *
440 soup_dns_entry_from_name (const char *name)
441 {
442         SoupDNSEntry *entry;
443         int pipes[2];
444
445         soup_dns_lock ();
446
447         /* Try the cache */
448         entry = soup_dns_lookup_entry (name);
449         if (entry) {
450                 soup_dns_unlock ();
451                 return entry;
452         }
453
454         entry = soup_dns_entry_new (name);
455
456         /* Try to read the name as if it were dotted decimal */
457         entry->h = new_hostent_from_phys (name);
458         if (entry->h) {
459                 entry->resolved = TRUE;
460                 soup_dns_unlock ();
461                 return entry;
462         }
463
464         /* Check to see if we are doing synchronous DNS lookups */
465         if (getenv ("SOUP_SYNC_DNS")) {
466                 entry->h = soup_gethostbyname_internal (name);
467                 entry->resolved = TRUE;
468                 soup_dns_unlock ();
469                 return entry;
470         }
471
472         /* Ok, we need to start a new lookup */
473
474         if (pipe (pipes) == -1) {
475                 entry->resolved = TRUE;
476                 soup_dns_unlock ();
477                 return entry;
478         }
479
480         entry->lookup_pid = fork ();
481         switch (entry->lookup_pid) {
482         case -1:
483                 g_warning ("Fork error: %s (%d)\n", g_strerror (errno), errno);
484                 close (pipes[0]);
485                 close (pipes[1]);
486
487                 entry->resolved = TRUE;
488                 soup_dns_unlock ();
489                 return entry;
490
491         case 0:
492                 /* Child */
493                 close (pipes[0]);
494
495                 entry->h = soup_gethostbyname_internal (name);
496                 if (entry->h)
497                         write_hostent (entry->h, pipes[1]);
498
499                 /* Close the socket */
500                 close (pipes[1]);
501
502                 /* Exit (we don't want atexit called, so do _exit instead) */
503                 _exit (EXIT_SUCCESS);
504
505         default:
506                 /* Parent */
507                 close (pipes[1]);
508
509                 entry->fd = pipes[0];
510                 soup_dns_unlock ();
511                 return entry;
512         }
513 }
514
515 /**
516  * soup_dns_entry_from_addr:
517  * @addr: pointer to address data (eg, an #in_addr_t)
518  * @family: address family of @addr
519  *
520  * Begins asynchronous resolution of @addr. The caller should
521  * periodically call soup_entry_check_lookup() to see if it is done,
522  * and call soup_entry_get_hostent() when soup_entry_check_lookup()
523  * returns %TRUE.
524  *
525  * Currently, this routine forks and does the lookup, which can cause
526  * some problems. In general, this will work ok for most programs most
527  * of the time. It will be slow or even fail when using operating
528  * systems that copy the entire process when forking.
529  *
530  * Returns: a #SoupDNSEntry, which will be freed when you call
531  * soup_entry_get_hostent() or soup_entry_cancel_lookup().
532  **/
533 SoupDNSEntry *
534 soup_dns_entry_from_addr (gconstpointer addr, int family)
535 {
536         SoupDNSEntry *entry;
537         int pipes[2];
538         char *name;
539
540         name = soup_dns_ntop (addr, family);
541         g_return_val_if_fail (name != NULL, NULL);
542
543         soup_dns_lock ();
544
545         /* Try the cache */
546         entry = soup_dns_lookup_entry (name);
547         if (entry) {
548                 g_free (name);
549                 soup_dns_unlock ();
550                 return entry;
551         }
552
553         entry = soup_dns_entry_new (name);
554
555         /* Check to see if we are doing synchronous DNS lookups */
556         if (getenv ("SOUP_SYNC_DNS")) {
557                 entry->h = soup_gethostbyaddr_internal (addr, family);
558                 entry->resolved = TRUE;
559                 soup_dns_unlock ();
560                 return entry;
561         }
562
563         if (pipe (pipes) != 0) {
564                 entry->resolved = TRUE;
565                 soup_dns_unlock ();
566                 return entry;
567         }
568
569         entry->lookup_pid = fork ();
570         switch (entry->lookup_pid) {
571         case -1:
572                 close (pipes[0]);
573                 close (pipes[1]);
574
575                 g_warning ("Fork error: %s (%d)\n", g_strerror(errno), errno);
576                 entry->resolved = TRUE;
577                 soup_dns_unlock ();
578                 return entry;
579
580         case 0:
581                 /* Child */
582                 close (pipes[0]);
583
584                 entry->h = soup_gethostbyaddr_internal (addr, family);
585                 if (entry->h)
586                         write_hostent (entry->h, pipes[1]);
587
588                 /* Close the socket */
589                 close (pipes[1]);
590
591                 /* Exit (we don't want atexit called, so do _exit instead) */
592                 _exit (EXIT_SUCCESS);
593
594         default:
595                 /* Parent */
596                 close (pipes[1]);
597
598                 entry->fd = pipes[0];
599                 soup_dns_unlock ();
600                 return entry;
601         }
602 }
603
604 static void
605 check_hostent (SoupDNSEntry *entry, gboolean block)
606 {
607         char buf[256], *namelenp, *name, *typep, *addrlenp, *addr;
608         int nread;
609         fd_set readfds;
610         struct timeval tv = { 0, 0 };
611
612         soup_dns_lock ();
613
614         if (entry->resolved || !entry->fd) {
615                 soup_dns_unlock ();
616                 return;
617         }
618
619         FD_ZERO (&readfds);
620         FD_SET (entry->fd, &readfds);
621         if (select (entry->fd + 1, &readfds, NULL, NULL, &tv) != 0) {
622                 soup_dns_unlock ();
623                 return;
624         }
625
626         nread = read (entry->fd, buf, sizeof (buf));
627         close (entry->fd);
628         entry->fd = -1;
629         kill (entry->lookup_pid, SIGKILL);
630         waitpid (entry->lookup_pid, NULL, 0);
631         entry->lookup_pid = 0;
632         entry->resolved = TRUE;
633
634         if (nread < 1) {
635                 soup_dns_unlock ();
636                 return;
637         }
638
639         namelenp = buf;
640         name = namelenp + 1;
641         typep = name + *namelenp;
642         addrlenp = typep + 1;
643         addr = addrlenp + 1;
644
645         if (addrlenp < buf + nread && (addr + *addrlenp) == buf + nread)
646                 entry->h = new_hostent (name, *typep, *addrlenp, addr);
647         soup_dns_unlock ();
648 }
649
650 gboolean
651 soup_dns_entry_check_lookup (SoupDNSEntry *entry)
652 {
653         check_hostent (entry, FALSE);
654         return entry->resolved;
655 }
656
657 struct hostent *
658 soup_dns_entry_get_hostent (SoupDNSEntry *entry)
659 {
660         struct hostent *h;
661
662         check_hostent (entry, TRUE);
663         h = copy_hostent (entry->h);
664         soup_dns_entry_unref (entry);
665
666         return h;
667 }
668
669 void
670 soup_dns_entry_cancel_lookup (SoupDNSEntry *entry)
671 {
672         soup_dns_entry_unref (entry);
673 }