From http://www.jwz.org/xscreensaver/xscreensaver-5.18.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 /* If 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 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                          (ifa->ifa_addr->sa_family == AF_UNIX  ? "local" :
594                           ifa->ifa_addr->sa_family == AF_LINK  ? "link"  :
595                           ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6"  :
596                           "other"));
597               continue;
598             }
599           in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
600           mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
601                         ->sin_addr.s_addr);
602           if (pd->debug_p)
603             fprintf (stderr, "%s:     if: %4s: inet = %s /%d 0x%08lx\n",
604                      progname,
605                      ifa->ifa_name,
606                      inet_ntoa (in2),
607                      mask_width (mask),
608                      mask);
609           if (in2.s_addr == 0x0100007f ||   /* 127.0.0.1 in network order */
610               mask == 0)
611             continue;
612
613           /* At least on the AT&T 3G network, pinging either of the two
614              hosts on a /31 network doesn't work, so don't try.
615            */
616           if (mask_width (mask) == 31)
617             {
618               char buf[255];
619               sprintf (buf,
620                        "Can't ping subnet:\n"
621                        "local network is\n"
622                        "%s/%d,\n"
623                        "a p2p bridge\n"
624                        "on if %s.",
625                        inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
626               if (*error_ret) free (*error_ret);
627               *error_ret = strdup (buf);
628               continue;
629             }
630
631           in = in2;
632           subnet_width = mask_width (mask);
633         }
634
635       if (in.s_addr)
636         {
637           if (*error_ret) free (*error_ret);
638           *error_ret = 0;
639           n_base = in.s_addr;  /* already in network order, I think? */
640         }
641       else if (!*error_ret)
642         *error_ret = strdup ("Unable to determine\nlocal IP address\n");
643
644       if (all) 
645         freeifaddrs (all);
646
647       if (*error_ret)
648         return 0;
649
650 # else /* !HAVE_GETIFADDRS */
651
652       /* If we can't walk the list of network interfaces to figure out
653          our local IP address, try to do it by finding the local host
654          name, then resolving that.
655       */
656       char hostname[BUFSIZ];
657       struct hostent *hent = 0;
658
659       if (gethostname(hostname, BUFSIZ)) 
660         {
661           *error_ret = strdup ("Unable to determine\n"
662                                "local host name!");
663           return 0;
664         }
665
666       /* Get our IP address and convert it to a string */
667
668       hent = gethostbyname(hostname);
669       if (! hent)
670         {
671           strcat (hostname, ".local");  /* Necessary on iphone */
672           hent = gethostbyname(hostname);
673         }
674
675       if (! hent)
676         {
677           sprintf(buf, 
678                   "Unable to resolve\n"
679                   "local host \"%s\"", 
680                   hostname);
681           *error_ret = strdup(buf);
682           return 0;
683         }
684
685       strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
686       n_base = pack_addr (hent->h_addr_list[0][0],
687                           hent->h_addr_list[0][1],
688                           hent->h_addr_list[0][2],
689                           hent->h_addr_list[0][3]);
690
691       if (n_base == 0x0100007f)   /* 127.0.0.1 in network order */
692         {
693           unsigned int a, b, c, d;
694           unpack_addr (n_base, &a, &b, &c, &d);
695           sprintf (buf,
696                    "Unable to determine\n"
697                    "local subnet address:\n"
698                    "\"%s\"\n"
699                    "resolves to\n"
700                    "loopback address\n"
701                    "%u.%u.%u.%u.",
702                    hostname, a, b, c, d);
703           *error_ret = strdup(buf);
704           return 0;
705         }
706
707 # endif /* !HAVE_GETIFADDRS */
708     }
709
710
711   /* Construct targets for all addresses in this subnet */
712
713   h_mask = width_mask (subnet_width);
714   h_base = ntohl (n_base);
715
716   if (desc_ret && !*desc_ret) {
717     unsigned int a, b, c, d;
718     char buf[255];
719     unpack_addr (n_base, &a, &b, &c, &d);
720     sprintf (buf, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
721     *desc_ret = strdup (buf);
722   }
723
724   for (i = 255; i >= 0; i--) {
725     unsigned int a, b, c, d;
726     int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
727       
728     if ((ip & h_mask) != (h_base & h_mask))  /* skip out-of-subnet host */
729       continue;
730     else if (subnet_width == 31)             /* 1-bit bridge: 2 hosts */
731       ;
732     else if ((ip & ~h_mask) == 0)            /* skip network address */
733       continue;
734     else if ((ip & ~h_mask) == ~h_mask)      /* skip broadcast address */
735       continue;
736
737     unpack_addr (htonl (ip), &a, &b, &c, &d);
738     sprintf (address, "%u.%u.%u.%u", a, b, c, d);
739
740     if (pd->debug_p > 1)
741       {
742         unsigned int aa, ab, ac, ad;
743         unsigned int ma, mb, mc, md;
744         unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
745         unpack_addr (htonl (h_mask),          &ma, &mb, &mc, &md);
746         fprintf (stderr,
747                  "%s:  subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
748                  progname, address,
749                  aa, ab, ac, ad,
750                  ma, mb, mc, md,
751                  subnet_width);
752       }
753
754     p = address + strlen(address) + 1;
755     sprintf(p, "%d", i);
756
757     new = bogie_for_host (ssd, address, pd->resolve_p);
758     if (new)
759       {
760         new->next = list;
761         list = new;
762       }
763   }
764
765   return list;
766 }
767
768
769 /* Send a ping packet.
770  */
771 static void
772 send_ping (ping_data *pd, const sonar_bogie *b)
773 {
774   ping_bogie *pb = (ping_bogie *) b->closure;
775   u_char *packet;
776   struct ICMP *icmph;
777   const char *token = "org.jwz.xscreensaver.sonar";
778
779   int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) + 
780                  strlen(b->name) + 1 +
781                  strlen(token) + 1 + 
782                  strlen(pd->version) + 1);
783
784   /* Create the ICMP packet */
785
786   if (! (packet = (u_char *) calloc(1, pcktsiz)))
787     return;  /* Out of memory */
788
789   icmph = (struct ICMP *) packet;
790   ICMP_TYPE(icmph) = ICMP_ECHO;
791   ICMP_CODE(icmph) = 0;
792   ICMP_CHECKSUM(icmph) = 0;
793   ICMP_ID(icmph) = pd->pid;
794   ICMP_SEQ(icmph) = pd->seq++;
795 # ifdef GETTIMEOFDAY_TWO_ARGS
796   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
797                (struct timezone *) 0);
798 # else
799   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
800 # endif
801
802   /* We store the name of the host we're pinging in the packet, and parse
803      that out of the return packet later (see get_ping() for why).
804      After that, we also include the name and version of this program,
805      just to give a clue to anyone sniffing and wondering what's up.
806    */
807   sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
808            "%s%c%s %s",
809            b->name, 0, token, pd->version);
810
811   ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
812
813   /* Send it */
814
815   if (sendto(pd->icmpsock, packet, pcktsiz, 0, 
816              &pb->address, sizeof(pb->address))
817       != pcktsiz)
818     {
819 #if 0
820       char buf[BUFSIZ];
821       sprintf(buf, "%s: pinging %s", progname, b->name);
822       perror(buf);
823 #endif
824     }
825 }
826
827 /* signal handler */
828 static void
829 sigcatcher (int sig)
830 {
831   timer_expired = 1;
832 }
833
834
835 /* Compute the checksum on a ping packet.
836  */
837 static u_short
838 checksum (u_short *packet, int size) 
839 {
840   register int nleft = size;
841   register u_short *w = packet;
842   register int sum = 0;
843   u_short answer = 0;
844
845   /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
846      to it, and at the end, fold back all the carry bits from the
847      top 16 bits into the lower 16 bits.
848    */
849   while (nleft > 1)
850     {
851       sum += *w++;
852       nleft -= 2;
853     }
854
855   /* mop up an odd byte, if necessary */
856
857   if (nleft == 1)
858     {
859       *(u_char *)(&answer) = *(u_char *)w ;
860       *(1 + (u_char *)(&answer)) = 0;
861       sum += answer;
862     }
863
864   /* add back carry outs from top 16 bits to low 16 bits */
865
866   sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
867   sum += (sum >> 16);                     /* add carry */
868   answer = ~sum;                          /* truncate to 16 bits */
869
870   return(answer);
871 }
872
873
874 /* Copies the sonar_bogie and the underlying ping_bogie.
875  */
876 static sonar_bogie *
877 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
878 {
879   sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
880   if (b->closure)
881     {
882       ping_bogie *pb  = (ping_bogie *) b->closure;
883       ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
884       pb2->address = pb->address;
885       b2->closure = pb2;
886     }
887   return b2;
888 }
889
890
891 /* Look for all outstanding ping replies.
892  */
893 static sonar_bogie *
894 get_ping (sonar_sensor_data *ssd)
895 {
896   ping_data *pd = (ping_data *) ssd->closure;
897   struct sockaddr from;
898   unsigned int fromlen;  /* Posix says socklen_t, but that's not portable */
899   int result;
900   u_char packet[1024];
901   struct timeval now;
902   struct timeval *then;
903   struct ip *ip;
904   int iphdrlen;
905   struct ICMP *icmph;
906   sonar_bogie *bl = 0;
907   sonar_bogie *new = 0;
908   struct sigaction sa;
909   struct itimerval it;
910   fd_set rfds;
911   struct timeval tv;
912
913   /* Set up a signal to interrupt our wait for a packet */
914
915   sigemptyset(&sa.sa_mask);
916   sa.sa_flags = 0;
917   sa.sa_handler = sigcatcher;
918   if (sigaction(SIGALRM, &sa, 0) == -1) 
919     {
920       char msg[1024];
921       sprintf(msg, "%s: unable to trap SIGALRM", progname);
922       perror(msg);
923       exit(1);
924     }
925
926   /* Set up a timer to interupt us if we don't get a packet */
927
928   it.it_interval.tv_sec = 0;
929   it.it_interval.tv_usec = 0;
930   it.it_value.tv_sec = 0;
931   it.it_value.tv_usec = pd->timeout;
932   timer_expired = 0;
933   setitimer(ITIMER_REAL, &it, 0);
934
935   /* Wait for a result packet */
936
937   fromlen = sizeof(from);
938   while (! timer_expired)
939     {
940       tv.tv_usec = pd->timeout;
941       tv.tv_sec = 0;
942 #if 0
943       /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
944       FD_ZERO(&rfds);
945 #else
946       memset (&rfds, 0, sizeof(rfds));
947 #endif
948       FD_SET(pd->icmpsock, &rfds);
949       /* only wait a little while, in case we raced with the timer expiration.
950          From Valentijn Sessink <valentyn@openoffice.nl> */
951       if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
952         {
953           result = recvfrom (pd->icmpsock, packet, sizeof(packet),
954                              0, &from, &fromlen);
955
956           /* Check the packet */
957
958 # ifdef GETTIMEOFDAY_TWO_ARGS
959           gettimeofday(&now, (struct timezone *) 0);
960 # else
961           gettimeofday(&now);
962 # endif
963           ip = (struct ip *) packet;
964           iphdrlen = IP_HDRLEN(ip) << 2;
965           icmph = (struct ICMP *) &packet[iphdrlen];
966           then  = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
967
968
969           /* Ignore anything but ICMP Replies */
970           if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY) 
971             continue;
972
973           /* Ignore packets not set from us */
974           if (ICMP_ID(icmph) != pd->pid)
975             continue;
976
977           /* Find the bogie in 'targets' that corresponds to this packet
978              and copy it, so that this bogie stays in the same spot (th)
979              on the screen, and so that we don't have to resolve it again.
980
981              We could find the bogie by comparing ip->ip_src.s_addr to
982              pb->address, but it is possible that, in certain weird router
983              or NAT situations, that the reply will come back from a 
984              different address than the one we sent it to.  So instead,
985              we parse the name out of the reply packet payload.
986            */
987           {
988             const char *name = (char *) &packet[iphdrlen +
989                                                 sizeof(struct ICMP) +
990                                                 sizeof(struct timeval)];
991             sonar_bogie *b;
992             for (b = pd->targets; b; b = b->next)
993               if (!strcmp (name, b->name))
994                 {
995                   new = copy_ping_bogie (ssd, b);
996                   break;
997                 }
998           }
999
1000           if (! new)      /* not in targets? */
1001             {
1002               unsigned int a, b, c, d;
1003               unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1004               fprintf (stderr, 
1005                        "%s: UNEXPECTED PING REPLY! "
1006                        "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1007                        progname, result, ICMP_SEQ(icmph), a, b, c, d);
1008               continue;
1009             }
1010
1011           new->next = bl;
1012           bl = new;
1013
1014           {
1015             double msec = delta(then, &now) / 1000.0;
1016
1017             if (pd->times_p)
1018               {
1019                 if (new->desc) free (new->desc);
1020                 new->desc = (char *) malloc (30);
1021                 if      (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1022                 else if (msec >  9) sprintf (new->desc, "%.1f ms", msec);
1023                 else if (msec >  1) sprintf (new->desc, "%.2f ms", msec);
1024                 else                sprintf (new->desc, "%.3f ms", msec);
1025               }
1026
1027             if (pd->debug_p && pd->times_p)  /* ping-like stdout log */
1028               {
1029                 char *s = strdup(new->name);
1030                 char *s2 = s;
1031                 if (strlen(s) > 28)
1032                   {
1033                     s2 = s + strlen(s) - 28;
1034                     strncpy (s2, "...", 3);
1035                   }
1036                 fprintf (stdout, 
1037                          "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1038                          result, s2, ICMP_SEQ(icmph), new->desc);
1039                 fflush (stdout);
1040                 free(s);
1041               }
1042
1043             /* The radius must be between 0.0 and 1.0.
1044                We want to display ping times on a logarithmic scale,
1045                with the three rings being 2.5, 70 and 2,000 milliseconds.
1046              */
1047             if (msec <= 0) msec = 0.001;
1048             new->r = log (msec * 10) / log (20000);
1049
1050             /* Don't put anyone *too* close to the center of the screen. */
1051             if (new->r < 0) new->r = 0;
1052             if (new->r < 0.1) new->r += 0.1;
1053           }
1054         }
1055     }
1056
1057   return bl;
1058 }
1059
1060
1061 /* difference between the two times in microseconds.
1062  */
1063 static long
1064 delta (struct timeval *then, struct timeval *now) 
1065 {
1066   return (((now->tv_sec - then->tv_sec) * 1000000) + 
1067           (now->tv_usec - then->tv_usec));  
1068 }
1069
1070
1071 static void
1072 ping_free_data (sonar_sensor_data *ssd, void *closure)
1073 {
1074   ping_data *pd = (ping_data *) closure;
1075   sonar_bogie *b = pd->targets;
1076   while (b)
1077     {
1078       sonar_bogie *b2 = b->next;
1079       sonar_free_bogie (ssd, b);
1080       b = b2;
1081     }
1082   free (pd);
1083 }
1084
1085 static void
1086 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1087 {
1088   free (closure);
1089 }
1090
1091
1092 /* Returns the current time in seconds as a double.
1093  */
1094 static double
1095 double_time (void)
1096 {
1097   struct timeval now;
1098 # ifdef GETTIMEOFDAY_TWO_ARGS
1099   struct timezone tzp;
1100   gettimeofday(&now, &tzp);
1101 # else
1102   gettimeofday(&now);
1103 # endif
1104
1105   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1106 }
1107
1108
1109 /* If a bogie is provided, pings it.
1110    Then, returns all outstanding ping replies.
1111  */
1112 static sonar_bogie *
1113 ping_scan (sonar_sensor_data *ssd)
1114 {
1115   ping_data *pd = (ping_data *) ssd->closure;
1116   double now = double_time();
1117   double ping_cycle = 10;   /* re-ping a given host every 10 seconds */
1118   double ping_interval = ping_cycle / pd->target_count;
1119
1120   if (now > pd->last_ping_time + ping_interval)   /* time to ping someone */
1121     {
1122       if (pd->last_pinged)
1123         pd->last_pinged = pd->last_pinged->next;
1124       if (! pd->last_pinged)
1125         pd->last_pinged = pd->targets;
1126       send_ping (pd, pd->last_pinged);
1127       pd->last_ping_time = now;
1128     }
1129
1130   return get_ping (ssd);
1131 }
1132
1133
1134 /* Returns a list of hosts to ping based on the "-ping" argument.
1135  */
1136 static sonar_bogie *
1137 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1138             const char *ping_arg, Bool ping_works_p)
1139 {
1140   ping_data *pd = (ping_data *) ssd->closure;
1141   char *source, *token, *end, dummy;
1142   sonar_bogie *hostlist = 0;
1143   const char *fallback = "subnet";
1144
1145  AGAIN:
1146
1147   if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1148     source = strdup(fallback);
1149   else if (ping_arg)
1150     source = strdup(ping_arg);
1151   else
1152     return 0;
1153
1154   token = source;
1155   end = source + strlen(source);
1156   while (token < end)
1157     {
1158       char *next;
1159       sonar_bogie *new = 0;
1160 # ifdef READ_FILES
1161       struct stat st;
1162 # endif
1163       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1164       char d;
1165
1166       for (next = token;
1167            *next &&
1168            *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1169            next++)
1170         ;
1171       *next = 0;
1172
1173
1174       if (pd->debug_p)
1175         fprintf (stderr, "%s: parsing %s\n", progname, token);
1176
1177       if (!ping_works_p)
1178         {
1179           *error_ret = strdup ("Sonar must be setuid to ping!\n"
1180                                "Running simulation instead.");
1181           return 0;
1182         }
1183
1184       if ((4 == sscanf (token, "%u.%u.%u/%u %c",    &n0,&n1,&n2,    &m,&d)) ||
1185           (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1186         {
1187           /* subnet: A.B.C.D/M
1188              subnet: A.B.C/M
1189            */
1190           unsigned long ip = pack_addr (n0, n1, n2, n3);
1191           new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1192         }
1193       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1194         {
1195           /* IP: A.B.C.D
1196            */
1197           new = bogie_for_host (ssd, token, pd->resolve_p);
1198         }
1199       else if (!strcmp (token, "subnet"))
1200         {
1201           new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1202         }
1203       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1204         {
1205           new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1206         }
1207       else if (*token == '.' || *token == '/' || 
1208                *token == '$' || *token == '~')
1209         {
1210 # ifdef READ_FILES
1211           new = read_hosts_file (ssd, token);
1212 # else
1213           if (pd->debug_p) fprintf (stderr, "%s:  skipping file\n", progname);
1214 # endif
1215         }
1216 # ifdef READ_FILES
1217       else if (!stat (token, &st))
1218         {
1219           new = read_hosts_file (ssd, token);
1220         }
1221 # endif /* READ_FILES */
1222       else
1223         {
1224           /* not an existant file - must be a host name
1225            */
1226           new = bogie_for_host (ssd, token, pd->resolve_p);
1227         }
1228
1229       if (new)
1230         {
1231           sonar_bogie *nn = new;
1232           while (nn->next)
1233             nn = nn->next;
1234           nn->next = hostlist;
1235           hostlist = new;
1236         }
1237
1238       token = next + 1;
1239       while (token < end &&
1240              (*token == ',' || *token == ' ' ||
1241               *token == '\t' || *token == '\n'))
1242         token++;
1243     }
1244
1245   free (source);
1246
1247   /* If the arg was completely unparsable, fall back to the local subnet.
1248      This happens if the default is "/etc/hosts" but READ_FILES is off.
1249      Or if we're on a /31 network, in which case we try twice then fail.
1250    */
1251   if (!hostlist && fallback)
1252     {
1253       if (pd->debug_p)
1254         fprintf (stderr, "%s: no hosts parsed! Trying %s\n", 
1255                  progname, fallback);
1256       ping_arg = fallback;
1257       fallback = 0;
1258       goto AGAIN;
1259     }
1260
1261   return hostlist;
1262 }
1263
1264
1265 sonar_sensor_data *
1266 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1267                  const char *subnet, int timeout,
1268                  Bool resolve_p, Bool times_p, Bool debug_p)
1269 {
1270   sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1271   ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1272   sonar_bogie *b;
1273   char *s;
1274   int i;
1275
1276   Bool socket_initted_p = False;
1277   Bool socket_raw_p     = False;
1278
1279   pd->resolve_p = resolve_p;
1280   pd->times_p   = times_p;
1281   pd->debug_p   = debug_p;
1282
1283   ssd->closure       = pd;
1284   ssd->scan_cb       = ping_scan;
1285   ssd->free_data_cb  = ping_free_data;
1286   ssd->free_bogie_cb = ping_free_bogie_data;
1287
1288   /* Get short version number. */
1289   s = strchr (screensaver_id, ' ');
1290   pd->version = strdup (s+1);
1291   s = strchr (pd->version, ' ');
1292   *s = 0;
1293
1294
1295   /* Create the ICMP socket.  Do this before dropping privs.
1296
1297      Raw sockets can only be opened by root (or setuid root), so we
1298      only try to do this when the effective uid is 0.
1299
1300      We used to just always try, and notice the failure.  But apparently
1301      that causes "SELinux" to log spurious warnings when running with the
1302      "strict" policy.  So to avoid that, we just don't try unless we
1303      know it will work.
1304
1305      On MacOS X, we can avoid the whole problem by using a
1306      non-privileged datagram instead of a raw socket.
1307    */
1308   if (global_icmpsock)
1309     {
1310       pd->icmpsock = global_icmpsock;
1311       socket_initted_p = True;
1312       if (debug_p)
1313         fprintf (stderr, "%s: re-using icmp socket\n", progname);
1314
1315     } 
1316   else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1317     {
1318       socket_initted_p = True;
1319     }
1320   else if (geteuid() == 0 &&
1321            (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1322     {
1323       socket_initted_p = True;
1324       socket_raw_p = True;
1325     }
1326
1327   if (socket_initted_p)
1328     {
1329       global_icmpsock = pd->icmpsock;
1330       socket_initted_p = True;
1331       if (debug_p)
1332         fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1333                  (socket_raw_p ? "raw" : "dgram"));
1334     } 
1335   else if (debug_p)
1336     fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1337
1338   /* Disavow privs */
1339   setuid(getuid());
1340
1341   pd->pid = getpid() & 0xFFFF;
1342   pd->seq = 0;
1343   pd->timeout = timeout;
1344
1345   /* Generate a list of targets */
1346
1347   pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1348                             socket_initted_p);
1349   pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1350
1351   if (debug_p)
1352     {
1353       fprintf (stderr, "%s: Target list:\n", progname);
1354       for (b = pd->targets; b; b = b->next)
1355         {
1356           ping_bogie *pb = (ping_bogie *) b->closure;
1357           struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
1358           unsigned long ip = iaddr->sin_addr.s_addr;
1359           fprintf (stderr, "%s:   ", progname);
1360           print_host (stderr, ip, b->name);
1361         }
1362     }
1363
1364   /* Make sure there is something to ping */
1365
1366   pd->target_count = 0;
1367   for (b = pd->targets; b; b = b->next)
1368     pd->target_count++;
1369
1370   if (pd->target_count == 0)
1371     {
1372       if (! *error_ret)
1373         *error_ret = strdup ("No hosts to ping!\n"
1374                              "Simulating instead.");
1375       if (pd) ping_free_data (ssd, pd);
1376       if (ssd) free (ssd);
1377       return 0;
1378     }
1379
1380   /* Distribute them evenly around the display field, clockwise.
1381      Even on a /24, allocated IPs tend to cluster together, so
1382      don't put any two hosts closer together than N degrees to
1383      avoid unnecessary overlap when we have plenty of space due
1384      to addresses that probably won't respond.
1385    */
1386   {
1387     int min_separation = 23;  /* degrees */
1388     int div = pd->target_count;
1389     if (div > 360 / min_separation)
1390       div = 360 / min_separation;
1391     for (i = 0, b = pd->targets; b; b = b->next, i++)
1392       b->th = (M_PI/2 -
1393                M_PI * 2 * ((div - i) % div) / div);
1394   }
1395
1396   return ssd;
1397 }
1398
1399 #endif /* HAVE_PING -- whole file */