/* IP-related user commands
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by PA0GRI */
#include "global.h"
#include "commands.h"
#include "mbuf.h"
#include "internet.h"
#include "timer.h"
#include "netuser.h"
#include "iface.h"
#include "session.h"
#include "rspf.h"
#include "domain.h"
#include "pool.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: ipcmd.c,v 1.22 2001/05/06 16:32:56 brian Exp $";
#endif

uint32 Ip_addr;
static int Route_Sort = 1;
int Route_Same = 0;


static int doadd (int argc, char *argv[], void *p);

#ifdef  IPACCESS
static int doaccess (int argc, char *argv[], void *p);
#endif

static int dodrop (int argc, char *argv[], void *p);
static int doflush (int argc, char *argv[], void *p);
static int doipaddr (int argc, char *argv[], void *p);
static int doipstat (int argc, char *argv[], void *p);
static int doiplook (int argc, char *argv[], void *p);
static int dortimer (int argc, char *argv[], void *p);
static int dottl (int argc, char *argv[], void *p);
static int doiphport (int argc, char *argv[], void *p);
static int doiphsize (int argc, char *argv[], void *p);
void dumproute (struct route * rp, char *p);
static int doroutesort (int argc, char *argv[], void *p);
static int doroutesame (int argc, char *argv[], void *p);

/* IP heard logging - WG7J */
static struct iph *iph_create (uint32 addr);
static struct iph *iph_lookup (uint32 addr);
static struct iph *Iph = NULLIPH;

static int Maxipheard = MAXIPHEARD;


#ifdef CATALOG
#include "catalog.h"

#define CAT ipcmd_catalog

#define accesshdr	__STR(0)
#define accesssyntax	__STR(1)
#define rtimerstr	__STR(2)
#define ttlstr		__STR(3)
#define iplookstr	__STR(4)
#define reassembly	__STR(5)
#define notfound	__STR(6)
#define noaddroute	__STR(7)
#define heardhdr	__STR(8)
#define sortstr		__STR(9)
#define iphsizestr	__STR(10)
#define samestr		__STR(11)

#else /* CATALOG */
static const char accesshdr[] = "Source Address  Len Dest Address    Len Interface    Proto   Low  High  State\n";
static const char accesssyntax[] = " Format: ip access <permit|deny|delete> <proto> <src addr>[/<bits>] <dest addr>[/<bits>] <if name> [low [high]]\n";
static const char rtimerstr[] = "IP reasm timeout (sec)";
static const char ttlstr[] = "IP Time-to-live";
static const char iplookstr[] = "Host %s (%s) unreachable\n";
static const char reassembly[] = "Reassembly fragments:\n";
static const char notfound[] = "Not found.\n";
static const char noaddroute[] = "Can't add route\n";
static const char heardhdr[] = "Tcp/Ip systems heard:\nAddress                Port       Since       Pkts\n";
static const char sortstr[] = "Route Sort flag";
static const char iphsizestr[] = "Max ip-heard";
static const char samestr[] = "Permit IP routing out same interface it was received";
#endif /* CATALOG */


static const char allstr[] = "all";
static const char anystr[] = "any";
static const char icmpstr[] = "icmp";
static const char tcpstr[] = "tcp";
static const char udpstr[] = "udp";
static const char denystr[] = "deny";
static const char permitstr[] = "permit";
static const char deletestr[] = "delete";
static const char strCR[] = "%s\n";
static const char defaultstr[] = "default";
static const char rspfstr[] = "rspf   ";
static const char manstr[] = "man    ";
static const char directstr[] = "direct";
static const char statdata[] = "(%2u)%-20s%10lu";
static const char re_src[] = "src %s";
static const char re_dest[] = " dest %s";
static const char re_id[] = " id %u pctl %u time %lu len %u\n";
static const char re_off[] = " offset %u last %u\n";
static const char hearddata[] = "%-22s %-8s %12s %5ld\n";
const char RouteHeader[] = "Destination      Len Interface Gateway          Metric P Timer  Use\n";


#ifdef POOLED
static struct mempool iph_pool = {NULLPOOLBLK, NULLPOOLBLK, 0, sizeof (struct iph), 50};
#endif




static struct cmds Ipcmds[] =
{
#ifdef  IPACCESS
	{ "access",	doaccess,	0, 0, NULLCHAR },
#endif
	{ "address",	doipaddr,	0, 0, NULLCHAR },
	{ "heard",	doipheard,	0, 0, NULLCHAR },
	{ "hport",	doiphport,	0, 0, NULLCHAR },
	{ "hsize",	doiphsize,	0, 0, NULLCHAR },
	{ "routesame",	doroutesame,	0, 0, NULLCHAR },
	{ "rtimer",	dortimer,	0, 0, NULLCHAR },
	{ "status",	doipstat,	0, 0, NULLCHAR },
	{ "ttl",	dottl,		0, 0, NULLCHAR },
	{ NULLCHAR,	NULL,		0, 0, NULLCHAR }
};



/* "route" subcommands */
static struct cmds Rtcmds[] =
{
	{ "add",	doadd,		0, 3, "route add <dest addr>[/<bits>] <if name> [<gateway> | direct [metric]]" },
	{ "addprivate",	doadd,		0, 3, "route addprivate <dest addr>[/<bits>] <if name> [<gateway> | direct [metric]]"},
	{ "drop",	dodrop,		0, 2, "route drop <dest addr>[/<bits>]" },
	{ "flush",	doflush,	0, 0, NULLCHAR },
	{ "lookup",	doiplook,	0, 2, "route lookup <dest addr>" },
	{ "sort",	doroutesort,	0, 0, NULLCHAR },
	{ NULLCHAR,	NULL,		0, 0, NULLCHAR }
};



int
doip (int argc, char *argv[], void *p)
{
	return subcmd (Ipcmds, argc, argv, p);
}



#ifdef  IPACCESS
static int
doaccess (int argc, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;
uint32 source, target;
unsigned sbits, tbits;
char *bitp;
int16 lport, hport, protocol, state;
char const *cp;		/* for printing the table */
struct rtaccess *tpacc;
struct rtaccess *btacc;
struct rtaccess *bfacc;
struct rtaccess *head;
char tmpbuf[15];

	if (argc == 1) {	/* print out the table */
		tputs (accesshdr);
		for (tpacc = IPaccess; tpacc != NULLACCESS; tpacc = tpacc->nxtiface) {
			for (btacc = tpacc; btacc != NULLACCESS; btacc = btacc->nxtbits) {
				if (btacc->source != 0)
					cp = inet_ntoa (btacc->source);
				else
					cp = allstr;
				tprintf ("%-16s%2u  ", cp, btacc->sbits);
				if (btacc->target != 0)
					cp = inet_ntoa (btacc->target);
				else
					cp = allstr;
				tprintf ("%-16s%2u  %-13s", cp, btacc->bits, btacc->iface->name);
				switch (btacc->protocol) {
					case 0:		cp = anystr;
							break;
					case ICMP_PTCL:	cp = icmpstr;
							break;
					case TCP_PTCL:	cp = tcpstr;
							break;
					case UDP_PTCL:	cp = udpstr;
							break;
					default:	cp = itoa (btacc->protocol, tmpbuf, 10);
				}
				tprintf ("%-5s %5u %5u ", cp, btacc->lowport, btacc->highport);
				if (btacc->status)
					cp = denystr;
				else
					cp = permitstr;
				tprintf ("%-6s\n", cp);
			}
		}
		return 0;
	}
	if (strcmp (argv[1], permitstr) == 0)
		state = 0;
	else {
		if ((strcmp (argv[1], denystr) == 0) || (strcmp (argv[1], deletestr) == 0))
			state = (int16) - 1;
		else {
			tputs (accesssyntax);
			return 1;
		}
	}

	if (argc < 6) {
		tputs (accesssyntax);
		return 1;
	}
	switch (*argv[2]) {
		case 'a':	/* ANY */
			protocol = 0;
			break;
		case 'i':	/* ICMP */
			protocol = ICMP_PTCL;
			break;
		case 't':	/* TCP */
			protocol = TCP_PTCL;
			break;
		case 'u':	/* UDP */
			protocol = UDP_PTCL;
			break;
		default:
			protocol = (int16) atoi (argv[2]);
	}

	if (strcmp (argv[3], allstr) == 0) {
		source = 0;
		sbits = 0;
	} else {
		/* If IP address is followed by an optional slash and
		 * a length field, (e.g., 128.96/16) get it;
		 * otherwise assume a full 32-bit address
		 */
		if ((bitp = strchr (argv[3], '/')) != NULLCHAR) {
			/* Terminate address token for resolve() call */
			*bitp++ = '\0';
			sbits = (unsigned int) atoi (bitp);
		} else
			sbits = 32;

		if ((source = resolve (argv[3])) == 0) {
			tprintf (Badhost, argv[3]);
			return 1;
		}
	}
	if (strcmp (argv[4], allstr) == 0) {
		target = 0;
		tbits = 0;
	} else {
		if ((bitp = strchr (argv[4], '/')) != NULLCHAR) {
			*bitp++ = '\0';
			tbits = (unsigned int) atoi (bitp);
		} else
			tbits = 32;

		if ((target = resolve (argv[4])) == 0) {
			tprintf (Badhost, argv[4]);
			return 1;
		}
	}

	if ((ifp = if_lookup (argv[5])) == NULLIF) {
		tprintf (Badinterface, argv[5]);
		return 1;
	}
	if (((protocol != TCP_PTCL) && (protocol != UDP_PTCL)) || (argc < 7)) {
		lport = 0;
		hport = 0;
	} else {
		if (strcmp (argv[6], allstr) == 0)
			lport = 0;
		else
			lport = (int16) atoi (argv[6]);

		if ((argc < 8) || (lport == 0))
			hport = lport;
		else
			hport = (int16) atoi (argv[7]);
	}

	if (strcmp (argv[1], deletestr) == 0) {
		head = IPaccess;
		for (tpacc = IPaccess; tpacc != NULLACCESS; head = tpacc, tpacc = tpacc->nxtiface) {
			if (tpacc->iface == ifp) {
				for (btacc = tpacc; btacc != NULLACCESS; head = btacc, btacc = btacc->nxtbits) {
					if ((btacc->protocol == protocol) &&
					    (btacc->source == source) &&
					    (btacc->sbits == sbits) &&
					    (btacc->target == target) &&
					    (btacc->bits == tbits) &&
					    (btacc->lowport == lport) &&
					    (btacc->highport == hport)) {	/*match*/
						bfacc = btacc;	/* save to unalloc */
						/*now delete. watch for special cases*/
						if (btacc != tpacc) {	/* not at head of list */
							head->nxtbits = btacc->nxtbits;
							free (bfacc);
							return 0;
						}
						if (btacc == IPaccess) {	/* real special case */
							if (IPaccess->nxtbits == NULLACCESS)
								IPaccess = btacc->nxtiface;
							else {
								IPaccess = btacc->nxtbits;
								(btacc->nxtbits)->nxtiface = btacc->nxtiface;
							}
						} else {	/* we know tpacc=btacc <> IPaccess */
							if (btacc->nxtbits == NULLACCESS)
								head->nxtiface = btacc->nxtiface;
							else {
								head->nxtiface = btacc->nxtbits;
								(btacc->nxtbits)->nxtiface = btacc->nxtiface;
							}
						}
						free (bfacc);
						return 0;
					}
				}
			}
		}
		tputs (notfound);
		return 1;
	}
	/* add the access */
	addaccess (protocol, source, sbits, target, tbits, ifp, lport, hport, state);
	return 0;
}
#endif



static int
doipaddr (int argc, char *argv[], void *p OPTIONAL)
{
uint32 n;

	if (argc < 2)
		tprintf (strCR, inet_ntoa (Ip_addr));
	else if ((n = resolve (argv[1])) == 0) {
		tprintf (Badhost, argv[1]);
		return 1;
	} else
		Ip_addr = n;
	return 0;
}



static int
dortimer (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong ((long *)&ipReasmTimeout, rtimerstr, argc, argv);
}



static int
dottl (int argc, char *argv[], void *p OPTIONAL)
{
	return setlong ((long *)&ipDefaultTTL, ttlstr, argc, argv);
}


/* Display and/or manipulate routing table */
int
doroute (int argc, char *argv[], void *p)
{
register int i, j, k, bits, flow_tmp;
register struct route *rp;
char *temp, temp2[80];

	if (argc >= 2)
		return subcmd (Rtcmds, argc, argv, p);

	/* Dump IP routing table
	 * Dest            Len Interface    Gateway          Use
	 * 192.001.002.003 32  sl0          192.002.003.004  0
	 * modified for Sorted output - D. Crompton 2.92
	 */

	flow_tmp = Current->flowmode;
	Current->flowmode = 1;

	tputs (RouteHeader);

	for (j = 0, bits = 31; bits >= 0; bits--)
		for (i = 0; i < HASHMOD; i++)
			for (rp = Routes[bits][i]; rp != NULLROUTE; j++, rp = rp->next)
				;

	if (j) {

		temp = mallocw ((unsigned) j * 80);

		for (bits = 31, k = 0; bits >= 0; bits--)
			for (i = 0; i < HASHMOD; i++)
				for (rp = Routes[bits][i]; rp != NULLROUTE; rp = rp->next, k += 80)
					dumproute (rp, &temp[k]);

		if (Route_Sort)
			qsort (temp, (size_t) j, 80, (int (*)(const void *, const void *)) strcmp);

		for (i = 0, k = 4; i < j; i++, k += 80) {
			tputs (&temp[k]);
			if (tputc ('\n') == EOF) {
				Current->flowmode = flow_tmp;
				free (temp);
				return 0;
			}
		}
		free (temp);
	}
	if (R_default.iface != NULLIF) {
		dumproute (&R_default, temp2);
		tprintf (strCR, &temp2[4]);
	}
	Current->flowmode = flow_tmp;
	return 0;
}



/* Dump a routing table entry */
void
dumproute (register struct route *rp, char *temp)
{
char const *cp;
unsigned int a = 4;
char *name;

	if (rp->target != 0) {
		if (DTranslate && (name = resolve_a (rp->target, !DVerbose)) != NULLCHAR) {
			strcpy (temp, name);
			free (name);
		} else {
			cp = inet_ntobos (rp->target);
			a = (unsigned int) SPRINTF ((temp, "%4s", cp));
		}
		cp = inet_ntoa (rp->target);
	} else {
		strcpy (temp, defaultstr);	/* Don't really matter, but avoid unknown value */
		cp = "default";
	}
	a += (unsigned int) SPRINTF ((&temp[a], "%-16.16s ", cp));
	a += (unsigned int) SPRINTF ((&temp[a], "%-4u", rp->bits));
	a += (unsigned int) SPRINTF ((&temp[a], "%-9.9s ", rp->iface->name));
	if (rp->gateway != 0)
		cp = inet_ntoa (rp->gateway);
	else
		cp = "";
	a += (unsigned int) SPRINTF ((&temp[a], "%-16.16s ", cp));
	a += (unsigned int) SPRINTF ((&temp[a], "%-6lu ", rp->metric));
	a += (unsigned int) SPRINTF ((&temp[a], "%c ", (rp->flags & RTPRIVATE) ? 'P' : ' '));
	if (rp->timer.state == TIMER_STOP) {
		if (rp->timer.duration == 1)
			a += (unsigned int) SPRINTF ((&temp[a], rspfstr));
		else
			a += (unsigned int) SPRINTF ((&temp[a], manstr));
	} else
		a += (unsigned int) SPRINTF ((&temp[a], "%-7lu", read_timer (&rp->timer) / 1000L));
	sprintf (&temp[a], "%lu", rp->uses);
}



/* Sort Route dump */
static int
doroutesort (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Route_Sort, sortstr, argc, argv);
}


static int
doroutesame (int argc, char *argv[], void *p OPTIONAL)
{
	return setbool (&Route_Same, samestr, argc, argv);
}


/* Add an entry to the routing table
 * E.g., "add 1.2.3.4 ax0 5.6.7.8 3"
 */
static int
doadd (int argc, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;
uint32 dest, gateway;
unsigned bits;
char *bitp;
int32 metric;
char private;

	if (strncmp (argv[0], "addp", 4) == 0)
		private = 1;
	else
		private = 0;
	if (strcmp (argv[1], defaultstr) == 0) {
		dest = 0L;
		bits = 0;
	} else {
		/* If IP address is followed by an optional slash and
		 * a length field, (e.g., 128.96/16) get it;
		 * otherwise assume a full 32-bit address
		 */
		if ((bitp = strchr (argv[1], '/')) != NULLCHAR) {
			/* Terminate address token for resolve() call */
			*bitp++ = '\0';
			bits = (unsigned int) atoi (bitp);
		} else
			bits = 32;

		if ((dest = resolve (argv[1])) == 0) {
			tprintf (Badhost, argv[1]);
			return 1;
		}
	}
	if ((ifp = if_lookup (argv[2])) == NULLIF) {
		tprintf (Badinterface, argv[2]);
		return 1;
	}
	metric = 1;

	if (argc > 3) {
		/* Next "trick is needed to set the metric on subnets
		 * higher as the default 1 for rspf.
		 * route add subnet/bits iface default 10
		 */
		if (strcmp (argv[3], directstr) == 0) {	/* N1BEE */
			gateway = 0;
			/* calculate a nice metric based on subnet mask size */
			if (bits != 0 && bits < 32)
				metric = (long) ((39 - bits) * 5 / 17);
		} else {
			if ((gateway = resolve (argv[3])) == 0) {
				tprintf (Badhost, argv[3]);
				return 1;
			}
		}
	} else
		gateway = 0;

	if (argc > 4)
		metric = atol (argv[4]);

	if (rt_add (dest, bits, gateway, ifp, metric, 0, private) == NULLROUTE)
		tputs (noaddroute);
#ifdef  RSPF
	if (!private)
		rspfrouteupcall (dest, bits, gateway);	/* Do an RSPF upcall */
#endif /* RSPF */
	return 0;
}



/* Drop an entry from the routing table
 * E.g., "drop 128.96/16
 */
static int
dodrop (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
char *bitp;
unsigned bits;
uint32 n;

	if (strcmp (argv[1], defaultstr) == 0) {
		n = 0L;
		bits = 0;
	} else {
		/* If IP address is followed by an optional slash and length field,
		 * (e.g., 128.96/16) get it; otherwise assume a full 32-bit address
		 */
		if ((bitp = strchr (argv[1], '/')) != NULLCHAR) {
			/* Terminate address token for resolve() call */
			*bitp++ = '\0';
			bits = (unsigned int) atoi (bitp);
		} else
			bits = 32;

		if ((n = resolve (argv[1])) == 0) {
			tprintf (Badhost, argv[1]);
			return 1;
		}
	}
	return rt_drop (n, bits);
}



/* Force a timeout on all temporary routes */
static int
doflush (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
register struct route *rp;
struct route *rptmp;
int i, j;

	if (R_default.timer.state == TIMER_RUN)
		(void) rt_drop (0, 0);			/* Drop default route */

	for (i = 0; i < HASHMOD; i++) {
		for (j = 0; j < 32; j++) {
			for (rp = Routes[j][i]; rp != NULLROUTE; rp = rptmp) {
				rptmp = rp->next;
				if (rp->timer.state == TIMER_RUN)
					(void) rt_drop (rp->target, rp->bits);
			}
		}
	}
	return 0;
}



static int
doiplook (int argc OPTIONAL, char *argv[], void *p OPTIONAL)
{
struct route *rp;
uint32 addr;
char temp[80];

	addr = resolve (argv[1]);
	if (addr == 0) {
		tprintf (Badhost, argv[1]);
		return 1;
	}
	if ((rp = rt_lookup (addr)) == NULLROUTE) {
		tprintf (iplookstr, argv[1], inet_ntoa (addr));
		return 1;
	}
	dumproute (rp, temp);
	tputs (RouteHeader);
	tprintf (strCR, &temp[4]);

	return 0;
}



static int
doipstat (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
register struct reasm *rp;
register struct frag *fp;
int i;

	for (i = 1; i <= NUMIPMIB; i++) {
		tprintf (statdata, i, Ip_mib[i].name, Ip_mib[i].value.integer);
		if (i % 2)
			tputs ("     ");
		else
			tputc ('\n');
	}
	if ((i % 2) == 0)
		tputc ('\n');

	if (Reasmq != NULLREASM)
		tputs (reassembly);
	for (rp = Reasmq; rp != NULLREASM; rp = rp->next) {
		tprintf (re_src, inet_ntoa (rp->source));
		tprintf (re_dest, inet_ntoa (rp->dest));
		if (tprintf (re_id, rp->id, uchar (rp->protocol), read_timer (&rp->timer), rp->length) == EOF)
			break;
		for (fp = rp->fraglist; fp != NULLFRAG; fp = fp->next) {
			if (tprintf (re_off, fp->offset, fp->last) == EOF)
				break;
		}
	}
	return 0;
}



static int 
doiphsize (int argc, char *argv[], void *p OPTIONAL)
{
	return setint (&Maxipheard, iphsizestr, argc, argv);
}



/* Configure a port to do ip-heard logging */
static int
doiphport (int argc, char *argv[], void *p)
{
	return (dosetflag (argc, argv, p, LOG_IPHEARD, 0));
}



int
doipheard (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
struct iph *iph;

	if (tputs (heardhdr) == EOF)
		return EOF;
	for (iph = Iph; iph; iph = iph->next) {
		if (tprintf (hearddata, inet_ntoa (iph->addr), iph->iface->name, tformat (secclock () - iph->time), iph->count) == EOF)
			return EOF;
	}
	return 0;
}



void
log_ipheard (uint32 addr, struct iface *ifp)
{
struct iph *niph;

	if ((niph = iph_lookup (addr)) == NULLIPH)
		if ((niph = iph_create (addr)) == NULLIPH)
			return;
	niph->iface = ifp;
	niph->count++;
	niph->time = secclock ();
}



/* Look up an entry in the ip data base */
static struct iph *
iph_lookup (uint32 addr)
{
register struct iph *ip;
struct iph *last = NULLIPH;

	for (ip = Iph; ip != NULLIPH; last = ip, ip = ip->next) {
		if (ip->addr == addr) {
			if (last != NULLIPH) {
				/* Move entry to top of list */
				last->next = ip->next;
				ip->next = Iph;
				Iph = ip;
			}
			return ip;
		}
	}
	return NULLIPH;
}



/* Create a new entry in the source database */
/* If there are too many entries, override the oldest one - WG7J */
static struct iph *
iph_create (uint32 addr)
{
static int numdb = 0;
register struct iph *iph;
struct iph *last = NULLIPH;

	if (Maxipheard && numdb == Maxipheard) {
		/* find and use last one in list */
		for (iph = Iph; iph->next != NULLIPH; last = iph, iph = iph->next)
			;
		/* delete entry from end */
		if (last != NULLIPH)
			last->next = NULLIPH;
	} else {		/* create a new entry */
		numdb++;
#ifdef POOLED
		iph = (struct iph *) pool_alloc (&iph_pool);
#else
		iph = (struct iph *) callocw (1, sizeof (struct iph));

#endif
	}
	iph->addr = addr;
	iph->next = Iph;
	Iph = iph;

	return iph;
}
