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