1 /* sonar, Copyright (c) 1998-2020 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 #include "async_netdb.h"
18 
19 #undef usleep /* conflicts with unistd.h on OSX */
20 
21 #ifdef USE_IPHONE
22   /* Note: to get this to compile for iPhone, you need to fix Xcode!
23      The icmp headers exist for the simulator build environment, but
24      not for the real-device build environment.  This appears to
25      just be an Apple bug, not intentional.
26 
27      xc=/Applications/Xcode.app/Contents
28      for path in    /Developer/Platforms/iPhone*?/Developer/SDKs/?* \
29                  $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do
30        for file in \
31          /usr/include/netinet/ip.h \
32          /usr/include/netinet/in_systm.h \
33          /usr/include/netinet/ip_icmp.h \
34          /usr/include/netinet/ip_var.h \
35          /usr/include/netinet/udp.h
36        do
37          ln -s "$file" "$path$file"
38        done
39      done
40   */
41 #endif
42 
43 #ifndef HAVE_MOBILE
44 # define READ_FILES
45 #endif
46 
47 #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
48 # include <unistd.h>
49 # include <sys/stat.h>
50 # include <limits.h>
51 # include <signal.h>
52 # include <fcntl.h>
53 # include <sys/types.h>
54 # include <sys/time.h>
55 # include <sys/ipc.h>
56 # ifndef HAVE_ANDROID
57 #  include <sys/shm.h>
58 # endif
59 # include <sys/socket.h>
60 # include <netinet/in_systm.h>
61 # include <netinet/in.h>
62 # include <netinet/ip.h>
63 # include <netinet/ip_icmp.h>
64 # include <netinet/udp.h>
65 # include <arpa/inet.h>
66 # include <netdb.h>
67 # include <errno.h>
68 # ifdef HAVE_GETIFADDRS
69 #  include <ifaddrs.h>
70 # endif
71 # ifdef HAVE_LIBCAP
72 #  include <sys/capability.h>
73 # endif
74 #endif /* HAVE_ICMP || HAVE_ICMPHDR */
75 
76 #if defined(HAVE_ICMP)
77 # define HAVE_PING
78 # define ICMP             icmp
79 # define ICMP_TYPE(p)     (p)->icmp_type
80 # define ICMP_CODE(p)     (p)->icmp_code
81 # define ICMP_CHECKSUM(p) (p)->icmp_cksum
82 # define ICMP_ID(p)       (p)->icmp_id
83 # define ICMP_SEQ(p)      (p)->icmp_seq
84 #elif defined(HAVE_ICMPHDR)
85 # define HAVE_PING
86 # define ICMP             icmphdr
87 # define ICMP_TYPE(p)     (p)->type
88 # define ICMP_CODE(p)     (p)->code
89 # define ICMP_CHECKSUM(p) (p)->checksum
90 # define ICMP_ID(p)       (p)->un.echo.id
91 # define ICMP_SEQ(p)      (p)->un.echo.sequence
92 #else
93 # undef HAVE_PING
94 #endif
95 
96 #ifndef HAVE_MOBILE
97 # define LOAD_FILES
98 #endif
99 
100 #ifndef HAVE_PING
101 
102 sonar_sensor_data *
sonar_init_ping(Display * dpy,char ** error_ret,char ** desc_ret,const char * subnet,int timeout,Bool resolve_p,Bool times_p,Bool debug_p)103 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
104                  const char *subnet, int timeout,
105                  Bool resolve_p, Bool times_p, Bool debug_p)
106 {
107   if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
108     fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
109              progname);
110   return 0;
111 }
112 
113 #else /* HAVE_PING -- whole file */
114 
115 
116 #if defined(__DECC) || defined(_IP_VHL)
117    /* This is how you do it on DEC C, and possibly some BSD systems. */
118 # define IP_HDRLEN(ip)   ((ip)->ip_vhl & 0x0F)
119 #else
120    /* This is how you do it on everything else. */
121 # define IP_HDRLEN(ip)   ((ip)->ip_hl)
122 #endif
123 
124 /* yes, there is only one, even when multiple savers are running in the
125    same address space - since we can only open this socket before dropping
126    privs.
127  */
128 static int global_icmpsock = 0;
129 
130 /* Set by a signal handler. */
131 static int timer_expired;
132 
133 
134 
135 static u_short checksum(u_short *, int);
136 static long delta(struct timeval *, struct timeval *);
137 
138 
139 typedef struct {
140   Display *dpy;                 /* Only used to get *useThreads. */
141 
142   char *version;		/* short version number of xscreensaver */
143   int icmpsock;			/* socket for sending pings */
144   int pid;			/* our process ID */
145   int seq;			/* packet sequence number */
146   int timeout;			/* packet timeout */
147 
148   int target_count;
149   sonar_bogie *targets;		/* the hosts we will ping;
150                                    those that pong end up on ssd->pending. */
151   sonar_bogie *last_pinged;	/* pointer into 'targets' list */
152   double last_ping_time;
153 
154   Bool resolve_p;
155   Bool times_p;
156   Bool debug_p;
157 
158 } ping_data;
159 
160 typedef struct {
161   async_name_from_addr_t lookup_name;
162   async_addr_from_name_t lookup_addr;
163   async_netdb_sockaddr_storage_t address;	/* ip address */
164   socklen_t addrlen;
165   char *fallback;
166 } ping_bogie;
167 
168 
169 
170 /* Packs an IP address quad into bigendian network order. */
171 static in_addr_t
pack_addr(unsigned int a,unsigned int b,unsigned int c,unsigned int d)172 pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
173 {
174   unsigned long i = (((a & 255) << 24) |
175                      ((b & 255) << 16) |
176                      ((c & 255) <<  8) |
177                      ((d & 255)      ));
178   return htonl (i);
179 }
180 
181 /* Unpacks an IP address quad from bigendian network order. */
182 static void
unpack_addr(unsigned long addr,unsigned int * a,unsigned int * b,unsigned int * c,unsigned int * d)183 unpack_addr (unsigned long addr,
184              unsigned int *a, unsigned int *b,
185              unsigned int *c, unsigned int *d)
186 {
187   addr = ntohl (addr);
188   *a = (addr >> 24) & 255;
189   *b = (addr >> 16) & 255;
190   *c = (addr >>  8) & 255;
191   *d = (addr      ) & 255;
192 }
193 
194 
195 
196 
197 /* Resolves the bogie's name (either a hostname or ip address string)
198    to a hostent.  Returns 1 if successful, 0 if something went wrong.
199  */
200 static int
resolve_bogie_hostname(ping_data * pd,sonar_bogie * sb,Bool resolve_p)201 resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p)
202 {
203   ping_bogie *pb = (ping_bogie *) sb->closure;
204 
205   unsigned int ip[4];
206   char c;
207 
208   if (4 == sscanf (sb->name, " %u.%u.%u.%u %c",
209                    &ip[0], &ip[1], &ip[2], &ip[3], &c))
210     {
211       /* It's an IP address.
212        */
213       struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address);
214 
215       if (ip[3] == 0)
216         {
217           if (pd->debug_p > 1)
218             fprintf (stderr, "%s:   ignoring bogus IP %s\n",
219                      progname, sb->name);
220           return 0;
221         }
222 
223       iaddr->sin_family = AF_INET;
224       iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]);
225       pb->addrlen = sizeof(struct sockaddr_in);
226 
227       if (resolve_p)
228         {
229           pb->lookup_name =
230             async_name_from_addr_start (pd->dpy,
231                                         (const struct sockaddr *)&pb->address,
232                                         pb->addrlen);
233           if (!pb->lookup_name)
234             {
235               fprintf (stderr, "%s:   unable to start host resolution.\n",
236                        progname);
237             }
238         }
239     }
240   else
241     {
242       /* It's a host name. */
243 
244       /* don't waste time being confused by non-hostname tokens
245          in .ssh/known_hosts */
246       if (!strcmp (sb->name, "ssh-rsa") ||
247           !strcmp (sb->name, "ssh-dsa") ||
248           !strcmp (sb->name, "ssh-dss") ||
249           !strncmp (sb->name, "ecdsa-", 6) ||
250           strlen (sb->name) >= 80)
251         return 0;
252 
253       /* .ssh/known_hosts sometimes contains weirdness like "[host]:port".
254          Ignore it. */
255       if (strchr (sb->name, '['))
256         {
257           if (pd->debug_p)
258             fprintf (stderr, "%s:   ignoring bogus address \"%s\"\n",
259                      progname, sb->name);
260           return 0;
261         }
262 
263       /* If the name contains a colon, it's probably IPv6. */
264       if (strchr (sb->name, ':'))
265         {
266           if (pd->debug_p)
267             fprintf (stderr, "%s:   ignoring ipv6 address \"%s\"\n",
268                      progname, sb->name);
269           return 0;
270         }
271 
272       pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name);
273       if (!pb->lookup_addr)
274         {
275           if (pd->debug_p)
276             /* Either address space exhaustion or RAM exhaustion. */
277             fprintf (stderr, "%s:   unable to start host resolution.\n",
278                      progname);
279           return 0;
280         }
281     }
282   return 1;
283 }
284 
285 
286 static void
print_address(FILE * out,int width,const void * sockaddr,socklen_t addrlen)287 print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen)
288 {
289 #ifdef HAVE_GETADDRINFO
290   char buf[NI_MAXHOST];
291 #else
292   char buf[50];
293 #endif
294 
295   const struct sockaddr *addr = (const struct sockaddr *)sockaddr;
296   const char *ips = buf;
297 
298   if (!addr->sa_family)
299     ips = "<no address>";
300   else
301     {
302 #ifdef HAVE_GETADDRINFO
303       int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf),
304                                    NULL, 0, NI_NUMERICHOST);
305       if (gai_error == EAI_SYSTEM)
306         ips = strerror(errno);
307       else if (gai_error)
308         ips = gai_strerror(gai_error);
309 #else
310       switch (addr->sa_family)
311         {
312         case AF_INET:
313           {
314             u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
315             unsigned int a, b, c, d;
316             unpack_addr (ip, &a, &b, &c, &d);   /* ip is in network order */
317             sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
318           }
319           break;
320         default:
321           ips = "<unknown>";
322           break;
323         }
324 #endif
325     }
326 
327   fprintf (out, "%-*s", width, ips);
328 }
329 
330 
331 static void
print_host(FILE * out,const sonar_bogie * sb)332 print_host (FILE *out, const sonar_bogie *sb)
333 {
334   const ping_bogie *pb = (const ping_bogie *) sb->closure;
335   const char *name = sb->name;
336   if (!name || !*name) name = "<unknown>";
337   print_address (out, 16, &pb->address, pb->addrlen);
338   fprintf (out, " %s\n", name);
339 }
340 
341 
342 static Bool
is_address_ok(Bool debug_p,const sonar_bogie * b)343 is_address_ok(Bool debug_p, const sonar_bogie *b)
344 {
345   const ping_bogie *pb = (const ping_bogie *) b->closure;
346   const struct sockaddr *addr = (const struct sockaddr *)&pb->address;
347 
348   switch (addr->sa_family)
349     {
350     case AF_INET:
351       {
352         struct sockaddr_in *iaddr = (struct sockaddr_in *) addr;
353 
354         /* Don't ever use loopback (127.0.0.x) hosts */
355         unsigned long ip = iaddr->sin_addr.s_addr;
356         if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L)  /* 127.0.0.x */
357           {
358             if (debug_p)
359               fprintf (stderr, "%s:   ignoring loopback host %s\n",
360                        progname, b->name);
361             return False;
362           }
363 
364         /* Don't ever use broadcast (255.x.x.x) hosts */
365         if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L)  /* 255.x.x.x */
366           {
367             if (debug_p)
368               fprintf (stderr, "%s:   ignoring broadcast host %s\n",
369                        progname, b->name);
370             return False;
371           }
372       }
373 
374       break;
375     }
376 
377   return True;
378 }
379 
380 
381 /* Create a sonar_bogie from a host name or ip address string.
382    Returns NULL if the name could not be resolved.
383  */
384 static sonar_bogie *
bogie_for_host(sonar_sensor_data * ssd,const char * name,const char * fallback)385 bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback)
386 {
387   ping_data *pd = (ping_data *) ssd->closure;
388   sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b));
389   ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb));
390   Bool resolve_p = pd->resolve_p;
391 
392   b->name = strdup (name);
393   b->closure = pb;
394 
395   if (! resolve_bogie_hostname (pd, b, resolve_p))
396     goto FAIL;
397 
398   if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b))
399     goto FAIL;
400 
401   if (pd->debug_p > 1)
402     {
403       fprintf (stderr, "%s:   added ", progname);
404       print_host (stderr, b);
405     }
406 
407   if (fallback)
408     pb->fallback = strdup (fallback);
409   return b;
410 
411  FAIL:
412   if (b) sonar_free_bogie (ssd, b);
413 
414   if (fallback)
415     return bogie_for_host (ssd, fallback, NULL);
416 
417   return 0;
418 }
419 
420 
421 #ifdef READ_FILES
422 
423 /* Return a list of bogies read from a file.
424    The file can be like /etc/hosts or .ssh/known_hosts or probably
425    just about anything that has host names in it.
426  */
427 static sonar_bogie *
read_hosts_file(sonar_sensor_data * ssd,const char * filename)428 read_hosts_file (sonar_sensor_data *ssd, const char *filename)
429 {
430   ping_data *pd = (ping_data *) ssd->closure;
431   FILE *fp;
432   char buf[LINE_MAX];
433   char *p;
434   sonar_bogie *list = 0;
435   char *addr, *name;
436   sonar_bogie *new;
437 
438   /* Kludge: on OSX, variables have not been expanded in the command
439      line arguments, so as a special case, allow the string to begin
440      with literal "$HOME/" or "~/".
441 
442      This is so that the "Known Hosts" menu item in sonar.xml works.
443    */
444   if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6))
445     {
446       char *s = strchr (filename, '/');
447       strcpy (buf, getenv("HOME"));
448       strcat (buf, s);
449       filename = buf;
450     }
451 
452   fp = fopen(filename, "r");
453   if (!fp)
454     {
455       char buf2[1024];
456       sprintf(buf2, "%s:  %s", progname, filename);
457 #ifdef HAVE_JWXYZ
458       if (pd->debug_p)  /* on OSX don't syslog this */
459 #endif
460         perror (buf2);
461       return 0;
462     }
463 
464   if (pd->debug_p)
465     fprintf (stderr, "%s:  reading \"%s\"\n", progname, filename);
466 
467   while ((p = fgets(buf, LINE_MAX, fp)))
468     {
469       while ((*p == ' ') || (*p == '\t'))	/* skip whitespace */
470         p++;
471       if (*p == '#')				/* skip comments */
472         continue;
473 
474       /* Get the name and address */
475 
476       if ((addr = strtok(buf, " ,;\t\n")))
477         name = strtok(0, " ,;\t\n");
478       else
479         continue;
480 
481       /* Check to see if the addr looks like an addr.  If not, assume
482          the addr is a name and there is no addr.  This way, we can
483          handle files whose lines have "xx.xx.xx.xx hostname" as their
484          first two tokens, and also files that have a hostname as their
485          first token (like .ssh/known_hosts and .rhosts.)
486       */
487       {
488         int i; char c;
489         if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
490           {
491             name = addr;
492             addr = 0;
493           }
494       }
495 
496       /* If the name is all digits, it's not a name. */
497       if (name)
498         {
499           const char *s;
500           for (s = name; *s; s++)
501             if (*s < '0' || *s > '9')
502               break;
503           if (! *s)
504             {
505               if (pd->debug_p > 1)
506                 fprintf (stderr, "%s:  skipping bogus name \"%s\" (%s)\n",
507                          progname, name, addr);
508               name = 0;
509             }
510         }
511 
512       /* Create a new target using first the name then the address */
513 
514       if (!name)
515         {
516           name = addr;
517           addr = NULL;
518         }
519 
520       new = bogie_for_host (ssd, name, addr);
521 
522       if (new)
523         {
524           new->next = list;
525           list = new;
526         }
527     }
528 
529   fclose(fp);
530   return list;
531 }
532 #endif /* READ_FILES */
533 
534 
535 static sonar_bogie **
found_duplicate_host(const ping_data * pd,sonar_bogie ** list,sonar_bogie * bogie)536 found_duplicate_host (const ping_data *pd, sonar_bogie **list,
537                       sonar_bogie *bogie)
538 {
539   if (pd->debug_p)
540   {
541     fprintf (stderr, "%s: deleted duplicate: ", progname);
542     print_host (stderr, bogie);
543   }
544 
545   return list;
546 }
547 
548 
549 static sonar_bogie **
find_duplicate_host(const ping_data * pd,sonar_bogie ** list,sonar_bogie * bogie)550 find_duplicate_host (const ping_data *pd, sonar_bogie **list,
551                      sonar_bogie *bogie)
552 {
553   const ping_bogie *pb = (const ping_bogie *) bogie->closure;
554   const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address);
555 
556   while(*list)
557     {
558       const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure;
559 
560       if (!pb2->lookup_addr)
561         {
562           const struct sockaddr *addr2 =
563             (const struct sockaddr *) &(pb2->address);
564 
565           if (addr1->sa_family == addr2->sa_family)
566             {
567               switch (addr1->sa_family)
568                 {
569                 case AF_INET:
570                   {
571                     unsigned long ip1 =
572                       ((const struct sockaddr_in *)addr1)->sin_addr.s_addr;
573                     const struct sockaddr_in *i2 =
574                       (const struct sockaddr_in *)addr2;
575                     unsigned long ip2 = i2->sin_addr.s_addr;
576 
577                     if (ip1 == ip2)
578                       return found_duplicate_host (pd, list, bogie);
579                   }
580                   break;
581 #ifdef AF_INET6
582                 case AF_INET6:
583                   {
584                     if (! memcmp(
585                             &((const struct sockaddr_in6 *)addr1)->sin6_addr,
586                             &((const struct sockaddr_in6 *)addr2)->sin6_addr,
587                             16))
588                       return found_duplicate_host (pd, list, bogie);
589                   }
590                   break;
591 #endif
592                 default:
593                   {
594                     /* Fallback behavior: Just memcmp the two addresses.
595 
596                        For this to work, unused space in the sockaddr must be
597                        set to zero. Which may actually be the case:
598                        - async_addr_from_name_finish won't put garbage into
599                          sockaddr_in.sin_zero or elsewhere unless getaddrinfo
600                          does.
601                        - ping_bogie is allocated with calloc(). */
602 
603                     if (pb->addrlen == pb2->addrlen &&
604                         ! memcmp(addr1, addr2, pb->addrlen))
605                       return found_duplicate_host (pd, list, bogie);
606                   }
607                   break;
608                 }
609             }
610         }
611 
612       list = &(*list)->next;
613     }
614 
615   return NULL;
616 }
617 
618 
619 static sonar_bogie *
delete_duplicate_hosts(sonar_sensor_data * ssd,sonar_bogie * list)620 delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
621 {
622   ping_data *pd = (ping_data *) ssd->closure;
623   sonar_bogie *head = list;
624   sonar_bogie *sb = head;
625 
626   while (sb)
627     {
628       ping_bogie *pb = (ping_bogie *) sb->closure;
629 
630       if (!pb->lookup_addr)
631         {
632           sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb);
633           if (sb2)
634             *sb2 = (*sb2)->next;
635             /* #### sb leaked */
636           else
637             sb = sb->next;
638         }
639       else
640         sb = sb->next;
641     }
642 
643   return head;
644 }
645 
646 
647 static unsigned long
width_mask(unsigned long width)648 width_mask (unsigned long width)
649 {
650   unsigned long m = 0;
651   int i;
652   for (i = 0; i < width; i++)
653     m |= (1L << (31-i));
654   return m;
655 }
656 
657 
658 #ifdef HAVE_GETIFADDRS
659 static unsigned int
mask_width(unsigned long mask)660 mask_width (unsigned long mask)
661 {
662   int i;
663   for (i = 0; i < 32; i++)
664     if (mask & (1 << i))
665       break;
666   return 32-i;
667 }
668 #endif
669 
670 
671 /* Generate a list of bogies consisting of all of the entries on
672   the same subnet.  'base' ip is in network order; 0 means localhost.
673  */
674 static sonar_bogie *
subnet_hosts(sonar_sensor_data * ssd,char ** error_ret,char ** desc_ret,unsigned long n_base,int subnet_width)675 subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
676               unsigned long n_base, int subnet_width)
677 {
678   ping_data *pd = (ping_data *) ssd->closure;
679   unsigned long h_mask;   /* host order */
680   unsigned long h_base;   /* host order */
681   char address[BUFSIZ];
682   char *p;
683   int i;
684   sonar_bogie *new;
685   sonar_bogie *list = 0;
686   char buf[1024];
687 
688   if (subnet_width < 24)
689     {
690       sprintf (buf,
691                "Pinging %lu hosts is a bad\n"
692                "idea.  Please use a subnet\n"
693                "mask of 24 bits or more.",
694                (unsigned long) (1L << (32 - subnet_width)) - 1);
695       *error_ret = strdup(buf);
696       return 0;
697     }
698   else if (subnet_width > 30)
699     {
700       sprintf (buf,
701                "An %d-bit subnet\n"
702                "doesn't make sense.\n"
703                "Try \"subnet/24\"\n"
704                "or \"subnet/29\".\n",
705                subnet_width);
706       *error_ret = strdup(buf);
707       return 0;
708     }
709 
710 
711   if (pd->debug_p)
712     fprintf (stderr, "%s:   adding %d-bit subnet\n", progname, subnet_width);
713 
714 
715   if (! n_base)
716     {
717 # ifdef HAVE_GETIFADDRS
718 
719       /* To determine the local subnet, we need to know the local IP address.
720          Do this by looking at the IPs of every network interface.
721       */
722       struct in_addr in = { 0, };
723       struct ifaddrs *all = 0, *ifa;
724 
725       if (pd->debug_p)
726         fprintf (stderr, "%s:   listing network interfaces\n", progname);
727 
728       getifaddrs (&all);
729       for (ifa = all; ifa; ifa = ifa->ifa_next)
730         {
731           struct in_addr in2;
732           unsigned long mask;
733           if (! ifa->ifa_addr)
734             continue;
735           else if (ifa->ifa_addr->sa_family != AF_INET)
736             {
737               if (pd->debug_p)
738                 fprintf (stderr, "%s:     if: %4s: %s\n", progname,
739                          ifa->ifa_name,
740                          (
741 # ifdef AF_UNIX
742                           ifa->ifa_addr->sa_family == AF_UNIX  ? "local" :
743 # endif
744 # ifdef AF_LINK
745                           ifa->ifa_addr->sa_family == AF_LINK  ? "link"  :
746 # endif
747 # ifdef AF_INET6
748                           ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6"  :
749 # endif
750                           "other"));
751               continue;
752             }
753           in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
754           mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask)
755                         ->sin_addr.s_addr);
756           if (pd->debug_p)
757             fprintf (stderr, "%s:     if: %4s: inet = %s /%d 0x%08lx\n",
758                      progname,
759                      ifa->ifa_name,
760                      inet_ntoa (in2),
761                      mask_width (mask),
762                      mask);
763           if (in2.s_addr == 0x0100007f ||   /* 127.0.0.1 in network order */
764               ((in2.s_addr & 0x000000ff) == 0x7f) ||  /* 127.0.0.0/24 */
765               mask == 0)
766             /* Assume all 127 addresses are loopback, not just 127.0.0.1. */
767             continue;
768 
769           /* At least on the AT&T 3G network, pinging either of the two
770              hosts on a /31 network doesn't work, so don't try.
771            */
772           if (mask_width (mask) == 31)
773             {
774               sprintf (buf,
775                        "Can't ping subnet:\n"
776                        "local network is\n"
777                        "%.100s/%d,\n"
778                        "a p2p bridge\n"
779                        "on if %.100s.",
780                        inet_ntoa (in2), mask_width (mask), ifa->ifa_name);
781               if (*error_ret) free (*error_ret);
782               *error_ret = strdup (buf);
783               continue;
784             }
785 
786           in = in2;
787           subnet_width = mask_width (mask);
788 
789           /* Take the first non-loopback network: prefer en0 over en1. */
790           if (in.s_addr && subnet_width)
791             break;
792         }
793 
794       if (in.s_addr)
795         {
796           if (*error_ret) free (*error_ret);
797           *error_ret = 0;
798           n_base = in.s_addr;  /* already in network order, I think? */
799         }
800       else if (!*error_ret)
801         *error_ret = strdup ("Unable to determine\nlocal IP address\n");
802 
803       if (all)
804         freeifaddrs (all);
805 
806       if (*error_ret)
807         return 0;
808 
809 # else /* !HAVE_GETIFADDRS */
810 
811       /* If we can't walk the list of network interfaces to figure out
812          our local IP address, try to do it by finding the local host
813          name, then resolving that.
814       */
815       char hostname[BUFSIZ];
816       struct hostent *hent = 0;
817 
818       if (gethostname(hostname, BUFSIZ))
819         {
820           *error_ret = strdup ("Unable to determine\n"
821                                "local host name!");
822           return 0;
823         }
824 
825       /* Get our IP address and convert it to a string */
826 
827       hent = gethostbyname(hostname);
828       if (! hent)
829         {
830           strcat (hostname, ".local");	/* Necessary on iphone */
831           hent = gethostbyname(hostname);
832         }
833 
834       if (! hent)
835         {
836           sprintf(buf,
837                   "Unable to resolve\n"
838                   "local host \"%.100s\"",
839                   hostname);
840           *error_ret = strdup(buf);
841           return 0;
842         }
843 
844       strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0])));
845       n_base = pack_addr (hent->h_addr_list[0][0],
846                           hent->h_addr_list[0][1],
847                           hent->h_addr_list[0][2],
848                           hent->h_addr_list[0][3]);
849 
850       if (n_base == 0x0100007f)   /* 127.0.0.1 in network order */
851         {
852           unsigned int a, b, c, d;
853           unpack_addr (n_base, &a, &b, &c, &d);
854           sprintf (buf,
855                    "Unable to determine\n"
856                    "local subnet address:\n"
857                    "\"%.100s\"\n"
858                    "resolves to\n"
859                    "loopback address\n"
860                    "%u.%u.%u.%u.",
861                    hostname, a, b, c, d);
862           *error_ret = strdup(buf);
863           return 0;
864         }
865 
866 # endif /* !HAVE_GETIFADDRS */
867     }
868 
869 
870   /* Construct targets for all addresses in this subnet */
871 
872   h_mask = width_mask (subnet_width);
873   h_base = ntohl (n_base);
874 
875   if (desc_ret && !*desc_ret) {
876     char buf2[255];
877     unsigned int a, b, c, d;
878     unsigned long bb = n_base & htonl(h_mask);
879     unpack_addr (bb, &a, &b, &c, &d);
880     if (subnet_width > 24)
881       sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width);
882     else
883       sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width);
884     *desc_ret = strdup (buf2);
885   }
886 
887   for (i = 255; i >= 0; i--) {
888     unsigned int a, b, c, d;
889     int ip = (h_base & 0xFFFFFF00L) | i;     /* host order */
890 
891     if ((ip & h_mask) != (h_base & h_mask))  /* skip out-of-subnet host */
892       continue;
893     else if (subnet_width == 31)	     /* 1-bit bridge: 2 hosts */
894       ;
895     else if ((ip & ~h_mask) == 0)	     /* skip network address */
896       continue;
897     else if ((ip & ~h_mask) == ~h_mask)	     /* skip broadcast address */
898       continue;
899 
900     unpack_addr (htonl (ip), &a, &b, &c, &d);
901     sprintf (address, "%u.%u.%u.%u", a, b, c, d);
902 
903     if (pd->debug_p > 1)
904       {
905         unsigned int aa, ab, ac, ad;
906         unsigned int ma, mb, mc, md;
907         unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
908         unpack_addr (htonl (h_mask),          &ma, &mb, &mc, &md);
909         fprintf (stderr,
910                  "%s:  subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
911                  progname, address,
912                  aa, ab, ac, ad,
913                  ma, mb, mc, md,
914                  subnet_width);
915       }
916 
917     p = address + strlen(address) + 1;
918     sprintf(p, "%d", i);
919 
920     new = bogie_for_host (ssd, address, NULL);
921     if (new)
922       {
923         new->next = list;
924         list = new;
925       }
926   }
927 
928   return list;
929 }
930 
931 
932 /* Send a ping packet.
933  */
934 static void
send_ping(ping_data * pd,const sonar_bogie * b)935 send_ping (ping_data *pd, const sonar_bogie *b)
936 {
937   ping_bogie *pb = (ping_bogie *) b->closure;
938   u_char *packet;
939   struct ICMP *icmph;
940   const char *token = "org.jwz.xscreensaver.sonar";
941   char *host_id;
942   struct timeval tval;
943 
944   unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
945                  sizeof(socklen_t) + pb->addrlen +
946                  strlen(token) + 1 +
947                  strlen(pd->version) + 1);
948 
949   /* Create the ICMP packet */
950 
951   if (! (packet = (u_char *) calloc(1, pcktsiz)))
952     return;  /* Out of memory */
953 
954   icmph = (struct ICMP *) packet;
955   ICMP_TYPE(icmph) = ICMP_ECHO;
956   ICMP_CODE(icmph) = 0;
957   ICMP_CHECKSUM(icmph) = 0;
958   ICMP_ID(icmph) = pd->pid;
959   ICMP_SEQ(icmph) = pd->seq++;
960   /* struct timeval needs alignment, so we first use aligned buffer for
961      gettimeofday() and later copy the result to packet buffer
962    */
963 # ifdef GETTIMEOFDAY_TWO_ARGS
964   gettimeofday((struct timeval *) &tval,
965                (struct timezone *) 0);
966 # else
967   gettimeofday((struct timeval *) &tval);
968 # endif
969   memcpy(&packet[sizeof(struct ICMP)], &tval, sizeof tval);
970 
971   /* We store the sockaddr of the host we're pinging in the packet, and parse
972      that out of the return packet later (see get_ping() for why).
973      After that, we also include the name and version of this program,
974      just to give a clue to anyone sniffing and wondering what's up.
975    */
976   host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)];
977   *(socklen_t *)host_id = pb->addrlen;
978   host_id += sizeof(socklen_t);
979   memcpy(host_id, &pb->address, pb->addrlen);
980   host_id += pb->addrlen;
981   sprintf (host_id, "%.20s %.20s", token, pd->version);
982 
983   ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
984 
985   /* Send it */
986 
987   if (sendto(pd->icmpsock, packet, pcktsiz, 0,
988              (struct sockaddr *)&pb->address, sizeof(pb->address))
989       != pcktsiz)
990     {
991 #if 0
992       char buf[BUFSIZ];
993       sprintf(buf, "%s: pinging %.100s", progname, b->name);
994       perror(buf);
995 #endif
996     }
997 
998   free (packet);
999 }
1000 
1001 /* signal handler */
1002 static void
sigcatcher(int sig)1003 sigcatcher (int sig)
1004 {
1005   timer_expired = 1;
1006 }
1007 
1008 
1009 /* Compute the checksum on a ping packet.
1010  */
1011 static u_short
checksum(u_short * packet,int size)1012 checksum (u_short *packet, int size)
1013 {
1014   register int nleft = size;
1015   register u_short *w = packet;
1016   register int sum = 0;
1017   u_short answer = 0;
1018 
1019   /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
1020      to it, and at the end, fold back all the carry bits from the
1021      top 16 bits into the lower 16 bits.
1022    */
1023   while (nleft > 1)
1024     {
1025       sum += *w++;
1026       nleft -= 2;
1027     }
1028 
1029   /* mop up an odd byte, if necessary */
1030 
1031   if (nleft == 1)
1032     {
1033       *(u_char *)(&answer) = *(u_char *)w ;
1034       *(1 + (u_char *)(&answer)) = 0;
1035       sum += answer;
1036     }
1037 
1038   /* add back carry outs from top 16 bits to low 16 bits */
1039 
1040   sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
1041   sum += (sum >> 16);                     /* add carry */
1042   answer = ~sum;                          /* truncate to 16 bits */
1043 
1044   return(answer);
1045 }
1046 
1047 
1048 /* Copies the sonar_bogie and the underlying ping_bogie.
1049  */
1050 static sonar_bogie *
copy_ping_bogie(sonar_sensor_data * ssd,const sonar_bogie * b)1051 copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
1052 {
1053   sonar_bogie *b2 = sonar_copy_bogie (ssd, b);
1054   if (b->closure)
1055     {
1056       ping_bogie *pb  = (ping_bogie *) b->closure;
1057       ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
1058       pb2->address = pb->address;
1059       b2->closure = pb2;
1060     }
1061   return b2;
1062 }
1063 
1064 
1065 /* Look for all outstanding ping replies.
1066  */
1067 static sonar_bogie *
get_ping(sonar_sensor_data * ssd)1068 get_ping (sonar_sensor_data *ssd)
1069 {
1070   ping_data *pd = (ping_data *) ssd->closure;
1071   struct sockaddr from;
1072   socklen_t fromlen;
1073   int result;
1074   u_char packet[1024];
1075   struct timeval now;
1076   struct timeval then;
1077   struct ip *ip;
1078   int iphdrlen;
1079   struct ICMP *icmph;
1080   sonar_bogie *bl = 0;
1081   sonar_bogie *new = 0;
1082   struct sigaction sa;
1083   struct itimerval it;
1084   fd_set rfds;
1085   struct timeval tv;
1086 
1087   /* Set up a signal to interrupt our wait for a packet */
1088 
1089   sigemptyset(&sa.sa_mask);
1090   sa.sa_flags = 0;
1091   sa.sa_handler = sigcatcher;
1092   if (sigaction(SIGALRM, &sa, 0) == -1)
1093     {
1094       char msg[1024];
1095       sprintf(msg, "%s: unable to trap SIGALRM", progname);
1096       perror(msg);
1097       exit(1);
1098     }
1099 
1100   /* Set up a timer to interupt us if we don't get a packet */
1101 
1102   it.it_interval.tv_sec = 0;
1103   it.it_interval.tv_usec = 0;
1104   it.it_value.tv_sec = 0;
1105   it.it_value.tv_usec = pd->timeout;
1106   timer_expired = 0;
1107   setitimer(ITIMER_REAL, &it, 0);
1108 
1109   /* Wait for a result packet */
1110 
1111   fromlen = sizeof(from);
1112   while (! timer_expired)
1113     {
1114       tv.tv_usec = pd->timeout;
1115       tv.tv_sec = 0;
1116 #if 0
1117       /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
1118       FD_ZERO(&rfds);
1119 #else
1120       memset (&rfds, 0, sizeof(rfds));
1121 #endif
1122       FD_SET(pd->icmpsock, &rfds);
1123       /* only wait a little while, in case we raced with the timer expiration.
1124          From Valentijn Sessink <valentyn@openoffice.nl> */
1125       if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
1126         {
1127           result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet),
1128                              0, &from, &fromlen);
1129 
1130           /* Check the packet */
1131 
1132 # ifdef GETTIMEOFDAY_TWO_ARGS
1133           gettimeofday(&now, (struct timezone *) 0);
1134 # else
1135           gettimeofday(&now);
1136 # endif
1137           ip = (struct ip *) packet;
1138           iphdrlen = IP_HDRLEN(ip) << 2;
1139           icmph = (struct ICMP *) &packet[iphdrlen];
1140            /* struct timeval data in packet is not aligned, move the data to
1141               the aligned buffer
1142              */
1143           memcpy(&then, &packet[iphdrlen + sizeof(struct ICMP)], sizeof then);
1144 
1145 
1146           /* Ignore anything but ICMP Replies */
1147           if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
1148             continue;
1149 
1150           /* Ignore packets not set from us */
1151           if (ICMP_ID(icmph) != pd->pid)
1152 	    continue;
1153 
1154           /* Find the bogie in 'targets' that corresponds to this packet
1155              and copy it, so that this bogie stays in the same spot (th)
1156              on the screen, and so that we don't have to resolve it again.
1157 
1158              We could find the bogie by comparing ip->ip_src.s_addr to
1159              pb->address, but it is possible that, in certain weird router
1160              or NAT situations, that the reply will come back from a
1161              different address than the one we sent it to.  So instead,
1162              we parse the sockaddr out of the reply packet payload.
1163            */
1164           {
1165             const socklen_t *host_id = (socklen_t *) &packet[
1166               iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)];
1167 
1168             sonar_bogie *b;
1169 
1170             /* Ensure that a maliciously-crafted return packet can't
1171                make us overflow in memcmp. */
1172             if (result > 0 && (const u_char *)(host_id + 1) <= packet + result)
1173               {
1174                 const u_char *host_end = (const u_char *)(host_id + 1) +
1175                                          *host_id;
1176 
1177                 if ((const u_char *)(host_id + 1) <= host_end &&
1178                     host_end <= packet + result)
1179                   {
1180                     for (b = pd->targets; b; b = b->next)
1181                       {
1182                         ping_bogie *pb = (ping_bogie *)b->closure;
1183                         if (*host_id == pb->addrlen &&
1184                             !memcmp(&pb->address, host_id + 1, pb->addrlen) )
1185                           {
1186                             /* Check to see if the name lookup is done. */
1187                             if (pb->lookup_name &&
1188                                 async_name_from_addr_is_done (pb->lookup_name))
1189                               {
1190                                 char *host = NULL;
1191 
1192                                 async_name_from_addr_finish (pb->lookup_name,
1193                                                              &host, NULL);
1194 
1195                                 if (pd->debug_p > 1)
1196                                   fprintf (stderr, "%s:   %s => %s\n",
1197                                            progname, b->name,
1198                                            host ? host : "<unknown>");
1199 
1200                                 if (host)
1201                                   {
1202                                     free(b->name);
1203                                     b->name = host;
1204                                   }
1205 
1206                                 pb->lookup_name = NULL;
1207                               }
1208 
1209                             new = copy_ping_bogie (ssd, b);
1210                             break;
1211                           }
1212                       }
1213                   }
1214               }
1215           }
1216 
1217           if (! new)      /* not in targets? */
1218             {
1219               unsigned int a, b, c, d;
1220               unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
1221               fprintf (stderr,
1222                        "%s: UNEXPECTED PING REPLY! "
1223                        "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
1224                        progname, result, ICMP_SEQ(icmph), a, b, c, d);
1225               continue;
1226             }
1227 
1228           new->next = bl;
1229           bl = new;
1230 
1231           {
1232             double msec = delta(&then, &now) / 1000.0;
1233 
1234             if (pd->times_p)
1235               {
1236                 if (new->desc) free (new->desc);
1237                 new->desc = (char *) malloc (30);
1238                 if      (msec > 99) sprintf (new->desc, "%.0f ms", msec);
1239                 else if (msec >  9) sprintf (new->desc, "%.1f ms", msec);
1240                 else if (msec >  1) sprintf (new->desc, "%.2f ms", msec);
1241                 else                sprintf (new->desc, "%.3f ms", msec);
1242               }
1243 
1244             if (pd->debug_p && pd->times_p)  /* ping-like stdout log */
1245               {
1246                 char *s = strdup(new->name);
1247                 char *s2 = s;
1248                 if (strlen(s) > 28)
1249                   {
1250                     s2 = s + strlen(s) - 28;
1251                     strncpy (s2, "...", 3);
1252                   }
1253                 fprintf (stdout,
1254                          "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
1255                          result, s2, ICMP_SEQ(icmph), new->desc);
1256                 fflush (stdout);
1257                 free(s);
1258               }
1259 
1260             /* The radius must be between 0.0 and 1.0.
1261                We want to display ping times on a logarithmic scale,
1262                with the three rings being 2.5, 70 and 2,000 milliseconds.
1263              */
1264             if (msec <= 0) msec = 0.001;
1265             new->r = log (msec * 10) / log (20000);
1266 
1267             /* Don't put anyone *too* close to the center of the screen. */
1268             if (new->r < 0) new->r = 0;
1269             if (new->r < 0.1) new->r += 0.1;
1270           }
1271         }
1272     }
1273 
1274   return bl;
1275 }
1276 
1277 
1278 /* difference between the two times in microseconds.
1279  */
1280 static long
delta(struct timeval * then,struct timeval * now)1281 delta (struct timeval *then, struct timeval *now)
1282 {
1283   return (((now->tv_sec - then->tv_sec) * 1000000) +
1284           (now->tv_usec - then->tv_usec));
1285 }
1286 
1287 
1288 static void
ping_free_data(sonar_sensor_data * ssd,void * closure)1289 ping_free_data (sonar_sensor_data *ssd, void *closure)
1290 {
1291   ping_data *pd = (ping_data *) closure;
1292   sonar_bogie *b = pd->targets;
1293   while (b)
1294     {
1295       sonar_bogie *b2 = b->next;
1296       sonar_free_bogie (ssd, b);
1297       b = b2;
1298     }
1299   free (pd);
1300 }
1301 
1302 static void
ping_free_bogie_data(sonar_sensor_data * sd,void * closure)1303 ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
1304 {
1305   ping_bogie *pb = (ping_bogie *) closure;
1306 
1307   if (pb->lookup_name)
1308     async_name_from_addr_cancel (pb->lookup_name);
1309   if (pb->lookup_addr)
1310     async_addr_from_name_cancel (pb->lookup_addr);
1311   free (pb->fallback);
1312 
1313   free (closure);
1314 }
1315 
1316 
1317 /* Returns the current time in seconds as a double.
1318  */
1319 static double
double_time(void)1320 double_time (void)
1321 {
1322   struct timeval now;
1323 # ifdef GETTIMEOFDAY_TWO_ARGS
1324   struct timezone tzp;
1325   gettimeofday(&now, &tzp);
1326 # else
1327   gettimeofday(&now);
1328 # endif
1329 
1330   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
1331 }
1332 
1333 
1334 static void
free_bogie_after_lookup(sonar_sensor_data * ssd,sonar_bogie ** sbp,sonar_bogie ** sb)1335 free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp,
1336                         sonar_bogie **sb)
1337 {
1338   ping_bogie *pb = (ping_bogie *)(*sb)->closure;
1339 
1340   *sbp = (*sb)->next;
1341   pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */
1342   sonar_free_bogie (ssd, *sb);
1343   *sb = NULL;
1344 }
1345 
1346 
1347 /* Pings the next bogie, if it's time.
1348    Returns all outstanding ping replies.
1349  */
1350 static sonar_bogie *
ping_scan(sonar_sensor_data * ssd)1351 ping_scan (sonar_sensor_data *ssd)
1352 {
1353   ping_data *pd = (ping_data *) ssd->closure;
1354   double now = double_time();
1355   double ping_cycle = 10;   /* re-ping a given host every 10 seconds */
1356   double ping_interval = ping_cycle / pd->target_count;
1357 
1358   if (now > pd->last_ping_time + ping_interval)   /* time to ping someone */
1359     {
1360       struct sonar_bogie **sbp;
1361 
1362       if (pd->last_pinged)
1363         {
1364           sbp = &pd->last_pinged->next;
1365           if (!*sbp)
1366             sbp = &pd->targets;
1367         }
1368       else
1369         sbp = &pd->targets;
1370 
1371       if (!*sbp)
1372         /* Aaaaand we're out of bogies. */
1373         pd->last_pinged = NULL;
1374       else
1375         {
1376           sonar_bogie *sb = *sbp;
1377           ping_bogie *pb = (ping_bogie *)sb->closure;
1378           if (pb->lookup_addr &&
1379               async_addr_from_name_is_done (pb->lookup_addr))
1380             {
1381               if (async_addr_from_name_finish (pb->lookup_addr, &pb->address,
1382                                                &pb->addrlen, NULL))
1383                 {
1384                   char *fallback = pb->fallback;
1385                   pb->fallback = NULL;
1386 
1387                   if (pd->debug_p)
1388                     fprintf (stderr, "%s:   could not resolve host:  %s\n",
1389                              progname, sb->name);
1390 
1391                   free_bogie_after_lookup (ssd, sbp, &sb);
1392 
1393                   /* Insert the fallback bogie right where the old one was. */
1394                   if (fallback)
1395                     {
1396                       sonar_bogie *new_bogie = bogie_for_host (ssd, fallback,
1397                                                                NULL);
1398                       if (new_bogie) {
1399                         new_bogie->next = *sbp;
1400 
1401                         if (! ((ping_bogie *)new_bogie->closure)->lookup_addr &&
1402                             ! find_duplicate_host(pd, &pd->targets, new_bogie))
1403                           *sbp = new_bogie;
1404                         else
1405                           sonar_free_bogie (ssd, new_bogie);
1406                       }
1407 
1408                       free (fallback);
1409                     }
1410                 }
1411               else
1412                 {
1413                   if (pd->debug_p > 1)
1414                     {
1415                       fprintf (stderr, "%s:   %s => ", progname, sb->name);
1416                       print_address (stderr, 0, &pb->address, pb->addrlen);
1417                       putc('\n', stderr);
1418                     }
1419 
1420                   if (! is_address_ok (pd->debug_p, sb))
1421                     free_bogie_after_lookup (ssd, sbp, &sb);
1422                   else if (find_duplicate_host (pd, &pd->targets, sb))
1423                     /* Tricky: find_duplicate_host skips the current bogie when
1424                        scanning the targets list because pb->lookup_addr hasn't
1425                        been NULL'd yet.
1426 
1427                        Not that it matters much, but behavior here is to
1428                        keep the existing address.
1429                      */
1430                     free_bogie_after_lookup (ssd, sbp, &sb);
1431                 }
1432 
1433               if (sb)
1434                 pb->lookup_addr = NULL;
1435             }
1436 
1437           if (sb && !pb->lookup_addr)
1438             {
1439               if (!pb->addrlen) abort();
1440               send_ping (pd, sb);
1441               pd->last_pinged = sb;
1442             }
1443         }
1444 
1445       pd->last_ping_time = now;
1446     }
1447 
1448   return get_ping (ssd);
1449 }
1450 
1451 
1452 /* Returns a list of hosts to ping based on the "-ping" argument.
1453  */
1454 static sonar_bogie *
parse_mode(sonar_sensor_data * ssd,char ** error_ret,char ** desc_ret,const char * ping_arg,Bool ping_works_p)1455 parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret,
1456             const char *ping_arg, Bool ping_works_p)
1457 {
1458   ping_data *pd = (ping_data *) ssd->closure;
1459   char *source, *token, *end, dummy;
1460   sonar_bogie *hostlist = 0;
1461   const char *fallback = "subnet";
1462 
1463  AGAIN:
1464 
1465   if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")))
1466     source = strdup(fallback);
1467   else if (ping_arg)
1468     source = strdup(ping_arg);
1469   else
1470     return 0;
1471 
1472   token = source;
1473   end = source + strlen(source);
1474   while (token < end)
1475     {
1476       char *next;
1477       sonar_bogie *new = 0;
1478 # ifdef READ_FILES
1479       struct stat st;
1480 # endif
1481       unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
1482       char d;
1483 
1484       for (next = token;
1485            *next &&
1486            *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
1487            next++)
1488         ;
1489       *next = 0;
1490 
1491 
1492       if (pd->debug_p)
1493         fprintf (stderr, "%s: parsing %s\n", progname, token);
1494 
1495       if (!ping_works_p)
1496         {
1497           *error_ret = strdup ("Sonar must be setuid or libcap to ping!\n"
1498                                "Running simulation instead.");
1499           return 0;
1500         }
1501 
1502       if ((4 == sscanf (token, "%u.%u.%u/%u %c",    &n0,&n1,&n2,    &m,&d)) ||
1503           (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
1504         {
1505           /* subnet: A.B.C.D/M
1506              subnet: A.B.C/M
1507            */
1508           unsigned long ip = pack_addr (n0, n1, n2, n3);
1509           new = subnet_hosts (ssd, error_ret, desc_ret, ip, m);
1510         }
1511       else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d))
1512         {
1513           /* IP: A.B.C.D
1514            */
1515           new = bogie_for_host (ssd, token, NULL);
1516         }
1517       else if (!strcmp (token, "subnet"))
1518         {
1519           new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24);
1520         }
1521       else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
1522         {
1523           new = subnet_hosts (ssd, error_ret, desc_ret, 0, m);
1524         }
1525       else if (*token == '.' || *token == '/' ||
1526                *token == '$' || *token == '~')
1527         {
1528 # ifdef READ_FILES
1529           new = read_hosts_file (ssd, token);
1530 # else
1531           if (pd->debug_p) fprintf (stderr, "%s:  skipping file\n", progname);
1532 # endif
1533         }
1534 # ifdef READ_FILES
1535       else if (!stat (token, &st))
1536         {
1537           new = read_hosts_file (ssd, token);
1538         }
1539 # endif /* READ_FILES */
1540       else
1541         {
1542           /* not an existant file - must be a host name
1543            */
1544           new = bogie_for_host (ssd, token, NULL);
1545         }
1546 
1547       if (new)
1548         {
1549           sonar_bogie *nn = new;
1550           while (nn->next)
1551             nn = nn->next;
1552           nn->next = hostlist;
1553           hostlist = new;
1554         }
1555 
1556       token = next + 1;
1557       while (token < end &&
1558              (*token == ',' || *token == ' ' ||
1559               *token == '\t' || *token == '\n'))
1560         token++;
1561     }
1562 
1563   free (source);
1564 
1565   /* If the arg was completely unparsable, fall back to the local subnet.
1566      This happens if the default is "/etc/hosts" but READ_FILES is off.
1567      Or if we're on a /31 network, in which case we try twice then fail.
1568    */
1569   if (!hostlist && fallback)
1570     {
1571       if (pd->debug_p)
1572         fprintf (stderr, "%s: no hosts parsed! Trying %s\n",
1573                  progname, fallback);
1574       ping_arg = fallback;
1575       fallback = 0;
1576       goto AGAIN;
1577     }
1578 
1579   return hostlist;
1580 }
1581 
1582 
1583 static Bool
set_net_raw_capalibity(int enable_p)1584 set_net_raw_capalibity(int enable_p)
1585 {
1586   Bool ret_status = False;
1587 # ifdef HAVE_LIBCAP
1588   cap_t cap_status;
1589   cap_value_t cap_value[] = { CAP_NET_RAW, };
1590   cap_flag_value_t cap_flag_value;
1591   cap_flag_value_t new_value = enable_p ? CAP_SET : CAP_CLEAR;
1592 
1593   cap_status = cap_get_proc();
1594   do {
1595     cap_flag_value = CAP_CLEAR;
1596     if (cap_get_flag (cap_status, CAP_NET_RAW, CAP_EFFECTIVE, &cap_flag_value))
1597       break;
1598     if (cap_flag_value == new_value)
1599       {
1600         ret_status = True;
1601         break;
1602       }
1603 
1604     cap_set_flag (cap_status, CAP_EFFECTIVE, 1, cap_value, new_value);
1605     if (!cap_set_proc(cap_status))
1606       ret_status = True;
1607   } while (0);
1608 
1609   if (cap_status) cap_free (cap_status);
1610 # endif /* HAVE_LIBCAP */
1611 
1612   return ret_status;
1613 }
1614 
1615 static Bool
set_ping_capability(void)1616 set_ping_capability (void)
1617 {
1618   if (geteuid() == 0) return True;
1619   return set_net_raw_capalibity (True);
1620 }
1621 
1622 
1623 sonar_sensor_data *
sonar_init_ping(Display * dpy,char ** error_ret,char ** desc_ret,const char * subnet,int timeout,Bool resolve_p,Bool times_p,Bool debug_p)1624 sonar_init_ping (Display *dpy, char **error_ret, char **desc_ret,
1625                  const char *subnet, int timeout,
1626                  Bool resolve_p, Bool times_p, Bool debug_p)
1627 {
1628   /* Important! Do not return from this function without disavowing privileges
1629      with setuid(getuid()).
1630    */
1631   sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd));
1632   ping_data *pd = (ping_data *) calloc (1, sizeof(*pd));
1633   sonar_bogie *b;
1634   char *s;
1635 
1636   Bool socket_initted_p = False;
1637   Bool socket_raw_p     = False;
1638 
1639   pd->dpy = dpy;
1640 
1641   pd->resolve_p = resolve_p;
1642   pd->times_p   = times_p;
1643   pd->debug_p   = debug_p;
1644 
1645   ssd->closure       = pd;
1646   ssd->scan_cb       = ping_scan;
1647   ssd->free_data_cb  = ping_free_data;
1648   ssd->free_bogie_cb = ping_free_bogie_data;
1649 
1650   /* Get short version number. */
1651   s = strchr (screensaver_id, ' ');
1652   pd->version = strdup (s+1);
1653   s = strchr (pd->version, ' ');
1654   *s = 0;
1655 
1656 
1657   /* Create the ICMP socket.  Do this before dropping privs.
1658 
1659      Raw sockets can only be opened by root (or setuid root), so we
1660      only try to do this when the effective uid is 0.
1661 
1662      We used to just always try, and notice the failure.  But apparently
1663      that causes "SELinux" to log spurious warnings when running with the
1664      "strict" policy.  So to avoid that, we just don't try unless we
1665      know it will work.
1666 
1667      On MacOS X, we can avoid the whole problem by using a
1668      non-privileged datagram instead of a raw socket.
1669 
1670      On recent Linux systems (2012-ish?) we can avoid setuid by instead
1671      using cap_set_flag(... CAP_NET_RAW). To make that call the executable
1672      needs to have "sudo setcap cap_net_raw=p sonar" done to it first.
1673    */
1674   if (global_icmpsock)
1675     {
1676       pd->icmpsock = global_icmpsock;
1677       socket_initted_p = True;
1678       if (debug_p)
1679         fprintf (stderr, "%s: re-using icmp socket\n", progname);
1680 
1681     }
1682   else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
1683     {
1684       socket_initted_p = True;
1685     }
1686   else if (set_ping_capability() &&
1687            (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
1688     {
1689       socket_initted_p = True;
1690       socket_raw_p = True;
1691     }
1692 
1693   if (socket_initted_p)
1694     {
1695       global_icmpsock = pd->icmpsock;
1696       socket_initted_p = True;
1697       if (debug_p)
1698         fprintf (stderr, "%s: opened %s icmp socket\n", progname,
1699                  (socket_raw_p ? "raw" : "dgram"));
1700     }
1701   else if (debug_p)
1702     fprintf (stderr, "%s: unable to open icmp socket\n", progname);
1703 
1704   /* Disavow privs */
1705   if (setuid(getuid()) == -1) abort();
1706 
1707   pd->pid = getpid() & 0xFFFF;
1708   pd->seq = 0;
1709   pd->timeout = timeout;
1710 
1711   /* Generate a list of targets */
1712 
1713   pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet,
1714                             socket_initted_p);
1715   pd->targets = delete_duplicate_hosts (ssd, pd->targets);
1716 
1717   if (debug_p)
1718     {
1719       fprintf (stderr, "%s: Target list:\n", progname);
1720       for (b = pd->targets; b; b = b->next)
1721         {
1722           fprintf (stderr, "%s:   ", progname);
1723           print_host (stderr, b);
1724         }
1725     }
1726 
1727   /* Make sure there is something to ping */
1728 
1729   pd->target_count = 0;
1730   for (b = pd->targets; b; b = b->next)
1731     pd->target_count++;
1732 
1733   if (pd->target_count == 0)
1734     {
1735       if (! *error_ret)
1736         *error_ret = strdup ("No hosts to ping!\n"
1737                              "Simulating instead.");
1738       if (pd) ping_free_data (ssd, pd);
1739       if (ssd) free (ssd);
1740       return 0;
1741     }
1742 
1743   /* Distribute them evenly around the display field, clockwise.
1744      Even on a /24, allocated IPs tend to cluster together, so
1745      don't put any two hosts closer together than N degrees to
1746      avoid unnecessary overlap when we have plenty of space due
1747      to addresses that probably won't respond.  And don't spread
1748      them out too far apart, because that looks too symmetrical
1749      when there are a small number of hosts.
1750    */
1751   {
1752     double th = frand(M_PI);
1753     double sep = 360.0 / pd->target_count;
1754     if (sep < 23) sep = 23;
1755     if (sep > 43) sep = 43;
1756     sep /= 180/M_PI;
1757     for (b = pd->targets; b; b = b->next) {
1758       b->th = th;
1759       th += sep;
1760     }
1761   }
1762 
1763   return ssd;
1764 }
1765 
1766 #endif /* HAVE_PING -- whole file */
1767