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