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