From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / sonar-icmp.c
index e74e45d1dc34f70094f38f8c389794233bac1fc9..e482ab6a580617061a58ca731922c86c4d5a60a6 100644 (file)
@@ -1,4 +1,4 @@
-/* sonar, Copyright (c) 1998-2009 Jamie Zawinski and Stephen Martin
+/* sonar, Copyright (c) 1998-2017 Jamie Zawinski and Stephen Martin
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
 #include "screenhackI.h"
 #include "sonar.h"
 #include "version.h"
+#include "async_netdb.h"
 
 #undef usleep /* conflicts with unistd.h on OSX */
 
+#ifdef USE_IPHONE
+  /* Note: to get this to compile for iPhone, you need to fix Xcode!
+     The icmp headers exist for the simulator build environment, but
+     not for the real-device build environment.  This appears to 
+     just be an Apple bug, not intentional.
+
+     xc=/Applications/Xcode.app/Contents
+     for path in    /Developer/Platforms/iPhone*?/Developer/SDKs/?* \
+                 $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do
+       for file in \
+         /usr/include/netinet/ip.h \
+         /usr/include/netinet/in_systm.h \
+         /usr/include/netinet/ip_icmp.h \
+         /usr/include/netinet/ip_var.h \
+         /usr/include/netinet/udp.h
+       do
+         ln -s "$file" "$path$file"
+       done
+     done
+  */
+#endif
+
+#ifndef HAVE_MOBILE
+# define READ_FILES
+#endif
+
 #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
 # include <unistd.h>
 # include <sys/stat.h>
@@ -26,7 +53,9 @@
 # include <sys/types.h>
 # include <sys/time.h>
 # include <sys/ipc.h>
-# include <sys/shm.h>
+# ifndef HAVE_ANDROID
+#  include <sys/shm.h>
+# endif
 # include <sys/socket.h>
 # include <netinet/in_systm.h>
 # include <netinet/in.h>
 # include <netinet/udp.h>
 # include <arpa/inet.h>
 # include <netdb.h>
+# include <errno.h>
+# ifdef HAVE_GETIFADDRS
+#  include <ifaddrs.h>
+# endif
 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
 
 #if defined(HAVE_ICMP)
 # undef HAVE_PING
 #endif
 
+#ifndef HAVE_MOBILE
+# define LOAD_FILES
+#endif
+
 #ifndef HAVE_PING
 
 sonar_sensor_data *
-init_ping (Display *dpy, char **error_ret, const char *subnet, int timeout,
-           Bool resolve_p, Bool times_p, Bool debug_p)
+sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret, 
+                 const char *subnet, int timeout,
+                 Bool resolve_p, Bool times_p, Bool debug_p)
 {
   if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
     fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
@@ -96,6 +134,8 @@ static long delta(struct timeval *, struct timeval *);
 
 
 typedef struct {
+  Display *dpy;                 /* Only used to get *useThreads. */
+
   char *version;               /* short version number of xscreensaver */
   int icmpsock;                        /* socket for sending pings */
   int pid;                     /* our process ID */
@@ -115,13 +155,17 @@ typedef struct {
 } ping_data;
 
 typedef struct {
-  struct sockaddr address;     /* ip address */
+  async_name_from_addr_t lookup_name;
+  async_addr_from_name_t lookup_addr;
+  async_netdb_sockaddr_storage_t address;      /* ip address */
+  socklen_t addrlen;
+  char *fallback;
 } ping_bogie;
 
 
 
 /* Packs an IP address quad into bigendian network order. */
-static unsigned long
+static in_addr_t
 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
 {
   unsigned long i = (((a & 255) << 24) |
@@ -147,27 +191,24 @@ unpack_addr (unsigned long addr,
 
 
 
-/* If resolves the bogie's name (either a hostname or ip address string)
-   to a hostent.  Returns 1 if successful, 0 if it failed to resolve.
+/* Resolves the bogie's name (either a hostname or ip address string)
+   to a hostent.  Returns 1 if successful, 0 if something went wrong.
  */
 static int
 resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
 {
   ping_bogie *pb = (ping_bogie *) sb->closure;
-  struct hostent *hent;
-  struct sockaddr_in *iaddr;
 
   unsigned int ip[4];
   char c;
 
-  iaddr = (struct sockaddr_in *) &(pb->address);
-  iaddr->sin_family = AF_INET;
-
   if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
                    &ip[0], &ip[1], &ip[2], &ip[3], &c))
     {
       /* It's an IP address.
        */
+      struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
+
       if (ip[3] == 0)
         {
           if (pd->debug_p > 1)
@@ -176,22 +217,22 @@ resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
           return 0;
         }
 
+      iaddr->sin_family = AF_INET;
       iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
-      if (resolve_p)
-        hent = gethostbyaddr ((const char *) &iaddr->sin_addr.s_addr,
-                              sizeof(iaddr->sin_addr.s_addr),
-                              AF_INET);
-      else
-        hent = 0;
-
-      if (pd->debug_p > 1)
-        fprintf (stderr, "%s:   %s => %s\n",
-                 progname, sb->name,
-                 ((hent && hent->h_name && *hent->h_name)
-                  ? hent->h_name : "<unknown>"));
+      pb->addrlen = sizeof(struct sockaddr_in);
 
-      if (hent && hent->h_name && *hent->h_name)
-        sb->name = strdup (hent->h_name);
+      if (resolve_p)
+        {
+          pb->lookup_name =
+            async_name_from_addr_start (pd->dpy,
+                                        (const struct sockaddr *)&pb->address,
+                                        pb->addrlen);
+          if (!pb->lookup_name)
+            {
+              fprintf (stderr, "%s:   unable to start host resolution.\n",
+                       progname);
+            }
+        }
     }
   else
     {
@@ -202,6 +243,7 @@ resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
       if (!strcmp (sb->name, "ssh-rsa") ||
           !strcmp (sb->name, "ssh-dsa") ||
           !strcmp (sb->name, "ssh-dss") ||
+          !strncmp (sb->name, "ecdsa-", 6) ||
           strlen (sb->name) >= 80)
         return 0;
 
@@ -224,53 +266,125 @@ resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
           return 0;
         }
 
-      hent = gethostbyname (sb->name);
-      if (!hent)
+      pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
+      if (!pb->lookup_addr)
         {
           if (pd->debug_p)
-            fprintf (stderr, "%s:   could not resolve host:  %s\n",
-                     progname, sb->name);
+            /* Either address space exhaustion or RAM exhaustion. */
+            fprintf (stderr, "%s:   unable to start host resolution.\n",
+                     progname);
           return 0;
         }
+    }
+  return 1;
+}
+
 
-      memcpy (&iaddr->sin_addr, hent->h_addr_list[0],
-              sizeof(iaddr->sin_addr));
+static void
+print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
+{
+#ifdef HAVE_GETADDRINFO
+  char buf[NI_MAXHOST];
+#else
+  char buf[50];
+#endif
 
-      if (pd->debug_p > 1)
+  const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
+  const char *ips = buf;
+
+  if (!addr->sa_family)
+    ips = "<no address>";
+  else
+    {
+#ifdef HAVE_GETADDRINFO
+      int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
+                                   NULL, 0, NI_NUMERICHOST);
+      if (gai_error == EAI_SYSTEM)
+        ips = strerror(errno);
+      else if (gai_error)
+        ips = gai_strerror(gai_error);
+#else
+      switch (addr->sa_family)
         {
-          unsigned int a, b, c, d;
-          unpack_addr (iaddr->sin_addr.s_addr, &a, &b, &c, &d);
-          fprintf (stderr, "%s:   %s => %d.%d.%d.%d\n",
-                   progname, sb->name, a, b, c, d);
+        case AF_INET:
+          {
+            u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
+            unsigned int a, b, c, d;
+            unpack_addr (ip, &a, &b, &c, &d);   /* ip is in network order */
+            sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
+          }
+          break;
+        default:
+          ips = "<unknown>";
+          break;
         }
+#endif
     }
-  return 1;
+
+  fprintf (out, "%-*s", width, ips);
 }
 
 
 static void
-print_host (FILE *out, unsigned long ip, const char *name)
+print_host (FILE *out, const sonar_bogie *sb)
 {
-  char ips[50];
-  unsigned int a, b, c, d;
-  unpack_addr (ip, &a, &b, &c, &d);            /* ip is in network order */
-  sprintf (ips, "%u.%u.%u.%u", a, b, c, d);
+  const ping_bogie *pb = (const ping_bogie *) sb->closure;
+  const char *name = sb->name;
   if (!name || !*name) name = "<unknown>";
-  fprintf (out, "%-16s %s\n", ips, name);
+  print_address (out, 16, &pb->address, pb->addrlen);
+  fprintf (out, " %s\n", name);
+}
+
+
+static Bool
+is_address_ok(Bool debug_p, const sonar_bogie *b)
+{
+  const ping_bogie *pb = (const ping_bogie *) b->closure;
+  const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
+
+  switch (addr->sa_family)
+    {
+    case AF_INET:
+      {
+        struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
+
+        /* Don't ever use loopback (127.0.0.x) hosts */
+        unsigned long ip = iaddr->sin_addr.s_addr;
+        if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L)  /* 127.0.0.x */
+          {
+            if (debug_p)
+              fprintf (stderr, "%s:   ignoring loopback host %s\n",
+                       progname, b->name);
+            return False;
+          }
+
+        /* Don't ever use broadcast (255.x.x.x) hosts */
+        if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L)  /* 255.x.x.x */
+          {
+            if (debug_p)
+              fprintf (stderr, "%s:   ignoring broadcast host %s\n",
+                       progname, b->name);
+            return False;
+          }
+      }
+
+      break;
+    }
+
+  return True;
 }
 
 
-/* Create a sonar_bogie a host name or ip address string.
+/* Create a sonar_bogie from a host name or ip address string.
    Returns NULL if the name could not be resolved.
  */
 static sonar_bogie *
-bogie_for_host (sonar_sensor_data *ssd, const char *name, Bool resolve_p)
+bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
 {
   ping_data *pd = (ping_data *) ssd->closure;
   sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
   ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
-  struct sockaddr_in *iaddr;
-  unsigned long ip;
+  Bool resolve_p = pd->resolve_p;
 
   b->name = strdup (name);
   b->closure = pb;
@@ -278,41 +392,31 @@ bogie_for_host (sonar_sensor_data *ssd, const char *name, Bool resolve_p)
   if (! resolve_bogie_hostname (pd, b, resolve_p))
     goto FAIL;
 
-  iaddr = (struct sockaddr_in *) &(pb->address);
-
-  /* Don't ever use loopback (127.0.0.x) hosts */
-  ip = iaddr->sin_addr.s_addr;
-  if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L)  /* 127.0.0.x */
-    {
-      if (pd->debug_p)
-        fprintf (stderr, "%s:   ignoring loopback host %s\n", 
-                 progname, b->name);
-      goto FAIL;
-    }
-
-  /* Don't ever use broadcast (255.x.x.x) hosts */
-  if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L)  /* 255.x.x.x */
-    {
-      if (pd->debug_p)
-        fprintf (stderr, "%s:   ignoring broadcast host %s\n",
-                 progname, b->name);
-      goto FAIL;
-    }
+  if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
+    goto FAIL;
 
   if (pd->debug_p > 1)
     {
       fprintf (stderr, "%s:   added ", progname);
-      print_host (stderr, ip, b->name);
+      print_host (stderr, b);
     }
 
+  if (fallback)
+    pb->fallback = strdup (fallback);
   return b;
 
  FAIL:
-  if (b) free_bogie (ssd, b);
+  if (b) sonar_free_bogie (ssd, b);
+
+  if (fallback)
+    return bogie_for_host (ssd, fallback, NULL);
+
   return 0;
 }
 
 
+#ifdef READ_FILES
+
 /* Return a list of bogies read from a file.
    The file can be like /etc/hosts or .ssh/known_hosts or probably
    just about anything that has host names in it.
@@ -345,12 +449,12 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename)
   fp = fopen(filename, "r");
   if (!fp)
     {
-      char buf[1024];
-      sprintf(buf, "%s:  %s", progname, filename);
-#ifdef HAVE_COCOA
+      char buf2[1024];
+      sprintf(buf2, "%s:  %s", progname, filename);
+#ifdef HAVE_JWXYZ
       if (pd->debug_p)  /* on OSX don't syslog this */
 #endif
-        perror (buf);
+        perror (buf2);
       return 0;
     }
 
@@ -404,11 +508,13 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename)
 
       /* Create a new target using first the name then the address */
 
-      new = 0;
-      if (name)
-        new = bogie_for_host (ssd, name, pd->resolve_p);
-      if (!new && addr)
-        new = bogie_for_host (ssd, addr, pd->resolve_p);
+      if (!name)
+        {
+          name = addr;
+          addr = NULL;
+        }
+
+      new = bogie_for_host (ssd, name, addr);
 
       if (new)
         {
@@ -420,6 +526,91 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename)
   fclose(fp);
   return list;
 }
+#endif /* READ_FILES */
+
+
+static sonar_bogie **
+found_duplicate_host (const ping_data *pd, sonar_bogie **list,
+                      sonar_bogie *bogie)
+{
+  if (pd->debug_p)
+  {
+    fprintf (stderr, "%s: deleted duplicate: ", progname);
+    print_host (stderr, bogie);
+  }
+
+  return list;
+}
+
+
+static sonar_bogie **
+find_duplicate_host (const ping_data *pd, sonar_bogie **list,
+                     sonar_bogie *bogie)
+{
+  const ping_bogie *pb = (const ping_bogie *) bogie->closure;
+  const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
+
+  while(*list)
+    {
+      const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
+
+      if (!pb2->lookup_addr)
+        {
+          const struct sockaddr *addr2 =
+            (const struct sockaddr *) &(pb2->address);
+
+          if (addr1->sa_family == addr2->sa_family)
+            {
+              switch (addr1->sa_family)
+                {
+                case AF_INET:
+                  {
+                    unsigned long ip1 =
+                      ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
+                    const struct sockaddr_in *i2 =
+                      (const struct sockaddr_in *)addr2;
+                    unsigned long ip2 = i2->sin_addr.s_addr;
+
+                    if (ip1 == ip2)
+                      return found_duplicate_host (pd, list, bogie);
+                  }
+                  break;
+#ifdef AF_INET6
+                case AF_INET6:
+                  {
+                    if (! memcmp(
+                            &((const struct sockaddr_in6 *)addr1)->sin6_addr,
+                            &((const struct sockaddr_in6 *)addr2)->sin6_addr,
+                            16))
+                      return found_duplicate_host (pd, list, bogie);
+                  }
+                  break;
+#endif
+                default:
+                  {
+                    /* Fallback behavior: Just memcmp the two addresses.
+
+                       For this to work, unused space in the sockaddr must be
+                       set to zero. Which may actually be the case:
+                       - async_addr_from_name_finish won't put garbage into
+                         sockaddr_in.sin_zero or elsewhere unless getaddrinfo
+                         does.
+                       - ping_bogie is allocated with calloc(). */
+
+                    if (pb->addrlen == pb2->addrlen &&
+                        ! memcmp(addr1, addr2, pb->addrlen))
+                      return found_duplicate_host (pd, list, bogie);
+                  }
+                  break;
+                }
+            }
+        }
+
+      list = &(*list)->next;
+    }
+
+  return NULL;
+}
 
 
 static sonar_bogie *
@@ -427,57 +618,64 @@ delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
 {
   ping_data *pd = (ping_data *) ssd->closure;
   sonar_bogie *head = list;
-  sonar_bogie *sb;
+  sonar_bogie *sb = head;
 
-  for (sb = head; sb; sb = sb->next)
+  while (sb)
     {
       ping_bogie *pb = (ping_bogie *) sb->closure;
-      struct sockaddr_in *i1 = (struct sockaddr_in *) &(pb->address);
-      unsigned long ip1 = i1->sin_addr.s_addr;
 
-      sonar_bogie *sb2;
-      for (sb2 = sb; sb2; sb2 = sb2->next)
+      if (!pb->lookup_addr)
         {
-          if (sb2 && sb2->next)
-            {
-              ping_bogie *pb2 = (ping_bogie *) sb2->next->closure;
-              struct sockaddr_in *i2 = (struct sockaddr_in *) &(pb2->address);
-              unsigned long ip2 = i2->sin_addr.s_addr;
-
-              if (ip1 == ip2)
-                {
-                  if (pd->debug_p)
-                    {
-                      fprintf (stderr, "%s: deleted duplicate: ", progname);
-                      print_host (stderr, ip2, sb2->next->name);
-                    }
-                  sb2->next = sb2->next->next;
-                  /* #### sb leaked */
-                }
-            }
+          sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
+          if (sb2)
+            *sb2 = (*sb2)->next;
+            /* #### sb leaked */
+          else
+            sb = sb->next;
         }
+      else
+        sb = sb->next;
     }
 
   return head;
 }
 
 
+static unsigned long
+width_mask (unsigned long width)
+{
+  unsigned long m = 0;
+  int i;
+  for (i = 0; i < width; i++)
+    m |= (1L << (31-i));
+  return m;
+}
+
+
+#ifdef HAVE_GETIFADDRS
+static unsigned int
+mask_width (unsigned long mask)
+{
+  int i;
+  for (i = 0; i < 32; i++)
+    if (mask & (1 << i))
+      break;
+  return 32-i;
+}
+#endif
+
+
 /* Generate a list of bogies consisting of all of the entries on
   the same subnet.  'base' ip is in network order; 0 means localhost.
  */
 static sonar_bogie *
-subnet_hosts (sonar_sensor_data *ssd, char **error_ret,
+subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
               unsigned long n_base, int subnet_width)
 {
   ping_data *pd = (ping_data *) ssd->closure;
   unsigned long h_mask;   /* host order */
   unsigned long h_base;   /* host order */
-
-  /* Local Variables */
-
-  char hostname[BUFSIZ];
   char address[BUFSIZ];
-  struct hostent *hent;
   char *p;
   int i;
   sonar_bogie *new;
@@ -510,67 +708,182 @@ subnet_hosts (sonar_sensor_data *ssd, char **error_ret,
   if (pd->debug_p)
     fprintf (stderr, "%s:   adding %d-bit subnet\n", progname, subnet_width);
 
-  /* Get our hostname */
 
-  if (gethostname(hostname, BUFSIZ)) 
+  if (! n_base)
     {
-      *error_ret = strdup ("Unable to determine\n"
-                           "local host name!");
-      return 0;
-    }
+# ifdef HAVE_GETIFADDRS
 
-  /* Get our IP address and convert it to a string */
+      /* To determine the local subnet, we need to know the local IP address.
+         Do this by looking at the IPs of every network interface.
+      */
+      struct in_addr in = { 0, };
+      struct ifaddrs *all = 0, *ifa;
 
-  if (! (hent = gethostbyname(hostname)))
-    {
-      sprintf(buf, 
-              "Unable to resolve\n"
-              "local host \"%s\"", 
-              hostname);
-      *error_ret = strdup(buf);
-      return 0;
+      if (pd->debug_p)
+        fprintf (stderr, "%s:   listing network interfaces\n", progname);
+
+      getifaddrs (&all);
+      for (ifa = all; ifa; ifa = ifa->ifa_next)
+        {
+          struct in_addr in2;
+          unsigned long mask;
+          if (ifa->ifa_addr->sa_family != AF_INET)
+            {
+              if (pd->debug_p)
+                fprintf (stderr, "%s:     if: %4s: %s\n", progname,
+                         ifa->ifa_name,
+                         (
+# ifdef AF_UNIX
+                          ifa->ifa_addr->sa_family == AF_UNIX  ? "local" :
+# endif
+# ifdef AF_LINK
+                          ifa->ifa_addr->sa_family == AF_LINK  ? "link"  :
+# endif
+# ifdef AF_INET6
+                          ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6"  :
+# endif
+                          "other"));
+              continue;
+            }
+          in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
+          mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
+                        ->sin_addr.s_addr);
+          if (pd->debug_p)
+            fprintf (stderr, "%s:     if: %4s: inet = %s /%d 0x%08lx\n",
+                     progname,
+                     ifa->ifa_name,
+                     inet_ntoa (in2),
+                     mask_width (mask),
+                     mask);
+          if (in2.s_addr == 0x0100007f ||   /* 127.0.0.1 in network order */
+              mask == 0)
+            continue;
+
+          /* At least on the AT&T 3G network, pinging either of the two
+             hosts on a /31 network doesn't work, so don't try.
+           */
+          if (mask_width (mask) == 31)
+            {
+              sprintf (buf,
+                       "Can't ping subnet:\n"
+                       "local network is\n"
+                       "%.100s/%d,\n"
+                       "a p2p bridge\n"
+                       "on if %.100s.",
+                       inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
+              if (*error_ret) free (*error_ret);
+              *error_ret = strdup (buf);
+              continue;
+            }
+
+          in = in2;
+          subnet_width = mask_width (mask);
+        }
+
+      if (in.s_addr)
+        {
+          if (*error_ret) free (*error_ret);
+          *error_ret = 0;
+          n_base = in.s_addr;  /* already in network order, I think? */
+        }
+      else if (!*error_ret)
+        *error_ret = strdup ("Unable to determine\nlocal IP address\n");
+
+      if (all) 
+        freeifaddrs (all);
+
+      if (*error_ret)
+        return 0;
+
+# else /* !HAVE_GETIFADDRS */
+
+      /* If we can't walk the list of network interfaces to figure out
+         our local IP address, try to do it by finding the local host
+         name, then resolving that.
+      */
+      char hostname[BUFSIZ];
+      struct hostent *hent = 0;
+
+      if (gethostname(hostname, BUFSIZ)) 
+        {
+          *error_ret = strdup ("Unable to determine\n"
+                               "local host name!");
+          return 0;
+        }
+
+      /* Get our IP address and convert it to a string */
+
+      hent = gethostbyname(hostname);
+      if (! hent)
+        {
+          strcat (hostname, ".local"); /* Necessary on iphone */
+          hent = gethostbyname(hostname);
+        }
+
+      if (! hent)
+        {
+          sprintf(buf, 
+                  "Unable to resolve\n"
+                  "local host \"%.100s\"", 
+                  hostname);
+          *error_ret = strdup(buf);
+          return 0;
+        }
+
+      strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
+      n_base = pack_addr (hent->h_addr_list[0][0],
+                          hent->h_addr_list[0][1],
+                          hent->h_addr_list[0][2],
+                          hent->h_addr_list[0][3]);
+
+      if (n_base == 0x0100007f)   /* 127.0.0.1 in network order */
+        {
+          unsigned int a, b, c, d;
+          unpack_addr (n_base, &a, &b, &c, &d);
+          sprintf (buf,
+                   "Unable to determine\n"
+                   "local subnet address:\n"
+                   "\"%.100s\"\n"
+                   "resolves to\n"
+                   "loopback address\n"
+                   "%u.%u.%u.%u.",
+                   hostname, a, b, c, d);
+          *error_ret = strdup(buf);
+          return 0;
+        }
+
+# endif /* !HAVE_GETIFADDRS */
     }
-  strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
 
-  /* Construct targets for all addresses in this subnet */
 
-  h_mask = 0;
-  for (i = 0; i < subnet_width; i++)
-    h_mask |= (1L << (31-i));
+  /* Construct targets for all addresses in this subnet */
 
-  /* If no base IP specified, assume localhost. */
-  if (n_base == 0)
-    n_base = pack_addr (hent->h_addr_list[0][0],
-                        hent->h_addr_list[0][1],
-                        hent->h_addr_list[0][2],
-                        hent->h_addr_list[0][3]);
+  h_mask = width_mask (subnet_width);
   h_base = ntohl (n_base);
 
-  if (h_base == 0x7F000001L)   /* 127.0.0.1 in host order */
-    {
-      unsigned int a, b, c, d;
-      unpack_addr (n_base, &a, &b, &c, &d);
-      sprintf (buf,
-               "Unable to determine\n"
-               "local subnet address:\n"
-               "\"%s\"\n"
-               "resolves to\n"
-               "loopback address\n"
-               "%u.%u.%u.%u.",
-               hostname, a, b, c, d);
-      *error_ret = strdup(buf);
-      return 0;
-    }
+  if (desc_ret && !*desc_ret) {
+    char buf2[255];
+    unsigned int a, b, c, d;
+    unsigned long bb = n_base & htonl(h_mask);
+    unpack_addr (bb, &a, &b, &c, &d);
+    if (subnet_width > 24)
+      sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
+    else
+      sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width);
+    *desc_ret = strdup (buf2);
+  }
 
   for (i = 255; i >= 0; i--) {
     unsigned int a, b, c, d;
     int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
       
-    if ((ip & h_mask) != (h_base & h_mask))  /* not in mask range at all */
+    if ((ip & h_mask) != (h_base & h_mask))  /* skip out-of-subnet host */
       continue;
-    if ((ip & ~h_mask) == 0)                 /* broadcast address */
+    else if (subnet_width == 31)            /* 1-bit bridge: 2 hosts */
+      ;
+    else if ((ip & ~h_mask) == 0)           /* skip network address */
       continue;
-    if ((ip & ~h_mask) == ~h_mask)           /* broadcast address */
+    else if ((ip & ~h_mask) == ~h_mask)             /* skip broadcast address */
       continue;
 
     unpack_addr (htonl (ip), &a, &b, &c, &d);
@@ -593,7 +906,7 @@ subnet_hosts (sonar_sensor_data *ssd, char **error_ret,
     p = address + strlen(address) + 1;
     sprintf(p, "%d", i);
 
-    new = bogie_for_host (ssd, address, pd->resolve_p);
+    new = bogie_for_host (ssd, address, NULL);
     if (new)
       {
         new->next = list;
@@ -614,10 +927,11 @@ send_ping (ping_data *pd, const sonar_bogie *b)
   u_char *packet;
   struct ICMP *icmph;
   const char *token = "org.jwz.xscreensaver.sonar";
+  char *host_id;
 
-  int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) + 
-                 strlen(b->name) + 1 +
-                 strlen(token) + 1 + 
+  unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
+                 sizeof(socklen_t) + pb->addrlen +
+                 strlen(token) + 1 +
                  strlen(pd->version) + 1);
 
   /* Create the ICMP packet */
@@ -638,26 +952,29 @@ send_ping (ping_data *pd, const sonar_bogie *b)
   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
 # endif
 
-  /* We store the name of the host we're pinging in the packet, and parse
+  /* We store the sockaddr of the host we're pinging in the packet, and parse
      that out of the return packet later (see get_ping() for why).
      After that, we also include the name and version of this program,
      just to give a clue to anyone sniffing and wondering what's up.
    */
-  sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
-           "%s%c%s %s",
-           b->name, 0, token, pd->version);
+  host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
+  *(socklen_t *)host_id = pb->addrlen;
+  host_id += sizeof(socklen_t);
+  memcpy(host_id, &pb->address, pb->addrlen);
+  host_id += pb->addrlen;
+  sprintf (host_id, "%.20s %.20s", token, pd->version);
 
   ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
 
   /* Send it */
 
   if (sendto(pd->icmpsock, packet, pcktsiz, 0, 
-             &pb->address, sizeof(pb->address))
+             (struct sockaddr *)&pb->address, sizeof(pb->address))
       != pcktsiz)
     {
 #if 0
       char buf[BUFSIZ];
-      sprintf(buf, "%s: pinging %s", progname, b->name);
+      sprintf(buf, "%s: pinging %.100s", progname, b->name);
       perror(buf);
 #endif
     }
@@ -715,7 +1032,7 @@ checksum (u_short *packet, int size)
 static sonar_bogie *
 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
 {
-  sonar_bogie *b2 = copy_bogie (ssd, b);
+  sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
   if (b->closure)
     {
       ping_bogie *pb  = (ping_bogie *) b->closure;
@@ -734,7 +1051,7 @@ get_ping (sonar_sensor_data *ssd)
 {
   ping_data *pd = (ping_data *) ssd->closure;
   struct sockaddr from;
-  unsigned int fromlen;  /* Posix says socklen_t, but that's not portable */
+  socklen_t fromlen;
   int result;
   u_char packet[1024];
   struct timeval now;
@@ -789,7 +1106,7 @@ get_ping (sonar_sensor_data *ssd)
          From Valentijn Sessink <valentyn@openoffice.nl> */
       if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
         {
-          result = recvfrom (pd->icmpsock, packet, sizeof(packet),
+          result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet),
                              0, &from, &fromlen);
 
           /* Check the packet */
@@ -821,19 +1138,59 @@ get_ping (sonar_sensor_data *ssd)
              pb->address, but it is possible that, in certain weird router
              or NAT situations, that the reply will come back from a 
              different address than the one we sent it to.  So instead,
-             we parse the name out of the reply packet payload.
+             we parse the sockaddr out of the reply packet payload.
            */
           {
-            const char *name = (char *) &packet[iphdrlen +
-                                                sizeof(struct ICMP) +
-                                                sizeof(struct timeval)];
+            const socklen_t *host_id = (socklen_t *) &packet[
+              iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
+
             sonar_bogie *b;
-            for (b = pd->targets; b; b = b->next)
-              if (!strcmp (name, b->name))
-                {
-                  new = copy_ping_bogie (ssd, b);
-                  break;
-                }
+
+            /* Ensure that a maliciously-crafted return packet can't
+               make us overflow in memcmp. */
+            if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
+              {
+                const u_char *host_end = (const u_char *)(host_id + 1) +
+                                         *host_id;
+
+                if ((const u_char *)(host_id + 1) <= host_end &&
+                    host_end <= packet + result)
+                  {
+                    for (b = pd->targets; b; b = b->next)
+                      {
+                        ping_bogie *pb = (ping_bogie *)b->closure;
+                        if (*host_id == pb->addrlen &&
+                            !memcmp(&pb->address, host_id + 1, pb->addrlen) )
+                          {
+                            /* Check to see if the name lookup is done. */
+                            if (pb->lookup_name &&
+                                async_name_from_addr_is_done (pb->lookup_name))
+                              {
+                                char *host = NULL;
+
+                                async_name_from_addr_finish (pb->lookup_name,
+                                                             &host, NULL);
+
+                                if (pd->debug_p > 1)
+                                  fprintf (stderr, "%s:   %s => %s\n",
+                                           progname, b->name,
+                                           host ? host : "<unknown>");
+
+                                if (host)
+                                  {
+                                    free(b->name);
+                                    b->name = host;
+                                  }
+
+                                pb->lookup_name = NULL;
+                              }
+
+                            new = copy_ping_bogie (ssd, b);
+                            break;
+                          }
+                      }
+                  }
+              }
           }
 
           if (! new)      /* not in targets? */
@@ -915,7 +1272,7 @@ ping_free_data (sonar_sensor_data *ssd, void *closure)
   while (b)
     {
       sonar_bogie *b2 = b->next;
-      free_bogie (ssd, b);
+      sonar_free_bogie (ssd, b);
       b = b2;
     }
   free (pd);
@@ -924,6 +1281,14 @@ ping_free_data (sonar_sensor_data *ssd, void *closure)
 static void
 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
 {
+  ping_bogie *pb = (ping_bogie *) closure;
+
+  if (pb->lookup_name)
+    async_name_from_addr_cancel (pb->lookup_name);
+  if (pb->lookup_addr)
+    async_addr_from_name_cancel (pb->lookup_addr);
+  free (pb->fallback);
+
   free (closure);
 }
 
@@ -945,8 +1310,21 @@ double_time (void)
 }
 
 
-/* If a bogie is provided, pings it.
-   Then, returns all outstanding ping replies.
+static void
+free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
+                        sonar_bogie **sb)
+{
+  ping_bogie *pb = (ping_bogie *)(*sb)->closure;
+
+  *sbp = (*sb)->next;
+  pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
+  sonar_free_bogie (ssd, *sb);
+  *sb = NULL;
+}
+
+
+/* Pings the next bogie, if it's time.
+   Returns all outstanding ping replies.
  */
 static sonar_bogie *
 ping_scan (sonar_sensor_data *ssd)
@@ -958,11 +1336,91 @@ ping_scan (sonar_sensor_data *ssd)
 
   if (now > pd->last_ping_time + ping_interval)   /* time to ping someone */
     {
+      struct sonar_bogie **sbp;
+
       if (pd->last_pinged)
-        pd->last_pinged = pd->last_pinged->next;
-      if (! pd->last_pinged)
-        pd->last_pinged = pd->targets;
-      send_ping (pd, pd->last_pinged);
+        {
+          sbp = &pd->last_pinged->next;
+          if (!*sbp)
+            sbp = &pd->targets;
+        }
+      else
+        sbp = &pd->targets;
+
+      if (!*sbp)
+        /* Aaaaand we're out of bogies. */
+        pd->last_pinged = NULL;
+      else
+        {
+          sonar_bogie *sb = *sbp;
+          ping_bogie *pb = (ping_bogie *)sb->closure;
+          if (pb->lookup_addr &&
+              async_addr_from_name_is_done (pb->lookup_addr))
+            {
+              if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
+                                               &pb->addrlen, NULL))
+                {
+                  char *fallback = pb->fallback;
+                  pb->fallback = NULL;
+
+                  if (pd->debug_p)
+                    fprintf (stderr, "%s:   could not resolve host:  %s\n",
+                             progname, sb->name);
+
+                  free_bogie_after_lookup (ssd, sbp, &sb);
+
+                  /* Insert the fallback bogie right where the old one was. */
+                  if (fallback)
+                    {
+                      sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
+                                                               NULL);
+                      if (new_bogie) {
+                        new_bogie->next = *sbp;
+
+                        if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
+                            ! find_duplicate_host(pd, &pd->targets, new_bogie))
+                          *sbp = new_bogie;
+                        else
+                          sonar_free_bogie (ssd, new_bogie);
+                      }
+
+                      free (fallback);
+                    }
+                }
+              else
+                {
+                  if (pd->debug_p > 1)
+                    {
+                      fprintf (stderr, "%s:   %s => ", progname, sb->name);
+                      print_address (stderr, 0, &pb->address, pb->addrlen);
+                      putc('\n', stderr);
+                    }
+
+                  if (! is_address_ok (pd->debug_p, sb))
+                    free_bogie_after_lookup (ssd, sbp, &sb);
+                  else if (find_duplicate_host (pd, &pd->targets, sb))
+                    /* Tricky: find_duplicate_host skips the current bogie when
+                       scanning the targets list because pb->lookup_addr hasn't
+                       been NULL'd yet.
+
+                       Not that it matters much, but behavior here is to
+                       keep the existing address.
+                     */
+                    free_bogie_after_lookup (ssd, sbp, &sb);
+                }
+
+              if (sb)
+                pb->lookup_addr = NULL;
+            }
+
+          if (sb && !pb->lookup_addr)
+            {
+              if (!pb->addrlen) abort();
+              send_ping (pd, sb);
+              pd->last_pinged = sb;
+            }
+        }
+
       pd->last_ping_time = now;
     }
 
@@ -973,25 +1431,32 @@ ping_scan (sonar_sensor_data *ssd)
 /* Returns a list of hosts to ping based on the "-ping" argument.
  */
 static sonar_bogie *
-parse_mode (sonar_sensor_data *ssd, char **error_ret,
+parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
             const char *ping_arg, Bool ping_works_p)
 {
   ping_data *pd = (ping_data *) ssd->closure;
   char *source, *token, *end, dummy;
   sonar_bogie *hostlist = 0;
+  const char *fallback = "subnet";
 
-  if (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default"))
-    source = strdup("subnet/28");
-  else
+ AGAIN:
+
+  if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
+    source = strdup(fallback);
+  else if (ping_arg)
     source = strdup(ping_arg);
+  else
+    return 0;
 
   token = source;
   end = source + strlen(source);
   while (token < end)
     {
       char *next;
-      sonar_bogie *new;
+      sonar_bogie *new = 0;
+# ifdef READ_FILES
       struct stat st;
+# endif
       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
       char d;
 
@@ -1020,35 +1485,42 @@ parse_mode (sonar_sensor_data *ssd, char **error_ret,
              subnet: A.B.C/M
            */
           unsigned long ip = pack_addr (n0, n1, n2, n3);
-          new = subnet_hosts (ssd, error_ret, ip, m);
+          new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
         }
       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
         {
           /* IP: A.B.C.D
            */
-          new = bogie_for_host (ssd, token, pd->resolve_p);
+          new = bogie_for_host (ssd, token, NULL);
         }
       else if (!strcmp (token, "subnet"))
         {
-          new = subnet_hosts (ssd, error_ret, 0, 24);
+          new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
         }
       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
         {
-          new = subnet_hosts (ssd, error_ret, 0, m);
+          new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
         }
       else if (*token == '.' || *token == '/' || 
-               *token == '$' || *token == '~' ||
-               !stat (token, &st))
+               *token == '$' || *token == '~')
+        {
+# ifdef READ_FILES
+          new = read_hosts_file (ssd, token);
+# else
+          if (pd->debug_p) fprintf (stderr, "%s:  skipping file\n", progname);
+# endif
+        }
+# ifdef READ_FILES
+      else if (!stat (token, &st))
         {
-          /* file name
-           */
           new = read_hosts_file (ssd, token);
         }
+# endif /* READ_FILES */
       else
         {
           /* not an existant file - must be a host name
            */
-          new = bogie_for_host (ssd, token, pd->resolve_p);
+          new = bogie_for_host (ssd, token, NULL);
         }
 
       if (new)
@@ -1068,24 +1540,43 @@ parse_mode (sonar_sensor_data *ssd, char **error_ret,
     }
 
   free (source);
+
+  /* If the arg was completely unparsable, fall back to the local subnet.
+     This happens if the default is "/etc/hosts" but READ_FILES is off.
+     Or if we're on a /31 network, in which case we try twice then fail.
+   */
+  if (!hostlist && fallback)
+    {
+      if (pd->debug_p)
+        fprintf (stderr, "%s: no hosts parsed! Trying %s\n", 
+                 progname, fallback);
+      ping_arg = fallback;
+      fallback = 0;
+      goto AGAIN;
+    }
+
   return hostlist;
 }
 
 
 sonar_sensor_data *
-init_ping (Display *dpy, char **error_ret, 
-           const char *subnet, int timeout,
-           Bool resolve_p, Bool times_p, Bool debug_p)
+sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
+                 const char *subnet, int timeout,
+                 Bool resolve_p, Bool times_p, Bool debug_p)
 {
+  /* Important! Do not return from this function without disavowing privileges
+     with setuid(getuid()).
+   */
   sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
   ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
   sonar_bogie *b;
   char *s;
-  int i, div;
 
   Bool socket_initted_p = False;
   Bool socket_raw_p     = False;
 
+  pd->dpy = dpy;
+
   pd->resolve_p = resolve_p;
   pd->times_p   = times_p;
   pd->debug_p   = debug_p;
@@ -1154,7 +1645,8 @@ init_ping (Display *dpy, char **error_ret,
 
   /* Generate a list of targets */
 
-  pd->targets = parse_mode (ssd, error_ret, subnet, socket_initted_p);
+  pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
+                            socket_initted_p);
   pd->targets = delete_duplicate_hosts (ssd, pd->targets);
 
   if (debug_p)
@@ -1162,11 +1654,8 @@ init_ping (Display *dpy, char **error_ret,
       fprintf (stderr, "%s: Target list:\n", progname);
       for (b = pd->targets; b; b = b->next)
         {
-          ping_bogie *pb = (ping_bogie *) b->closure;
-          struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
-          unsigned long ip = iaddr->sin_addr.s_addr;
           fprintf (stderr, "%s:   ", progname);
-          print_host (stderr, ip, b->name);
+          print_host (stderr, b);
         }
     }
 
@@ -1186,12 +1675,25 @@ init_ping (Display *dpy, char **error_ret,
       return 0;
     }
 
-  /* Distribute them evenly around the display field.
+  /* Distribute them evenly around the display field, clockwise.
+     Even on a /24, allocated IPs tend to cluster together, so
+     don't put any two hosts closer together than N degrees to
+     avoid unnecessary overlap when we have plenty of space due
+     to addresses that probably won't respond.  And don't spread
+     them out too far apart, because that looks too symmetrical
+     when there are a small number of hosts.
    */
-  div = pd->target_count;
-  if (div > 90) div = 90;  /* no closer together than 4 degrees */
-  for (i = 0, b = pd->targets; b; b = b->next, i++)
-    b->th = M_PI * 2 * ((div - i) % div) / div;
+  {
+    double th = frand(M_PI);
+    double sep = 360.0 / pd->target_count;
+    if (sep < 23) sep = 23;
+    if (sep > 43) sep = 43;
+    sep /= 180/M_PI;
+    for (b = pd->targets; b; b = b->next) {
+      b->th = th;
+      th += sep;
+    }
+  }
 
   return ssd;
 }