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