source: trunk/third/libsoup/libsoup/soup-dns.c @ 21108

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