http://www.jwz.org/xscreensaver/xscreensaver-5.09.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, 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       name = addr = 0;
370       if ((addr = strtok(buf, " ,;\t\n")))
371         name = strtok(0, " ,;\t\n");
372       else
373         continue;
374
375       /* Check to see if the addr looks like an addr.  If not, assume
376          the addr is a name and there is no addr.  This way, we can
377          handle files whose lines have "xx.xx.xx.xx hostname" as their
378          first two tokens, and also files that have a hostname as their
379          first token (like .ssh/known_hosts and .rhosts.)
380       */
381       {
382         int i; char c;
383         if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
384           {
385             name = addr;
386             addr = 0;
387           }
388       }
389
390       /* If the name is all digits, it's not a name. */
391       if (name)
392         {
393           const char *s;
394           for (s = name; *s; s++)
395             if (*s < '0' || *s > '9')
396               break;
397           if (! *s)
398             {
399               if (pd->debug_p > 1)
400                 fprintf (stderr, "%s:  skipping bogus name \"%s\" (%s)\n",
401                          progname, name, addr);
402               name = 0;
403             }
404         }
405
406       /* Create a new target using first the name then the address */
407
408       new = 0;
409       if (name)
410         new = bogie_for_host (ssd, name, pd->resolve_p);
411       if (!new && addr)
412         new = bogie_for_host (ssd, addr, pd->resolve_p);
413
414       if (new)
415         {
416           new->next = list;
417           list = new;
418         }
419     }
420
421   fclose(fp);
422   return list;
423 }
424
425
426 static sonar_bogie *
427 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
428 {
429   ping_data *pd = (ping_data *) ssd->closure;
430   sonar_bogie *head = list;
431   sonar_bogie *sb;
432
433   for (sb = head; sb; sb = sb->next)
434     {
435       ping_bogie *pb = (ping_bogie *) sb->closure;
436       struct sockaddr_in *i1 = (struct sockaddr_in *) &(pb->address);
437       unsigned long ip1 = i1->sin_addr.s_addr;
438
439       sonar_bogie *sb2;
440       for (sb2 = sb; sb2; sb2 = sb2->next)
441         {
442           if (sb2 && sb2->next)
443             {
444               ping_bogie *pb2 = (ping_bogie *) sb2->next->closure;
445               struct sockaddr_in *i2 = (struct sockaddr_in *) &(pb2->address);
446               unsigned long ip2 = i2->sin_addr.s_addr;
447
448               if (ip1 == ip2)
449                 {
450                   if (pd->debug_p)
451                     {
452                       fprintf (stderr, "%s: deleted duplicate: ", progname);
453                       print_host (stderr, ip2, sb2->next->name);
454                     }
455                   sb2->next = sb2->next->next;
456                   /* #### sb leaked */
457                 }
458             }
459         }
460     }
461
462   return head;
463 }
464
465
466 /* Generate a list of bogies consisting of all of the entries on
467   the same subnet.  'base' ip is in network order; 0 means localhost.
468  */
469 static sonar_bogie *
470 subnet_hosts (sonar_sensor_data *ssd, char **error_ret,
471               unsigned long n_base, int subnet_width)
472 {
473   ping_data *pd = (ping_data *) ssd->closure;
474   unsigned long h_mask;   /* host order */
475   unsigned long h_base;   /* host order */
476
477   /* Local Variables */
478
479   char hostname[BUFSIZ];
480   char address[BUFSIZ];
481   struct hostent *hent;
482   char *p;
483   int i;
484   sonar_bogie *new;
485   sonar_bogie *list = 0;
486   char buf[1024];
487
488   if (subnet_width < 24)
489     {
490       sprintf (buf,
491                "Pinging %lu hosts is a bad\n"
492                "idea.  Please use a subnet\n"
493                "mask of 24 bits or more.",
494                (unsigned long) (1L << (32 - subnet_width)) - 1);
495       *error_ret = strdup(buf);
496       return 0;
497     }
498   else if (subnet_width > 30)
499     {
500       sprintf (buf, 
501                "An %d-bit subnet\n"
502                "doesn't make sense.\n"
503                "Try \"subnet/24\"\n"
504                "or \"subnet/29\".\n",
505                subnet_width);
506       *error_ret = strdup(buf);
507       return 0;
508     }
509
510
511   if (pd->debug_p)
512     fprintf (stderr, "%s:   adding %d-bit subnet\n", progname, subnet_width);
513
514   /* Get our hostname */
515
516   if (gethostname(hostname, BUFSIZ)) 
517     {
518       *error_ret = strdup ("Unable to determine\n"
519                            "local host name!");
520       return 0;
521     }
522
523   /* Get our IP address and convert it to a string */
524
525   if (! (hent = gethostbyname(hostname)))
526     {
527       sprintf(buf, 
528               "Unable to resolve\n"
529               "local host \"%s\"", 
530               hostname);
531       *error_ret = strdup(buf);
532       return 0;
533     }
534   strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
535
536   /* Construct targets for all addresses in this subnet */
537
538   h_mask = 0;
539   for (i = 0; i < subnet_width; i++)
540     h_mask |= (1L << (31-i));
541
542   /* If no base IP specified, assume localhost. */
543   if (n_base == 0)
544     n_base = pack_addr (hent->h_addr_list[0][0],
545                         hent->h_addr_list[0][1],
546                         hent->h_addr_list[0][2],
547                         hent->h_addr_list[0][3]);
548   h_base = ntohl (n_base);
549
550   if (h_base == 0x7F000001L)   /* 127.0.0.1 in host order */
551     {
552       unsigned int a, b, c, d;
553       unpack_addr (n_base, &a, &b, &c, &d);
554       sprintf (buf,
555                "Unable to determine\n"
556                "local subnet address:\n"
557                "\"%s\"\n"
558                "resolves to\n"
559                "loopback address\n"
560                "%u.%u.%u.%u.",
561                hostname, a, b, c, d);
562       *error_ret = strdup(buf);
563       return 0;
564     }
565
566   for (i = 255; i >= 0; i--) {
567     unsigned int a, b, c, d;
568     int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
569       
570     if ((ip & h_mask) != (h_base & h_mask))  /* not in mask range at all */
571       continue;
572     if ((ip & ~h_mask) == 0)                 /* broadcast address */
573       continue;
574     if ((ip & ~h_mask) == ~h_mask)           /* broadcast address */
575       continue;
576
577     unpack_addr (htonl (ip), &a, &b, &c, &d);
578     sprintf (address, "%u.%u.%u.%u", a, b, c, d);
579
580     if (pd->debug_p > 1)
581       {
582         unsigned int aa, ab, ac, ad;
583         unsigned int ma, mb, mc, md;
584         unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
585         unpack_addr (htonl (h_mask),          &ma, &mb, &mc, &md);
586         fprintf (stderr,
587                  "%s:  subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
588                  progname, address,
589                  aa, ab, ac, ad,
590                  ma, mb, mc, md,
591                  subnet_width);
592       }
593
594     p = address + strlen(address) + 1;
595     sprintf(p, "%d", i);
596
597     new = bogie_for_host (ssd, address, pd->resolve_p);
598     if (new)
599       {
600         new->next = list;
601         list = new;
602       }
603   }
604
605   return list;
606 }
607
608
609 /* Send a ping packet.
610  */
611 static void
612 send_ping (ping_data *pd, const sonar_bogie *b)
613 {
614   ping_bogie *pb = (ping_bogie *) b->closure;
615   u_char *packet;
616   struct ICMP *icmph;
617   int result;
618   const char *token = "org.jwz.xscreensaver.sonar";
619
620   int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) + 
621                  strlen(b->name) + 1 +
622                  strlen(token) + 1 + 
623                  strlen(pd->version) + 1);
624
625   /* Create the ICMP packet */
626
627   if (! (packet = (u_char *) calloc(1, pcktsiz)))
628     return;  /* Out of memory */
629
630   icmph = (struct ICMP *) packet;
631   ICMP_TYPE(icmph) = ICMP_ECHO;
632   ICMP_CODE(icmph) = 0;
633   ICMP_CHECKSUM(icmph) = 0;
634   ICMP_ID(icmph) = pd->pid;
635   ICMP_SEQ(icmph) = pd->seq++;
636 # ifdef GETTIMEOFDAY_TWO_ARGS
637   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
638                (struct timezone *) 0);
639 # else
640   gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
641 # endif
642
643   /* We store the name of the host we're pinging in the packet, and parse
644      that out of the return packet later (see get_ping() for why).
645      After that, we also include the name and version of this program,
646      just to give a clue to anyone sniffing and wondering what's up.
647    */
648   sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)],
649            "%s%c%s %s",
650            b->name, 0, token, pd->version);
651
652   ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
653
654   /* Send it */
655
656   if ((result = sendto(pd->icmpsock, packet, pcktsiz, 0, 
657                        &pb->address, sizeof(pb->address)))
658       != pcktsiz)
659     {
660 #if 0
661       char buf[BUFSIZ];
662       sprintf(buf, "%s: pinging %s", progname, b->name);
663       perror(buf);
664 #endif
665     }
666 }
667
668 /* signal handler */
669 static void
670 sigcatcher (int sig)
671 {
672   timer_expired = 1;
673 }
674
675
676 /* Compute the checksum on a ping packet.
677  */
678 static u_short
679 checksum (u_short *packet, int size) 
680 {
681   register int nleft = size;
682   register u_short *w = packet;
683   register int sum = 0;
684   u_short answer = 0;
685
686   /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
687      to it, and at the end, fold back all the carry bits from the
688      top 16 bits into the lower 16 bits.
689    */
690   while (nleft > 1)
691     {
692       sum += *w++;
693       nleft -= 2;
694     }
695
696   /* mop up an odd byte, if necessary */
697
698   if (nleft == 1)
699     {
700       *(u_char *)(&answer) = *(u_char *)w ;
701       *(1 + (u_char *)(&answer)) = 0;
702       sum += answer;
703     }
704
705   /* add back carry outs from top 16 bits to low 16 bits */
706
707   sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
708   sum += (sum >> 16);                     /* add carry */
709   answer = ~sum;                          /* truncate to 16 bits */
710
711   return(answer);
712 }
713
714
715 /* Copies the sonar_bogie and the underlying ping_bogie.
716  */
717 static sonar_bogie *
718 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
719 {
720   sonar_bogie *b2 = copy_bogie (ssd, b);
721   if (b->closure)
722     {
723       ping_bogie *pb  = (ping_bogie *) b->closure;
724       ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
725       pb2->address = pb->address;
726       b2->closure = pb2;
727     }
728   return b2;
729 }
730
731
732 /* Look for all outstanding ping replies.
733  */
734 static sonar_bogie *
735 get_ping (sonar_sensor_data *ssd)
736 {
737   ping_data *pd = (ping_data *) ssd->closure;
738   struct sockaddr from;
739   unsigned int fromlen;  /* Posix says socklen_t, but that's not portable */
740   int result;
741   u_char packet[1024];
742   struct timeval now;
743   struct timeval *then;
744   struct ip *ip;
745   int iphdrlen;
746   struct ICMP *icmph;
747   sonar_bogie *bl = 0;
748   sonar_bogie *new = 0;
749   struct sigaction sa;
750   struct itimerval it;
751   fd_set rfds;
752   struct timeval tv;
753
754   /* Set up a signal to interrupt our wait for a packet */
755
756   sigemptyset(&sa.sa_mask);
757   sa.sa_flags = 0;
758   sa.sa_handler = sigcatcher;
759   if (sigaction(SIGALRM, &sa, 0) == -1) 
760     {
761       char msg[1024];
762       sprintf(msg, "%s: unable to trap SIGALRM", progname);
763       perror(msg);
764       exit(1);
765     }
766
767   /* Set up a timer to interupt us if we don't get a packet */
768
769   it.it_interval.tv_sec = 0;
770   it.it_interval.tv_usec = 0;
771   it.it_value.tv_sec = 0;
772   it.it_value.tv_usec = pd->timeout;
773   timer_expired = 0;
774   setitimer(ITIMER_REAL, &it, 0);
775
776   /* Wait for a result packet */
777
778   fromlen = sizeof(from);
779   while (! timer_expired)
780     {
781       tv.tv_usec = pd->timeout;
782       tv.tv_sec = 0;
783 #if 0
784       /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
785       FD_ZERO(&rfds);
786 #else
787       memset (&rfds, 0, sizeof(rfds));
788 #endif
789       FD_SET(pd->icmpsock, &rfds);
790       /* only wait a little while, in case we raced with the timer expiration.
791          From Valentijn Sessink <valentyn@openoffice.nl> */
792       if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
793         {
794           result = recvfrom (pd->icmpsock, packet, sizeof(packet),
795                              0, &from, &fromlen);
796
797           /* Check the packet */
798
799 # ifdef GETTIMEOFDAY_TWO_ARGS
800           gettimeofday(&now, (struct timezone *) 0);
801 # else
802           gettimeofday(&now);
803 # endif
804           ip = (struct ip *) packet;
805           iphdrlen = IP_HDRLEN(ip) << 2;
806           icmph = (struct ICMP *) &packet[iphdrlen];
807           then  = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
808
809
810           /* Ignore anything but ICMP Replies */
811           if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY) 
812             continue;
813
814           /* Ignore packets not set from us */
815           if (ICMP_ID(icmph) != pd->pid)
816             continue;
817
818           /* Find the bogie in 'targets' that corresponds to this packet
819              and copy it, so that this bogie stays in the same spot (th)
820              on the screen, and so that we don't have to resolve it again.
821
822              We could find the bogie by comparing ip->ip_src.s_addr to
823              pb->address, but it is possible that, in certain weird router
824              or NAT situations, that the reply will come back from a 
825              different address than the one we sent it to.  So instead,
826              we parse the name out of the reply packet payload.
827            */
828           {
829             const char *name = (char *) &packet[iphdrlen +
830                                                 sizeof(struct ICMP) +
831                                                 sizeof(struct timeval)];
832             sonar_bogie *b;
833             for (b = pd->targets; b; b = b->next)
834               if (!strcmp (name, b->name))
835                 {
836                   new = copy_ping_bogie (ssd, b);
837                   break;
838                 }
839           }
840
841           if (! new)      /* not in targets? */
842             {
843               unsigned int a, b, c, d;
844               unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
845               fprintf (stderr, 
846                        "%s: UNEXPECTED PING REPLY! "
847                        "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
848                        progname, result, ICMP_SEQ(icmph), a, b, c, d);
849               continue;
850             }
851
852           new->next = bl;
853           bl = new;
854
855           {
856             double msec = delta(then, &now) / 1000.0;
857
858             if (pd->times_p)
859               {
860                 if (new->desc) free (new->desc);
861                 new->desc = (char *) malloc (30);
862                 if      (msec > 99) sprintf (new->desc, "%.0f ms", msec);
863                 else if (msec >  9) sprintf (new->desc, "%.1f ms", msec);
864                 else if (msec >  1) sprintf (new->desc, "%.2f ms", msec);
865                 else                sprintf (new->desc, "%.3f ms", msec);
866               }
867
868             if (pd->debug_p && pd->times_p)  /* ping-like stdout log */
869               {
870                 char *s = strdup(new->name);
871                 char *s2 = s;
872                 if (strlen(s) > 28)
873                   {
874                     s2 = s + strlen(s) - 28;
875                     strncpy (s2, "...", 3);
876                   }
877                 fprintf (stdout, 
878                          "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
879                          result, s2, ICMP_SEQ(icmph), new->desc);
880                 fflush (stdout);
881                 free(s);
882               }
883
884             /* The radius must be between 0.0 and 1.0.
885                We want to display ping times on a logarithmic scale,
886                with the three rings being 2.5, 70 and 2,000 milliseconds.
887              */
888             if (msec <= 0) msec = 0.001;
889             new->r = log (msec * 10) / log (20000);
890
891             /* Don't put anyone *too* close to the center of the screen. */
892             if (new->r < 0) new->r = 0;
893             if (new->r < 0.1) new->r += 0.1;
894           }
895         }
896     }
897
898   return bl;
899 }
900
901
902 /* difference between the two times in microseconds.
903  */
904 static long
905 delta (struct timeval *then, struct timeval *now) 
906 {
907   return (((now->tv_sec - then->tv_sec) * 1000000) + 
908           (now->tv_usec - then->tv_usec));  
909 }
910
911
912 static void
913 ping_free_data (sonar_sensor_data *ssd, void *closure)
914 {
915   ping_data *pd = (ping_data *) closure;
916   sonar_bogie *b = pd->targets;
917   while (b)
918     {
919       sonar_bogie *b2 = b->next;
920       free_bogie (ssd, b);
921       b = b2;
922     }
923   free (pd);
924 }
925
926 static void
927 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
928 {
929   free (closure);
930 }
931
932
933 /* Returns the current time in seconds as a double.
934  */
935 static double
936 double_time (void)
937 {
938   struct timeval now;
939 # ifdef GETTIMEOFDAY_TWO_ARGS
940   struct timezone tzp;
941   gettimeofday(&now, &tzp);
942 # else
943   gettimeofday(&now);
944 # endif
945
946   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
947 }
948
949
950 /* If a bogie is provided, pings it.
951    Then, returns all outstanding ping replies.
952  */
953 static sonar_bogie *
954 ping_scan (sonar_sensor_data *ssd)
955 {
956   ping_data *pd = (ping_data *) ssd->closure;
957   double now = double_time();
958   double ping_cycle = 10;   /* re-ping a given host every 10 seconds */
959   double ping_interval = ping_cycle / pd->target_count;
960
961   if (now > pd->last_ping_time + ping_interval)   /* time to ping someone */
962     {
963       if (pd->last_pinged)
964         pd->last_pinged = pd->last_pinged->next;
965       if (! pd->last_pinged)
966         pd->last_pinged = pd->targets;
967       send_ping (pd, pd->last_pinged);
968       pd->last_ping_time = now;
969     }
970
971   return get_ping (ssd);
972 }
973
974
975 /* Returns a list of hosts to ping based on the "-ping" argument.
976  */
977 static sonar_bogie *
978 parse_mode (sonar_sensor_data *ssd, char **error_ret,
979             const char *ping_arg, Bool ping_works_p)
980 {
981   ping_data *pd = (ping_data *) ssd->closure;
982   char *source, *token, *end, dummy;
983   sonar_bogie *hostlist = 0;
984
985   if (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default"))
986     source = strdup("subnet/28");
987   else
988     source = strdup(ping_arg);
989
990   token = source;
991   end = source + strlen(source);
992   while (token < end)
993     {
994       char *next;
995       sonar_bogie *new;
996       struct stat st;
997       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
998       char d;
999
1000       for (next = token;
1001            *next &&
1002            *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1003            next++)
1004         ;
1005       *next = 0;
1006
1007
1008       if (pd->debug_p)
1009         fprintf (stderr, "%s: parsing %s\n", progname, token);
1010
1011       if (!ping_works_p)
1012         {
1013           *error_ret = strdup ("Sonar must be setuid to ping!\n"
1014                                "Running simulation instead.");
1015           return 0;
1016         }
1017
1018       if ((4 == sscanf (token, "%u.%u.%u/%u %c",    &n0,&n1,&n2,    &m,&d)) ||
1019           (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1020         {
1021           /* subnet: A.B.C.D/M
1022              subnet: A.B.C/M
1023            */
1024           unsigned long ip = pack_addr (n0, n1, n2, n3);
1025           new = subnet_hosts (ssd, error_ret, ip, m);
1026         }
1027       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1028         {
1029           /* IP: A.B.C.D
1030            */
1031           new = bogie_for_host (ssd, token, pd->resolve_p);
1032         }
1033       else if (!strcmp (token, "subnet"))
1034         {
1035           new = subnet_hosts (ssd, error_ret, 0, 24);
1036         }
1037       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1038         {
1039           new = subnet_hosts (ssd, error_ret, 0, m);
1040         }
1041       else if (*token == '.' || *token == '/' || 
1042                *token == '$' || *token == '~' ||
1043                !stat (token, &st))
1044         {
1045           /* file name
1046            */
1047           new = read_hosts_file (ssd, token);
1048         }
1049       else
1050         {
1051           /* not an existant file - must be a host name
1052            */
1053           new = bogie_for_host (ssd, token, pd->resolve_p);
1054         }
1055
1056       if (new)
1057         {
1058           sonar_bogie *nn = new;
1059           while (nn && nn->next)
1060             nn = nn->next;
1061           nn->next = hostlist;
1062           hostlist = new;
1063         }
1064
1065       token = next + 1;
1066       while (token < end &&
1067              (*token == ',' || *token == ' ' ||
1068               *token == '\t' || *token == '\n'))
1069         token++;
1070     }
1071
1072   free (source);
1073   return hostlist;
1074 }
1075
1076
1077 sonar_sensor_data *
1078 init_ping (Display *dpy, char **error_ret, 
1079            const char *subnet, int timeout,
1080            Bool resolve_p, Bool times_p, Bool debug_p)
1081 {
1082   sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1083   ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1084   sonar_bogie *b;
1085   char *s;
1086   int i, div;
1087
1088   Bool socket_initted_p = False;
1089   Bool socket_raw_p     = False;
1090
1091   pd->resolve_p = resolve_p;
1092   pd->times_p   = times_p;
1093   pd->debug_p   = debug_p;
1094
1095   ssd->closure       = pd;
1096   ssd->scan_cb       = ping_scan;
1097   ssd->free_data_cb  = ping_free_data;
1098   ssd->free_bogie_cb = ping_free_bogie_data;
1099
1100   /* Get short version number. */
1101   s = strchr (screensaver_id, ' ');
1102   pd->version = strdup (s+1);
1103   s = strchr (pd->version, ' ');
1104   *s = 0;
1105
1106
1107   /* Create the ICMP socket.  Do this before dropping privs.
1108
1109      Raw sockets can only be opened by root (or setuid root), so we
1110      only try to do this when the effective uid is 0.
1111
1112      We used to just always try, and notice the failure.  But apparently
1113      that causes "SELinux" to log spurious warnings when running with the
1114      "strict" policy.  So to avoid that, we just don't try unless we
1115      know it will work.
1116
1117      On MacOS X, we can avoid the whole problem by using a
1118      non-privileged datagram instead of a raw socket.
1119    */
1120   if (global_icmpsock)
1121     {
1122       pd->icmpsock = global_icmpsock;
1123       socket_initted_p = True;
1124       if (debug_p)
1125         fprintf (stderr, "%s: re-using icmp socket\n", progname);
1126
1127     } 
1128   else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1129     {
1130       socket_initted_p = True;
1131     }
1132   else if (geteuid() == 0 &&
1133            (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1134     {
1135       socket_initted_p = True;
1136       socket_raw_p = True;
1137     }
1138
1139   if (socket_initted_p)
1140     {
1141       global_icmpsock = pd->icmpsock;
1142       socket_initted_p = True;
1143       if (debug_p)
1144         fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1145                  (socket_raw_p ? "raw" : "dgram"));
1146     } 
1147   else if (debug_p)
1148     fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1149
1150   /* Disavow privs */
1151   setuid(getuid());
1152
1153   pd->pid = getpid() & 0xFFFF;
1154   pd->seq = 0;
1155   pd->timeout = timeout;
1156
1157   /* Generate a list of targets */
1158
1159   pd->targets = parse_mode (ssd, error_ret, subnet, socket_initted_p);
1160   pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1161
1162   if (debug_p)
1163     {
1164       fprintf (stderr, "%s: Target list:\n", progname);
1165       for (b = pd->targets; b; b = b->next)
1166         {
1167           ping_bogie *pb = (ping_bogie *) b->closure;
1168           struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
1169           unsigned long ip = iaddr->sin_addr.s_addr;
1170           fprintf (stderr, "%s:   ", progname);
1171           print_host (stderr, ip, b->name);
1172         }
1173     }
1174
1175   /* Make sure there is something to ping */
1176
1177   pd->target_count = 0;
1178   for (b = pd->targets; b; b = b->next)
1179     pd->target_count++;
1180
1181   if (pd->target_count == 0)
1182     {
1183       if (! *error_ret)
1184         *error_ret = strdup ("No hosts to ping!\n"
1185                              "Simulating instead.");
1186       if (pd) ping_free_data (ssd, pd);
1187       if (ssd) free (ssd);
1188       return 0;
1189     }
1190
1191   /* Distribute them evenly around the display field.
1192    */
1193   div = pd->target_count;
1194   if (div > 90) div = 90;  /* no closer together than 4 degrees */
1195   for (i = 0, b = pd->targets; b; b = b->next, i++)
1196     b->th = M_PI * 2 * ((div - i) % div) / div;
1197
1198   return ssd;
1199 }
1200
1201 #endif /* HAVE_PING -- whole file */