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