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