/* File MSNICM.C
 * ICMP packet processor
 *
 * Copyright (C) 1991, University of Waterloo.
 * Copyright (C) 1985, 1993, Trustees of Columbia University in the 
 * City of New York.  Permission is granted to any individual or institution
 * to use this software as long as it is not sold for profit.  This copyright
 * notice must be retained.  This software may not be included in commercial
 * products without written permission of Columbia University.
 *
 * Original version created by Erick Engelke of the University of
 *  Waterloo, Waterloo, Ontario, Canada.
 * Adapted and modified for MS-DOS Kermit by Joe R. Doupnik, 
 *  Utah State University, jrd@cc.usu.edu, jrd@usu.Bitnet.
 *
 * Last edit
 * 27 August 1992 v3.13
 */
#include "msntcp.h"
#include "msnlib.h"

/*
 * ICMP - RFC 792
 */

static byte *unreach[] = {
	"Network Unreachable",
	"Host Unreachable",
	"Protocol Unreachable",
	"Port Unreachable",
	"Fragmentation needed and DF set",
	"Source Route Failed" };

static byte *exceed[] = {
	"TTL exceeded in transit",
	"Frag ReAsm time exceeded" };

static byte *redirect[] = {
	"Redirect for Network",
	"Redirect for Host",
	"Redirect for TOS and Network",
	"Redirect for TOS and Host" };

/* constants */


typedef struct icmp_unused {
    byte 	type;
    byte	code;
    word	checksum;
    longword	unused;
    in_Header	ip;
    byte	spares[ 8 ];
};

typedef struct icmp_pointer {
    byte	type;
    byte	code;
    word	checksum;
    byte	pointer;
    byte	unused[ 3 ];
    in_Header	ip;
};
typedef struct icmp_ip {
    byte	type;
    byte	code;
    word	checksum;
    longword	ipaddr;
    in_Header	ip;
};
typedef struct icmp_echo {
    byte	type;
    byte	code;
    word	checksum;
    word	identifier;
    word	sequence;
};

typedef struct icmp_timestamp {
    byte	type;
    byte	code;
    word	checksum;
    word	identifier;
    word	sequence;
    longword	original;	/* original timestamp */
    longword	receive;	/* receive timestamp */
    longword	transmit;	/* transmit timestamp */
};

typedef struct icmp_info {
    byte	type;
    byte	code;
    word	checksum;
    word	identifier;
    word	sequence;
};

typedef union  {
	struct icmp_unused	unused;
	struct icmp_pointer	pointer;
	struct icmp_ip		ip;
	struct icmp_echo	echo;
	struct icmp_timestamp	timestamp;
	struct icmp_info	info;
} icmp_pkt;

typedef struct pkt {
	in_Header 	in;
	icmp_pkt 	icmp;
	in_Header	data;
};

static word icmp_id = 0;
static longword ping_hcache = 0L;	/* host */
static longword ping_tcache = 0L;	/* time */
static longword ping_number = 0L;
extern	word debug_on;
extern	word sourcequench;		/* non-zero if rcv'd ICMP S. Q. */
int	icmp_unreach = 0;		/* tell world about unreachables */
int	icmp_redirect = 0;

void
icmp_init()				/* reinit all local statics */
{
	ping_hcache = ping_tcache = ping_number = 0L;
	icmp_id = 0;
	return;
}

#ifndef KERMIT
longword 
chk_ping(longword host, longword *ptr)
{
	if ((ping_hcache == host) && (ptr != NULL))
		{
		ping_hcache = 0xffffffffL;
		*ptr = ping_number;
		return (ping_tcache);
		}
    return (0xffffffffL);
}
#endif /* KERMIT */

void
icmp_print(byte *msg)
{
	if (msg == NULL) return;
	outs("\n\r ICMP: ");
	outs(msg);
}

struct pkt *
icmp_Format(longword destip)
{
	eth_address dest;

	    /* we use arp rather than supplied hardware address */
	    /* after first ping this will still be in cache */

	if (arp_resolve(destip, dest) == 0)
		return (NULL);			/* unable to find address */
	return ((struct pkt*)eth_formatpacket(dest, TYPE_IP));
}
/*
 * icmp_Reply - format a reply packet
 *  	      - note that src and dest are NETWORK order not host!!!!
 */
int
icmp_Reply(struct pkt *p, longword src, longword dest, int icmp_length)
{

	if (p == NULL) return (0);		/* failure */

	/* finish the icmp checksum portion */
	p->icmp.unused.checksum = 0;
	p->icmp.unused.checksum = ~checksum(&p->icmp, icmp_length);

	/* encapsulate into a nice IP packet */
	p->in.hdrlen_ver = 0x45;
	p->in.length = htons(sizeof(in_Header) + icmp_length);
	p->in.tos = 0;
	p->in.identification = htons(icmp_id++);	/* not using IP id */
	p->in.frag = 0;
	p->in.ttl = 250;
	p->in.proto = ICMP_PROTO;
	p->in.checksum = 0;
	p->in.source = src;
	p->in.destination = dest;
	p->in.checksum = ~checksum(&p->in, sizeof(in_Header));

	return (eth_send(htons(p->in.length)));		/* send the reply */
}

int
do_ping(byte * hostid, longword hostip)
{
	register struct pkt *pkt;
	byte mybuf[17];

	if (hostip == 0L) 		/* if no host IP number yet */
		if ((hostip = resolve(hostid)) == 0L)
		return (-1);		/* failed to resolve host */

	pkt = icmp_Format(hostip);
	pkt->icmp.echo.type = 8;			/* Echo Request */
	pkt->icmp.echo.code = 0;
	pkt->icmp.echo.identifier = (word)(htonl(my_ip_addr) & 0xffff);
	pkt->icmp.echo.sequence = (word)((ping_number++) & 0xffff);
	pkt->icmp.timestamp.original = htonl(0x12345678);
	icmp_Reply(pkt, htonl(my_ip_addr), htonl(hostip),
					4 + sizeof(struct icmp_echo));
	outs("\r\n Sending Ping to ");
	ntoa(mybuf, hostip); outs(mybuf);
	return (0);
}

int
icmp_handler(in_Header *ip)
{
	register icmp_pkt *icmp, *newicmp;
	struct pkt *pkt;
	int len, code;
	in_Header *ret;
	byte nametemp[17];
	extern void do_redirect(long, void *);

	len = in_GetHdrlenBytes(ip);
	icmp = (icmp_pkt *)((byte *)ip + len);
	len = ntohs(ip->length) - len;
	if (checksum(icmp, len) != 0xffff)
		return (0);				/* 0 = failure */

	code = icmp->unused.code;
	switch (icmp->unused.type)
	{
	case 0: 			/* icmp echo reply received */
	/*	 icmp_print("received icmp echo receipt");  */

		/* check if we were waiting for it */
		if (icmp->echo.identifier == (word)(ntohl(my_ip_addr)&0xffff))
			{			/* answer to our request */
			outs("\r\n host is alive\r\n");
			break;
			}
#ifndef KERMIT
		ping_hcache = ntohl(ip->source);
		ping_tcache = set_timeout(1) -
			*(longword *)(&icmp->echo.identifier);
		if (ping_tcache > 0xffffffffL)
			ping_tcache += 0x1800b0L;

		ping_number =
			*(longword*)(((byte*)(&icmp->echo.identifier)) + 4);
						/* do more */
#endif /* Kermit */
		break;

	case 3 : /* destination or port unreachable message */
		if (code < 6)
			{
			icmp_print(unreach[code]);	/* display msg */
			icmp_unreach = 1 + code; /* say unreachable condx */
						/* handle udp or tcp socket */
			ret = &icmp->ip.ip;	/* ret'd IP header + 8 bytes*/
			if (ret->proto == TCP_PROTO)
				tcp_cancel(ret);
			if (ret->proto == UDP_PROTO)
				udp_cancel(ret);
			}
		break;

	case 4:				/* source quench */
/*		icmp_print("Source Quench"); */
		sourcequench = 1;	/* set flag, used in TCP code */
		break;

	case 5:				 /* redirect */
		if (code < 4)
			{
					/* new gateway IP address to use */ 
			arp_register(ntohl(icmp->ip.ipaddr), 
				ntohl(icmp->ip.ip.destination));
					/* for this host IP address */
					/* and add to list of gateways */
			ntoa(nametemp, ntohl(icmp->ip.ipaddr));
			icmp_print(redirect[code]);
			outs(" to gateway "); outs(nametemp);
			do_redirect(ntohl(icmp->ip.ipaddr), &icmp->ip.ip);
				/* new gateway		returned IP header */
			}
		break;

	case 8: /* icmp echo request */
/*		 icmp_print("PING requested of us"); */

	        /* format the packet with the request's hardware address */
		pkt = (struct pkt*)(eth_formatpacket(
				(eth_address *)eth_hardware(ip), TYPE_IP));
		newicmp = &pkt->icmp;
		bcopy(icmp, newicmp, len);
		newicmp->echo.type = 0;
		newicmp->echo.code = (byte)(code & 0xff);

		/* use supplied ip values in case we ever multi-home */
		/* note that ip values are still in network order */

		icmp_Reply(pkt, ip->destination, ip->source, len);
/* 		icmp_print("PING reply sent"); */
		break;

	case 11: 			/* time exceeded message */
		if (code < 2)
			icmp_print(exceed[code]);
		break;

	case 12: 			/* parameter problem message */
		icmp_print("IP Parameter problem");
		break;

	case 13: 			/* timestamp message */
/*		icmp_print("Timestamp message"); */
		/* send reply */
		break;

	case 14:			 /* timestamp reply */
/*		icmp_print("Timestamp reply"); */
		/* should store */
		break;

	case 15: 			/* info request */
/*		icmp_print("Info requested"); */
		/* send reply */
		break;

	case 16: 			/* info reply */
/*		icmp_print("Info reply"); */
		break;
	}				/* end of switch */
	return (1);			/* status of success */
}

/* Return ICMP Destination and Port Unreachable message */
void
icmp_noport(in_Header *ip)
{
	register icmp_pkt *icmp, *newicmp;
	struct pkt *pkt;
	int len;

	len = in_GetHdrlenBytes(ip);
	icmp = (icmp_pkt *)((byte *)ip + len);
	pkt = (struct pkt*)(eth_formatpacket(
			(eth_address *)eth_hardware(ip), TYPE_IP));
	newicmp = &pkt->icmp;
	newicmp->unused.type = 3;	/* destination unreachable */
	newicmp->unused.code = 3;	/* port unreachable */
	newicmp->unused.unused = 0L;	/* must be zero for unreachables */
		/* send back Internet header + first 64 bits of datagram */
	bcopy(ip, &newicmp->unused.ip, 8 + sizeof(in_Header));
	icmp_Reply(pkt, ip->destination, ip->source, 
		sizeof(struct icmp_unused));	/* compose and send reply */
}
