#include <stdio.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <string.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>
#include <errno.h>
#include <stdlib.h>
#ifndef __FreeBSD__
#include <malloc.h>
#endif
#include <unistd.h>
#include "list.h"
#include "config.h"
#include "pinger.h"


/* Define this on unsafe systems to prevent non-privileged users to
   somewhat access raw sockets
*/   

#undef DONT_DROP_ROOT

int debug=0;
ListItem pinger_iterator;

#include "pinger.h"

unsigned long timeval_diff(struct timeval *a, struct timeval *b)
{
	return ( (long)(b->tv_usec - a->tv_usec) + 
	       ( (long)(b->tv_sec - a->tv_sec) * 1000000L) );
}


int pinger_init(pinger *p)
{
	if (!(p->proto=getprotobyname("icmp"))) {
		printf("Error: unknown protocol icmp\n");
		return -1;
	}
	if ((p->socket=socket(AF_INET,SOCK_RAW,p->proto->p_proto))<1) {
		if (errno==EPERM) 
			fprintf(stderr,"Error: this program must be run as root\n");
		else
			perror("Socket");
		return -2;
	}
	
	/* Drop Root */
#ifndef DONT_DROP_ROOT
	setreuid(getuid(),getuid());
	setregid(getgid(),getgid());
#endif
	ListInit(&p->hosts);
	
	p->ident =  getpid() & 0xFFFF;

	return 0;
}

pinger_host *pinger_addhost(pinger *pg, char *hostname)
{
	struct hostent *h;
	pinger_host *p;
	int i;	
	struct sockaddr_in *sock;
	sock = (struct sockaddr_in*)(malloc(sizeof(struct sockaddr_in)));
	
	if (!sock) {
		perror("malloc\n");
		return 0;
	}

	memset(sock, 0, sizeof(struct sockaddr_in));
	sock->sin_family = AF_INET;
#ifdef HAVE_INET_ATON
	if (!(inet_aton(hostname,&sock->sin_addr))) {
#else
	/* inet_addr returns -1 when errors occur */
	if ((sock->sin_addr.s_addr=inet_addr(hostname)) == -1) {
#endif 
		if (!(h = gethostbyname(hostname))) {
			printf("Unknown host: %s",hostname);
			free(sock);
			return 0;
		}
		 sock->sin_family = h->h_addrtype;
                 memcpy(&sock->sin_addr, h->h_addr, h->h_length);
	}
	
	p=(pinger_host*)(malloc(sizeof(pinger_host)));
	if (!p) {
		perror("malloc");
		free(sock);
		return 0;
	}
	memset(p,0,sizeof(pinger_host));
	
	p->sock = sock;
	p->lastping = time(0);
	p->lastreply = time(0);
	p->sequence = 0;
	p->timeoutptr = 0;
	ListInsert(&pg->hosts, (void*)p);
	
	/* This shouldn't be here though */
	pinger_iterator=0;
	memset(p->timeout, 0, sizeof(p->timeout));
	return p;
}

/* taken from GNU ping.c */

static 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 pinger_poll(pinger *p, pinger_host *h)
{
	STRUCT_ICMP *icp;
	if (h->flags& PINGER_DONTPING) return 0;
	
	if (h->tcpport) {
		return tcp_isalive(h);
	}
	else if (h->udpport) {
		return udp_isalive(h);
	}
	if (debug) fprintf(stderr,"Pinging %s...\n",
	inet_ntoa(*(struct in_addr *)&h->sock->sin_addr.s_addr));
	icp = (STRUCT_ICMP *)p->outbuf;
	h->lastping = time(0);
	
	
	icp->ICMP_TYPE = ICMP_ECHO;
        icp->ICMP_CODE = 0;
        icp->ICMP_CKSUM = 0;
        icp->ICMP_SEQ = h->sequence++;   
        icp->ICMP_ID = p->ident;                   /* ID */
	icp->ICMP_CKSUM = in_cksum((u_short *)icp, DATALEN + 8);

	gettimeofday(&h->lp,0);
	return sendto(p->socket, (char *)p->outbuf, DATALEN+8 , 0, 
	(struct sockaddr *) h->sock,
            sizeof(struct sockaddr));
}

int pinger_pollall(pinger *p)
{
	ListItem iterator;
	
	for (iterator=p->hosts;iterator;iterator=iterator->next) 
	{
		pinger_poll(p, (pinger_host*)ListData(iterator) );
	}
	return 0;
}

pinger_host *pinger_pollinit(pinger *p)
{
	pinger_iterator=p->hosts;
	return (pinger_host*)ListData(pinger_iterator);
}

pinger_host *pinger_pollnext(pinger *p)
{
	pinger_host *phost;
	if (!pinger_iterator) {
		pinger_iterator=p->hosts;
		return 0;
	}
	if (pinger_iterator) {
		phost=(pinger_host*)ListData(pinger_iterator);
		pinger_poll(p, phost );
		pinger_iterator=pinger_iterator->next;
		return phost;
	} 
	else 
	  return 0;
}



unsigned long pinger_checkoneandsleep(pinger *p,unsigned long usecs)
{
	struct timeval tv;
#ifndef linux
	struct timeval delay,enddelay;
	long diff;
#endif
	struct sockaddr_in host;
	struct ip *iph;
	STRUCT_ICMP *icp;
	int hlen,i,fdmax=0;
	ListItem iterator;
	pinger_host *h;
	int s,count,size;
	
	tv.tv_sec=0;
	tv.tv_usec=usecs;
	
	FD_ZERO(&p->fdmask_read);
	FD_ZERO(&p->fdmask_write);
	FD_SET(p->socket,&p->fdmask_read);
	
	fdmax = p->socket;
	
	for(i=1, iterator=p->hosts; iterator; iterator=iterator->next) {
		h=(pinger_host*)ListData(iterator);
		
		if (h->tcp_socket) {
			FD_SET(h->tcp_socket, &p->fdmask_write);
		}
			
		if (h->tcp_socket>fdmax) fdmax=h->tcp_socket;
	}
#ifndef linux
	gettimeofday(&delay,0);
#endif	
	if ((s=select(fdmax+1, &p->fdmask_read, &p->fdmask_write, 0, &tv))>0) {
	    if (FD_ISSET(p->socket, &p->fdmask_read)) {
		size=sizeof(struct sockaddr_in);
		if ((count=recvfrom(p->socket,(char*) p->inbuf, MAXPACKET, 0,
			(struct sockaddr*)&host, &size))>=0) {
			
			/* Handle Packet */
			
			iph=(struct ip*)p->inbuf;
			hlen = iph->ip_hl << 2;
			icp = (STRUCT_ICMP*)(p->inbuf + hlen);
			if (icp->ICMP_TYPE == ICMP_ECHOREPLY || 
			(icp->ICMP_TYPE == ICMP_UNREACH 
			    && icp->ICMP_CODE==ICMP_UNREACH_PORT) ) {
				
				
				if (icp->ICMP_TYPE == ICMP_UNREACH || icp->ICMP_ID == p->ident) 
				
				for (iterator=p->hosts; iterator; iterator=iterator->next) {
					h=(pinger_host *)ListData(iterator);
					if (h->sock->sin_addr.s_addr == host.sin_addr.s_addr)
					{
						int incoming_udp_port;
						struct ip *iph;
						struct udphdr *udph;
						/* Calculate port */
						
						if (icp->ICMP_TYPE == ICMP_UNREACH){
						 iph = (struct ip*)((char*)icp + sizeof(STRUCT_ICMP));
						if (iph->ip_p == IPPROTO_UDP) {
							udph = (struct udphdr *)
							   ((char *)iph + sizeof(struct ip));
							incoming_udp_port = ntohs(udph->UDP_DPORT);
							if (incoming_udp_port != h->udpport)
							 continue;
							 if (debug) fprintf(stderr,"ICMP port unreachable UDP %d from %s\n",
							incoming_udp_port,
							inet_ntoa(*(struct in_addr *)&host.sin_addr.s_addr));
						}
						}
						if (debug && !h->udpport)
						printf("Incoming from host %s\n",
						inet_ntoa(*(struct in_addr *)&host.sin_addr.s_addr));
						if (h->udpport) {
							h->udpstatus=1;
						} else
						{
						h->lastreply = time(0);
						
						gettimeofday(&h->lr,0);
						
						if (debug) printf("Got reply from %s, timeout %lu ms (stored at position %d)\n",
						inet_ntoa(*(struct in_addr *)&host.sin_addr.s_addr),
						timeval_diff(&h->lp, &h->lr)/1000, 
						h->timeoutptr);
						h->timeout[h->timeoutptr++] = timeval_diff
						(&h->lp, &h->lr) / 1000;
						if (h->timeoutptr > TIMEOUT_SIZE)
							h->timeoutptr=0;
						}
#ifndef linux
						gettimeofday(&enddelay,0);
						diff = usecs - timeval_diff(&delay, &enddelay);
						if (diff<0) diff=0;
						tv.tv_usec=diff;
#endif
						return tv.tv_usec;
						
					}
				}
			} else
			                    
			if (debug) printf("Unknown ICMP type %d\n",icp->ICMP_TYPE);
		} else perror("recvfrom");
	    }
	    /* Check the rest of them */
	    for (iterator=p->hosts;iterator;iterator=iterator->next) {
	    	h = (pinger_host*)ListData(iterator);
	    	if (h->tcp_socket && FD_ISSET(h->tcp_socket, &p->fdmask_write)) {
	    	    int dummy,dummysize=sizeof(int);
	    	  /*  
	    	    dummy= connect(h->tcp_socket, (struct sockaddr*) h->tcpsock,
	    	    sizeof(struct sockaddr_in));
	    	    if (dummy == 0) {
	    	    	h->lastreply = time(0);
	    	    	if (debug) fprintf(stderr,"Success connecting to %s port %d\n",
	    	    	inet_ntoa(*(struct in_addr *)&h->sock->sin_addr.s_addr), h->tcpport);
	    	    	
	    	    } else if (debug) {
	    	    	fprintf(stderr,"Error connecting to %s port %d (socket %d) - ",
	    	    	inet_ntoa(*(struct in_addr *)&h->sock->sin_addr.s_addr), h->tcpport,
	    	    	h->tcp_socket);
	    	    	perror("connect");
	    	    	}
	    	    	
*/
		    /* Check the SO_ERROR */
		    if (getsockopt(h->tcp_socket,
		        SOL_SOCKET, SO_ERROR, &dummy, &dummysize)<0) {
		    	perror("pinger.c: pinger_checkoneandsleep() - getsockopt");    
		    }
		    else {
		    	if (!dummy) /* Ok, connnect succeeded */ {
		    	    	h->lastreply = time(0);
		    	    	if (debug) fprintf(stderr,"Success connecting to %s port %d\n",
	    		    	inet_ntoa(*(struct in_addr *)&h->sock->sin_addr.s_addr), h->tcpport);    	
		    	} else if (debug) {
		    	    	fprintf(stderr,"Error connecting to %s port %d (socket %d) - ",
		    	    	inet_ntoa(*(struct in_addr *)&h->sock->sin_addr.s_addr), h->tcpport,
		    	    	h->tcp_socket);
		    	    	fprintf(stderr,"SOERROR %d\n",dummy);
	    	    	}		    	
		    }
		    	    	    
	    	    close(h->tcp_socket);
	    	    h->tcp_socket = 0;
	    	    free(h->tcpsock);
	    	}
	    }
	}

#ifndef linux
	gettimeofday(&enddelay,0);
	diff = usecs - timeval_diff(&delay, &enddelay);
	if (diff<0) diff=0;
	tv.tv_usec=diff;
#endif
	return tv.tv_usec;
}


syntax highlighted by Code2HTML, v. 0.9.1