From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / glx / sonar-icmp.c
1 /* sonar, Copyright (c) 1998-2018 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)
731             continue;
732           else if (ifa->ifa_addr->sa_family != AF_INET)
733             {
734               if (pd->debug_p)
735                 fprintf (stderr, "%s:     if: %4s: %s\n", progname,
736                          ifa->ifa_name,
737                          (
738 # ifdef AF_UNIX
739                           ifa->ifa_addr->sa_family == AF_UNIX  ? "local" :
740 # endif
741 # ifdef AF_LINK
742                           ifa->ifa_addr->sa_family == AF_LINK  ? "link"  :
743 # endif
744 # ifdef AF_INET6
745                           ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6"  :
746 # endif
747                           "other"));
748               continue;
749             }
750           in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
751           mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
752                         ->sin_addr.s_addr);
753           if (pd->debug_p)
754             fprintf (stderr, "%s:     if: %4s: inet = %s /%d 0x%08lx\n",
755                      progname,
756                      ifa->ifa_name,
757                      inet_ntoa (in2),
758                      mask_width (mask),
759                      mask);
760           if (in2.s_addr == 0x0100007f ||   /* 127.0.0.1 in network order */
761               mask == 0)
762             continue;
763
764           /* At least on the AT&T 3G network, pinging either of the two
765              hosts on a /31 network doesn't work, so don't try.
766            */
767           if (mask_width (mask) == 31)
768             {
769               sprintf (buf,
770                        "Can't ping subnet:\n"
771                        "local network is\n"
772                        "%.100s/%d,\n"
773                        "a p2p bridge\n"
774                        "on if %.100s.",
775                        inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
776               if (*error_ret) free (*error_ret);
777               *error_ret = strdup (buf);
778               continue;
779             }
780
781           in = in2;
782           subnet_width = mask_width (mask);
783         }
784
785       if (in.s_addr)
786         {
787           if (*error_ret) free (*error_ret);
788           *error_ret = 0;
789           n_base = in.s_addr;  /* already in network order, I think? */
790         }
791       else if (!*error_ret)
792         *error_ret = strdup ("Unable to determine\nlocal IP address\n");
793
794       if (all) 
795         freeifaddrs (all);
796
797       if (*error_ret)
798         return 0;
799
800 # else /* !HAVE_GETIFADDRS */
801
802       /* If we can't walk the list of network interfaces to figure out
803          our local IP address, try to do it by finding the local host
804          name, then resolving that.
805       */
806       char hostname[BUFSIZ];
807       struct hostent *hent = 0;
808
809       if (gethostname(hostname, BUFSIZ)) 
810         {
811           *error_ret = strdup ("Unable to determine\n"
812                                "local host name!");
813           return 0;
814         }
815
816       /* Get our IP address and convert it to a string */
817
818       hent = gethostbyname(hostname);
819       if (! hent)
820         {
821           strcat (hostname, ".local");  /* Necessary on iphone */
822           hent = gethostbyname(hostname);
823         }
824
825       if (! hent)
826         {
827           sprintf(buf, 
828                   "Unable to resolve\n"
829                   "local host \"%.100s\"", 
830                   hostname);
831           *error_ret = strdup(buf);
832           return 0;
833         }
834
835       strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
836       n_base = pack_addr (hent->h_addr_list[0][0],
837                           hent->h_addr_list[0][1],
838                           hent->h_addr_list[0][2],
839                           hent->h_addr_list[0][3]);
840
841       if (n_base == 0x0100007f)   /* 127.0.0.1 in network order */
842         {
843           unsigned int a, b, c, d;
844           unpack_addr (n_base, &a, &b, &c, &d);
845           sprintf (buf,
846                    "Unable to determine\n"
847                    "local subnet address:\n"
848                    "\"%.100s\"\n"
849                    "resolves to\n"
850                    "loopback address\n"
851                    "%u.%u.%u.%u.",
852                    hostname, a, b, c, d);
853           *error_ret = strdup(buf);
854           return 0;
855         }
856
857 # endif /* !HAVE_GETIFADDRS */
858     }
859
860
861   /* Construct targets for all addresses in this subnet */
862
863   h_mask = width_mask (subnet_width);
864   h_base = ntohl (n_base);
865
866   if (desc_ret && !*desc_ret) {
867     char buf2[255];
868     unsigned int a, b, c, d;
869     unsigned long bb = n_base & htonl(h_mask);
870     unpack_addr (bb, &a, &b, &c, &d);
871     if (subnet_width > 24)
872       sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
873     else
874       sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width);
875     *desc_ret = strdup (buf2);
876   }
877
878   for (i = 255; i >= 0; i--) {
879     unsigned int a, b, c, d;
880     int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
881       
882     if ((ip & h_mask) != (h_base & h_mask))  /* skip out-of-subnet host */
883       continue;
884     else if (subnet_width == 31)             /* 1-bit bridge: 2 hosts */
885       ;
886     else if ((ip & ~h_mask) == 0)            /* skip network address */
887       continue;
888     else if ((ip & ~h_mask) == ~h_mask)      /* skip broadcast address */
889       continue;
890
891     unpack_addr (htonl (ip), &a, &b, &c, &d);
892     sprintf (address, "%u.%u.%u.%u", a, b, c, d);
893
894     if (pd->debug_p > 1)
895       {
896         unsigned int aa, ab, ac, ad;
897         unsigned int ma, mb, mc, md;
898         unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
899         unpack_addr (htonl (h_mask),          &ma, &mb, &mc, &md);
900         fprintf (stderr,
901                  "%s:  subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
902                  progname, address,
903                  aa, ab, ac, ad,
904                  ma, mb, mc, md,
905                  subnet_width);
906       }
907
908     p = address + strlen(address) + 1;
909     sprintf(p, "%d", i);
910
911     new = bogie_for_host (ssd, address, NULL);
912     if (new)
913       {
914         new->next = list;
915         list = new;
916       }
917   }
918
919   return list;
920 }
921
922
923 /* Send a ping packet.
924  */
925 static void
926 send_ping (ping_data *pd, const sonar_bogie *b)
927 {
928   ping_bogie *pb = (ping_bogie *) b->closure;
929   u_char *packet;
930   struct ICMP *icmph;
931   const char *token = "org.jwz.xscreensaver.sonar";
932   char *host_id;
933
934   unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
935                  sizeof(socklen_t) + pb->addrlen +
936                  strlen(token) + 1 +
937                  strlen(pd->version) + 1);
938
939   /* Create the ICMP packet */
940
941   if (! (packet = (u_char *) calloc(1, pcktsiz)))
942     return;  /* Out of memory */
943
944   icmph = (struct ICMP *) packet;
945   ICMP_TYPE(icmph) = ICMP_ECHO;
946   ICMP_CODE(icmph) = 0;
947   ICMP_CHECKSUM(icmph) = 0;
948   ICMP_ID(icmph) = pd->pid;
949   ICMP_SEQ(icmph) = pd->seq++;
950 # ifdef GETTIMEOFDAY_TWO_ARGS
951   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
952                (struct timezone *) 0);
953 # else
954   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
955 # endif
956
957   /* We store the sockaddr of the host we're pinging in the packet, and parse
958      that out of the return packet later (see get_ping() for why).
959      After that, we also include the name and version of this program,
960      just to give a clue to anyone sniffing and wondering what's up.
961    */
962   host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
963   *(socklen_t *)host_id = pb->addrlen;
964   host_id += sizeof(socklen_t);
965   memcpy(host_id, &pb->address, pb->addrlen);
966   host_id += pb->addrlen;
967   sprintf (host_id, "%.20s %.20s", token, pd->version);
968
969   ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
970
971   /* Send it */
972
973   if (sendto(pd->icmpsock, packet, pcktsiz, 0, 
974              (struct sockaddr *)&pb->address, sizeof(pb->address))
975       != pcktsiz)
976     {
977 #if 0
978       char buf[BUFSIZ];
979       sprintf(buf, "%s: pinging %.100s", progname, b->name);
980       perror(buf);
981 #endif
982     }
983 }
984
985 /* signal handler */
986 static void
987 sigcatcher (int sig)
988 {
989   timer_expired = 1;
990 }
991
992
993 /* Compute the checksum on a ping packet.
994  */
995 static u_short
996 checksum (u_short *packet, int size) 
997 {
998   register int nleft = size;
999   register u_short *w = packet;
1000   register int sum = 0;
1001   u_short answer = 0;
1002
1003   /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
1004      to it, and at the end, fold back all the carry bits from the
1005      top 16 bits into the lower 16 bits.
1006    */
1007   while (nleft > 1)
1008     {
1009       sum += *w++;
1010       nleft -= 2;
1011     }
1012
1013   /* mop up an odd byte, if necessary */
1014
1015   if (nleft == 1)
1016     {
1017       *(u_char *)(&answer) = *(u_char *)w ;
1018       *(1 + (u_char *)(&answer)) = 0;
1019       sum += answer;
1020     }
1021
1022   /* add back carry outs from top 16 bits to low 16 bits */
1023
1024   sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
1025   sum += (sum >> 16);                     /* add carry */
1026   answer = ~sum;                          /* truncate to 16 bits */
1027
1028   return(answer);
1029 }
1030
1031
1032 /* Copies the sonar_bogie and the underlying ping_bogie.
1033  */
1034 static sonar_bogie *
1035 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1036 {
1037   sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1038   if (b->closure)
1039     {
1040       ping_bogie *pb  = (ping_bogie *) b->closure;
1041       ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1042       pb2->address = pb->address;
1043       b2->closure = pb2;
1044     }
1045   return b2;
1046 }
1047
1048
1049 /* Look for all outstanding ping replies.
1050  */
1051 static sonar_bogie *
1052 get_ping (sonar_sensor_data *ssd)
1053 {
1054   ping_data *pd = (ping_data *) ssd->closure;
1055   struct sockaddr from;
1056   socklen_t fromlen;
1057   int result;
1058   u_char packet[1024];
1059   struct timeval now;
1060   struct timeval *then;
1061   struct ip *ip;
1062   int iphdrlen;
1063   struct ICMP *icmph;
1064   sonar_bogie *bl = 0;
1065   sonar_bogie *new = 0;
1066   struct sigaction sa;
1067   struct itimerval it;
1068   fd_set rfds;
1069   struct timeval tv;
1070
1071   /* Set up a signal to interrupt our wait for a packet */
1072
1073   sigemptyset(&sa.sa_mask);
1074   sa.sa_flags = 0;
1075   sa.sa_handler = sigcatcher;
1076   if (sigaction(SIGALRM, &sa, 0) == -1) 
1077     {
1078       char msg[1024];
1079       sprintf(msg, "%s: unable to trap SIGALRM", progname);
1080       perror(msg);
1081       exit(1);
1082     }
1083
1084   /* Set up a timer to interupt us if we don't get a packet */
1085
1086   it.it_interval.tv_sec = 0;
1087   it.it_interval.tv_usec = 0;
1088   it.it_value.tv_sec = 0;
1089   it.it_value.tv_usec = pd->timeout;
1090   timer_expired = 0;
1091   setitimer(ITIMER_REAL, &it, 0);
1092
1093   /* Wait for a result packet */
1094
1095   fromlen = sizeof(from);
1096   while (! timer_expired)
1097     {
1098       tv.tv_usec = pd->timeout;
1099       tv.tv_sec = 0;
1100 #if 0
1101       /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1102       FD_ZERO(&rfds);
1103 #else
1104       memset (&rfds, 0, sizeof(rfds));
1105 #endif
1106       FD_SET(pd->icmpsock, &rfds);
1107       /* only wait a little while, in case we raced with the timer expiration.
1108          From Valentijn Sessink <valentyn@openoffice.nl> */
1109       if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1110         {
1111           result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet),
1112                              0, &from, &fromlen);
1113
1114           /* Check the packet */
1115
1116 # ifdef GETTIMEOFDAY_TWO_ARGS
1117           gettimeofday(&now, (struct timezone *) 0);
1118 # else
1119           gettimeofday(&now);
1120 # endif
1121           ip = (struct ip *) packet;
1122           iphdrlen = IP_HDRLEN(ip) << 2;
1123           icmph = (struct ICMP *) &packet[iphdrlen];
1124           then  = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
1125
1126
1127           /* Ignore anything but ICMP Replies */
1128           if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY) 
1129             continue;
1130
1131           /* Ignore packets not set from us */
1132           if (ICMP_ID(icmph) != pd->pid)
1133             continue;
1134
1135           /* Find the bogie in 'targets' that corresponds to this packet
1136              and copy it, so that this bogie stays in the same spot (th)
1137              on the screen, and so that we don't have to resolve it again.
1138
1139              We could find the bogie by comparing ip->ip_src.s_addr to
1140              pb->address, but it is possible that, in certain weird router
1141              or NAT situations, that the reply will come back from a 
1142              different address than the one we sent it to.  So instead,
1143              we parse the sockaddr out of the reply packet payload.
1144            */
1145           {
1146             const socklen_t *host_id = (socklen_t *) &packet[
1147               iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1148
1149             sonar_bogie *b;
1150
1151             /* Ensure that a maliciously-crafted return packet can't
1152                make us overflow in memcmp. */
1153             if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1154               {
1155                 const u_char *host_end = (const u_char *)(host_id + 1) +
1156                                          *host_id;
1157
1158                 if ((const u_char *)(host_id + 1) <= host_end &&
1159                     host_end <= packet + result)
1160                   {
1161                     for (b = pd->targets; b; b = b->next)
1162                       {
1163                         ping_bogie *pb = (ping_bogie *)b->closure;
1164                         if (*host_id == pb->addrlen &&
1165                             !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1166                           {
1167                             /* Check to see if the name lookup is done. */
1168                             if (pb->lookup_name &&
1169                                 async_name_from_addr_is_done (pb->lookup_name))
1170                               {
1171                                 char *host = NULL;
1172
1173                                 async_name_from_addr_finish (pb->lookup_name,
1174                                                              &host, NULL);
1175
1176                                 if (pd->debug_p > 1)
1177                                   fprintf (stderr, "%s:   %s => %s\n",
1178                                            progname, b->name,
1179                                            host ? host : "<unknown>");
1180
1181                                 if (host)
1182                                   {
1183                                     free(b->name);
1184                                     b->name = host;
1185                                   }
1186
1187                                 pb->lookup_name = NULL;
1188                               }
1189
1190                             new = copy_ping_bogie (ssd, b);
1191                             break;
1192                           }
1193                       }
1194                   }
1195               }
1196           }
1197
1198           if (! new)      /* not in targets? */
1199             {
1200               unsigned int a, b, c, d;
1201               unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1202               fprintf (stderr, 
1203                        "%s: UNEXPECTED PING REPLY! "
1204                        "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1205                        progname, result, ICMP_SEQ(icmph), a, b, c, d);
1206               continue;
1207             }
1208
1209           new->next = bl;
1210           bl = new;
1211
1212           {
1213             double msec = delta(then, &now) / 1000.0;
1214
1215             if (pd->times_p)
1216               {
1217                 if (new->desc) free (new->desc);
1218                 new->desc = (char *) malloc (30);
1219                 if      (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1220                 else if (msec >  9) sprintf (new->desc, "%.1f ms", msec);
1221                 else if (msec >  1) sprintf (new->desc, "%.2f ms", msec);
1222                 else                sprintf (new->desc, "%.3f ms", msec);
1223               }
1224
1225             if (pd->debug_p && pd->times_p)  /* ping-like stdout log */
1226               {
1227                 char *s = strdup(new->name);
1228                 char *s2 = s;
1229                 if (strlen(s) > 28)
1230                   {
1231                     s2 = s + strlen(s) - 28;
1232                     strncpy (s2, "...", 3);
1233                   }
1234                 fprintf (stdout, 
1235                          "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1236                          result, s2, ICMP_SEQ(icmph), new->desc);
1237                 fflush (stdout);
1238                 free(s);
1239               }
1240
1241             /* The radius must be between 0.0 and 1.0.
1242                We want to display ping times on a logarithmic scale,
1243                with the three rings being 2.5, 70 and 2,000 milliseconds.
1244              */
1245             if (msec <= 0) msec = 0.001;
1246             new->r = log (msec * 10) / log (20000);
1247
1248             /* Don't put anyone *too* close to the center of the screen. */
1249             if (new->r < 0) new->r = 0;
1250             if (new->r < 0.1) new->r += 0.1;
1251           }
1252         }
1253     }
1254
1255   return bl;
1256 }
1257
1258
1259 /* difference between the two times in microseconds.
1260  */
1261 static long
1262 delta (struct timeval *then, struct timeval *now) 
1263 {
1264   return (((now->tv_sec - then->tv_sec) * 1000000) + 
1265           (now->tv_usec - then->tv_usec));  
1266 }
1267
1268
1269 static void
1270 ping_free_data (sonar_sensor_data *ssd, void *closure)
1271 {
1272   ping_data *pd = (ping_data *) closure;
1273   sonar_bogie *b = pd->targets;
1274   while (b)
1275     {
1276       sonar_bogie *b2 = b->next;
1277       sonar_free_bogie (ssd, b);
1278       b = b2;
1279     }
1280   free (pd);
1281 }
1282
1283 static void
1284 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1285 {
1286   ping_bogie *pb = (ping_bogie *) closure;
1287
1288   if (pb->lookup_name)
1289     async_name_from_addr_cancel (pb->lookup_name);
1290   if (pb->lookup_addr)
1291     async_addr_from_name_cancel (pb->lookup_addr);
1292   free (pb->fallback);
1293
1294   free (closure);
1295 }
1296
1297
1298 /* Returns the current time in seconds as a double.
1299  */
1300 static double
1301 double_time (void)
1302 {
1303   struct timeval now;
1304 # ifdef GETTIMEOFDAY_TWO_ARGS
1305   struct timezone tzp;
1306   gettimeofday(&now, &tzp);
1307 # else
1308   gettimeofday(&now);
1309 # endif
1310
1311   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1312 }
1313
1314
1315 static void
1316 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1317                         sonar_bogie **sb)
1318 {
1319   ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1320
1321   *sbp = (*sb)->next;
1322   pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1323   sonar_free_bogie (ssd, *sb);
1324   *sb = NULL;
1325 }
1326
1327
1328 /* Pings the next bogie, if it's time.
1329    Returns all outstanding ping replies.
1330  */
1331 static sonar_bogie *
1332 ping_scan (sonar_sensor_data *ssd)
1333 {
1334   ping_data *pd = (ping_data *) ssd->closure;
1335   double now = double_time();
1336   double ping_cycle = 10;   /* re-ping a given host every 10 seconds */
1337   double ping_interval = ping_cycle / pd->target_count;
1338
1339   if (now > pd->last_ping_time + ping_interval)   /* time to ping someone */
1340     {
1341       struct sonar_bogie **sbp;
1342
1343       if (pd->last_pinged)
1344         {
1345           sbp = &pd->last_pinged->next;
1346           if (!*sbp)
1347             sbp = &pd->targets;
1348         }
1349       else
1350         sbp = &pd->targets;
1351
1352       if (!*sbp)
1353         /* Aaaaand we're out of bogies. */
1354         pd->last_pinged = NULL;
1355       else
1356         {
1357           sonar_bogie *sb = *sbp;
1358           ping_bogie *pb = (ping_bogie *)sb->closure;
1359           if (pb->lookup_addr &&
1360               async_addr_from_name_is_done (pb->lookup_addr))
1361             {
1362               if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1363                                                &pb->addrlen, NULL))
1364                 {
1365                   char *fallback = pb->fallback;
1366                   pb->fallback = NULL;
1367
1368                   if (pd->debug_p)
1369                     fprintf (stderr, "%s:   could not resolve host:  %s\n",
1370                              progname, sb->name);
1371
1372                   free_bogie_after_lookup (ssd, sbp, &sb);
1373
1374                   /* Insert the fallback bogie right where the old one was. */
1375                   if (fallback)
1376                     {
1377                       sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1378                                                                NULL);
1379                       if (new_bogie) {
1380                         new_bogie->next = *sbp;
1381
1382                         if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1383                             ! find_duplicate_host(pd, &pd->targets, new_bogie))
1384                           *sbp = new_bogie;
1385                         else
1386                           sonar_free_bogie (ssd, new_bogie);
1387                       }
1388
1389                       free (fallback);
1390                     }
1391                 }
1392               else
1393                 {
1394                   if (pd->debug_p > 1)
1395                     {
1396                       fprintf (stderr, "%s:   %s => ", progname, sb->name);
1397                       print_address (stderr, 0, &pb->address, pb->addrlen);
1398                       putc('\n', stderr);
1399                     }
1400
1401                   if (! is_address_ok (pd->debug_p, sb))
1402                     free_bogie_after_lookup (ssd, sbp, &sb);
1403                   else if (find_duplicate_host (pd, &pd->targets, sb))
1404                     /* Tricky: find_duplicate_host skips the current bogie when
1405                        scanning the targets list because pb->lookup_addr hasn't
1406                        been NULL'd yet.
1407
1408                        Not that it matters much, but behavior here is to
1409                        keep the existing address.
1410                      */
1411                     free_bogie_after_lookup (ssd, sbp, &sb);
1412                 }
1413
1414               if (sb)
1415                 pb->lookup_addr = NULL;
1416             }
1417
1418           if (sb && !pb->lookup_addr)
1419             {
1420               if (!pb->addrlen) abort();
1421               send_ping (pd, sb);
1422               pd->last_pinged = sb;
1423             }
1424         }
1425
1426       pd->last_ping_time = now;
1427     }
1428
1429   return get_ping (ssd);
1430 }
1431
1432
1433 /* Returns a list of hosts to ping based on the "-ping" argument.
1434  */
1435 static sonar_bogie *
1436 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1437             const char *ping_arg, Bool ping_works_p)
1438 {
1439   ping_data *pd = (ping_data *) ssd->closure;
1440   char *source, *token, *end, dummy;
1441   sonar_bogie *hostlist = 0;
1442   const char *fallback = "subnet";
1443
1444  AGAIN:
1445
1446   if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1447     source = strdup(fallback);
1448   else if (ping_arg)
1449     source = strdup(ping_arg);
1450   else
1451     return 0;
1452
1453   token = source;
1454   end = source + strlen(source);
1455   while (token < end)
1456     {
1457       char *next;
1458       sonar_bogie *new = 0;
1459 # ifdef READ_FILES
1460       struct stat st;
1461 # endif
1462       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1463       char d;
1464
1465       for (next = token;
1466            *next &&
1467            *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1468            next++)
1469         ;
1470       *next = 0;
1471
1472
1473       if (pd->debug_p)
1474         fprintf (stderr, "%s: parsing %s\n", progname, token);
1475
1476       if (!ping_works_p)
1477         {
1478           *error_ret = strdup ("Sonar must be setuid to ping!\n"
1479                                "Running simulation instead.");
1480           return 0;
1481         }
1482
1483       if ((4 == sscanf (token, "%u.%u.%u/%u %c",    &n0,&n1,&n2,    &m,&d)) ||
1484           (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1485         {
1486           /* subnet: A.B.C.D/M
1487              subnet: A.B.C/M
1488            */
1489           unsigned long ip = pack_addr (n0, n1, n2, n3);
1490           new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1491         }
1492       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1493         {
1494           /* IP: A.B.C.D
1495            */
1496           new = bogie_for_host (ssd, token, NULL);
1497         }
1498       else if (!strcmp (token, "subnet"))
1499         {
1500           new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1501         }
1502       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1503         {
1504           new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1505         }
1506       else if (*token == '.' || *token == '/' || 
1507                *token == '$' || *token == '~')
1508         {
1509 # ifdef READ_FILES
1510           new = read_hosts_file (ssd, token);
1511 # else
1512           if (pd->debug_p) fprintf (stderr, "%s:  skipping file\n", progname);
1513 # endif
1514         }
1515 # ifdef READ_FILES
1516       else if (!stat (token, &st))
1517         {
1518           new = read_hosts_file (ssd, token);
1519         }
1520 # endif /* READ_FILES */
1521       else
1522         {
1523           /* not an existant file - must be a host name
1524            */
1525           new = bogie_for_host (ssd, token, NULL);
1526         }
1527
1528       if (new)
1529         {
1530           sonar_bogie *nn = new;
1531           while (nn->next)
1532             nn = nn->next;
1533           nn->next = hostlist;
1534           hostlist = new;
1535         }
1536
1537       token = next + 1;
1538       while (token < end &&
1539              (*token == ',' || *token == ' ' ||
1540               *token == '\t' || *token == '\n'))
1541         token++;
1542     }
1543
1544   free (source);
1545
1546   /* If the arg was completely unparsable, fall back to the local subnet.
1547      This happens if the default is "/etc/hosts" but READ_FILES is off.
1548      Or if we're on a /31 network, in which case we try twice then fail.
1549    */
1550   if (!hostlist && fallback)
1551     {
1552       if (pd->debug_p)
1553         fprintf (stderr, "%s: no hosts parsed! Trying %s\n", 
1554                  progname, fallback);
1555       ping_arg = fallback;
1556       fallback = 0;
1557       goto AGAIN;
1558     }
1559
1560   return hostlist;
1561 }
1562
1563
1564 sonar_sensor_data *
1565 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1566                  const char *subnet, int timeout,
1567                  Bool resolve_p, Bool times_p, Bool debug_p)
1568 {
1569   /* Important! Do not return from this function without disavowing privileges
1570      with setuid(getuid()).
1571    */
1572   sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1573   ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1574   sonar_bogie *b;
1575   char *s;
1576
1577   Bool socket_initted_p = False;
1578   Bool socket_raw_p     = False;
1579
1580   pd->dpy = dpy;
1581
1582   pd->resolve_p = resolve_p;
1583   pd->times_p   = times_p;
1584   pd->debug_p   = debug_p;
1585
1586   ssd->closure       = pd;
1587   ssd->scan_cb       = ping_scan;
1588   ssd->free_data_cb  = ping_free_data;
1589   ssd->free_bogie_cb = ping_free_bogie_data;
1590
1591   /* Get short version number. */
1592   s = strchr (screensaver_id, ' ');
1593   pd->version = strdup (s+1);
1594   s = strchr (pd->version, ' ');
1595   *s = 0;
1596
1597
1598   /* Create the ICMP socket.  Do this before dropping privs.
1599
1600      Raw sockets can only be opened by root (or setuid root), so we
1601      only try to do this when the effective uid is 0.
1602
1603      We used to just always try, and notice the failure.  But apparently
1604      that causes "SELinux" to log spurious warnings when running with the
1605      "strict" policy.  So to avoid that, we just don't try unless we
1606      know it will work.
1607
1608      On MacOS X, we can avoid the whole problem by using a
1609      non-privileged datagram instead of a raw socket.
1610    */
1611   if (global_icmpsock)
1612     {
1613       pd->icmpsock = global_icmpsock;
1614       socket_initted_p = True;
1615       if (debug_p)
1616         fprintf (stderr, "%s: re-using icmp socket\n", progname);
1617
1618     } 
1619   else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1620     {
1621       socket_initted_p = True;
1622     }
1623   else if (geteuid() == 0 &&
1624            (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1625     {
1626       socket_initted_p = True;
1627       socket_raw_p = True;
1628     }
1629
1630   if (socket_initted_p)
1631     {
1632       global_icmpsock = pd->icmpsock;
1633       socket_initted_p = True;
1634       if (debug_p)
1635         fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1636                  (socket_raw_p ? "raw" : "dgram"));
1637     } 
1638   else if (debug_p)
1639     fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1640
1641   /* Disavow privs */
1642   setuid(getuid());
1643
1644   pd->pid = getpid() & 0xFFFF;
1645   pd->seq = 0;
1646   pd->timeout = timeout;
1647
1648   /* Generate a list of targets */
1649
1650   pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1651                             socket_initted_p);
1652   pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1653
1654   if (debug_p)
1655     {
1656       fprintf (stderr, "%s: Target list:\n", progname);
1657       for (b = pd->targets; b; b = b->next)
1658         {
1659           fprintf (stderr, "%s:   ", progname);
1660           print_host (stderr, b);
1661         }
1662     }
1663
1664   /* Make sure there is something to ping */
1665
1666   pd->target_count = 0;
1667   for (b = pd->targets; b; b = b->next)
1668     pd->target_count++;
1669
1670   if (pd->target_count == 0)
1671     {
1672       if (! *error_ret)
1673         *error_ret = strdup ("No hosts to ping!\n"
1674                              "Simulating instead.");
1675       if (pd) ping_free_data (ssd, pd);
1676       if (ssd) free (ssd);
1677       return 0;
1678     }
1679
1680   /* Distribute them evenly around the display field, clockwise.
1681      Even on a /24, allocated IPs tend to cluster together, so
1682      don't put any two hosts closer together than N degrees to
1683      avoid unnecessary overlap when we have plenty of space due
1684      to addresses that probably won't respond.  And don't spread
1685      them out too far apart, because that looks too symmetrical
1686      when there are a small number of hosts.
1687    */
1688   {
1689     double th = frand(M_PI);
1690     double sep = 360.0 / pd->target_count;
1691     if (sep < 23) sep = 23;
1692     if (sep > 43) sep = 43;
1693     sep /= 180/M_PI;
1694     for (b = pd->targets; b; b = b->next) {
1695       b->th = th;
1696       th += sep;
1697     }
1698   }
1699
1700   return ssd;
1701 }
1702
1703 #endif /* HAVE_PING -- whole file */