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