/* napping is a standalone program to be used with nap. It was written
   by Sebastian Zagrodzki <s.zagrodzki@mimuw.edu.pl>, and modified by
   Peter Selinger <selinger@users.sourceforge.net>. Some parts of the
   code were taken from the nap sources by Kevin Sullivan, and
   probably came from somewhere else before that.

   Problem: sending out PING packages requires raw network protocol
   access, which normally requires root privileges on linux. However,
   it would be dangerous to make nap setuid. That's why a typical user
   can't perform pings on nap.

   Solution: Put the code which collects the ping responses into an
   external application, "napping". This can be safely made setuid,
   because it drops root privileges immediately after the call to
   socket(), which is the first call in main(). "napping" is called
   integrated with "nap", thus, every user can see ping response
   times without putting the system to danger.

   "napping" reads a set of IP addresses, on per line, from stdin.  It
   outputs lines which consist of an IP address and a response time
   (in microseconds). It exits when enough ping responses have been
   received, or after 3 seconds. It outputs an empty line at the end,
   for the benefit of cygwin, which seems unable otherwise to detect
   the end of file.

   "napping" of course stands for "nap PING", not for "taking a nap".  */

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define ICMP_ECHO 8
#define MAX_ICMP 200

/* format of a PING (icmp) packet. Note: this representation is
   dependent on endianness. However, since type and code are 1-byte
   values, and id, sequence, and checksum are more or less arbitrary,
   it doesn't seem to matter. Also note: this struct is only 8 bytes
   long, but 56 zero bytes will be appended to make a 64-byte
   packet. See also RFC 792 (Internet Control Message Protocol).  */

struct icmphdr {
  unsigned char type, code;
  unsigned short checksum;
  union {
    struct {
      unsigned short id, sequence;
    } echo;
    unsigned long gateway;
    struct {
      unsigned short blah, mtu;
    } frag;
  } un;
};

/* calculate checksum for a PING (icmp) packet. The algorithm given
   here is (1) dependent on endianness, (2) not the one given in RFC
   792. Is is arbitrary? */
int in_cksum(u_short *addr, int len)
{
        register int nleft = len;
        register u_short *w = addr;
        register int sum = 0;
        u_short answer = 0;

        /*
         * Our algorithm is simple, 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 ;
                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);
}

int finished_receiving;

void sighandler_alarm(int nr)
{
        finished_receiving = 1;
}

int main(int ac, char *av[])
{
	struct sockaddr_in me, dst;
	int dstlen = sizeof(dst);
	struct icmphdr *icmp;
	char buf[256];
	struct timeval t;
	uid_t uid; 
	gid_t gid; 
	int r, i, addr_count;
	struct in_addr arr_addr[MAX_ICMP];
	struct timeval arr_time[MAX_ICMP];
	int cur_send, num_received;
	fd_set readfds, writefds;
	int sock, socket_errno;

	sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	socket_errno = errno;

	// DROP SUID !!! just after socket(), and just after main().
	// This makes this program safe.
	uid = getuid();
	setuid(uid);

	// also drop SGID, just in case. (Although napping is not
	// normally installed sgid).
        gid = getgid();
        setgid(gid);

	if (sock < 0) {
	  	if (socket_errno == EPERM) {
	              	fprintf(stderr, "%s must be installed suid root\n", av[0]);
			exit(1);
		}
		fprintf(stderr, "socket: %s\n", strerror(socket_errno));
		exit(1);
	}
	
	me.sin_addr.s_addr = INADDR_ANY;
	me.sin_family = AF_INET;
	if (bind(sock, (struct sockaddr *)&me, sizeof(me)) < 0) {
		fprintf(stderr, "bind: %s\n", strerror(errno));
		close(sock);
		exit(1);
	}

	/* it seems that everything works... give a prompt now so that
           nap knows to expect pings. */
	printf("IP addresses (separated by newlines):\n");
	fflush(stdout);

	/* parse IP addresses from stdin, one per line */
	addr_count = 0;
	while (fgets(buf, 128, stdin) && addr_count < MAX_ICMP) {
		r = inet_aton(buf, &arr_addr[addr_count]);
		if (r!=0) {  /* simply ignore bad lines */
		        addr_count++;
                }
	}
	
	// give'em 3 seconds
	signal(SIGALRM, (*sighandler_alarm));
	alarm(3);

	cur_send = 0;
	num_received = 0;
	finished_receiving = 0;  

	/* finished_receiving will be set to 1 after 3 seconds by
           sighandler_alarm. Note that the signal also interrupts the
           select() call.  */
	
	while(!finished_receiving && num_received < addr_count) {
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_SET(sock, &readfds);
		if (cur_send < addr_count)
			FD_SET(sock, &writefds);
		
		r = select(sock+1, &readfds, &writefds, NULL, NULL);
		if (r == -1 || r == 0)
			continue;
		
		if (FD_ISSET(sock, &writefds)) {
			/* send out a PING packet */
			char d[64];
			memset(d, 0, 64);
			dst.sin_addr = arr_addr[cur_send];
			dst.sin_family = AF_INET;
			icmp = (struct icmphdr *)d;
			icmp->type = ICMP_ECHO;
			icmp->code = 0;
			icmp->un.echo.id = (getpid()&0xffff);
			icmp->un.echo.sequence = 0;
			icmp->checksum = in_cksum((u_short *)icmp, 64);
			sendto(sock, d, 64, 0,
			       (struct sockaddr *)&dst, sizeof(dst));
			gettimeofday(&arr_time[cur_send], NULL);
			cur_send++;
		}
		if (FD_ISSET(sock, &readfds)) {
		    /* receive a PING packet */
		    if (recvfrom(sock, buf, sizeof(buf), 0,
				 (struct sockaddr *)&dst,
				 &dstlen) != -1) {
			gettimeofday(&t, NULL);
			for (i = 0; i < addr_count; i++) {
			    if (arr_addr[i].s_addr == dst.sin_addr.s_addr) {
				printf("%s %lu\n", inet_ntoa(dst.sin_addr),
				    (t.tv_sec - arr_time[i].tv_sec) * 1000000 +
				    t.tv_usec - arr_time[i].tv_usec);
				fflush(stdout);
				arr_addr[i].s_addr = 0;
				num_received++;
				break;
			    }
			}
		    }
		}
	}
	close(sock);
	printf("\n");
	return 0;
}
