X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Fglx%2Fsonar-icmp.c;h=e482ab6a580617061a58ca731922c86c4d5a60a6;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hp=b389f5e2ec700ce1c8d8aeedb0efcb867cf001d0;hpb=7b34ef992563d7bcbb64cc5597dc45fa24470b05;p=xscreensaver diff --git a/hacks/glx/sonar-icmp.c b/hacks/glx/sonar-icmp.c index b389f5e2..e482ab6a 100644 --- a/hacks/glx/sonar-icmp.c +++ b/hacks/glx/sonar-icmp.c @@ -1,4 +1,4 @@ -/* sonar, Copyright (c) 1998-2008 Jamie Zawinski and Stephen Martin +/* sonar, Copyright (c) 1998-2017 Jamie Zawinski and Stephen Martin * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -14,9 +14,36 @@ #include "screenhackI.h" #include "sonar.h" #include "version.h" +#include "async_netdb.h" #undef usleep /* conflicts with unistd.h on OSX */ +#ifdef USE_IPHONE + /* Note: to get this to compile for iPhone, you need to fix Xcode! + The icmp headers exist for the simulator build environment, but + not for the real-device build environment. This appears to + just be an Apple bug, not intentional. + + xc=/Applications/Xcode.app/Contents + for path in /Developer/Platforms/iPhone*?/Developer/SDKs/?* \ + $xc/Developer/Platforms/iPhone*?/Developer/SDKs/?* ; do + for file in \ + /usr/include/netinet/ip.h \ + /usr/include/netinet/in_systm.h \ + /usr/include/netinet/ip_icmp.h \ + /usr/include/netinet/ip_var.h \ + /usr/include/netinet/udp.h + do + ln -s "$file" "$path$file" + done + done + */ +#endif + +#ifndef HAVE_MOBILE +# define READ_FILES +#endif + #if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR) # include # include @@ -26,7 +53,9 @@ # include # include # include -# include +# ifndef HAVE_ANDROID +# include +# endif # include # include # include @@ -35,6 +64,10 @@ # include # include # include +# include +# ifdef HAVE_GETIFADDRS +# include +# endif #endif /* HAVE_ICMP || HAVE_ICMPHDR */ #if defined(HAVE_ICMP) @@ -57,11 +90,16 @@ # undef HAVE_PING #endif +#ifndef HAVE_MOBILE +# define LOAD_FILES +#endif + #ifndef HAVE_PING sonar_sensor_data * -init_ping (Display *dpy, const char *subnet, int timeout, - Bool resolve_p, Bool times_p, Bool debug_p) +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) { if (! (!subnet || !*subnet || !strcmp(subnet, "default"))) fprintf (stderr, "%s: not compiled with support for pinging hosts.\n", @@ -96,6 +134,8 @@ static long delta(struct timeval *, struct timeval *); typedef struct { + Display *dpy; /* Only used to get *useThreads. */ + char *version; /* short version number of xscreensaver */ int icmpsock; /* socket for sending pings */ int pid; /* our process ID */ @@ -115,13 +155,17 @@ typedef struct { } ping_data; typedef struct { - struct sockaddr address; /* ip address */ + async_name_from_addr_t lookup_name; + async_addr_from_name_t lookup_addr; + async_netdb_sockaddr_storage_t address; /* ip address */ + socklen_t addrlen; + char *fallback; } ping_bogie; /* Packs an IP address quad into bigendian network order. */ -static unsigned long +static in_addr_t pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d) { unsigned long i = (((a & 255) << 24) | @@ -147,27 +191,24 @@ unpack_addr (unsigned long addr, -/* If resolves the bogie's name (either a hostname or ip address string) - to a hostent. Returns 1 if successful, 0 if it failed to resolve. +/* Resolves the bogie's name (either a hostname or ip address string) + to a hostent. Returns 1 if successful, 0 if something went wrong. */ static int resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p) { ping_bogie *pb = (ping_bogie *) sb->closure; - struct hostent *hent; - struct sockaddr_in *iaddr; unsigned int ip[4]; char c; - iaddr = (struct sockaddr_in *) &(pb->address); - iaddr->sin_family = AF_INET; - if (4 == sscanf (sb->name, " %u.%u.%u.%u %c", &ip[0], &ip[1], &ip[2], &ip[3], &c)) { /* It's an IP address. */ + struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address); + if (ip[3] == 0) { if (pd->debug_p > 1) @@ -176,22 +217,22 @@ resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p) return 0; } + iaddr->sin_family = AF_INET; iaddr->sin_addr.s_addr = pack_addr (ip[0], ip[1], ip[2], ip[3]); - if (resolve_p) - hent = gethostbyaddr ((const char *) &iaddr->sin_addr.s_addr, - sizeof(iaddr->sin_addr.s_addr), - AF_INET); - else - hent = 0; - - if (pd->debug_p > 1) - fprintf (stderr, "%s: %s => %s\n", - progname, sb->name, - ((hent && hent->h_name && *hent->h_name) - ? hent->h_name : "")); + pb->addrlen = sizeof(struct sockaddr_in); - if (hent && hent->h_name && *hent->h_name) - sb->name = strdup (hent->h_name); + if (resolve_p) + { + pb->lookup_name = + async_name_from_addr_start (pd->dpy, + (const struct sockaddr *)&pb->address, + pb->addrlen); + if (!pb->lookup_name) + { + fprintf (stderr, "%s: unable to start host resolution.\n", + progname); + } + } } else { @@ -202,27 +243,37 @@ resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p) if (!strcmp (sb->name, "ssh-rsa") || !strcmp (sb->name, "ssh-dsa") || !strcmp (sb->name, "ssh-dss") || + !strncmp (sb->name, "ecdsa-", 6) || strlen (sb->name) >= 80) return 0; - hent = gethostbyname (sb->name); - if (!hent) + /* .ssh/known_hosts sometimes contains weirdness like "[host]:port". + Ignore it. */ + if (strchr (sb->name, '[')) { if (pd->debug_p) - fprintf (stderr, "%s: could not resolve host: %s\n", + fprintf (stderr, "%s: ignoring bogus address \"%s\"\n", progname, sb->name); return 0; } - memcpy (&iaddr->sin_addr, hent->h_addr_list[0], - sizeof(iaddr->sin_addr)); + /* If the name contains a colon, it's probably IPv6. */ + if (strchr (sb->name, ':')) + { + if (pd->debug_p) + fprintf (stderr, "%s: ignoring ipv6 address \"%s\"\n", + progname, sb->name); + return 0; + } - if (pd->debug_p > 1) + pb->lookup_addr = async_addr_from_name_start(pd->dpy, sb->name); + if (!pb->lookup_addr) { - unsigned int a, b, c, d; - unpack_addr (iaddr->sin_addr.s_addr, &a, &b, &c, &d); - fprintf (stderr, "%s: %s => %d.%d.%d.%d\n", - progname, sb->name, a, b, c, d); + if (pd->debug_p) + /* Either address space exhaustion or RAM exhaustion. */ + fprintf (stderr, "%s: unable to start host resolution.\n", + progname); + return 0; } } return 1; @@ -230,28 +281,110 @@ resolve_bogie_hostname (ping_data *pd, sonar_bogie *sb, Bool resolve_p) static void -print_host (FILE *out, unsigned long ip, const char *name) +print_address (FILE *out, int width, const void *sockaddr, socklen_t addrlen) { - char ips[50]; - unsigned int a, b, c, d; - unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */ - sprintf (ips, "%u.%u.%u.%u", a, b, c, d); +#ifdef HAVE_GETADDRINFO + char buf[NI_MAXHOST]; +#else + char buf[50]; +#endif + + const struct sockaddr *addr = (const struct sockaddr *)sockaddr; + const char *ips = buf; + + if (!addr->sa_family) + ips = ""; + else + { +#ifdef HAVE_GETADDRINFO + int gai_error = getnameinfo (sockaddr, addrlen, buf, sizeof(buf), + NULL, 0, NI_NUMERICHOST); + if (gai_error == EAI_SYSTEM) + ips = strerror(errno); + else if (gai_error) + ips = gai_strerror(gai_error); +#else + switch (addr->sa_family) + { + case AF_INET: + { + u_long ip = ((struct sockaddr_in *)sockaddr)->sin_addr.s_addr; + unsigned int a, b, c, d; + unpack_addr (ip, &a, &b, &c, &d); /* ip is in network order */ + sprintf (buf, "%u.%u.%u.%u", a, b, c, d); + } + break; + default: + ips = ""; + break; + } +#endif + } + + fprintf (out, "%-*s", width, ips); +} + + +static void +print_host (FILE *out, const sonar_bogie *sb) +{ + const ping_bogie *pb = (const ping_bogie *) sb->closure; + const char *name = sb->name; if (!name || !*name) name = ""; - fprintf (out, "%-16s %s\n", ips, name); + print_address (out, 16, &pb->address, pb->addrlen); + fprintf (out, " %s\n", name); } -/* Create a sonar_bogie a host name or ip address string. +static Bool +is_address_ok(Bool debug_p, const sonar_bogie *b) +{ + const ping_bogie *pb = (const ping_bogie *) b->closure; + const struct sockaddr *addr = (const struct sockaddr *)&pb->address; + + switch (addr->sa_family) + { + case AF_INET: + { + struct sockaddr_in *iaddr = (struct sockaddr_in *) addr; + + /* Don't ever use loopback (127.0.0.x) hosts */ + unsigned long ip = iaddr->sin_addr.s_addr; + if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */ + { + if (debug_p) + fprintf (stderr, "%s: ignoring loopback host %s\n", + progname, b->name); + return False; + } + + /* Don't ever use broadcast (255.x.x.x) hosts */ + if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */ + { + if (debug_p) + fprintf (stderr, "%s: ignoring broadcast host %s\n", + progname, b->name); + return False; + } + } + + break; + } + + return True; +} + + +/* Create a sonar_bogie from a host name or ip address string. Returns NULL if the name could not be resolved. */ static sonar_bogie * -bogie_for_host (sonar_sensor_data *ssd, const char *name, Bool resolve_p) +bogie_for_host (sonar_sensor_data *ssd, const char *name, const char *fallback) { ping_data *pd = (ping_data *) ssd->closure; sonar_bogie *b = (sonar_bogie *) calloc (1, sizeof(*b)); ping_bogie *pb = (ping_bogie *) calloc (1, sizeof(*pb)); - struct sockaddr_in *iaddr; - unsigned long ip; + Bool resolve_p = pd->resolve_p; b->name = strdup (name); b->closure = pb; @@ -259,41 +392,31 @@ bogie_for_host (sonar_sensor_data *ssd, const char *name, Bool resolve_p) if (! resolve_bogie_hostname (pd, b, resolve_p)) goto FAIL; - iaddr = (struct sockaddr_in *) &(pb->address); - - /* Don't ever use loopback (127.0.0.x) hosts */ - ip = iaddr->sin_addr.s_addr; - if ((ntohl (ip) & 0xFFFFFF00L) == 0x7f000000L) /* 127.0.0.x */ - { - if (pd->debug_p) - fprintf (stderr, "%s: ignoring loopback host %s\n", - progname, b->name); - goto FAIL; - } - - /* Don't ever use broadcast (255.x.x.x) hosts */ - if ((ntohl (ip) & 0xFF000000L) == 0xFF000000L) /* 255.x.x.x */ - { - if (pd->debug_p) - fprintf (stderr, "%s: ignoring broadcast host %s\n", - progname, b->name); - goto FAIL; - } + if (! pb->lookup_addr && ! is_address_ok (pd->debug_p, b)) + goto FAIL; if (pd->debug_p > 1) { fprintf (stderr, "%s: added ", progname); - print_host (stderr, ip, b->name); + print_host (stderr, b); } + if (fallback) + pb->fallback = strdup (fallback); return b; FAIL: - if (b) free_bogie (ssd, b); + if (b) sonar_free_bogie (ssd, b); + + if (fallback) + return bogie_for_host (ssd, fallback, NULL); + return 0; } +#ifdef READ_FILES + /* Return a list of bogies read from a file. The file can be like /etc/hosts or .ssh/known_hosts or probably just about anything that has host names in it. @@ -326,12 +449,12 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename) fp = fopen(filename, "r"); if (!fp) { - char buf[1024]; - sprintf(buf, "%s: %s", progname, filename); -#ifdef HAVE_COCOA + char buf2[1024]; + sprintf(buf2, "%s: %s", progname, filename); +#ifdef HAVE_JWXYZ if (pd->debug_p) /* on OSX don't syslog this */ #endif - perror (buf); + perror (buf2); return 0; } @@ -347,7 +470,6 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename) /* Get the name and address */ - name = addr = 0; if ((addr = strtok(buf, " ,;\t\n"))) name = strtok(0, " ,;\t\n"); else @@ -386,11 +508,13 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename) /* Create a new target using first the name then the address */ - new = 0; - if (name) - new = bogie_for_host (ssd, name, pd->resolve_p); - if (!new && addr) - new = bogie_for_host (ssd, addr, pd->resolve_p); + if (!name) + { + name = addr; + addr = NULL; + } + + new = bogie_for_host (ssd, name, addr); if (new) { @@ -402,6 +526,91 @@ read_hosts_file (sonar_sensor_data *ssd, const char *filename) fclose(fp); return list; } +#endif /* READ_FILES */ + + +static sonar_bogie ** +found_duplicate_host (const ping_data *pd, sonar_bogie **list, + sonar_bogie *bogie) +{ + if (pd->debug_p) + { + fprintf (stderr, "%s: deleted duplicate: ", progname); + print_host (stderr, bogie); + } + + return list; +} + + +static sonar_bogie ** +find_duplicate_host (const ping_data *pd, sonar_bogie **list, + sonar_bogie *bogie) +{ + const ping_bogie *pb = (const ping_bogie *) bogie->closure; + const struct sockaddr *addr1 = (const struct sockaddr *) &(pb->address); + + while(*list) + { + const ping_bogie *pb2 = (const ping_bogie *) (*list)->closure; + + if (!pb2->lookup_addr) + { + const struct sockaddr *addr2 = + (const struct sockaddr *) &(pb2->address); + + if (addr1->sa_family == addr2->sa_family) + { + switch (addr1->sa_family) + { + case AF_INET: + { + unsigned long ip1 = + ((const struct sockaddr_in *)addr1)->sin_addr.s_addr; + const struct sockaddr_in *i2 = + (const struct sockaddr_in *)addr2; + unsigned long ip2 = i2->sin_addr.s_addr; + + if (ip1 == ip2) + return found_duplicate_host (pd, list, bogie); + } + break; +#ifdef AF_INET6 + case AF_INET6: + { + if (! memcmp( + &((const struct sockaddr_in6 *)addr1)->sin6_addr, + &((const struct sockaddr_in6 *)addr2)->sin6_addr, + 16)) + return found_duplicate_host (pd, list, bogie); + } + break; +#endif + default: + { + /* Fallback behavior: Just memcmp the two addresses. + + For this to work, unused space in the sockaddr must be + set to zero. Which may actually be the case: + - async_addr_from_name_finish won't put garbage into + sockaddr_in.sin_zero or elsewhere unless getaddrinfo + does. + - ping_bogie is allocated with calloc(). */ + + if (pb->addrlen == pb2->addrlen && + ! memcmp(addr1, addr2, pb->addrlen)) + return found_duplicate_host (pd, list, bogie); + } + break; + } + } + } + + list = &(*list)->next; + } + + return NULL; +} static sonar_bogie * @@ -409,57 +618,64 @@ delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list) { ping_data *pd = (ping_data *) ssd->closure; sonar_bogie *head = list; - sonar_bogie *sb; + sonar_bogie *sb = head; - for (sb = head; sb; sb = sb->next) + while (sb) { ping_bogie *pb = (ping_bogie *) sb->closure; - struct sockaddr_in *i1 = (struct sockaddr_in *) &(pb->address); - unsigned long ip1 = i1->sin_addr.s_addr; - sonar_bogie *sb2; - for (sb2 = sb; sb2; sb2 = sb2->next) + if (!pb->lookup_addr) { - if (sb2 && sb2->next) - { - ping_bogie *pb2 = (ping_bogie *) sb2->next->closure; - struct sockaddr_in *i2 = (struct sockaddr_in *) &(pb2->address); - unsigned long ip2 = i2->sin_addr.s_addr; - - if (ip1 == ip2) - { - if (pd->debug_p) - { - fprintf (stderr, "%s: deleted duplicate: ", progname); - print_host (stderr, ip2, sb2->next->name); - } - sb2->next = sb2->next->next; - /* #### sb leaked */ - } - } + sonar_bogie **sb2 = find_duplicate_host (pd, &sb->next, sb); + if (sb2) + *sb2 = (*sb2)->next; + /* #### sb leaked */ + else + sb = sb->next; } + else + sb = sb->next; } return head; } +static unsigned long +width_mask (unsigned long width) +{ + unsigned long m = 0; + int i; + for (i = 0; i < width; i++) + m |= (1L << (31-i)); + return m; +} + + +#ifdef HAVE_GETIFADDRS +static unsigned int +mask_width (unsigned long mask) +{ + int i; + for (i = 0; i < 32; i++) + if (mask & (1 << i)) + break; + return 32-i; +} +#endif + + /* Generate a list of bogies consisting of all of the entries on the same subnet. 'base' ip is in network order; 0 means localhost. */ static sonar_bogie * -subnet_hosts (sonar_sensor_data *ssd, char **error_ret, +subnet_hosts (sonar_sensor_data *ssd, char **error_ret, char **desc_ret, unsigned long n_base, int subnet_width) { ping_data *pd = (ping_data *) ssd->closure; unsigned long h_mask; /* host order */ unsigned long h_base; /* host order */ - - /* Local Variables */ - - char hostname[BUFSIZ]; char address[BUFSIZ]; - struct hostent *hent; char *p; int i; sonar_bogie *new; @@ -492,67 +708,182 @@ subnet_hosts (sonar_sensor_data *ssd, char **error_ret, if (pd->debug_p) fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width); - /* Get our hostname */ - if (gethostname(hostname, BUFSIZ)) + if (! n_base) { - *error_ret = strdup ("Unable to determine\n" - "local host name!"); - return 0; - } +# ifdef HAVE_GETIFADDRS - /* Get our IP address and convert it to a string */ + /* To determine the local subnet, we need to know the local IP address. + Do this by looking at the IPs of every network interface. + */ + struct in_addr in = { 0, }; + struct ifaddrs *all = 0, *ifa; - if (! (hent = gethostbyname(hostname))) - { - sprintf(buf, - "Unable to resolve\n" - "local host \"%s\"", - hostname); - *error_ret = strdup(buf); - return 0; + if (pd->debug_p) + fprintf (stderr, "%s: listing network interfaces\n", progname); + + getifaddrs (&all); + for (ifa = all; ifa; ifa = ifa->ifa_next) + { + struct in_addr in2; + unsigned long mask; + if (ifa->ifa_addr->sa_family != AF_INET) + { + if (pd->debug_p) + fprintf (stderr, "%s: if: %4s: %s\n", progname, + ifa->ifa_name, + ( +# ifdef AF_UNIX + ifa->ifa_addr->sa_family == AF_UNIX ? "local" : +# endif +# ifdef AF_LINK + ifa->ifa_addr->sa_family == AF_LINK ? "link" : +# endif +# ifdef AF_INET6 + ifa->ifa_addr->sa_family == AF_INET6 ? "ipv6" : +# endif + "other")); + continue; + } + in2 = ((struct sockaddr_in *) ifa->ifa_addr)->sin_addr; + mask = ntohl (((struct sockaddr_in *) ifa->ifa_netmask) + ->sin_addr.s_addr); + if (pd->debug_p) + fprintf (stderr, "%s: if: %4s: inet = %s /%d 0x%08lx\n", + progname, + ifa->ifa_name, + inet_ntoa (in2), + mask_width (mask), + mask); + if (in2.s_addr == 0x0100007f || /* 127.0.0.1 in network order */ + mask == 0) + continue; + + /* At least on the AT&T 3G network, pinging either of the two + hosts on a /31 network doesn't work, so don't try. + */ + if (mask_width (mask) == 31) + { + sprintf (buf, + "Can't ping subnet:\n" + "local network is\n" + "%.100s/%d,\n" + "a p2p bridge\n" + "on if %.100s.", + inet_ntoa (in2), mask_width (mask), ifa->ifa_name); + if (*error_ret) free (*error_ret); + *error_ret = strdup (buf); + continue; + } + + in = in2; + subnet_width = mask_width (mask); + } + + if (in.s_addr) + { + if (*error_ret) free (*error_ret); + *error_ret = 0; + n_base = in.s_addr; /* already in network order, I think? */ + } + else if (!*error_ret) + *error_ret = strdup ("Unable to determine\nlocal IP address\n"); + + if (all) + freeifaddrs (all); + + if (*error_ret) + return 0; + +# else /* !HAVE_GETIFADDRS */ + + /* If we can't walk the list of network interfaces to figure out + our local IP address, try to do it by finding the local host + name, then resolving that. + */ + char hostname[BUFSIZ]; + struct hostent *hent = 0; + + if (gethostname(hostname, BUFSIZ)) + { + *error_ret = strdup ("Unable to determine\n" + "local host name!"); + return 0; + } + + /* Get our IP address and convert it to a string */ + + hent = gethostbyname(hostname); + if (! hent) + { + strcat (hostname, ".local"); /* Necessary on iphone */ + hent = gethostbyname(hostname); + } + + if (! hent) + { + sprintf(buf, + "Unable to resolve\n" + "local host \"%.100s\"", + hostname); + *error_ret = strdup(buf); + return 0; + } + + strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0]))); + n_base = pack_addr (hent->h_addr_list[0][0], + hent->h_addr_list[0][1], + hent->h_addr_list[0][2], + hent->h_addr_list[0][3]); + + if (n_base == 0x0100007f) /* 127.0.0.1 in network order */ + { + unsigned int a, b, c, d; + unpack_addr (n_base, &a, &b, &c, &d); + sprintf (buf, + "Unable to determine\n" + "local subnet address:\n" + "\"%.100s\"\n" + "resolves to\n" + "loopback address\n" + "%u.%u.%u.%u.", + hostname, a, b, c, d); + *error_ret = strdup(buf); + return 0; + } + +# endif /* !HAVE_GETIFADDRS */ } - strcpy (address, inet_ntoa(*((struct in_addr *)hent->h_addr_list[0]))); - /* Construct targets for all addresses in this subnet */ - h_mask = 0; - for (i = 0; i < subnet_width; i++) - h_mask |= (1L << (31-i)); + /* Construct targets for all addresses in this subnet */ - /* If no base IP specified, assume localhost. */ - if (n_base == 0) - n_base = pack_addr (hent->h_addr_list[0][0], - hent->h_addr_list[0][1], - hent->h_addr_list[0][2], - hent->h_addr_list[0][3]); + h_mask = width_mask (subnet_width); h_base = ntohl (n_base); - if (h_base == 0x7F000001L) /* 127.0.0.1 in host order */ - { - unsigned int a, b, c, d; - unpack_addr (n_base, &a, &b, &c, &d); - sprintf (buf, - "Unable to determine\n" - "local subnet address:\n" - "\"%s\"\n" - "resolves to\n" - "loopback address\n" - "%u.%u.%u.%u.", - hostname, a, b, c, d); - *error_ret = strdup(buf); - return 0; - } + if (desc_ret && !*desc_ret) { + char buf2[255]; + unsigned int a, b, c, d; + unsigned long bb = n_base & htonl(h_mask); + unpack_addr (bb, &a, &b, &c, &d); + if (subnet_width > 24) + sprintf (buf2, "%u.%u.%u.%u/%d", a, b, c, d, subnet_width); + else + sprintf (buf2, "%u.%u.%u/%d", a, b, c, subnet_width); + *desc_ret = strdup (buf2); + } for (i = 255; i >= 0; i--) { unsigned int a, b, c, d; int ip = (h_base & 0xFFFFFF00L) | i; /* host order */ - if ((ip & h_mask) != (h_base & h_mask)) /* not in mask range at all */ + if ((ip & h_mask) != (h_base & h_mask)) /* skip out-of-subnet host */ continue; - if ((ip & ~h_mask) == 0) /* broadcast address */ + else if (subnet_width == 31) /* 1-bit bridge: 2 hosts */ + ; + else if ((ip & ~h_mask) == 0) /* skip network address */ continue; - if ((ip & ~h_mask) == ~h_mask) /* broadcast address */ + else if ((ip & ~h_mask) == ~h_mask) /* skip broadcast address */ continue; unpack_addr (htonl (ip), &a, &b, &c, &d); @@ -575,7 +906,7 @@ subnet_hosts (sonar_sensor_data *ssd, char **error_ret, p = address + strlen(address) + 1; sprintf(p, "%d", i); - new = bogie_for_host (ssd, address, pd->resolve_p); + new = bogie_for_host (ssd, address, NULL); if (new) { new->next = list; @@ -595,12 +926,12 @@ send_ping (ping_data *pd, const sonar_bogie *b) ping_bogie *pb = (ping_bogie *) b->closure; u_char *packet; struct ICMP *icmph; - int result; const char *token = "org.jwz.xscreensaver.sonar"; + char *host_id; - int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) + - strlen(b->name) + 1 + - strlen(token) + 1 + + unsigned long pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) + + sizeof(socklen_t) + pb->addrlen + + strlen(token) + 1 + strlen(pd->version) + 1); /* Create the ICMP packet */ @@ -621,26 +952,29 @@ send_ping (ping_data *pd, const sonar_bogie *b) gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]); # endif - /* We store the name of the host we're pinging in the packet, and parse + /* We store the sockaddr of the host we're pinging in the packet, and parse that out of the return packet later (see get_ping() for why). After that, we also include the name and version of this program, just to give a clue to anyone sniffing and wondering what's up. */ - sprintf ((char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)], - "%s%c%s %s", - b->name, 0, token, pd->version); + host_id = (char *) &packet[sizeof(struct ICMP) + sizeof(struct timeval)]; + *(socklen_t *)host_id = pb->addrlen; + host_id += sizeof(socklen_t); + memcpy(host_id, &pb->address, pb->addrlen); + host_id += pb->addrlen; + sprintf (host_id, "%.20s %.20s", token, pd->version); ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz); /* Send it */ - if ((result = sendto(pd->icmpsock, packet, pcktsiz, 0, - &pb->address, sizeof(pb->address))) + if (sendto(pd->icmpsock, packet, pcktsiz, 0, + (struct sockaddr *)&pb->address, sizeof(pb->address)) != pcktsiz) { #if 0 char buf[BUFSIZ]; - sprintf(buf, "%s: pinging %s", progname, b->name); + sprintf(buf, "%s: pinging %.100s", progname, b->name); perror(buf); #endif } @@ -698,7 +1032,7 @@ checksum (u_short *packet, int size) static sonar_bogie * copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b) { - sonar_bogie *b2 = copy_bogie (ssd, b); + sonar_bogie *b2 = sonar_copy_bogie (ssd, b); if (b->closure) { ping_bogie *pb = (ping_bogie *) b->closure; @@ -717,7 +1051,7 @@ get_ping (sonar_sensor_data *ssd) { ping_data *pd = (ping_data *) ssd->closure; struct sockaddr from; - unsigned int fromlen; /* Posix says socklen_t, but that's not portable */ + socklen_t fromlen; int result; u_char packet[1024]; struct timeval now; @@ -772,7 +1106,7 @@ get_ping (sonar_sensor_data *ssd) From Valentijn Sessink */ if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0) { - result = recvfrom (pd->icmpsock, packet, sizeof(packet), + result = (int)recvfrom (pd->icmpsock, packet, sizeof(packet), 0, &from, &fromlen); /* Check the packet */ @@ -804,19 +1138,59 @@ get_ping (sonar_sensor_data *ssd) pb->address, but it is possible that, in certain weird router or NAT situations, that the reply will come back from a different address than the one we sent it to. So instead, - we parse the name out of the reply packet payload. + we parse the sockaddr out of the reply packet payload. */ { - const char *name = (char *) &packet[iphdrlen + - sizeof(struct ICMP) + - sizeof(struct timeval)]; + const socklen_t *host_id = (socklen_t *) &packet[ + iphdrlen + sizeof(struct ICMP) + sizeof(struct timeval)]; + sonar_bogie *b; - for (b = pd->targets; b; b = b->next) - if (!strcmp (name, b->name)) - { - new = copy_ping_bogie (ssd, b); - break; - } + + /* Ensure that a maliciously-crafted return packet can't + make us overflow in memcmp. */ + if (result > 0 && (const u_char *)(host_id + 1) <= packet + result) + { + const u_char *host_end = (const u_char *)(host_id + 1) + + *host_id; + + if ((const u_char *)(host_id + 1) <= host_end && + host_end <= packet + result) + { + for (b = pd->targets; b; b = b->next) + { + ping_bogie *pb = (ping_bogie *)b->closure; + if (*host_id == pb->addrlen && + !memcmp(&pb->address, host_id + 1, pb->addrlen) ) + { + /* Check to see if the name lookup is done. */ + if (pb->lookup_name && + async_name_from_addr_is_done (pb->lookup_name)) + { + char *host = NULL; + + async_name_from_addr_finish (pb->lookup_name, + &host, NULL); + + if (pd->debug_p > 1) + fprintf (stderr, "%s: %s => %s\n", + progname, b->name, + host ? host : ""); + + if (host) + { + free(b->name); + b->name = host; + } + + pb->lookup_name = NULL; + } + + new = copy_ping_bogie (ssd, b); + break; + } + } + } + } } if (! new) /* not in targets? */ @@ -898,7 +1272,7 @@ ping_free_data (sonar_sensor_data *ssd, void *closure) while (b) { sonar_bogie *b2 = b->next; - free_bogie (ssd, b); + sonar_free_bogie (ssd, b); b = b2; } free (pd); @@ -907,6 +1281,14 @@ ping_free_data (sonar_sensor_data *ssd, void *closure) static void ping_free_bogie_data (sonar_sensor_data *sd, void *closure) { + ping_bogie *pb = (ping_bogie *) closure; + + if (pb->lookup_name) + async_name_from_addr_cancel (pb->lookup_name); + if (pb->lookup_addr) + async_addr_from_name_cancel (pb->lookup_addr); + free (pb->fallback); + free (closure); } @@ -928,8 +1310,21 @@ double_time (void) } -/* If a bogie is provided, pings it. - Then, returns all outstanding ping replies. +static void +free_bogie_after_lookup(sonar_sensor_data *ssd, sonar_bogie **sbp, + sonar_bogie **sb) +{ + ping_bogie *pb = (ping_bogie *)(*sb)->closure; + + *sbp = (*sb)->next; + pb->lookup_addr = NULL; /* Prevent double-free in sonar_free_bogie. */ + sonar_free_bogie (ssd, *sb); + *sb = NULL; +} + + +/* Pings the next bogie, if it's time. + Returns all outstanding ping replies. */ static sonar_bogie * ping_scan (sonar_sensor_data *ssd) @@ -941,11 +1336,91 @@ ping_scan (sonar_sensor_data *ssd) if (now > pd->last_ping_time + ping_interval) /* time to ping someone */ { + struct sonar_bogie **sbp; + if (pd->last_pinged) - pd->last_pinged = pd->last_pinged->next; - if (! pd->last_pinged) - pd->last_pinged = pd->targets; - send_ping (pd, pd->last_pinged); + { + sbp = &pd->last_pinged->next; + if (!*sbp) + sbp = &pd->targets; + } + else + sbp = &pd->targets; + + if (!*sbp) + /* Aaaaand we're out of bogies. */ + pd->last_pinged = NULL; + else + { + sonar_bogie *sb = *sbp; + ping_bogie *pb = (ping_bogie *)sb->closure; + if (pb->lookup_addr && + async_addr_from_name_is_done (pb->lookup_addr)) + { + if (async_addr_from_name_finish (pb->lookup_addr, &pb->address, + &pb->addrlen, NULL)) + { + char *fallback = pb->fallback; + pb->fallback = NULL; + + if (pd->debug_p) + fprintf (stderr, "%s: could not resolve host: %s\n", + progname, sb->name); + + free_bogie_after_lookup (ssd, sbp, &sb); + + /* Insert the fallback bogie right where the old one was. */ + if (fallback) + { + sonar_bogie *new_bogie = bogie_for_host (ssd, fallback, + NULL); + if (new_bogie) { + new_bogie->next = *sbp; + + if (! ((ping_bogie *)new_bogie->closure)->lookup_addr && + ! find_duplicate_host(pd, &pd->targets, new_bogie)) + *sbp = new_bogie; + else + sonar_free_bogie (ssd, new_bogie); + } + + free (fallback); + } + } + else + { + if (pd->debug_p > 1) + { + fprintf (stderr, "%s: %s => ", progname, sb->name); + print_address (stderr, 0, &pb->address, pb->addrlen); + putc('\n', stderr); + } + + if (! is_address_ok (pd->debug_p, sb)) + free_bogie_after_lookup (ssd, sbp, &sb); + else if (find_duplicate_host (pd, &pd->targets, sb)) + /* Tricky: find_duplicate_host skips the current bogie when + scanning the targets list because pb->lookup_addr hasn't + been NULL'd yet. + + Not that it matters much, but behavior here is to + keep the existing address. + */ + free_bogie_after_lookup (ssd, sbp, &sb); + } + + if (sb) + pb->lookup_addr = NULL; + } + + if (sb && !pb->lookup_addr) + { + if (!pb->addrlen) abort(); + send_ping (pd, sb); + pd->last_pinged = sb; + } + } + pd->last_ping_time = now; } @@ -956,25 +1431,32 @@ ping_scan (sonar_sensor_data *ssd) /* Returns a list of hosts to ping based on the "-ping" argument. */ static sonar_bogie * -parse_mode (sonar_sensor_data *ssd, char **error_ret, +parse_mode (sonar_sensor_data *ssd, char **error_ret, char **desc_ret, const char *ping_arg, Bool ping_works_p) { ping_data *pd = (ping_data *) ssd->closure; char *source, *token, *end, dummy; sonar_bogie *hostlist = 0; + const char *fallback = "subnet"; - if (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default")) - source = strdup("subnet/28"); - else + AGAIN: + + if (fallback && (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default"))) + source = strdup(fallback); + else if (ping_arg) source = strdup(ping_arg); + else + return 0; token = source; end = source + strlen(source); while (token < end) { char *next; - sonar_bogie *new; + sonar_bogie *new = 0; +# ifdef READ_FILES struct stat st; +# endif unsigned int n0=0, n1=0, n2=0, n3=0, m=0; char d; @@ -1003,41 +1485,48 @@ parse_mode (sonar_sensor_data *ssd, char **error_ret, subnet: A.B.C/M */ unsigned long ip = pack_addr (n0, n1, n2, n3); - new = subnet_hosts (ssd, error_ret, ip, m); + new = subnet_hosts (ssd, error_ret, desc_ret, ip, m); } else if (4 == sscanf (token, "%u.%u.%u.%u %c", &n0, &n1, &n2, &n3, &d)) { /* IP: A.B.C.D */ - new = bogie_for_host (ssd, token, pd->resolve_p); + new = bogie_for_host (ssd, token, NULL); } else if (!strcmp (token, "subnet")) { - new = subnet_hosts (ssd, error_ret, 0, 24); + new = subnet_hosts (ssd, error_ret, desc_ret, 0, 24); } else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy)) { - new = subnet_hosts (ssd, error_ret, 0, m); + new = subnet_hosts (ssd, error_ret, desc_ret, 0, m); } else if (*token == '.' || *token == '/' || - *token == '$' || *token == '~' || - !stat (token, &st)) + *token == '$' || *token == '~') + { +# ifdef READ_FILES + new = read_hosts_file (ssd, token); +# else + if (pd->debug_p) fprintf (stderr, "%s: skipping file\n", progname); +# endif + } +# ifdef READ_FILES + else if (!stat (token, &st)) { - /* file name - */ new = read_hosts_file (ssd, token); } +# endif /* READ_FILES */ else { /* not an existant file - must be a host name */ - new = bogie_for_host (ssd, token, pd->resolve_p); + new = bogie_for_host (ssd, token, NULL); } if (new) { sonar_bogie *nn = new; - while (nn && nn->next) + while (nn->next) nn = nn->next; nn->next = hostlist; hostlist = new; @@ -1051,24 +1540,43 @@ parse_mode (sonar_sensor_data *ssd, char **error_ret, } free (source); + + /* If the arg was completely unparsable, fall back to the local subnet. + This happens if the default is "/etc/hosts" but READ_FILES is off. + Or if we're on a /31 network, in which case we try twice then fail. + */ + if (!hostlist && fallback) + { + if (pd->debug_p) + fprintf (stderr, "%s: no hosts parsed! Trying %s\n", + progname, fallback); + ping_arg = fallback; + fallback = 0; + goto AGAIN; + } + return hostlist; } sonar_sensor_data * -init_ping (Display *dpy, char **error_ret, - const char *subnet, int timeout, - Bool resolve_p, Bool times_p, Bool debug_p) +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) { + /* Important! Do not return from this function without disavowing privileges + with setuid(getuid()). + */ sonar_sensor_data *ssd = (sonar_sensor_data *) calloc (1, sizeof(*ssd)); ping_data *pd = (ping_data *) calloc (1, sizeof(*pd)); sonar_bogie *b; char *s; - int i, div; Bool socket_initted_p = False; Bool socket_raw_p = False; + pd->dpy = dpy; + pd->resolve_p = resolve_p; pd->times_p = times_p; pd->debug_p = debug_p; @@ -1137,7 +1645,8 @@ init_ping (Display *dpy, char **error_ret, /* Generate a list of targets */ - pd->targets = parse_mode (ssd, error_ret, subnet, socket_initted_p); + pd->targets = parse_mode (ssd, error_ret, desc_ret, subnet, + socket_initted_p); pd->targets = delete_duplicate_hosts (ssd, pd->targets); if (debug_p) @@ -1145,11 +1654,8 @@ init_ping (Display *dpy, char **error_ret, fprintf (stderr, "%s: Target list:\n", progname); for (b = pd->targets; b; b = b->next) { - ping_bogie *pb = (ping_bogie *) b->closure; - struct sockaddr_in *iaddr = (struct sockaddr_in *) &(pb->address); - unsigned long ip = iaddr->sin_addr.s_addr; fprintf (stderr, "%s: ", progname); - print_host (stderr, ip, b->name); + print_host (stderr, b); } } @@ -1169,12 +1675,25 @@ init_ping (Display *dpy, char **error_ret, return 0; } - /* Distribute them evenly around the display field. + /* Distribute them evenly around the display field, clockwise. + Even on a /24, allocated IPs tend to cluster together, so + don't put any two hosts closer together than N degrees to + avoid unnecessary overlap when we have plenty of space due + to addresses that probably won't respond. And don't spread + them out too far apart, because that looks too symmetrical + when there are a small number of hosts. */ - div = pd->target_count; - if (div > 90) div = 90; /* no closer together than 4 degrees */ - for (i = 0, b = pd->targets; b; b = b->next, i++) - b->th = M_PI * 2 * ((div - i) % div) / div; + { + double th = frand(M_PI); + double sep = 360.0 / pd->target_count; + if (sep < 23) sep = 23; + if (sep > 43) sep = 43; + sep /= 180/M_PI; + for (b = pd->targets; b; b = b->next) { + b->th = th; + th += sep; + } + } return ssd; }