/* $Id: icmp.c,v 1.27 2000/06/06 05:26:10 jared Exp $ */
#include "config.h"

extern struct protoent *icmpproto;
extern int glob_icmp_fd;

#define ICMP_PACKET_SIZE 60				/* keep this low,
	otherwise if we get a lot of sync checks going on, we can
	cause some (broken?) os'es to suck up all their kernel memory */
#define ICMP_HOLD_QUEUE (ICMP_PACKET_SIZE*9000)		/* Hold 9000 packets */
#define ICMP_PHDR_LEN	sizeof(struct timeval)

#define A(bit)          temp.rcvd_tbl[(bit)>>3]  /* identify byte in array */
#define B(bit)          (1 << ((bit) & 0x07))   /* identify bit in byte */
#define CLR(bit)        (A(bit) &= (~B(bit)))  /* do something... */

/* All the variables needed for pinging (I think) */
struct pingdata {
        struct sockaddr_in *to;
        struct ICMPHDR *icp;
        struct IPHDR *ip;
        int hlen;
        struct my_hostent *hp;
        unsigned char *datap, *packet;
        int counter;
        int ident;                  /* process id to identify our packets */
        int packetsent;             /* number of packets sent */
	struct sockaddr whereto;        /* who to ping */
	unsigned char outpack[128];            /* the packet we output */
	char rcvd_tbl[128];		/* for doing bit shifts */
	int nreceived;                  /* # of packets we got back */
	struct timeval lastsentat;
} temp;

int in_cksum();
void pinger(struct pingdata*, struct monitorent*);

/*
 *
 * FIXME: Generate Ident sequentially instead of randomly
 *
 */
unsigned short int generate_ident()
{
	/* Generate an ident that is not currently in use */
	static unsigned short int this;

	this++;
	this++;
	if ((this % 2) == 0)
	{
		this++;
	}

	return this;

}

/*
 * set up the file descriptor as necessary for use by the rest
 * of the process
 *
 * do a isroot(), and possibly revoke root in future versions
 */
void setup_icmp_fd()
{
	int retval;
	unsigned int hold = ICMP_HOLD_QUEUE;

	if (glob_icmp_fd != -1)
	{
		return;
	}

	glob_icmp_fd = socket(AF_INET, SOCK_RAW, icmpproto->p_proto);

	if (glob_icmp_fd == -1)
	{
		if (errno == EPERM)
		{
			print_err(1, "We are not root, unable to perform icmp check, exiting");
			exit(1);
		}
		perror("icmp.c:setup_icmp_fd: error!");
		print_err(1, "imcp.c:glob_icmp_fd setup was -1, errno = %d",
			errno);
	}

	retval = 0;
	while (retval == -1)
	{
		retval = setsockopt(glob_icmp_fd, SOL_SOCKET,
			SO_RCVBUF, (char *)&hold, sizeof(hold));

		if (retval == -1)
		{
			if (errno == ENOBUFS)
			{
				hold = hold / 2;
				continue;
			}
			perror("icmp.c:setsockopt");
			print_err(1, 
				"icmp.c:setsockopt returned -1, errno = %d",
				errno);
			break;
		}
	}
	print_err(0, "sysmond: INFO: hold queue set to %d for icmp packets", hold);

        set_nonblock(glob_icmp_fd);

	return;
}

/*
 * Handle the icmp responses we may get, and coordinate the
 * responses appropriateley
 */
void	handle_icmp_responses()
{
	struct monitorent *here;
        struct pingdata *localstruct = NULL;
        struct sockaddr_in from;
	char rcvd_pkt[ICMP_PACKET_SIZE];
        int cc = 0, fromlen = 0;

	if (glob_icmp_fd == -1)
	{
		return;
	}

	fromlen = sizeof(from); /* no comment */

	while (data_waiting_read(glob_icmp_fd, 0))
	{
	        if ((cc = recvfrom(glob_icmp_fd, 
			rcvd_pkt, ICMP_PACKET_SIZE, 0,
			(struct sockaddr *)&from, &fromlen)) < 0)
		{
			if (errno == EINTR) /* if we get interrupted */
			{
				print_err(1, "imcp.c: got interrupted while attempting to recvfrom");
				/* Decrease the counter */
				localstruct->counter--;
				continue; /* and try again */
			}
			perror("ping: recvfrom");
			localstruct->counter--; /* decrease the counter */
			continue; /* try again */
		}

		/* determine if it was ours */

		/* Walk the queue to find active checks that are ping/icmp type */
		for (here = queuehead; here != NULL; here = here->next)
		{
			if (here->checkent->type != SYSM_TYPE_PING)
			{
				continue;
			}
			/* address the check */
			localstruct = here->monitordata;

			if (localstruct == NULL)
				continue;

			/* Check the IP header */
			localstruct->ip = (struct IPHDR *)rcvd_pkt;
			localstruct->hlen = (localstruct->ip->IHL & 0x0f) << 2;

			/* Now the ICMP part */
			localstruct->icp=(struct ICMPHDR *)(rcvd_pkt + localstruct->hlen);

			/* Check to see if it's an ICMP_ECHOREPLY */
			if (localstruct->icp->ICMP_TYPE == ICMP_ECHOREPLY)
			{
			/* Do some debugging and print out what we
			   generated with what we got back */
#ifdef HIGHDEBUG
				if (debug)
				{
					print_err(0, "icmp.c:Comparing ICMP id %d to %d", 
						localstruct->icp->ICMP_ECHO_ID, 
						localstruct->ident);
				}
#endif /* HIGHDEBUG */

				/* Determine if it was ours */
				if (localstruct->icp->ICMP_ECHO_ID==localstruct->ident)
				{
						/* Increment our count */
			                        localstruct->nreceived++;
				}
			}	
		}
	}
}

/*
 *
 */
void	start_test_ping(struct monitorent *here)
{
	struct pingdata *localstruct;
	/* set things all up, and we should be Ok */

	if (glob_icmp_fd == -1)
	{
		/* If there is no icmp fd, say it's ok */
		here->retval = SYSM_OK;
		return;
	}

	here->monitordata = MALLOC(sizeof(struct pingdata), "icmp-localstruct");

	memset(here->monitordata, 0, sizeof(struct pingdata));

	localstruct = here->monitordata;

        gettimeofday(&here->lastserv, NULL);

	localstruct->nreceived = 0; /* zero it out */

	localstruct->packetsent = 0; /* zero it out */

	localstruct->datap = &localstruct->outpack[8 + sizeof(struct timeval)];

	/* initalize the variable */
	memset(&localstruct->whereto, 0, sizeof(struct sockaddr));

	if (debug) 
	{
		print_err(0, "setting up ping of host %s", 
			here->checkent->hostname);
	}

	/* set to */
        localstruct->to = (struct sockaddr_in *)&localstruct->whereto;

	/* internet protocol */
        localstruct->to->sin_family = AF_INET;

        localstruct->to->sin_addr.s_addr = here->checkent->inaddr;

        /* do a dns lookup on the hostname we're being passed */
        localstruct->hp = my_gethostbyname(here->checkent->hostname);

        if (!localstruct->hp)  /* did we get an error doing a dns query */
	{
		/* if so, do the return thang */
		here->retval = SYSM_NODNS;
		FREE(localstruct);
		here->monitordata = NULL;
		return; /* can't forget this else we core */
	}

	/* set the family type */
	localstruct->to->sin_family = localstruct->hp->h_addrtype;

	memcpy((caddr_t)&localstruct->to->sin_addr, localstruct->hp->my_h_addr, 
		localstruct->hp->h_length);

        if (!(localstruct->packet = (u_char *)MALLOC(ICMP_PACKET_SIZE, "icmp.c:packet_data"))) 
	{
		/* aie!! */
		print_err(0, "icmp.c: out of memory.");
		here->retval = here->checkent->lastcheck;
		FREE(localstruct);
		here->monitordata = NULL;
		return;
        }

        for (localstruct->counter = 8; localstruct->counter < 128;
		++localstruct->counter)  /* do something.. */
	{
			*localstruct->datap++ = localstruct->counter;
					/* and keep doing it */
	}

	/* generate our identity for the packet */
        localstruct->ident = generate_ident();

	if (debug)
	{
		print_err(0, "icmp.c:Created ICMP identity id of %d", localstruct->ident);
	}

	/*  Send a ping */
	pinger(localstruct, here);

	if (debug)
	{
		print_err(0, "icmp.c:Sent an ICMP echo-request to %s",
			here->checkent->hostname);
	}

	/* Track the last time that we sent a ping */
	gettimeofday(&localstruct->lastsentat, NULL);
	
	/* end of setup, we send the packet, and leave it at that  -- 
		service_test_ping watches for replies*/

	return;
}

void	service_test_ping(struct monitorent *here)
{
	struct pingdata *localstruct = NULL;
	struct timeval right_now;

	if (here == NULL)
	{
		return;
	}
	/* This must be done first, else we're pointing to nowhere land */
	localstruct = here->monitordata;
	
	if (localstruct == NULL)
	{
		print_err(0, "icmp.c:bug - localstruct == NULL in icmp.c:service_test_ping");
		return;
	}
        gettimeofday(&here->lastserv, NULL);

	/* Do some time calculation: */

	gettimeofday(&right_now, NULL);

	if (mydifftime(localstruct->lastsentat, right_now) <= 2)
		return;
		/* It's not time yet to watch for more packets */

	/* watch for imcp echo-replies and send more
		echo requests if we need to */

	/* If we've sent too many icmp echo-requests, bail out */
	if (debug)
	{
		print_err(0, "icmp.c:service_test_ping:While pinging %s, packetsent = %d",
			here->checkent->hostname, localstruct->packetsent);
	}

	if (localstruct->packetsent > MAX_TRIES)
	{
                /* if so, do the return thang */
                here->retval = SYSM_UNPINGABLE; /* It's not pingable */
		FREE(localstruct->packet); /* Free our packet */
		FREE(localstruct); /* Free memory we'd normally leak */
		here->monitordata = NULL; /* tag memory as freed internally */
		return; /* go back to where we came from */
	}

	gettimeofday(&right_now, NULL);
        if ((localstruct->nreceived == 0) &&
                (mydifftime(localstruct->lastsentat, right_now) >= 1))
        {
		/* Send another ping */
                pinger(localstruct, here);
		if (debug)
		{
			print_err(0, "icmp.c:Sent an ICMP echo-request to %s",
				here->checkent->hostname);
		}
                return;
        }

	/* Execution should reach here only upon one circumstance:
		We received an ICMP response from the host in question.
	 */

	here->retval = SYSM_OK;

	if (debug)
	{
		print_err(0, "icmp.c:Got an ICMP reply from %s", here->checkent->hostname);
	}



	/* damnit leaking aren't we */
	FREE(localstruct->packet);

	/* Free memory we'd normally leak */
	FREE(localstruct);
	
	here->monitordata = NULL;

	return;
}

/*
 * pinger --
 *      Compose and transmit an ICMP ECHO REQUEST packet.  The IP packet
 * will be added on by the kernel.  The ID field is a random ID
 * and the sequence number is an ascending integer.  The first 8 bytes
 * of the data portion are used to hold a UNIX "timeval" struct in VAX
 * byte-order, to compute the round-trip time.
 *
 * This is not important in the network monitoring world...
 */
void	pinger(struct pingdata *localdata, struct monitorent *here)
{
	int cc, ret1;
	int serrno;

	if (glob_icmp_fd == -1)
		return;

	localdata->packetsent++;
	localdata->icp = (struct ICMPHDR *)localdata->outpack;/* cast it */
	localdata->icp->ICMP_TYPE = ICMP_ECHO;	/* It's an ICMP_ECHO */
	localdata->icp->ICMP_CODE = 0;		/* icmp_code subtype */

		/* put the sequence in the packet */
	localdata->icp->ICMP_SEQ = localdata->packetsent;

		/* stick the icmp id in the packet */
	localdata->icp->ICMP_ECHO_ID = localdata->ident;

	CLR(localdata->icp->ICMP_SEQ % 1024);	/* Clear it */

	cc = (128+ICMP_PHDR_LEN);		/* skips ICMP portion */

	/* compute ICMP checksum here */
	localdata->icp->ICMP_CHECKSUM = in_cksum((u_short *)localdata->icp, cc);

	/* send the packet */
	ret1 = sendto(glob_icmp_fd, (char *)localdata->outpack, 
		cc, 0, &localdata->whereto, sizeof(struct sockaddr));
	serrno = errno;

	if (ret1 < 0 || ret1 != cc)  
	{
	        switch(serrno)
	        {
	                case ENETUNREACH:
	                        here->retval = SYSM_NETUNRCH;
				break;
	                case EHOSTDOWN:
	                case EHOSTUNREACH:
	                        here->retval =  SYSM_HOSTDOWN;
				break;
			default:
			/* A new one to me */
				perror("icmp.c:pinger:sendto");
		}
	}
        /* Track it */
        gettimeofday(&localdata->lastsentat, NULL);

}

/*
 * in_cksum --
 *	Checksum routine for Internet Protocol family headers (C Version)
 */
int	in_cksum(u_short *addr, int len)
{
	int sum = 0, nleft = len;
	u_short answer = 0, *w = addr;

	/*
	 * 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)
	{
		*(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);
}

void    stop_test_ping(struct monitorent *here)
{
        struct pingdata *localstruct = NULL;

        localstruct = here->monitordata;

	if (localstruct != NULL)
	{
		if (localstruct->packet != NULL)
		{
			FREE(localstruct->packet); /* Free our packet */
		}
		FREE(localstruct);

	}
        here->monitordata = NULL;
        return;
}

