/*
 *	Copyright (c) 1995, 1996, Sun Microsystems, Inc.
 *
 * This software is provided by Sun ``AS IS'' and any express or implied
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose are disclaimed.
 * In no event shall Sun Microsystems be liable for any direct, indirect,
 * incidental, special, exemplary, or consequential damages.
 *
 * This software is not a product, and is provided for evaluation purposes
 * only.
 *
 * This software may not be resold without the express permission of
 * Sun Microsystems.
 *
 * Feedback on this tool may be sent to packet-shell-owner@sunroof.eng.sun.com.
 *
 * Packet Shell
 */

#pragma ident	"@(#)icmp.c	1.16	00/01/14 SMI"

#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netinet/ip_icmp.h>

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "name2int.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

static char *proto_name;

#define	NICMP_TYPES 13
static struct nameint typetbl[NICMP_TYPES] = {
	{ "unreachable", ICMP_UNREACH },
	{ "time exceeded", ICMP_TIMXCEED },
	{ "parameter problem", ICMP_PARAMPROB },
	{ "echo", ICMP_ECHO },
	{ "echo reply", ICMP_ECHOREPLY },
	{ "source quench", ICMP_SOURCEQUENCH },
	{ "redirect", ICMP_REDIRECT },
	{ "tstamp", ICMP_TSTAMP },
	{ "tstamp-reply", ICMP_TSTAMPREPLY },
	{ "ireq", ICMP_IREQ },
	{ "ireq-reply", ICMP_IREQREPLY },
	{ "maskreq", ICMP_MASKREQ },
	{ "maskreq-reply", ICMP_MASKREPLY },
};

#define	NICMP_UNREACH_CODES	6
static struct nameint unreachtbl[NICMP_UNREACH_CODES] = {
	{ "unreachable net", ICMP_UNREACH_NET },
	{ "unreachable host", ICMP_UNREACH_HOST },
	{ "unreachable protocol", ICMP_UNREACH_PROTOCOL },
	{ "unreachable port", ICMP_UNREACH_PORT },
	{ "unreachable needfrag", ICMP_UNREACH_NEEDFRAG },
	{ "unreachable srcfail", ICMP_UNREACH_SRCFAIL },
};

#define	NICMP_TIMXCEED_CODES	2
static struct nameint timetbl[NICMP_TIMXCEED_CODES] = {
	{ "in transit", ICMP_TIMXCEED_INTRANS },
	{ "in reassembly", ICMP_TIMXCEED_REASS }
};

/* no codes for echo and group ICMPs */

static char *
icmp_type_name(int type)
{
	char *p;

	p = int2name(type, typetbl, NICMP_TYPES);
	if (p == NULL) {
		p = "<invalid-ICMP>";
	}
	return (p);
}

static char *
icmp_code_name(int type, int code)
{
	switch (type) {
	case ICMP_UNREACH:
		return (int2name(code, unreachtbl, NICMP_UNREACH_CODES));
	case ICMP_TIMXCEED:
		return (int2name(code, timetbl, NICMP_TIMXCEED_CODES));
	case ICMP_ECHO:
	case ICMP_ECHOREPLY:
	case ICMP_SOURCEQUENCH:
	case ICMP_REDIRECT:
	case ICMP_PARAMPROB:
	case ICMP_TSTAMP:
	case ICMP_TSTAMPREPLY:
	case ICMP_IREQ:
	case ICMP_IREQREPLY:
	case ICMP_MASKREQ:
	case ICMP_MASKREPLY:
		if (code != 0) {
			return ("<broken ICMP packet>");
		} else {
			return ("none");
		}
	default:
		return ("<invalid-ICMP>");
	}
}

/*
 * Initialization
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
		    NULL);
		return (0);
	}
	return (1);
}

/*
 * Create a "default" packet.
 */
int
pinit(ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	if (pckt->p_len == 0)
		pckt->p_len = sizeof (struct icmp);
	if (ptbl_update(pckt, 1, NULL, pckt->p_len) == 0) {
		return (TCL_ERROR);
	}
	return (TCL_OK);
}


int
decode(struct packet *pkt, int element, char **name, int *next_offset)
{
	*name = "decoded protocol name goes here";
	*next_offset = pkt->p_tbl[element].pt_offset
	/* + sizeof (this protocol header) XXX */;
	return (1);
}

/*
 * Get protocol fields.
 */
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct ether_addr	eaddr;
	struct icmp		icpkt;
	char			tmp[128];

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget ", proto_name,
		    " <pckt> <item>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&icpkt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (icpkt));
	if (strcmp(argv[0], "type") == 0) {
		(void) sprintf(tmp, "0x%x", icpkt.icmp_type);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "code") == 0) {
		(void) sprintf(tmp, "0x%x", icpkt.icmp_code);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "cksum") == 0) {
		(void) sprintf(tmp, "0x%x", icpkt.icmp_cksum);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "word1") == 0) {
		(void) sprintf(tmp, "0x%x", icpkt.icmp_void);
		Tcl_AppendResult(interp, tmp, NULL);
#if 0
	} else if (strcmp(argv[0], "target") == 0) {
		psh_addr2ascii(AF_INET, &icpkt.icmp_targ,
		    sizeof (struct in_addr), tmp);
		Tcl_AppendResult(interp, tmp, NULL);
#endif
	} else if (strcmp(argv[0], "target-ether") == 0) {
		(void) memcpy(&eaddr,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp) + 2, sizeof (eaddr));
		/* XXX first byte should be 2 */
		(void) sprintf(tmp, "%x:%x %s",
		    pckt->p_buf[pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp)],
		    pckt->p_buf[pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp) + 1],
		    ether_ntoa(&eaddr));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "target-ether") == 0) {
		(void) memcpy(&eaddr,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp) + 2, sizeof (eaddr));
		/* XXX first byte should be 1 */
		(void) sprintf(tmp, "%x:%x %s",
		    pckt->p_buf[pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp)],
		    pckt->p_buf[pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp) + 1],
		    ether_ntoa(&eaddr));
		Tcl_AppendResult(interp, tmp, NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

/*
 * Set a protocol element
 */
int
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct icmp	icpkt;
	struct ether_addr *eap, eaddr;
	int		val;
	char		*p;

	if (argc == 1 && strcmp(argv[0], "cksum") == 0) {
		/*
		 * Special case must come before general usage check.
		 * If no value is given, set cksum to the correct value.
		 * First, zero out the checksum field.
		 */
		int len;
		unsigned short sum;

		(void) memcpy(&icpkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (icpkt));
		icpkt.icmp_cksum = 0;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		len = pckt->p_tbl[element + 1].pt_offset -
		    pckt->p_tbl[element].pt_offset;
		sum = 0;
		ip_cksum(&sum,
		    (uchar_t *)pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    len);
		icpkt.icmp_cksum = htons(sum);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	}
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: pset ", proto_name,
		    " <pckt> <item> <value>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&icpkt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (icpkt));
	if (strcmp(argv[0], "type") == 0) {
		if (isdigit(*argv[1])) {
			if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
				goto type_error;
			}
			if (val > 255 || val < 0) {
				goto type_error;
			}
		} else if (!name2int(argv[1], &val, typetbl, NICMP_TYPES)) {
type_error:
			Tcl_AppendResult(interp, "Unrecognized ", proto_name,
			    " type value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp_type = (uchar_t)val;
	} else if (strcmp(argv[0], "code") == 0) {
		if (isdigit(*argv[1])) {
			if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
				goto code_error;
			}
			if (val > 255 || val < 0) {
				goto code_error;
			}
/* XXX should allow symbollic code representation but too hard for now */
/*		} else if (!name2int(argv[1], &val, codetbl, NICMP_TYPES)) { */
		} else {
code_error:
			Tcl_AppendResult(interp, "Unrecognized ", proto_name,
			    " type value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp_code = (uchar_t)val;
	} else if (strcmp(argv[0], "cksum") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK ||
		    val > 65535 || val < 0) {
			Tcl_AppendResult(interp, "Bad checksum value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp_cksum = htons(val);
	} else if (strcmp(argv[0], "word1") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp_void = val;
	} else if (strcmp(argv[0], "target-ether") == 0) {
		/* XXX move other stuff up, if needed */
		if ((eap = ether_aton(argv[1])) == NULL) {
			eap = &eaddr;
			if (ether_hostton(argv[1], &eaddr) != 0) {
				Tcl_AppendResult(interp,
				    "Badly formed ether addr: ", argv[1],
				    NULL);
				return (TCL_ERROR);
			}
		}
		p = pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp);
		*p++ = 2;
		*p++ = 1;
		(void) memcpy(p, eap, sizeof (struct ether_addr));
	} else if (strcmp(argv[0], "source-ether") == 0) {
		/* XXX move other stuff up, if needed */
		if ((eap = ether_aton(argv[1])) == NULL) {
			eap = &eaddr;
			if (ether_hostton(argv[1], &eaddr) != 0) {
				Tcl_AppendResult(interp,
				    "Badly formed ether addr: ", argv[1],
				    NULL);
				return (TCL_ERROR);
			}
		}
		p = pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp);
		*p++ = 1;
		*p++ = 1;
		(void) memcpy(p, eap, sizeof (struct ether_addr));
	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset, &icpkt,
	    sizeof (icpkt));
	return (TCL_OK);
}

static bool_t
verify_cksum(char *icpkt, int len)
{
	unsigned short sum = 0;

	ip_cksum(&sum, (uchar_t *)icpkt, len);
	return (sum == 0);
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct icmp	icpkt;
	char		tmp[128];
	unsigned char	*p;

	(void) memcpy(&icpkt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (icpkt));
	Tcl_AppendResult(interp, "type ", icmp_type_name(icpkt.icmp_type),
	    " code ", icmp_code_name(icpkt.icmp_type, icpkt.icmp_code), NULL);
	(void) sprintf(tmp, "0x%x (%s)", icpkt.icmp_cksum,
	    verify_cksum(pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    pckt->p_tbl[element + 1].pt_offset -
	    pckt->p_tbl[element].pt_offset) ?  "correct" : "invalid");
	Tcl_AppendResult(interp, " cksum ", tmp, NULL);
	if (pckt->p_tbl[element + 1].pt_offset -
	    pckt->p_tbl[element].pt_offset > sizeof (struct icmp)) {
		/*
		 * Some options must be present
		 */
		Tcl_AppendResult(interp, " options ", NULL);
		p = (unsigned char *)pckt->p_buf +
		    pckt->p_tbl[element].pt_offset + sizeof (struct icmp);
		while ((char *)p - pckt->p_buf <
		    pckt->p_tbl[element + 1].pt_offset) {
			switch (*p) {
			case 0:	/* XXX */
				Tcl_AppendResult(interp, " no-op ", NULL);
				p++;
				break;
			case 1:	/* source link address */
				/* XXX generalize--allowed to be any length */
				(void) sprintf(tmp, " source-ether "
				    "%x:%x:%x:%x:%x:%x ",
				    p[2], p[3], p[4], p[5], p[6], p[7]);
				Tcl_AppendResult(interp, tmp, NULL);
				p += p[1] * 8;
				break;
			case 2: /* target link address */
				/* XXX generalize--allowed to be any length */
				(void) sprintf(tmp, " target-ether "
				    "%x:%x:%x:%x:%x:%x ",
				    p[2], p[3], p[4], p[5], p[6], p[7]);
				Tcl_AppendResult(interp, tmp, NULL);
				p += p[1] * 8;
				break;
			default:
				(void) sprintf(tmp, " unknown %d\n ", *p);
				Tcl_AppendResult(interp, tmp, NULL);
				p += p[1] * 8;
			}
		}
	}
	/*
	 * XXX lots more to decode, all pretty special case
	 */
	return (TCL_OK);
}
