--- /dev/null
+/* sonar, Copyright (c) 1998-2008 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * This implements the "ping" sensor for sonar.
+ */
+
+#include "screenhackI.h"
+#include "sonar.h"
+#include "version.h"
+
+#undef usleep /* conflicts with unistd.h on OSX */
+
+#if defined(HAVE_ICMP) || defined(HAVE_ICMPHDR)
+# include <unistd.h>
+# include <sys/stat.h>
+# include <limits.h>
+# include <signal.h>
+# include <fcntl.h>
+# include <sys/types.h>
+# include <sys/time.h>
+# include <sys/ipc.h>
+# include <sys/shm.h>
+# include <sys/socket.h>
+# include <netinet/in_systm.h>
+# include <netinet/in.h>
+# include <netinet/ip.h>
+# include <netinet/ip_icmp.h>
+# include <netinet/udp.h>
+# include <arpa/inet.h>
+# include <netdb.h>
+#endif /* HAVE_ICMP || HAVE_ICMPHDR */
+
+#if defined(HAVE_ICMP)
+# define HAVE_PING
+# define ICMP icmp
+# define ICMP_TYPE(p) (p)->icmp_type
+# define ICMP_CODE(p) (p)->icmp_code
+# define ICMP_CHECKSUM(p) (p)->icmp_cksum
+# define ICMP_ID(p) (p)->icmp_id
+# define ICMP_SEQ(p) (p)->icmp_seq
+#elif defined(HAVE_ICMPHDR)
+# define HAVE_PING
+# define ICMP icmphdr
+# define ICMP_TYPE(p) (p)->type
+# define ICMP_CODE(p) (p)->code
+# define ICMP_CHECKSUM(p) (p)->checksum
+# define ICMP_ID(p) (p)->un.echo.id
+# define ICMP_SEQ(p) (p)->un.echo.sequence
+#else
+# undef HAVE_PING
+#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)
+{
+ if (! (!subnet || !*subnet || !strcmp(subnet, "default")))
+ fprintf (stderr, "%s: not compiled with support for pinging hosts.\n",
+ progname);
+ return 0;
+}
+
+#else /* HAVE_PING -- whole file */
+
+
+#if defined(__DECC) || defined(_IP_VHL)
+ /* This is how you do it on DEC C, and possibly some BSD systems. */
+# define IP_HDRLEN(ip) ((ip)->ip_vhl & 0x0F)
+#else
+ /* This is how you do it on everything else. */
+# define IP_HDRLEN(ip) ((ip)->ip_hl)
+#endif
+
+/* yes, there is only one, even when multiple savers are running in the
+ same address space - since we can only open this socket before dropping
+ privs.
+ */
+static int global_icmpsock = 0;
+
+/* Set by a signal handler. */
+static int timer_expired;
+
+
+
+static u_short checksum(u_short *, int);
+static long delta(struct timeval *, struct timeval *);
+
+
+typedef struct {
+ char *version; /* short version number of xscreensaver */
+ int icmpsock; /* socket for sending pings */
+ int pid; /* our process ID */
+ int seq; /* packet sequence number */
+ int timeout; /* packet timeout */
+
+ int target_count;
+ sonar_bogie *targets; /* the hosts we will ping;
+ those that pong end up on ssd->pending. */
+ sonar_bogie *last_pinged; /* pointer into 'targets' list */
+ double last_ping_time;
+
+ Bool resolve_p;
+ Bool times_p;
+ Bool debug_p;
+
+} ping_data;
+
+typedef struct {
+ struct sockaddr address; /* ip address */
+} ping_bogie;
+
+
+
+/* Packs an IP address quad into bigendian network order. */
+static unsigned long
+pack_addr (unsigned int a, unsigned int b, unsigned int c, unsigned int d)
+{
+ unsigned long i = (((a & 255) << 24) |
+ ((b & 255) << 16) |
+ ((c & 255) << 8) |
+ ((d & 255) ));
+ return htonl (i);
+}
+
+/* Unpacks an IP address quad from bigendian network order. */
+static void
+unpack_addr (unsigned long addr,
+ unsigned int *a, unsigned int *b,
+ unsigned int *c, unsigned int *d)
+{
+ addr = ntohl (addr);
+ *a = (addr >> 24) & 255;
+ *b = (addr >> 16) & 255;
+ *c = (addr >> 8) & 255;
+ *d = (addr ) & 255;
+}
+
+
+
+
+/* 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.
+ */
+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.
+ */
+ if (ip[3] == 0)
+ {
+ if (pd->debug_p > 1)
+ fprintf (stderr, "%s: ignoring bogus IP %s\n",
+ progname, sb->name);
+ return 0;
+ }
+
+ 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 : "<unknown>"));
+
+ if (hent && hent->h_name && *hent->h_name)
+ sb->name = strdup (hent->h_name);
+ }
+ else
+ {
+ /* It's a host name. */
+
+ /* don't waste time being confused by non-hostname tokens
+ in .ssh/known_hosts */
+ if (!strcmp (sb->name, "ssh-rsa") ||
+ !strcmp (sb->name, "ssh-dsa") ||
+ !strcmp (sb->name, "ssh-dss") ||
+ strlen (sb->name) >= 80)
+ return 0;
+
+ hent = gethostbyname (sb->name);
+ if (!hent)
+ {
+ if (pd->debug_p)
+ fprintf (stderr, "%s: could not resolve host: %s\n",
+ progname, sb->name);
+ return 0;
+ }
+
+ memcpy (&iaddr->sin_addr, hent->h_addr_list[0],
+ sizeof(iaddr->sin_addr));
+
+ if (pd->debug_p > 1)
+ {
+ 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);
+ }
+ }
+ return 1;
+}
+
+
+static void
+print_host (FILE *out, unsigned long ip, const char *name)
+{
+ 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);
+ if (!name || !*name) name = "<unknown>";
+ fprintf (out, "%-16s %s\n", ips, name);
+}
+
+
+/* Create a sonar_bogie 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)
+{
+ 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;
+
+ b->name = strdup (name);
+ b->closure = pb;
+
+ 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 (pd->debug_p > 1)
+ {
+ fprintf (stderr, "%s: added ", progname);
+ print_host (stderr, ip, b->name);
+ }
+
+ return b;
+
+ FAIL:
+ if (b) free_bogie (ssd, b);
+ return 0;
+}
+
+
+/* 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.
+ */
+static sonar_bogie *
+read_hosts_file (sonar_sensor_data *ssd, const char *filename)
+{
+ ping_data *pd = (ping_data *) ssd->closure;
+ FILE *fp;
+ char buf[LINE_MAX];
+ char *p;
+ sonar_bogie *list = 0;
+ char *addr, *name;
+ sonar_bogie *new;
+
+ /* Kludge: on OSX, variables have not been expanded in the command
+ line arguments, so as a special case, allow the string to begin
+ with literal "$HOME/" or "~/".
+
+ This is so that the "Known Hosts" menu item in sonar.xml works.
+ */
+ if (!strncmp(filename, "~/", 2) || !strncmp(filename, "$HOME/", 6))
+ {
+ char *s = strchr (filename, '/');
+ strcpy (buf, getenv("HOME"));
+ strcat (buf, s);
+ filename = buf;
+ }
+
+ fp = fopen(filename, "r");
+ if (!fp)
+ {
+ char buf[1024];
+ sprintf(buf, "%s: %s", progname, filename);
+#ifdef HAVE_COCOA
+ if (pd->debug_p) /* on OSX don't syslog this */
+#endif
+ perror (buf);
+ return 0;
+ }
+
+ if (pd->debug_p)
+ fprintf (stderr, "%s: reading \"%s\"\n", progname, filename);
+
+ while ((p = fgets(buf, LINE_MAX, fp)))
+ {
+ while ((*p == ' ') || (*p == '\t')) /* skip whitespace */
+ p++;
+ if (*p == '#') /* skip comments */
+ continue;
+
+ /* Get the name and address */
+
+ name = addr = 0;
+ if ((addr = strtok(buf, " ,;\t\n")))
+ name = strtok(0, " ,;\t\n");
+ else
+ continue;
+
+ /* Check to see if the addr looks like an addr. If not, assume
+ the addr is a name and there is no addr. This way, we can
+ handle files whose lines have "xx.xx.xx.xx hostname" as their
+ first two tokens, and also files that have a hostname as their
+ first token (like .ssh/known_hosts and .rhosts.)
+ */
+ {
+ int i; char c;
+ if (4 != sscanf(addr, "%d.%d.%d.%d%c", &i, &i, &i, &i, &c))
+ {
+ name = addr;
+ addr = 0;
+ }
+ }
+
+ /* If the name is all digits, it's not a name. */
+ if (name)
+ {
+ const char *s;
+ for (s = name; *s; s++)
+ if (*s < '0' || *s > '9')
+ break;
+ if (! *s)
+ {
+ if (pd->debug_p > 1)
+ fprintf (stderr, "%s: skipping bogus name \"%s\" (%s)\n",
+ progname, name, addr);
+ name = 0;
+ }
+ }
+
+ /* 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 (new)
+ {
+ new->next = list;
+ list = new;
+ }
+ }
+
+ fclose(fp);
+ return list;
+}
+
+
+static sonar_bogie *
+delete_duplicate_hosts (sonar_sensor_data *ssd, sonar_bogie *list)
+{
+ ping_data *pd = (ping_data *) ssd->closure;
+ sonar_bogie *head = list;
+ sonar_bogie *sb;
+
+ for (sb = head; sb; sb = sb->next)
+ {
+ 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 (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 */
+ }
+ }
+ }
+ }
+
+ return head;
+}
+
+
+/* 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,
+ 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;
+ sonar_bogie *list = 0;
+ char buf[1024];
+
+ if (subnet_width < 24)
+ {
+ sprintf (buf,
+ "Pinging %lu hosts is a bad\n"
+ "idea. Please use a subnet\n"
+ "mask of 24 bits or more.",
+ (unsigned long) (1L << (32 - subnet_width)) - 1);
+ *error_ret = strdup(buf);
+ return 0;
+ }
+ else if (subnet_width > 30)
+ {
+ sprintf (buf,
+ "An %d-bit subnet\n"
+ "doesn't make sense.\n"
+ "Try \"subnet/24\"\n"
+ "or \"subnet/29\".\n",
+ subnet_width);
+ *error_ret = strdup(buf);
+ return 0;
+ }
+
+
+ if (pd->debug_p)
+ fprintf (stderr, "%s: adding %d-bit subnet\n", progname, subnet_width);
+
+ /* Get our hostname */
+
+ 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 */
+
+ if (! (hent = gethostbyname(hostname)))
+ {
+ sprintf(buf,
+ "Unable to resolve\n"
+ "local host \"%s\"",
+ hostname);
+ *error_ret = strdup(buf);
+ return 0;
+ }
+ 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));
+
+ /* 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_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;
+ }
+
+ 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 */
+ continue;
+ if ((ip & ~h_mask) == 0) /* broadcast address */
+ continue;
+ if ((ip & ~h_mask) == ~h_mask) /* broadcast address */
+ continue;
+
+ unpack_addr (htonl (ip), &a, &b, &c, &d);
+ sprintf (address, "%u.%u.%u.%u", a, b, c, d);
+
+ if (pd->debug_p > 1)
+ {
+ unsigned int aa, ab, ac, ad;
+ unsigned int ma, mb, mc, md;
+ unpack_addr (htonl (h_base & h_mask), &aa, &ab, &ac, &ad);
+ unpack_addr (htonl (h_mask), &ma, &mb, &mc, &md);
+ fprintf (stderr,
+ "%s: subnet: %s (%u.%u.%u.%u & %u.%u.%u.%u / %d)\n",
+ progname, address,
+ aa, ab, ac, ad,
+ ma, mb, mc, md,
+ subnet_width);
+ }
+
+ p = address + strlen(address) + 1;
+ sprintf(p, "%d", i);
+
+ new = bogie_for_host (ssd, address, pd->resolve_p);
+ if (new)
+ {
+ new->next = list;
+ list = new;
+ }
+ }
+
+ return list;
+}
+
+
+/* Send a ping packet.
+ */
+static void
+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";
+
+ int pcktsiz = (sizeof(struct ICMP) + sizeof(struct timeval) +
+ strlen(b->name) + 1 +
+ strlen(token) + 1 +
+ strlen(pd->version) + 1);
+
+ /* Create the ICMP packet */
+
+ if (! (packet = (u_char *) calloc(1, pcktsiz)))
+ return; /* Out of memory */
+
+ icmph = (struct ICMP *) packet;
+ ICMP_TYPE(icmph) = ICMP_ECHO;
+ ICMP_CODE(icmph) = 0;
+ ICMP_CHECKSUM(icmph) = 0;
+ ICMP_ID(icmph) = pd->pid;
+ ICMP_SEQ(icmph) = pd->seq++;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)],
+ (struct timezone *) 0);
+# else
+ gettimeofday((struct timeval *) &packet[sizeof(struct ICMP)]);
+# endif
+
+ /* We store the name 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);
+
+ ICMP_CHECKSUM(icmph) = checksum((u_short *)packet, pcktsiz);
+
+ /* Send it */
+
+ if ((result = sendto(pd->icmpsock, packet, pcktsiz, 0,
+ &pb->address, sizeof(pb->address)))
+ != pcktsiz)
+ {
+#if 0
+ char buf[BUFSIZ];
+ sprintf(buf, "%s: pinging %s", progname, b->name);
+ perror(buf);
+#endif
+ }
+}
+
+/* signal handler */
+static void
+sigcatcher (int sig)
+{
+ timer_expired = 1;
+}
+
+
+/* Compute the checksum on a ping packet.
+ */
+static u_short
+checksum (u_short *packet, int size)
+{
+ register int nleft = size;
+ register u_short *w = packet;
+ register int sum = 0;
+ u_short answer = 0;
+
+ /* Using a 32 bit accumulator (sum), we add sequential 16 bit words
+ to it, and at the end, fold back all the carry bits from the
+ top 16 bits into the lower 16 bits.
+ */
+ while (nleft > 1)
+ {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ /* mop up an odd byte, if necessary */
+
+ if (nleft == 1)
+ {
+ *(u_char *)(&answer) = *(u_char *)w ;
+ *(1 + (u_char *)(&answer)) = 0;
+ sum += answer;
+ }
+
+ /* add back carry outs from top 16 bits to low 16 bits */
+
+ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+
+ return(answer);
+}
+
+
+/* Copies the sonar_bogie and the underlying ping_bogie.
+ */
+static sonar_bogie *
+copy_ping_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
+{
+ sonar_bogie *b2 = copy_bogie (ssd, b);
+ if (b->closure)
+ {
+ ping_bogie *pb = (ping_bogie *) b->closure;
+ ping_bogie *pb2 = (ping_bogie *) calloc (1, sizeof(*pb));
+ pb2->address = pb->address;
+ b2->closure = pb2;
+ }
+ return b2;
+}
+
+
+/* Look for all outstanding ping replies.
+ */
+static sonar_bogie *
+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 */
+ int result;
+ u_char packet[1024];
+ struct timeval now;
+ struct timeval *then;
+ struct ip *ip;
+ int iphdrlen;
+ struct ICMP *icmph;
+ sonar_bogie *bl = 0;
+ sonar_bogie *new = 0;
+ struct sigaction sa;
+ struct itimerval it;
+ fd_set rfds;
+ struct timeval tv;
+
+ /* Set up a signal to interrupt our wait for a packet */
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sigcatcher;
+ if (sigaction(SIGALRM, &sa, 0) == -1)
+ {
+ char msg[1024];
+ sprintf(msg, "%s: unable to trap SIGALRM", progname);
+ perror(msg);
+ exit(1);
+ }
+
+ /* Set up a timer to interupt us if we don't get a packet */
+
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = pd->timeout;
+ timer_expired = 0;
+ setitimer(ITIMER_REAL, &it, 0);
+
+ /* Wait for a result packet */
+
+ fromlen = sizeof(from);
+ while (! timer_expired)
+ {
+ tv.tv_usec = pd->timeout;
+ tv.tv_sec = 0;
+#if 0
+ /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
+ FD_ZERO(&rfds);
+#else
+ memset (&rfds, 0, sizeof(rfds));
+#endif
+ FD_SET(pd->icmpsock, &rfds);
+ /* only wait a little while, in case we raced with the timer expiration.
+ From Valentijn Sessink <valentyn@openoffice.nl> */
+ if (select(pd->icmpsock + 1, &rfds, 0, 0, &tv) >0)
+ {
+ result = recvfrom (pd->icmpsock, packet, sizeof(packet),
+ 0, &from, &fromlen);
+
+ /* Check the packet */
+
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ gettimeofday(&now, (struct timezone *) 0);
+# else
+ gettimeofday(&now);
+# endif
+ ip = (struct ip *) packet;
+ iphdrlen = IP_HDRLEN(ip) << 2;
+ icmph = (struct ICMP *) &packet[iphdrlen];
+ then = (struct timeval *) &packet[iphdrlen + sizeof(struct ICMP)];
+
+
+ /* Ignore anything but ICMP Replies */
+ if (ICMP_TYPE(icmph) != ICMP_ECHOREPLY)
+ continue;
+
+ /* Ignore packets not set from us */
+ if (ICMP_ID(icmph) != pd->pid)
+ continue;
+
+ /* Find the bogie in 'targets' that corresponds to this packet
+ and copy it, so that this bogie stays in the same spot (th)
+ on the screen, and so that we don't have to resolve it again.
+
+ We could find the bogie by comparing ip->ip_src.s_addr to
+ 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.
+ */
+ {
+ const char *name = (char *) &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;
+ }
+ }
+
+ if (! new) /* not in targets? */
+ {
+ unsigned int a, b, c, d;
+ unpack_addr (ip->ip_src.s_addr, &a, &b, &c, &d);
+ fprintf (stderr,
+ "%s: UNEXPECTED PING REPLY! "
+ "%4d bytes, icmp_seq=%-4d from %d.%d.%d.%d\n",
+ progname, result, ICMP_SEQ(icmph), a, b, c, d);
+ continue;
+ }
+
+ new->next = bl;
+ bl = new;
+
+ {
+ double msec = delta(then, &now) / 1000.0;
+
+ if (pd->times_p)
+ {
+ if (new->desc) free (new->desc);
+ new->desc = (char *) malloc (30);
+ if (msec > 99) sprintf (new->desc, "%.0f ms", msec);
+ else if (msec > 9) sprintf (new->desc, "%.1f ms", msec);
+ else if (msec > 1) sprintf (new->desc, "%.2f ms", msec);
+ else sprintf (new->desc, "%.3f ms", msec);
+ }
+
+ if (pd->debug_p && pd->times_p) /* ping-like stdout log */
+ {
+ char *s = strdup(new->name);
+ char *s2 = s;
+ if (strlen(s) > 28)
+ {
+ s2 = s + strlen(s) - 28;
+ strncpy (s2, "...", 3);
+ }
+ fprintf (stdout,
+ "%3d bytes from %28s: icmp_seq=%-4d time=%s\n",
+ result, s2, ICMP_SEQ(icmph), new->desc);
+ fflush (stdout);
+ free(s);
+ }
+
+ /* The radius must be between 0.0 and 1.0.
+ We want to display ping times on a logarithmic scale,
+ with the three rings being 2.5, 70 and 2,000 milliseconds.
+ */
+ if (msec <= 0) msec = 0.001;
+ new->r = log (msec * 10) / log (20000);
+
+ /* Don't put anyone *too* close to the center of the screen. */
+ if (new->r < 0) new->r = 0;
+ if (new->r < 0.1) new->r += 0.1;
+ }
+ }
+ }
+
+ return bl;
+}
+
+
+/* difference between the two times in microseconds.
+ */
+static long
+delta (struct timeval *then, struct timeval *now)
+{
+ return (((now->tv_sec - then->tv_sec) * 1000000) +
+ (now->tv_usec - then->tv_usec));
+}
+
+
+static void
+ping_free_data (sonar_sensor_data *ssd, void *closure)
+{
+ ping_data *pd = (ping_data *) closure;
+ sonar_bogie *b = pd->targets;
+ while (b)
+ {
+ sonar_bogie *b2 = b->next;
+ free_bogie (ssd, b);
+ b = b2;
+ }
+ free (pd);
+}
+
+static void
+ping_free_bogie_data (sonar_sensor_data *sd, void *closure)
+{
+ free (closure);
+}
+
+
+/* Returns the current time in seconds as a double.
+ */
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+
+/* If a bogie is provided, pings it.
+ Then, returns all outstanding ping replies.
+ */
+static sonar_bogie *
+ping_scan (sonar_sensor_data *ssd)
+{
+ ping_data *pd = (ping_data *) ssd->closure;
+ double now = double_time();
+ double ping_cycle = 10; /* re-ping a given host every 10 seconds */
+ double ping_interval = ping_cycle / pd->target_count;
+
+ if (now > pd->last_ping_time + ping_interval) /* time to ping someone */
+ {
+ 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);
+ pd->last_ping_time = now;
+ }
+
+ return get_ping (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,
+ const char *ping_arg, Bool ping_works_p)
+{
+ ping_data *pd = (ping_data *) ssd->closure;
+ char *source, *token, *end, dummy;
+ sonar_bogie *hostlist = 0;
+
+ if (!ping_arg || !*ping_arg || !strcmp (ping_arg, "default"))
+ source = strdup("subnet/28");
+ else
+ source = strdup(ping_arg);
+
+ token = source;
+ end = source + strlen(source);
+ while (token < end)
+ {
+ char *next;
+ sonar_bogie *new;
+ struct stat st;
+ unsigned int n0=0, n1=0, n2=0, n3=0, m=0;
+ char d;
+
+ for (next = token;
+ *next &&
+ *next != ',' && *next != ' ' && *next != '\t' && *next != '\n';
+ next++)
+ ;
+ *next = 0;
+
+
+ if (pd->debug_p)
+ fprintf (stderr, "%s: parsing %s\n", progname, token);
+
+ if (!ping_works_p)
+ {
+ *error_ret = strdup ("Sonar must be setuid to ping!\n"
+ "Running simulation instead.");
+ return 0;
+ }
+
+ if ((4 == sscanf (token, "%u.%u.%u/%u %c", &n0,&n1,&n2, &m,&d)) ||
+ (5 == sscanf (token, "%u.%u.%u.%u/%u %c", &n0,&n1,&n2,&n3,&m,&d)))
+ {
+ /* subnet: A.B.C.D/M
+ subnet: A.B.C/M
+ */
+ unsigned long ip = pack_addr (n0, n1, n2, n3);
+ new = subnet_hosts (ssd, error_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);
+ }
+ else if (!strcmp (token, "subnet"))
+ {
+ new = subnet_hosts (ssd, error_ret, 0, 24);
+ }
+ else if (1 == sscanf (token, "subnet/%u %c", &m, &dummy))
+ {
+ new = subnet_hosts (ssd, error_ret, 0, m);
+ }
+ else if (*token == '.' || *token == '/' ||
+ *token == '$' || *token == '~' ||
+ !stat (token, &st))
+ {
+ /* file name
+ */
+ new = read_hosts_file (ssd, token);
+ }
+ else
+ {
+ /* not an existant file - must be a host name
+ */
+ new = bogie_for_host (ssd, token, pd->resolve_p);
+ }
+
+ if (new)
+ {
+ sonar_bogie *nn = new;
+ while (nn && nn->next)
+ nn = nn->next;
+ nn->next = hostlist;
+ hostlist = new;
+ }
+
+ token = next + 1;
+ while (token < end &&
+ (*token == ',' || *token == ' ' ||
+ *token == '\t' || *token == '\n'))
+ token++;
+ }
+
+ free (source);
+ 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_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->resolve_p = resolve_p;
+ pd->times_p = times_p;
+ pd->debug_p = debug_p;
+
+ ssd->closure = pd;
+ ssd->scan_cb = ping_scan;
+ ssd->free_data_cb = ping_free_data;
+ ssd->free_bogie_cb = ping_free_bogie_data;
+
+ /* Get short version number. */
+ s = strchr (screensaver_id, ' ');
+ pd->version = strdup (s+1);
+ s = strchr (pd->version, ' ');
+ *s = 0;
+
+
+ /* Create the ICMP socket. Do this before dropping privs.
+
+ Raw sockets can only be opened by root (or setuid root), so we
+ only try to do this when the effective uid is 0.
+
+ We used to just always try, and notice the failure. But apparently
+ that causes "SELinux" to log spurious warnings when running with the
+ "strict" policy. So to avoid that, we just don't try unless we
+ know it will work.
+
+ On MacOS X, we can avoid the whole problem by using a
+ non-privileged datagram instead of a raw socket.
+ */
+ if (global_icmpsock)
+ {
+ pd->icmpsock = global_icmpsock;
+ socket_initted_p = True;
+ if (debug_p)
+ fprintf (stderr, "%s: re-using icmp socket\n", progname);
+
+ }
+ else if ((pd->icmpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) >= 0)
+ {
+ socket_initted_p = True;
+ }
+ else if (geteuid() == 0 &&
+ (pd->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) >= 0)
+ {
+ socket_initted_p = True;
+ socket_raw_p = True;
+ }
+
+ if (socket_initted_p)
+ {
+ global_icmpsock = pd->icmpsock;
+ socket_initted_p = True;
+ if (debug_p)
+ fprintf (stderr, "%s: opened %s icmp socket\n", progname,
+ (socket_raw_p ? "raw" : "dgram"));
+ }
+ else if (debug_p)
+ fprintf (stderr, "%s: unable to open icmp socket\n", progname);
+
+ /* Disavow privs */
+ setuid(getuid());
+
+ pd->pid = getpid() & 0xFFFF;
+ pd->seq = 0;
+ pd->timeout = timeout;
+
+ /* Generate a list of targets */
+
+ pd->targets = parse_mode (ssd, error_ret, subnet, socket_initted_p);
+ pd->targets = delete_duplicate_hosts (ssd, pd->targets);
+
+ if (debug_p)
+ {
+ 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);
+ }
+ }
+
+ /* Make sure there is something to ping */
+
+ pd->target_count = 0;
+ for (b = pd->targets; b; b = b->next)
+ pd->target_count++;
+
+ if (pd->target_count == 0)
+ {
+ if (! *error_ret)
+ *error_ret = strdup ("No hosts to ping!\n"
+ "Simulating instead.");
+ if (pd) ping_free_data (ssd, pd);
+ if (ssd) free (ssd);
+ return 0;
+ }
+
+ /* Distribute them evenly around the display field.
+ */
+ 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;
+
+ return ssd;
+}
+
+#endif /* HAVE_PING -- whole file */