From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / sonar-icmp.c
1 /* sonar, Copyright (c) 1998-2017 Jamie Zawinski and Stephen Martin
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * This implements the "ping" sensor for sonar.
12  */
13
14 #include "screenhackI.h"
15 #include "sonar.h"
16 #include "version.h"
17 #include "async_netdb.h"
18
19 #undef usleep /* conflicts with unistd.h on OSX */
20
21 #ifdef USE_IPHONE
22   /* Note: to get this to compile for iPhone, you need to fix Xcode!
23      The icmp headers exist for the simulator build environment, but
24      not for the real-device build environment.  This appears to 
25      just be an Apple bug, not intentional.
26
27      xc=/Applications/Xcode.app/Contents
28      for path in    /Developer/Platforms/iPhone*?/Developer/SDKs/?* \
29                  $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do
30        for file in \
31          /usr/include/netinet/ip.h \
32          /usr/include/netinet/in_systm.h \
33          /usr/include/netinet/ip_icmp.h \
34          /usr/include/netinet/ip_var.h \
35          /usr/include/netinet/udp.h
36        do
37          ln -s "$file" "$path$file"
38        done
39      done
40   */
41 #endif
42
43 #ifndef HAVE_MOBILE
44 # define READ_FILES
45 #endif
46
47 #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
48 # include <unistd.h>
49 # include <sys/stat.h>
50 # include <limits.h>
51 # include <signal.h>
52 # include <fcntl.h>
53 # include <sys/types.h>
54 # include <sys/time.h>
55 # include <sys/ipc.h>
56 # ifndef HAVE_ANDROID
57 #  include <sys/shm.h>
58 # endif
59 # include <sys/socket.h>
60 # include <netinet/in_systm.h>
61 # include <netinet/in.h>
62 # include <netinet/ip.h>
63 # include <netinet/ip_icmp.h>
64 # include <netinet/udp.h>
65 # include <arpa/inet.h>
66 # include <netdb.h>
67 # include <errno.h>
68 # ifdef HAVE_GETIFADDRS
69 #  include <ifaddrs.h>
70 # endif
71 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
72
73 #if defined(HAVE_ICMP)
74 # define HAVE_PING
75 # define ICMP             icmp
76 # define ICMP_TYPE(p)     (p)->icmp_type
77 # define ICMP_CODE(p)     (p)->icmp_code
78 # define ICMP_CHECKSUM(p) (p)->icmp_cksum
79 # define ICMP_ID(p)       (p)->icmp_id
80 # define ICMP_SEQ(p)      (p)->icmp_seq
81 #elif defined(HAVE_ICMPHDR)
82 # define HAVE_PING
83 # define ICMP             icmphdr
84 # define ICMP_TYPE(p)     (p)->type
85 # define ICMP_CODE(p)     (p)->code
86 # define ICMP_CHECKSUM(p) (p)->checksum
87 # define ICMP_ID(p)       (p)->un.echo.id
88 # define ICMP_SEQ(p)      (p)->un.echo.sequence
89 #else
90 # undef HAVE_PING
91 #endif
92
93 #ifndef HAVE_MOBILE
94 # define LOAD_FILES
95 #endif
96
97 #ifndef HAVE_PING
98
99 sonar_sensor_data *
100 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret, 
101                  const char *subnet, int timeout,
102                  Bool resolve_p, Bool times_p, Bool debug_p)
103 {
104   if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
105     fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
106              progname);
107   return 0;
108 }
109
110 #else /* HAVE_PING -- whole file */
111
112
113 #if defined(__DECC) || defined(_IP_VHL)
114    /* This is how you do it on DEC C, and possibly some BSD systems. */
115 # define IP_HDRLEN(ip)   ((ip)->ip_vhl & 0x0F)
116 #else
117    /* This is how you do it on everything else. */
118 # define IP_HDRLEN(ip)   ((ip)->ip_hl)
119 #endif
120
121 /* yes, there is only one, even when multiple savers are running in the
122    same address space - since we can only open this socket before dropping
123    privs.
124  */
125 static int global_icmpsock = 0;
126
127 /* Set by a signal handler. */
128 static int timer_expired;
129
130
131
132 static u_short checksum(u_short *, int);
133 static long delta(struct timeval *, struct timeval *);
134
135
136 typedef struct {
137   Display *dpy;                 /* Only used to get *useThreads. */
138
139   char *version;                /* short version number of xscreensaver */
140   int icmpsock;                 /* socket for sending pings */
141   int pid;                      /* our process ID */
142   int seq;                      /* packet sequence number */
143   int timeout;                  /* packet timeout */
144
145   int target_count;
146   sonar_bogie *targets;         /* the hosts we will ping;
147                                    those that pong end up on ssd->pending. */
148   sonar_bogie *last_pinged;     /* pointer into 'targets' list */
149   double last_ping_time;
150
151   Bool resolve_p;
152   Bool times_p;
153   Bool debug_p;
154
155 } ping_data;
156
157 typedef struct {
158   async_name_from_addr_t lookup_name;
159   async_addr_from_name_t lookup_addr;
160   async_netdb_sockaddr_storage_t address;       /* ip address */
161   socklen_t addrlen;
162   char *fallback;
163 } ping_bogie;
164
165
166
167 /* Packs an IP address quad into bigendian network order. */
168 static in_addr_t
169 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
170 {
171   unsigned long i = (((a & 255) << 24) |
172                      ((b & 255) << 16) |
173                      ((c & 255) <<  8) |
174                      ((d & 255)      ));
175   return htonl (i);
176 }
177
178 /* Unpacks an IP address quad from bigendian network order. */
179 static void
180 unpack_addr (unsigned long addr,
181              unsigned int *a, unsigned int *b,
182              unsigned int *c, unsigned int *d)
183 {
184   addr = ntohl (addr);
185   *a = (addr >> 24) & 255;
186   *b = (addr >> 16) & 255;
187   *c = (addr >>  8) & 255;
188   *d = (addr      ) & 255;
189 }
190
191
192
193
194 /* Resolves the bogie's name (either a hostname or ip address string)
195    to a hostent.  Returns 1 if successful, 0 if something went wrong.
196  */
197 static int
198 resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
199 {
200   ping_bogie *pb = (ping_bogie *) sb->closure;
201
202   unsigned int ip[4];
203   char c;
204
205   if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
206                    &ip[0], &ip[1], &ip[2], &ip[3], &c))
207     {
208       /* It's an IP address.
209        */
210       struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
211
212       if (ip[3] == 0)
213         {
214           if (pd->debug_p > 1)
215             fprintf (stderr, "%s:   ignoring bogus IP %s\n",
216                      progname, sb->name);
217           return 0;
218         }
219
220       iaddr->sin_family = AF_INET;
221       iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
222       pb->addrlen = sizeof(struct sockaddr_in);
223
224       if (resolve_p)
225         {
226           pb->lookup_name =
227             async_name_from_addr_start (pd->dpy,
228                                         (const struct sockaddr *)&pb->address,
229                                         pb->addrlen);
230           if (!pb->lookup_name)
231             {
232               fprintf (stderr, "%s:   unable to start host resolution.\n",
233                        progname);
234             }
235         }
236     }
237   else
238     {
239       /* It's a host name. */
240
241       /* don't waste time being confused by non-hostname tokens
242          in .ssh/known_hosts */
243       if (!strcmp (sb->name, "ssh-rsa") ||
244           !strcmp (sb->name, "ssh-dsa") ||
245           !strcmp (sb->name, "ssh-dss") ||
246           !strncmp (sb->name, "ecdsa-", 6) ||
247           strlen (sb->name) >= 80)
248         return 0;
249
250       /* .ssh/known_hosts sometimes contains weirdness like "[host]:port".
251          Ignore it. */
252       if (strchr (sb->name, '['))
253         {
254           if (pd->debug_p)
255             fprintf (stderr, "%s:   ignoring bogus address \"%s\"\n", 
256                      progname, sb->name);
257           return 0;
258         }
259
260       /* If the name contains a colon, it's probably IPv6. */
261       if (strchr (sb->name, ':'))
262         {
263           if (pd->debug_p)
264             fprintf (stderr, "%s:   ignoring ipv6 address \"%s\"\n", 
265                      progname, sb->name);
266           return 0;
267         }
268
269       pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
270       if (!pb->lookup_addr)
271         {
272           if (pd->debug_p)
273             /* Either address space exhaustion or RAM exhaustion. */
274             fprintf (stderr, "%s:   unable to start host resolution.\n",
275                      progname);
276           return 0;
277         }
278     }
279   return 1;
280 }
281
282
283 static void
284 print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
285 {
286 #ifdef HAVE_GETADDRINFO
287   char buf[NI_MAXHOST];
288 #else
289   char buf[50];
290 #endif
291
292   const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
293   const char *ips = buf;
294
295   if (!addr->sa_family)
296     ips = "<no address>";
297   else
298     {
299 #ifdef HAVE_GETADDRINFO
300       int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
301                                    NULL, 0, NI_NUMERICHOST);
302       if (gai_error == EAI_SYSTEM)
303         ips = strerror(errno);
304       else if (gai_error)
305         ips = gai_strerror(gai_error);
306 #else
307       switch (addr->sa_family)
308         {
309         case AF_INET:
310           {
311             u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
312             unsigned int a, b, c, d;
313             unpack_addr (ip, &a, &b, &c, &d);   /* ip is in network order */
314             sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
315           }
316           break;
317         default:
318           ips = "<unknown>";
319           break;
320         }
321 #endif
322     }
323
324   fprintf (out, "%-*s", width, ips);
325 }
326
327
328 static void
329 print_host (FILE *out, const sonar_bogie *sb)
330 {
331   const ping_bogie *pb = (const ping_bogie *) sb->closure;
332   const char *name = sb->name;
333   if (!name || !*name) name = "<unknown>";
334   print_address (out, 16, &pb->address, pb->addrlen);
335   fprintf (out, " %s\n", name);
336 }
337
338
339 static Bool
340 is_address_ok(Bool debug_p, const sonar_bogie *b)
341 {
342   const ping_bogie *pb = (const ping_bogie *) b->closure;
343   const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
344
345   switch (addr->sa_family)
346     {
347     case AF_INET:
348       {
349         struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
350
351         /* Don't ever use loopback (127.0.0.x) hosts */
352         unsigned long ip = iaddr->sin_addr.s_addr;
353         if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L)  /* 127.0.0.x */
354           {
355             if (debug_p)
356               fprintf (stderr, "%s:   ignoring loopback host %s\n",
357                        progname, b->name);
358             return False;
359           }
360
361         /* Don't ever use broadcast (255.x.x.x) hosts */
362         if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L)  /* 255.x.x.x */
363           {
364             if (debug_p)
365               fprintf (stderr, "%s:   ignoring broadcast host %s\n",
366                        progname, b->name);
367             return False;
368           }
369       }
370
371       break;
372     }
373
374   return True;
375 }
376
377
378 /* Create a sonar_bogie from a host name or ip address string.
379    Returns NULL if the name could not be resolved.
380  */
381 static sonar_bogie *
382 bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
383 {
384   ping_data *pd = (ping_data *) ssd->closure;
385   sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
386   ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
387   Bool resolve_p = pd->resolve_p;
388
389   b->name = strdup (name);
390   b->closure = pb;
391
392   if (! resolve_bogie_hostname (pd, b, resolve_p))
393     goto FAIL;
394
395   if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
396     goto FAIL;
397
398   if (pd->debug_p > 1)
399     {
400       fprintf (stderr, "%s:   added ", progname);
401       print_host (stderr, b);
402     }
403
404   if (fallback)
405     pb->fallback = strdup (fallback);
406   return b;
407
408  FAIL:
409   if (b) sonar_free_bogie (ssd, b);
410
411   if (fallback)
412     return bogie_for_host (ssd, fallback, NULL);
413
414   return 0;
415 }
416
417
418 #ifdef READ_FILES
419
420 /* Return a list of bogies read from a file.
421    The file can be like /etc/hosts or .ssh/known_hosts or probably
422    just about anything that has host names in it.
423  */
424 static sonar_bogie *
425 read_hosts_file (sonar_sensor_data *ssd, const char *filename) 
426 {
427   ping_data *pd = (ping_data *) ssd->closure;
428   FILE *fp;
429   char buf[LINE_MAX];
430   char *p;
431   sonar_bogie *list = 0;
432   char *addr, *name;
433   sonar_bogie *new;
434
435   /* Kludge: on OSX, variables have not been expanded in the command
436      line arguments, so as a special case, allow the string to begin
437      with literal "$HOME/" or "~/".
438
439      This is so that the "Known Hosts" menu item in sonar.xml works.
440    */
441   if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6)) 
442     {
443       char *s = strchr (filename, '/');
444       strcpy (buf, getenv("HOME"));
445       strcat (buf, s);
446       filename = buf;
447     }
448
449   fp = fopen(filename, "r");
450   if (!fp)
451     {
452       char buf2[1024];
453       sprintf(buf2, "%s:  %s", progname, filename);
454 #ifdef HAVE_JWXYZ
455       if (pd->debug_p)  /* on OSX don't syslog this */
456 #endif
457         perror (buf2);
458       return 0;
459     }
460
461   if (pd->debug_p)
462     fprintf (stderr, "%s:  reading \"%s\"\n", progname, filename);
463
464   while ((p = fgets(buf, LINE_MAX, fp)))
465     {
466       while ((*p == ' ') || (*p == '\t'))       /* skip whitespace */
467         p++;
468       if (*p == '#')                            /* skip comments */
469         continue;
470
471       /* Get the name and address */
472
473       if ((addr = strtok(buf, " ,;\t\n")))
474         name = strtok(0, " ,;\t\n");
475       else
476         continue;
477
478       /* Check to see if the addr looks like an addr.  If not, assume
479          the addr is a name and there is no addr.  This way, we can
480          handle files whose lines have "xx.xx.xx.xx hostname" as their
481          first two tokens, and also files that have a hostname as their
482          first token (like .ssh/known_hosts and .rhosts.)
483       */
484       {
485         int i; char c;
486         if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
487           {
488             name = addr;
489             addr = 0;
490           }
491       }
492
493       /* If the name is all digits, it's not a name. */
494       if (name)
495         {
496           const char *s;
497           for (s = name; *s; s++)
498             if (*s < '0' || *s > '9')
499               break;
500           if (! *s)
501             {
502               if (pd->debug_p > 1)
503                 fprintf (stderr, "%s:  skipping bogus name \"%s\" (%s)\n",
504                          progname, name, addr);
505               name = 0;
506             }
507         }
508
509       /* Create a new target using first the name then the address */
510
511       if (!name)
512         {
513           name = addr;
514           addr = NULL;
515         }
516
517       new = bogie_for_host (ssd, name, addr);
518
519       if (new)
520         {
521           new->next = list;
522           list = new;
523         }
524     }
525
526   fclose(fp);
527   return list;
528 }
529 #endif /* READ_FILES */
530
531
532 static sonar_bogie **
533 found_duplicate_host (const ping_data *pd, sonar_bogie **list,
534                       sonar_bogie *bogie)
535 {
536   if (pd->debug_p)
537   {
538     fprintf (stderr, "%s: deleted duplicate: ", progname);
539     print_host (stderr, bogie);
540   }
541
542   return list;
543 }
544
545
546 static sonar_bogie **
547 find_duplicate_host (const ping_data *pd, sonar_bogie **list,
548                      sonar_bogie *bogie)
549 {
550   const ping_bogie *pb = (const ping_bogie *) bogie->closure;
551   const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
552
553   while(*list)
554     {
555       const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
556
557       if (!pb2->lookup_addr)
558         {
559           const struct sockaddr *addr2 =
560             (const struct sockaddr *) &(pb2->address);
561
562           if (addr1->sa_family == addr2->sa_family)
563             {
564               switch (addr1->sa_family)
565                 {
566                 case AF_INET:
567                   {
568                     unsigned long ip1 =
569                       ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
570                     const struct sockaddr_in *i2 =
571                       (const struct sockaddr_in *)addr2;
572                     unsigned long ip2 = i2->sin_addr.s_addr;
573
574                     if (ip1 == ip2)
575                       return found_duplicate_host (pd, list, bogie);
576                   }
577                   break;
578 #ifdef AF_INET6
579                 case AF_INET6:
580                   {
581                     if (! memcmp(
582                             &((const struct sockaddr_in6 *)addr1)->sin6_addr,
583                             &((const struct sockaddr_in6 *)addr2)->sin6_addr,
584                             16))
585                       return found_duplicate_host (pd, list, bogie);
586                   }
587                   break;
588 #endif
589                 default:
590                   {
591                     /* Fallback behavior: Just memcmp the two addresses.
592
593                        For this to work, unused space in the sockaddr must be
594                        set to zero. Which may actually be the case:
595                        - async_addr_from_name_finish won't put garbage into
596                          sockaddr_in.sin_zero or elsewhere unless getaddrinfo
597                          does.
598                        - ping_bogie is allocated with calloc(). */
599
600                     if (pb->addrlen == pb2->addrlen &&
601                         ! memcmp(addr1, addr2, pb->addrlen))
602                       return found_duplicate_host (pd, list, bogie);
603                   }
604                   break;
605                 }
606             }
607         }
608
609       list = &(*list)->next;
610     }
611
612   return NULL;
613 }
614
615
616 static sonar_bogie *
617 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
618 {
619   ping_data *pd = (ping_data *) ssd->closure;
620   sonar_bogie *head = list;
621   sonar_bogie *sb = head;
622
623   while (sb)
624     {
625       ping_bogie *pb = (ping_bogie *) sb->closure;
626
627       if (!pb->lookup_addr)
628         {
629           sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
630           if (sb2)
631             *sb2 = (*sb2)->next;
632             /* #### sb leaked */
633           else
634             sb = sb->next;
635         }
636       else
637         sb = sb->next;
638     }
639
640   return head;
641 }
642
643
644 static unsigned long
645 width_mask (unsigned long width)
646 {
647   unsigned long m = 0;
648   int i;
649   for (i = 0; i < width; i++)
650     m |= (1L << (31-i));
651   return m;
652 }
653
654
655 #ifdef HAVE_GETIFADDRS
656 static unsigned int
657 mask_width (unsigned long mask)
658 {
659   int i;
660   for (i = 0; i < 32; i++)
661     if (mask & (1 << i))
662       break;
663   return 32-i;
664 }
665 #endif
666
667
668 /* Generate a list of bogies consisting of all of the entries on
669   the same subnet.  'base' ip is in network order; 0 means localhost.
670  */
671 static sonar_bogie *
672 subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
673               unsigned long n_base, int subnet_width)
674 {
675   ping_data *pd = (ping_data *) ssd->closure;
676   unsigned long h_mask;   /* host order */
677   unsigned long h_base;   /* host order */
678   char address[BUFSIZ];
679   char *p;
680   int i;
681   sonar_bogie *new;
682   sonar_bogie *list = 0;
683   char buf[1024];
684
685   if (subnet_width < 24)
686     {
687       sprintf (buf,
688                "Pinging %lu hosts is a bad\n"
689                "idea.  Please use a subnet\n"
690                "mask of 24 bits or more.",
691                (unsigned long) (1L << (32 - subnet_width)) - 1);
692       *error_ret = strdup(buf);
693       return 0;
694     }
695   else if (subnet_width > 30)
696     {
697       sprintf (buf, 
698                "An %d-bit subnet\n"
699                "doesn't make sense.\n"
700                "Try \"subnet/24\"\n"
701                "or \"subnet/29\".\n",
702                subnet_width);
703       *error_ret = strdup(buf);
704       return 0;
705     }
706
707
708   if (pd->debug_p)
709     fprintf (stderr, "%s:   adding %d-bit subnet\n", progname, subnet_width);
710
711
712   if (! n_base)
713     {
714 # ifdef HAVE_GETIFADDRS
715
716       /* To determine the local subnet, we need to know the local IP address.
717          Do this by looking at the IPs of every network interface.
718       */
719       struct in_addr in = { 0, };
720       struct ifaddrs *all = 0, *ifa;
721
722       if (pd->debug_p)
723         fprintf (stderr, "%s:   listing network interfaces\n", progname);
724
725       getifaddrs (&all);
726       for (ifa = all; ifa; ifa = ifa->ifa_next)
727         {
728           struct in_addr in2;
729           unsigned long mask;
730           if (ifa->ifa_addr->sa_family != AF_INET)
731             {
732               if (pd->debug_p)
733                 fprintf (stderr, "%s:     if: %4s: %s\n", progname,
734                          ifa->ifa_name,
735                          (
736 # ifdef AF_UNIX
737                           ifa->ifa_addr->sa_family == AF_UNIX  ? "local" :
738 # endif
739 # ifdef AF_LINK
740                           ifa->ifa_addr->sa_family == AF_LINK  ? "link"  :
741 # endif
742 # ifdef AF_INET6
743                           ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6"  :
744 # endif
745                           "other"));
746               continue;
747             }
748           in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
749           mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
750                         ->sin_addr.s_addr);
751           if (pd->debug_p)
752             fprintf (stderr, "%s:     if: %4s: inet = %s /%d 0x%08lx\n",
753                      progname,
754                      ifa->ifa_name,
755                      inet_ntoa (in2),
756                      mask_width (mask),
757                      mask);
758           if (in2.s_addr == 0x0100007f ||   /* 127.0.0.1 in network order */
759               mask == 0)
760             continue;
761
762           /* At least on the AT&T 3G network, pinging either of the two
763              hosts on a /31 network doesn't work, so don't try.
764            */
765           if (mask_width (mask) == 31)
766             {
767               sprintf (buf,
768                        "Can't ping subnet:\n"
769                        "local network is\n"
770                        "%.100s/%d,\n"
771                        "a p2p bridge\n"
772                        "on if %.100s.",
773                        inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
774               if (*error_ret) free (*error_ret);
775               *error_ret = strdup (buf);
776               continue;
777             }
778
779           in = in2;
780           subnet_width = mask_width (mask);
781         }
782
783       if (in.s_addr)
784         {
785           if (*error_ret) free (*error_ret);
786           *error_ret = 0;
787           n_base = in.s_addr;  /* already in network order, I think? */
788         }
789       else if (!*error_ret)
790         *error_ret = strdup ("Unable to determine\nlocal IP address\n");
791
792       if (all) 
793         freeifaddrs (all);
794
795       if (*error_ret)
796         return 0;
797
798 # else /* !HAVE_GETIFADDRS */
799
800       /* If we can't walk the list of network interfaces to figure out
801          our local IP address, try to do it by finding the local host
802          name, then resolving that.
803       */
804       char hostname[BUFSIZ];
805       struct hostent *hent = 0;
806
807       if (gethostname(hostname, BUFSIZ)) 
808         {
809           *error_ret = strdup ("Unable to determine\n"
810                                "local host name!");
811           return 0;
812         }
813
814       /* Get our IP address and convert it to a string */
815
816       hent = gethostbyname(hostname);
817       if (! hent)
818         {
819           strcat (hostname, ".local");  /* Necessary on iphone */
820           hent = gethostbyname(hostname);
821         }
822
823       if (! hent)
824         {
825           sprintf(buf, 
826                   "Unable to resolve\n"
827                   "local host \"%.100s\"", 
828                   hostname);
829           *error_ret = strdup(buf);
830           return 0;
831         }
832
833       strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
834       n_base = pack_addr (hent->h_addr_list[0][0],
835                           hent->h_addr_list[0][1],
836                           hent->h_addr_list[0][2],
837                           hent->h_addr_list[0][3]);
838
839       if (n_base == 0x0100007f)   /* 127.0.0.1 in network order */
840         {
841           unsigned int a, b, c, d;
842           unpack_addr (n_base, &a, &b, &c, &d);
843           sprintf (buf,
844                    "Unable to determine\n"
845                    "local subnet address:\n"
846                    "\"%.100s\"\n"
847                    "resolves to\n"
848                    "loopback address\n"
849                    "%u.%u.%u.%u.",
850                    hostname, a, b, c, d);
851           *error_ret = strdup(buf);
852           return 0;
853         }
854
855 # endif /* !HAVE_GETIFADDRS */
856     }
857
858
859   /* Construct targets for all addresses in this subnet */
860
861   h_mask = width_mask (subnet_width);
862   h_base = ntohl (n_base);
863
864   if (desc_ret && !*desc_ret) {
865     char buf2[255];
866     unsigned int a, b, c, d;
867     unsigned long bb = n_base & htonl(h_mask);
868     unpack_addr (bb, &a, &b, &c, &d);
869     if (subnet_width > 24)
870       sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
871     else
872       sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width);
873     *desc_ret = strdup (buf2);
874   }
875
876   for (i = 255; i >= 0; i--) {
877     unsigned int a, b, c, d;
878     int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
879       
880     if ((ip & h_mask) != (h_base & h_mask))  /* skip out-of-subnet host */
881       continue;
882     else if (subnet_width == 31)             /* 1-bit bridge: 2 hosts */
883       ;
884     else if ((ip & ~h_mask) == 0)            /* skip network address */
885       continue;
886     else if ((ip & ~h_mask) == ~h_mask)      /* skip broadcast address */
887       continue;
888
889     unpack_addr (htonl (ip), &a, &b, &c, &d);
890     sprintf (address, "%u.%u.%u.%u", a, b, c, d);
891
892     if (pd->debug_p > 1)
893       {
894         unsigned int aa, ab, ac, ad;
895         unsigned int ma, mb, mc, md;
896         unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
897         unpack_addr (htonl (h_mask),          &ma, &mb, &mc, &md);
898         fprintf (stderr,
899                  "%s:  subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
900                  progname, address,
901                  aa, ab, ac, ad,
902                  ma, mb, mc, md,
903                  subnet_width);
904       }
905
906     p = address + strlen(address) + 1;
907     sprintf(p, "%d", i);
908
909     new = bogie_for_host (ssd, address, NULL);
910     if (new)
911       {
912         new->next = list;
913         list = new;
914       }
915   }
916
917   return list;
918 }
919
920
921 /* Send a ping packet.
922  */
923 static void
924 send_ping (ping_data *pd, const sonar_bogie *b)
925 {
926   ping_bogie *pb = (ping_bogie *) b->closure;
927   u_char *packet;
928   struct ICMP *icmph;
929   const char *token = "org.jwz.xscreensaver.sonar";
930   char *host_id;
931
932   unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
933                  sizeof(socklen_t) + pb->addrlen +
934                  strlen(token) + 1 +
935                  strlen(pd->version) + 1);
936
937   /* Create the ICMP packet */
938
939   if (! (packet = (u_char *) calloc(1, pcktsiz)))
940     return;  /* Out of memory */
941
942   icmph = (struct ICMP *) packet;
943   ICMP_TYPE(icmph) = ICMP_ECHO;
944   ICMP_CODE(icmph) = 0;
945   ICMP_CHECKSUM(icmph) = 0;
946   ICMP_ID(icmph) = pd->pid;
947   ICMP_SEQ(icmph) = pd->seq++;
948 # ifdef GETTIMEOFDAY_TWO_ARGS
949   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
950                (struct timezone *) 0);
951 # else
952   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
953 # endif
954
955   /* We store the sockaddr of the host we're pinging in the packet, and parse
956      that out of the return packet later (see get_ping() for why).
957      After that, we also include the name and version of this program,
958      just to give a clue to anyone sniffing and wondering what's up.
959    */
960   host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
961   *(socklen_t *)host_id = pb->addrlen;
962   host_id += sizeof(socklen_t);
963   memcpy(host_id, &pb->address, pb->addrlen);
964   host_id += pb->addrlen;
965   sprintf (host_id, "%.20s %.20s", token, pd->version);
966
967   ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
968
969   /* Send it */
970
971   if (sendto(pd->icmpsock, packet, pcktsiz, 0, 
972              (struct sockaddr *)&pb->address, sizeof(pb->address))
973       != pcktsiz)
974     {
975 #if 0
976       char buf[BUFSIZ];
977       sprintf(buf, "%s: pinging %.100s", progname, b->name);
978       perror(buf);
979 #endif
980     }
981 }
982
983 /* signal handler */
984 static void
985 sigcatcher (int sig)
986 {
987   timer_expired = 1;
988 }
989
990
991 /* Compute the checksum on a ping packet.
992  */
993 static u_short
994 checksum (u_short *packet, int size) 
995 {
996   register int nleft = size;
997   register u_short *w = packet;
998   register int sum = 0;
999   u_short answer = 0;
1000
1001   /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
1002      to it, and at the end, fold back all the carry bits from the
1003      top 16 bits into the lower 16 bits.
1004    */
1005   while (nleft > 1)
1006     {
1007       sum += *w++;
1008       nleft -= 2;
1009     }
1010
1011   /* mop up an odd byte, if necessary */
1012
1013   if (nleft == 1)
1014     {
1015       *(u_char *)(&answer) = *(u_char *)w ;
1016       *(1 + (u_char *)(&answer)) = 0;
1017       sum += answer;
1018     }
1019
1020   /* add back carry outs from top 16 bits to low 16 bits */
1021
1022   sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
1023   sum += (sum >> 16);                     /* add carry */
1024   answer = ~sum;                          /* truncate to 16 bits */
1025
1026   return(answer);
1027 }
1028
1029
1030 /* Copies the sonar_bogie and the underlying ping_bogie.
1031  */
1032 static sonar_bogie *
1033 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1034 {
1035   sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1036   if (b->closure)
1037     {
1038       ping_bogie *pb  = (ping_bogie *) b->closure;
1039       ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1040       pb2->address = pb->address;
1041       b2->closure = pb2;
1042     }
1043   return b2;
1044 }
1045
1046
1047 /* Look for all outstanding ping replies.
1048  */
1049 static sonar_bogie *
1050 get_ping (sonar_sensor_data *ssd)
1051 {
1052   ping_data *pd = (ping_data *) ssd->closure;
1053   struct sockaddr from;
1054   socklen_t fromlen;
1055   int result;
1056   u_char packet[1024];
1057   struct timeval now;
1058   struct timeval *then;
1059   struct ip *ip;
1060   int iphdrlen;
1061   struct ICMP *icmph;
1062   sonar_bogie *bl = 0;
1063   sonar_bogie *new = 0;
1064   struct sigaction sa;
1065   struct itimerval it;
1066   fd_set rfds;
1067   struct timeval tv;
1068
1069   /* Set up a signal to interrupt our wait for a packet */
1070
1071   sigemptyset(&sa.sa_mask);
1072   sa.sa_flags = 0;
1073   sa.sa_handler = sigcatcher;
1074   if (sigaction(SIGALRM, &sa, 0) == -1) 
1075     {
1076       char msg[1024];
1077       sprintf(msg, "%s: unable to trap SIGALRM", progname);
1078       perror(msg);
1079       exit(1);
1080     }
1081
1082   /* Set up a timer to interupt us if we don't get a packet */
1083
1084   it.it_interval.tv_sec = 0;
1085   it.it_interval.tv_usec = 0;
1086   it.it_value.tv_sec = 0;
1087   it.it_value.tv_usec = pd->timeout;
1088   timer_expired = 0;
1089   setitimer(ITIMER_REAL, &it, 0);
1090
1091   /* Wait for a result packet */
1092
1093   fromlen = sizeof(from);
1094   while (! timer_expired)
1095     {
1096       tv.tv_usec = pd->timeout;
1097       tv.tv_sec = 0;
1098 #if 0
1099       /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1100       FD_ZERO(&rfds);
1101 #else
1102       memset (&rfds, 0, sizeof(rfds));
1103 #endif
1104       FD_SET(pd->icmpsock, &rfds);
1105       /* only wait a little while, in case we raced with the timer expiration.
1106          From Valentijn Sessink <valentyn@openoffice.nl> */
1107       if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1108         {
1109           result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet),
1110                              0, &from, &fromlen);
1111
1112           /* Check the packet */
1113
1114 # ifdef GETTIMEOFDAY_TWO_ARGS
1115           gettimeofday(&now, (struct timezone *) 0);
1116 # else
1117           gettimeofday(&now);
1118 # endif
1119           ip = (struct ip *) packet;
1120           iphdrlen = IP_HDRLEN(ip) << 2;
1121           icmph = (struct ICMP *) &packet[iphdrlen];
1122           then  = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1123
1124
1125           /* Ignore anything but ICMP Replies */
1126           if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY) 
1127             continue;
1128
1129           /* Ignore packets not set from us */
1130           if (ICMP_ID(icmph) != pd->pid)
1131             continue;
1132
1133           /* Find the bogie in 'targets' that corresponds to this packet
1134              and copy it, so that this bogie stays in the same spot (th)
1135              on the screen, and so that we don't have to resolve it again.
1136
1137              We could find the bogie by comparing ip->ip_src.s_addr to
1138              pb->address, but it is possible that, in certain weird router
1139              or NAT situations, that the reply will come back from a 
1140              different address than the one we sent it to.  So instead,
1141              we parse the sockaddr out of the reply packet payload.
1142            */
1143           {
1144             const socklen_t *host_id = (socklen_t *) &packet[
1145               iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1146
1147             sonar_bogie *b;
1148
1149             /* Ensure that a maliciously-crafted return packet can't
1150                make us overflow in memcmp. */
1151             if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1152               {
1153                 const u_char *host_end = (const u_char *)(host_id + 1) +
1154                                          *host_id;
1155
1156                 if ((const u_char *)(host_id + 1) <= host_end &&
1157                     host_end <= packet + result)
1158                   {
1159                     for (b = pd->targets; b; b = b->next)
1160                       {
1161                         ping_bogie *pb = (ping_bogie *)b->closure;
1162                         if (*host_id == pb->addrlen &&
1163                             !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1164                           {
1165                             /* Check to see if the name lookup is done. */
1166                             if (pb->lookup_name &&
1167                                 async_name_from_addr_is_done (pb->lookup_name))
1168                               {
1169                                 char *host = NULL;
1170
1171                                 async_name_from_addr_finish (pb->lookup_name,
1172                                                              &host, NULL);
1173
1174                                 if (pd->debug_p > 1)
1175                                   fprintf (stderr, "%s:   %s => %s\n",
1176                                            progname, b->name,
1177                                            host ? host : "<unknown>");
1178
1179                                 if (host)
1180                                   {
1181                                     free(b->name);
1182                                     b->name = host;
1183                                   }
1184
1185                                 pb->lookup_name = NULL;
1186                               }
1187
1188                             new = copy_ping_bogie (ssd, b);
1189                             break;
1190                           }
1191                       }
1192                   }
1193               }
1194           }
1195
1196           if (! new)      /* not in targets? */
1197             {
1198               unsigned int a, b, c, d;
1199               unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1200               fprintf (stderr, 
1201                        "%s: UNEXPECTED PING REPLY! "
1202                        "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1203                        progname, result, ICMP_SEQ(icmph), a, b, c, d);
1204               continue;
1205             }
1206
1207           new->next = bl;
1208           bl = new;
1209
1210           {
1211             double msec = delta(then, &now) / 1000.0;
1212
1213             if (pd->times_p)
1214               {
1215                 if (new->desc) free (new->desc);
1216                 new->desc = (char *) malloc (30);
1217                 if      (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1218                 else if (msec >  9) sprintf (new->desc, "%.1f ms", msec);
1219                 else if (msec >  1) sprintf (new->desc, "%.2f ms", msec);
1220                 else                sprintf (new->desc, "%.3f ms", msec);
1221               }
1222
1223             if (pd->debug_p && pd->times_p)  /* ping-like stdout log */
1224               {
1225                 char *s = strdup(new->name);
1226                 char *s2 = s;
1227                 if (strlen(s) > 28)
1228                   {
1229                     s2 = s + strlen(s) - 28;
1230                     strncpy (s2, "...", 3);
1231                   }
1232                 fprintf (stdout, 
1233                          "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1234                          result, s2, ICMP_SEQ(icmph), new->desc);
1235                 fflush (stdout);
1236                 free(s);
1237               }
1238
1239             /* The radius must be between 0.0 and 1.0.
1240                We want to display ping times on a logarithmic scale,
1241                with the three rings being 2.5, 70 and 2,000 milliseconds.
1242              */
1243             if (msec <= 0) msec = 0.001;
1244             new->r = log (msec * 10) / log (20000);
1245
1246             /* Don't put anyone *too* close to the center of the screen. */
1247             if (new->r < 0) new->r = 0;
1248             if (new->r < 0.1) new->r += 0.1;
1249           }
1250         }
1251     }
1252
1253   return bl;
1254 }
1255
1256
1257 /* difference between the two times in microseconds.
1258  */
1259 static long
1260 delta (struct timeval *then, struct timeval *now) 
1261 {
1262   return (((now->tv_sec - then->tv_sec) * 1000000) + 
1263           (now->tv_usec - then->tv_usec));  
1264 }
1265
1266
1267 static void
1268 ping_free_data (sonar_sensor_data *ssd, void *closure)
1269 {
1270   ping_data *pd = (ping_data *) closure;
1271   sonar_bogie *b = pd->targets;
1272   while (b)
1273     {
1274       sonar_bogie *b2 = b->next;
1275       sonar_free_bogie (ssd, b);
1276       b = b2;
1277     }
1278   free (pd);
1279 }
1280
1281 static void
1282 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1283 {
1284   ping_bogie *pb = (ping_bogie *) closure;
1285
1286   if (pb->lookup_name)
1287     async_name_from_addr_cancel (pb->lookup_name);
1288   if (pb->lookup_addr)
1289     async_addr_from_name_cancel (pb->lookup_addr);
1290   free (pb->fallback);
1291
1292   free (closure);
1293 }
1294
1295
1296 /* Returns the current time in seconds as a double.
1297  */
1298 static double
1299 double_time (void)
1300 {
1301   struct timeval now;
1302 # ifdef GETTIMEOFDAY_TWO_ARGS
1303   struct timezone tzp;
1304   gettimeofday(&now, &tzp);
1305 # else
1306   gettimeofday(&now);
1307 # endif
1308
1309   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1310 }
1311
1312
1313 static void
1314 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1315                         sonar_bogie **sb)
1316 {
1317   ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1318
1319   *sbp = (*sb)->next;
1320   pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1321   sonar_free_bogie (ssd, *sb);
1322   *sb = NULL;
1323 }
1324
1325
1326 /* Pings the next bogie, if it's time.
1327    Returns all outstanding ping replies.
1328  */
1329 static sonar_bogie *
1330 ping_scan (sonar_sensor_data *ssd)
1331 {
1332   ping_data *pd = (ping_data *) ssd->closure;
1333   double now = double_time();
1334   double ping_cycle = 10;   /* re-ping a given host every 10 seconds */
1335   double ping_interval = ping_cycle / pd->target_count;
1336
1337   if (now > pd->last_ping_time + ping_interval)   /* time to ping someone */
1338     {
1339       struct sonar_bogie **sbp;
1340
1341       if (pd->last_pinged)
1342         {
1343           sbp = &pd->last_pinged->next;
1344           if (!*sbp)
1345             sbp = &pd->targets;
1346         }
1347       else
1348         sbp = &pd->targets;
1349
1350       if (!*sbp)
1351         /* Aaaaand we're out of bogies. */
1352         pd->last_pinged = NULL;
1353       else
1354         {
1355           sonar_bogie *sb = *sbp;
1356           ping_bogie *pb = (ping_bogie *)sb->closure;
1357           if (pb->lookup_addr &&
1358               async_addr_from_name_is_done (pb->lookup_addr))
1359             {
1360               if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1361                                                &pb->addrlen, NULL))
1362                 {
1363                   char *fallback = pb->fallback;
1364                   pb->fallback = NULL;
1365
1366                   if (pd->debug_p)
1367                     fprintf (stderr, "%s:   could not resolve host:  %s\n",
1368                              progname, sb->name);
1369
1370                   free_bogie_after_lookup (ssd, sbp, &sb);
1371
1372                   /* Insert the fallback bogie right where the old one was. */
1373                   if (fallback)
1374                     {
1375                       sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1376                                                                NULL);
1377                       if (new_bogie) {
1378                         new_bogie->next = *sbp;
1379
1380                         if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1381                             ! find_duplicate_host(pd, &pd->targets, new_bogie))
1382                           *sbp = new_bogie;
1383                         else
1384                           sonar_free_bogie (ssd, new_bogie);
1385                       }
1386
1387                       free (fallback);
1388                     }
1389                 }
1390               else
1391                 {
1392                   if (pd->debug_p > 1)
1393                     {
1394                       fprintf (stderr, "%s:   %s => ", progname, sb->name);
1395                       print_address (stderr, 0, &pb->address, pb->addrlen);
1396                       putc('\n', stderr);
1397                     }
1398
1399                   if (! is_address_ok (pd->debug_p, sb))
1400                     free_bogie_after_lookup (ssd, sbp, &sb);
1401                   else if (find_duplicate_host (pd, &pd->targets, sb))
1402                     /* Tricky: find_duplicate_host skips the current bogie when
1403                        scanning the targets list because pb->lookup_addr hasn't
1404                        been NULL'd yet.
1405
1406                        Not that it matters much, but behavior here is to
1407                        keep the existing address.
1408                      */
1409                     free_bogie_after_lookup (ssd, sbp, &sb);
1410                 }
1411
1412               if (sb)
1413                 pb->lookup_addr = NULL;
1414             }
1415
1416           if (sb && !pb->lookup_addr)
1417             {
1418               if (!pb->addrlen) abort();
1419               send_ping (pd, sb);
1420               pd->last_pinged = sb;
1421             }
1422         }
1423
1424       pd->last_ping_time = now;
1425     }
1426
1427   return get_ping (ssd);
1428 }
1429
1430
1431 /* Returns a list of hosts to ping based on the "-ping" argument.
1432  */
1433 static sonar_bogie *
1434 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1435             const char *ping_arg, Bool ping_works_p)
1436 {
1437   ping_data *pd = (ping_data *) ssd->closure;
1438   char *source, *token, *end, dummy;
1439   sonar_bogie *hostlist = 0;
1440   const char *fallback = "subnet";
1441
1442  AGAIN:
1443
1444   if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1445     source = strdup(fallback);
1446   else if (ping_arg)
1447     source = strdup(ping_arg);
1448   else
1449     return 0;
1450
1451   token = source;
1452   end = source + strlen(source);
1453   while (token < end)
1454     {
1455       char *next;
1456       sonar_bogie *new = 0;
1457 # ifdef READ_FILES
1458       struct stat st;
1459 # endif
1460       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1461       char d;
1462
1463       for (next = token;
1464            *next &&
1465            *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1466            next++)
1467         ;
1468       *next = 0;
1469
1470
1471       if (pd->debug_p)
1472         fprintf (stderr, "%s: parsing %s\n", progname, token);
1473
1474       if (!ping_works_p)
1475         {
1476           *error_ret = strdup ("Sonar must be setuid to ping!\n"
1477                                "Running simulation instead.");
1478           return 0;
1479         }
1480
1481       if ((4 == sscanf (token, "%u.%u.%u/%u %c",    &n0,&n1,&n2,    &m,&d)) ||
1482           (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1483         {
1484           /* subnet: A.B.C.D/M
1485              subnet: A.B.C/M
1486            */
1487           unsigned long ip = pack_addr (n0, n1, n2, n3);
1488           new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1489         }
1490       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1491         {
1492           /* IP: A.B.C.D
1493            */
1494           new = bogie_for_host (ssd, token, NULL);
1495         }
1496       else if (!strcmp (token, "subnet"))
1497         {
1498           new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1499         }
1500       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1501         {
1502           new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1503         }
1504       else if (*token == '.' || *token == '/' || 
1505                *token == '$' || *token == '~')
1506         {
1507 # ifdef READ_FILES
1508           new = read_hosts_file (ssd, token);
1509 # else
1510           if (pd->debug_p) fprintf (stderr, "%s:  skipping file\n", progname);
1511 # endif
1512         }
1513 # ifdef READ_FILES
1514       else if (!stat (token, &st))
1515         {
1516           new = read_hosts_file (ssd, token);
1517         }
1518 # endif /* READ_FILES */
1519       else
1520         {
1521           /* not an existant file - must be a host name
1522            */
1523           new = bogie_for_host (ssd, token, NULL);
1524         }
1525
1526       if (new)
1527         {
1528           sonar_bogie *nn = new;
1529           while (nn->next)
1530             nn = nn->next;
1531           nn->next = hostlist;
1532           hostlist = new;
1533         }
1534
1535       token = next + 1;
1536       while (token < end &&
1537              (*token == ',' || *token == ' ' ||
1538               *token == '\t' || *token == '\n'))
1539         token++;
1540     }
1541
1542   free (source);
1543
1544   /* If the arg was completely unparsable, fall back to the local subnet.
1545      This happens if the default is "/etc/hosts" but READ_FILES is off.
1546      Or if we're on a /31 network, in which case we try twice then fail.
1547    */
1548   if (!hostlist && fallback)
1549     {
1550       if (pd->debug_p)
1551         fprintf (stderr, "%s: no hosts parsed! Trying %s\n", 
1552                  progname, fallback);
1553       ping_arg = fallback;
1554       fallback = 0;
1555       goto AGAIN;
1556     }
1557
1558   return hostlist;
1559 }
1560
1561
1562 sonar_sensor_data *
1563 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1564                  const char *subnet, int timeout,
1565                  Bool resolve_p, Bool times_p, Bool debug_p)
1566 {
1567   /* Important! Do not return from this function without disavowing privileges
1568      with setuid(getuid()).
1569    */
1570   sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1571   ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1572   sonar_bogie *b;
1573   char *s;
1574
1575   Bool socket_initted_p = False;
1576   Bool socket_raw_p     = False;
1577
1578   pd->dpy = dpy;
1579
1580   pd->resolve_p = resolve_p;
1581   pd->times_p   = times_p;
1582   pd->debug_p   = debug_p;
1583
1584   ssd->closure       = pd;
1585   ssd->scan_cb       = ping_scan;
1586   ssd->free_data_cb  = ping_free_data;
1587   ssd->free_bogie_cb = ping_free_bogie_data;
1588
1589   /* Get short version number. */
1590   s = strchr (screensaver_id, ' ');
1591   pd->version = strdup (s+1);
1592   s = strchr (pd->version, ' ');
1593   *s = 0;
1594
1595
1596   /* Create the ICMP socket.  Do this before dropping privs.
1597
1598      Raw sockets can only be opened by root (or setuid root), so we
1599      only try to do this when the effective uid is 0.
1600
1601      We used to just always try, and notice the failure.  But apparently
1602      that causes "SELinux" to log spurious warnings when running with the
1603      "strict" policy.  So to avoid that, we just don't try unless we
1604      know it will work.
1605
1606      On MacOS X, we can avoid the whole problem by using a
1607      non-privileged datagram instead of a raw socket.
1608    */
1609   if (global_icmpsock)
1610     {
1611       pd->icmpsock = global_icmpsock;
1612       socket_initted_p = True;
1613       if (debug_p)
1614         fprintf (stderr, "%s: re-using icmp socket\n", progname);
1615
1616     } 
1617   else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1618     {
1619       socket_initted_p = True;
1620     }
1621   else if (geteuid() == 0 &&
1622            (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1623     {
1624       socket_initted_p = True;
1625       socket_raw_p = True;
1626     }
1627
1628   if (socket_initted_p)
1629     {
1630       global_icmpsock = pd->icmpsock;
1631       socket_initted_p = True;
1632       if (debug_p)
1633         fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1634                  (socket_raw_p ? "raw" : "dgram"));
1635     } 
1636   else if (debug_p)
1637     fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1638
1639   /* Disavow privs */
1640   setuid(getuid());
1641
1642   pd->pid = getpid() & 0xFFFF;
1643   pd->seq = 0;
1644   pd->timeout = timeout;
1645
1646   /* Generate a list of targets */
1647
1648   pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1649                             socket_initted_p);
1650   pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1651
1652   if (debug_p)
1653     {
1654       fprintf (stderr, "%s: Target list:\n", progname);
1655       for (b = pd->targets; b; b = b->next)
1656         {
1657           fprintf (stderr, "%s:   ", progname);
1658           print_host (stderr, b);
1659         }
1660     }
1661
1662   /* Make sure there is something to ping */
1663
1664   pd->target_count = 0;
1665   for (b = pd->targets; b; b = b->next)
1666     pd->target_count++;
1667
1668   if (pd->target_count == 0)
1669     {
1670       if (! *error_ret)
1671         *error_ret = strdup ("No hosts to ping!\n"
1672                              "Simulating instead.");
1673       if (pd) ping_free_data (ssd, pd);
1674       if (ssd) free (ssd);
1675       return 0;
1676     }
1677
1678   /* Distribute them evenly around the display field, clockwise.
1679      Even on a /24, allocated IPs tend to cluster together, so
1680      don't put any two hosts closer together than N degrees to
1681      avoid unnecessary overlap when we have plenty of space due
1682      to addresses that probably won't respond.  And don't spread
1683      them out too far apart, because that looks too symmetrical
1684      when there are a small number of hosts.
1685    */
1686   {
1687     double th = frand(M_PI);
1688     double sep = 360.0 / pd->target_count;
1689     if (sep < 23) sep = 23;
1690     if (sep > 43) sep = 43;
1691     sep /= 180/M_PI;
1692     for (b = pd->targets; b; b = b->next) {
1693       b->th = th;
1694       th += sep;
1695     }
1696   }
1697
1698   return ssd;
1699 }
1700
1701 #endif /* HAVE_PING -- whole file */