/***********************************************************
        Copyright 1996 by Carnegie Mellon University

                      All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of CMU not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

CMU DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.
******************************************************************/

#include "defs.h"
#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_var.h>
#include <netdb.h>

#include "defaults.h"
#include "report.h"
#include "my_syslog.h"
#include "my_strerror.h"

#include "bootp.h" /* must be included before we can include utils.h */
#include "bootpd.h" /* for runas_uid */
#include "dhcpd.h" /* must be included before we can include dhcp.h */
#include "dhcp.h"  /* must be included before we can include utils.h */
#include "utils.h" /* for change_eid() */


#define MAXPACKET   (65536 - 60 - 8)/* max packet size */

static char buffer[MAXPACKET];
static unsigned short sequence =0;
extern int errno;
#define MAX_DUP_CHK (8 * 128)
static int mx_dup_ck = MAX_DUP_CHK;
static char rcvd_tbl[MAX_DUP_CHK / 8];
#define A(bit)      rcvd_tbl[(bit)>>3]  /* identify byte in array */
#define B(bit)      (1 << ((bit) & 0x07))   /* identify bit in byte */
#define SET(bit)    (A(bit) |= B(bit))
#define CLR(bit)    (A(bit) &= (~B(bit)))
#define TST(bit)    (A(bit) & B(bit))



/* return values :
	0	echo reply received
	1	timeout
	2	error
*/
int ping(addr,retrys,secs,usecs)
struct in_addr addr;
int retrys,secs,usecs;
{
	struct icmp *icp;
	struct ip *ip;
	struct protoent *proto;
	int s,rc,cc,readfds,hlen;
	socklen_t fromlen;
	struct sockaddr_in from,saddr;
	struct timeval tv;
	unsigned short ident;
	struct in_addr t;
	int start_seq;
	unsigned long mask;
	
    if (!(proto = getprotobyname("icmp"))) {
		report(LOG_ERR,"ping: unknown protocol icmp.");
		return 2;
    }

	if (runas_uid) /* we changed our effective uid to this earlier; we're not presently root */
		change_eid(1, 0, 0);  /* temporarily switch back to effective uid 0 so we can open a SOCK_RAW socket */
							  /* if this fails, it loggs an error msg.  try to socket() anyway, but it won't work. */

    if ((s = socket(AF_INET, SOCK_RAW, proto->p_proto)) < 0) {
		report(LOG_ERR,"open socket for ping: %s",strerror(errno));
		return 2;
    }

	if (runas_uid) /* we're not normally running as root, and switched above to root (or at least tried) */
		change_eid(1, runas_uid, 0);  /* switch back to effective uid 'runas_uid'  */
									  /* if that fails, it logs an error msg.  */
 
	ident=pid & 0xffff;
	cc = 64;
	start_seq = sequence+1;
	bzero(rcvd_tbl,sizeof(rcvd_tbl));
	while(retrys--){
		icp=(struct icmp*)buffer;
		icp->icmp_type= ICMP_ECHO;
		icp->icmp_code = 0;
		icp->icmp_cksum = 0;
		icp->icmp_id=ident;
		sequence= (++sequence % 0xffff);
		icp->icmp_seq = sequence;
	    icp->icmp_cksum = in_cksum((u_short *)icp, cc);
		SET((int)icp->icmp_seq % mx_dup_ck);
		
		bzero(&saddr,sizeof(saddr));
		saddr.sin_family = AF_INET;
		saddr.sin_addr.s_addr=addr.s_addr;

		readfds = 1 <<s;
		tv.tv_sec=secs;
		tv.tv_usec=usecs;

		
		rc=sendto(s,(char*)icp,cc,0,(struct sockaddr*)&saddr,sizeof(struct sockaddr));
		if(rc == -1){
			report(LOG_ERR,"ping sendto : %s ",strerror(errno));
			return 2;
		}
		rc=select(s+1,(fd_set*)&readfds,NULL,NULL,&tv);

		if(rc <= 0)
			continue;
		else {
			bzero(&from,sizeof(from));
			fromlen = sizeof(from);
			rc=recvfrom(s,buffer,sizeof(buffer),0,(struct sockaddr*)&from,&fromlen);
			if(rc == -1 ){
				report(LOG_ERR,"recv_from: %s",strerror(errno));
				continue;
			}
			
			ip = (struct ip*) buffer;
			hlen = ip->ip_hl << 2;
			
			rc-=hlen;
			icp = (struct icmp *)(buffer + hlen);
			if( icp->icmp_type == ICMP_ECHOREPLY && icp->icmp_id == ident 
					&& TST((int)icp->icmp_seq %mx_dup_ck)){
				close(s);
				return 0;
			} 
		}
	}

	close(s);
	return 1;
}
			
			
			
			

/*
 * in_cksum --
 *  Checksum routine for Internet Protocol family headers (C Version)
 */
in_cksum(addr, len)
    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);
}


