/* Packet tracing - top level and generic routines, including hex/ascii
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Mods by G1EMM
 *
 * Tracing to session taken from WNOS3, by Johan. K. Reinalda, WG7J
 */
#include "global.h"
#include "ctype.h"
#include "commands.h"
#ifndef MSDOS
#include <time.h>
#endif
#include <stdarg.h>
#include "mbuf.h"
#include "iface.h"
#include "pktdrvr.h"
#include "session.h"
#include "trace.h"
#include "usock.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: trace.c,v 1.24 2000/05/09 16:48:29 brian Exp $";
#endif

/* this variable and the patch to traceprintf assumes that no kwaits
   will be placed in any of the files used for tracing. */

int tracesock;


#ifdef TRACE
static void ascii_dump (FILE * fp, struct mbuf ** bpp);
static void ctohex (char *buf, int16 c);
static void fmtline (FILE * fp, int16 addr, char *buf, int16 len);
static void hex_dump (FILE * fp, struct mbuf ** bpp, int iftype);
static void showtrace (struct iface * ifp);
static void plain_dump (FILE * fp, register struct mbuf ** bpp);

extern int Tracesession;
extern struct session *Trace;

#ifdef CTRACE
extern int SYSfore;
extern int SYSback;
int Avar_color (int x);
int use_ctrace = 0;
/* ansi string stuff */
char Avar[10] = "\033[37m;"; /* dynamic */
char Aalert[] = "\033[31;1m";
char Abold[] = "\033[1m";
char Aboldoff[] = "\033[21m";
char Ablack[] = "\033[30m";
char Ared[] = "\033[31m";
char Agreen[] = "\033[32m";
char Ayellow[] = "\033[33m";
char Ablue[] = "\033[34m";
char Amag[] = "\033[35m";
char Acyan[] = "\033[36m";
char Awhite[] = "\033[37m";
#endif /* CTRACE */

#include "slip.h"

#ifdef MONITOR
static const char *kissname (struct iface * ifp, struct mbuf * bp, unsigned type);

#ifdef CTRACE
/* feed me ints 0-7: fore ground colors; black, red, green, yellow, blue, mag,
 * cyan, and white respectively, and I'll fill array Avar with the appropriate
 * ansi str.
 */
int Avar_color (int x)
{
int i;
char cindex[8] = "01234567";
	if (x < 0 || x > 7)
		return 1;
	for (i = 0; i < 10; i++)
		Avar[i] = (char) NULL;
	strncpy (Avar, "\033[3", 3);
	strncat (Avar, cindex + x, 1);
	strncat (Avar, "m", 1);
	return 0;
}
#endif /* CTRACE */

static const char *
kissname (struct iface *ifp, struct mbuf *bp, unsigned type)
{
int port;

	if (ifp->type != CL_AX25 || type != CL_KISS)
		return ifp->name;
	port = (bp->data[0] & 0xF0) >> 4;
	if (Slip[ifp->xdev].kiss[port] == NULLIF)
		return ifp->name;
	return Slip[ifp->xdev].kiss[port]->name;
}

#endif


int
dostrace (int argc, char *argv[], void *p OPTIONAL)
{
	if (Trace == NULLSESSION) {
		if (argc > 1)
			tputs ("Session tracing not available!\007\n");
		argc = 0;	/* No session setup, so don't allow turning it on ! */
	}
	return setbool (&Tracesession, "Trace to session", argc, argv);
}


/* Redefined here so that programs calling dump in the library won't pull
 * in the rest of the package
 */

static char nospace[] = "No space!!\n";

static struct tracecmd Tracecmd[] =
{
	{ "input",	IF_TRACE_IN,	IF_TRACE_IN },
	{ "-input",	0,		IF_TRACE_IN },
	{ "output",	IF_TRACE_OUT,	IF_TRACE_OUT },
	{ "-output",	0,		IF_TRACE_OUT },
	{ "broadcast",	0,		IF_TRACE_NOBC },
	{ "-broadcast",	IF_TRACE_NOBC,	IF_TRACE_NOBC },
	{ "raw",	IF_TRACE_RAW,	IF_TRACE_RAW },
	{ "-raw",	0,		IF_TRACE_RAW },
	{ "ascii",	IF_TRACE_ASCII,	IF_TRACE_ASCII | IF_TRACE_HEX },
	{ "-ascii",	0,		IF_TRACE_ASCII | IF_TRACE_HEX },
	{ "hex",	IF_TRACE_HEX,	IF_TRACE_ASCII | IF_TRACE_HEX },
	{ "-hex",	IF_TRACE_ASCII,	IF_TRACE_ASCII | IF_TRACE_HEX },
#ifdef MONITOR
/* borrow a meaningless combination for the new trace type */
#define IF_TRACE_PLAIN (IF_TRACE_ASCII|IF_TRACE_HEX)
	{ "monitor",	IF_TRACE_PLAIN,	IF_TRACE_ASCII | IF_TRACE_HEX },
	{ "-monitor",	IF_TRACE_ASCII,	IF_TRACE_ASCII | IF_TRACE_HEX },
#endif
#ifdef CTRACE
/* add opt color */
	{ "color",	IF_TRACE_COLOR,	IF_TRACE_COLOR },
	{ "-color",	0,		IF_TRACE_COLOR },
#endif
	{ "off",	0,		0xffff },
	{ NULLCHAR,	0,		0 }
};



void
dump (register struct iface *ifp, int direction, unsigned type, struct mbuf *bp)
{
struct mbuf *tbp;
void (*func) (FILE *, struct mbuf **, int);
int16 size;
time_t timer;
char *cp;

#ifdef KISS			/* Let's straighten out this multiport tracing - K5JB */
	if (type == CL_KISS) {	/* I don't think we will see CL_AX25 */
		int port;	/* don't need this but it improves readability */
		struct iface *kifp;

		port = (bp->data[0] & 0xF0) >> 4;
		if ((kifp = Slip[ifp->xdev].kiss[port]) != NULLIF)
			ifp = kifp;
	}
#endif

	if (ifp == NULL || (ifp->trace & direction) == 0)
		return;		/* Nothing to trace */

#ifdef UNIX
	/* need to check if the traced-to session is a "blocking" session */
	if (ifp->trsock != -1 && sm_blocked (Tracesession ? Trace : Command))
		return;
#else
	if (Tracesession) {
		/* Disable trace if this is not Trace-sessions,
	         * or when shelled out, and not tracing to file */
		if ((Current != Trace) && (ifp->trfp == stdout))
			return;	/* Nothing to trace */
	} else {
		/* Disable trace on non-command sessions or when shelled out */
		if ((Current != Command) && (ifp->trfp == stdout))
			return;	/* Nothing to trace */
	}
#endif

	if (ifp->trsock != -1)
		tracesock = ifp->trsock;
	else if (Tracesession)
		tracesock = Trace->output;
	else
		tracesock = Command->output;

	(void) time (&timer);
	cp = ctime (&timer);
	cp[24] = '\0';

	switch (direction) {
		case IF_TRACE_IN:
			if ((ifp->trace & IF_TRACE_NOBC)
			    && (Tracef[type].addrtest != NULLFP ((struct iface *, struct mbuf *)))
			    && (*Tracef[type].addrtest) (ifp, bp) == 0)
				return;	/* broadcasts are suppressed */
#ifdef MONITOR
			if ((ifp->trace & IF_TRACE_PLAIN) == IF_TRACE_PLAIN)
#ifdef CTRACE
				if ((use_ctrace = (ifp->trace & IF_TRACE_COLOR)))
				    traceprintf (ifp->trfp, "%s%s(%s %s%srx%s%s [%ld]) ",
					 Aboldoff, Acyan, kissname (ifp, bp, type), Agreen, Abold, Aboldoff, Acyan, ifp->rawrecvcnt);
				else
#endif /* CTRACE */				
				traceprintf (ifp->trfp, "(%s) ", kissname (ifp, bp, type));
			else
#endif
#ifdef CTRACE
				if ((use_ctrace = (ifp->trace & IF_TRACE_COLOR)))
					traceprintf (ifp->trfp, "\n%s%s%s - %s %s%srecv:\n%s%s",
					    Aboldoff, Acyan, cp, ifp->name, Abold, Agreen, Aboldoff, Acyan);
				else
#endif /* CTRACE */				
				traceprintf (ifp->trfp, "\n%s - %s recv:\n", cp, ifp->name);
			break;
		case IF_TRACE_OUT:
#ifdef MONITOR
			if ((ifp->trace & IF_TRACE_PLAIN) == IF_TRACE_PLAIN)
#ifdef CTRACE
				if ((use_ctrace = (ifp->trace & IF_TRACE_COLOR)))
					traceprintf (ifp->trfp, "%s%s(%s %s%stx%s%s [%ld]) ",
					     Aboldoff, Acyan, kissname (ifp, bp, type), Ared, Abold, Aboldoff, Acyan, ifp->rawsndcnt);
				else
#endif /* CTRACE */
				traceprintf (ifp->trfp, "(%s) ", kissname (ifp, bp, type));
			else
#endif
#ifdef CTRACE
				if ((use_ctrace = (ifp->trace & IF_TRACE_COLOR)))
					traceprintf (ifp->trfp, "\n%s%s%s - %s %ssent:\n%s%s",
						Aboldoff, Acyan, cp, ifp->name, Aalert, Aboldoff, Acyan);
				else
#endif /* CTRACE */
				traceprintf (ifp->trfp, "\n%s - %s sent:\n", cp, ifp->name);
			break;
		default:
			break;
	}
	if (bp == NULLBUF || (size = len_p (bp)) == 0) {
		traceprintf (ifp->trfp, "empty packet!!\n");
		return;
	}
	if (type < NCLASS)
		func = Tracef[type].tracef;
	else
		func = NULLVFP ((FILE *, struct mbuf **, int));

	(void) dup_p (&tbp, bp, 0, size);
	if (tbp == NULLBUF) {
		traceprintf (ifp->trfp, nospace);
		return;
	}
	if (func != NULLVFP ((FILE *, struct mbuf **, int)))
		(*func) (ifp->trfp, &tbp, 1);
#ifdef MONITOR
	if ((ifp->trace & IF_TRACE_PLAIN) == IF_TRACE_PLAIN)
		plain_dump (ifp->trfp, &tbp);
	else
#endif
	if (ifp->trace & IF_TRACE_ASCII) {
		/* Dump only data portion of packet in ascii */
		ascii_dump (ifp->trfp, &tbp);
	} else if (ifp->trace & IF_TRACE_HEX) {
		/* Dump entire packet in hex/ascii */
		free_p (tbp);
		(void) dup_p (&tbp, bp, 0, len_p (bp));
		if (tbp != NULLBUF)
			hex_dump (ifp->trfp, &tbp, ifp->type);
		else
			traceprintf (ifp->trfp, nospace);
	}
	free_p (tbp);
}


/* Dump packet bytes, no interpretation */
void
raw_dump (struct iface *ifp, int direction, struct mbuf *bp)
{
struct mbuf *tbp;

	/* Dump entire packet in hex/ascii */
	traceprintf (ifp->trfp, "\n******* raw packet dump (%s %s)\n",
		 ((direction & IF_TRACE_OUT) ? "send" : "recv"), ifp->name);
	(void) dup_p (&tbp, bp, 0, len_p (bp));
	if (tbp != NULLBUF)
		hex_dump (ifp->trfp, &tbp, ifp->type);
	else
		traceprintf (ifp->trfp, nospace);
	traceprintf (ifp->trfp, "*******\n");
	free_p (tbp);
	return;
}


/* Dump an mbuf in hex */
static void
hex_dump (FILE * fp, register struct mbuf **bpp, int iftype)
{
int16 n;
int16 address;
char buf[16];

#ifdef CTRACE
	if (use_ctrace)
	    traceprintf (fp, Awhite);
#endif
	if (bpp == NULLBUFP || *bpp == NULLBUF)
		return;

	address = 0;
	if (iftype == CL_AX25)
		(void) pullup (bpp, (unsigned char *) buf, 1);	/* remove first zero byte */
	while ((n = pullup (bpp, (unsigned char *) buf, sizeof (buf))) != 0) {
		fmtline (fp, address, buf, n);
		address += n;
	}
}


/* Dump an mbuf in ascii */
static void
ascii_dump (FILE * fp, register struct mbuf **bpp)
{
int c;
register int16 tot;

#ifdef CTRACE
	if (use_ctrace)
		traceprintf (fp, (SYSback == 7)? Ablue : Awhite);
#endif
	if (bpp == NULLBUFP || *bpp == NULLBUF)
		return;

	tot = 0;
	while ((c = PULLCHAR (bpp)) != -1) {
		if ((tot % 64) == 0)
			traceprintf (fp, "%04x  ", tot);
#ifdef CTRACE
		if (use_ctrace)
			traceprintf (fp, "%c", isprint (uchar (c)) ? c : '_');
		else
#endif
		traceprintf (fp, "%c", isprint (uchar (c)) ? c : '.');
		if ((++tot % 64) == 0)
			traceprintf (fp, "\n");
	}
	if ((tot % 64) != 0)
		traceprintf (fp, "\n");
}


/* Print a buffer up to 16 bytes long in formatted hex with ascii
 * translation, e.g.,
 * 0000: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f  0123456789:;<=>?
 */
static void
fmtline (FILE * fp, int16 addr, char *buf, int16 len)
{
char line[100];
register char *aptr, *cptr;
register char c;

	memset (line, ' ', sizeof (line));
	ctohex (line, (int16) hibyte (addr));
	ctohex (line + 2, (int16) lobyte (addr));
	aptr = &line[6];
	cptr = &line[55];
	while (len-- != 0) {
		c = *buf++;
		ctohex (aptr, (int16) uchar (c));
		aptr += 3;
		c &= 0x7f;
		*cptr++ = isprint (uchar (c)) ? c : '.';
	}
	*cptr++ = '\n';
	*cptr = 0;
	traceprintf (fp, "%s", line);
}


/* Convert byte to two ascii-hex characters */
static void
ctohex (register char *buf, register int16 c)
{
static char hex[] = "0123456789abcdef";

	*buf++ = hex[hinibble (c)];
	*buf = hex[lonibble (c)];
}


#ifdef MONITOR
/* Dump an mbuf in ascii with newlines but no others. */
/* Actually, we do limited VT100 parsing, since that seems popular here */
static void
plain_dump (FILE * fp, register struct mbuf **bpp)
{
struct mbuf *tmp, **tmpp = &tmp;
int c, esc, nl;

	if (bpp == NULLBUFP || *bpp == NULLBUF)
		return;

	/* check for lots of non-ASCII, non-VT100 and ascii_dump instead? */
	(void) dup_p (&tmp, *bpp, 0, len_p (*bpp));
	nl = 0;
	while ((c = PULLCHAR (tmpp)) != -1) {
		/*
         * Printable characters are okay, as are \n \t \r \b \f \a \E
         * Nulls and other control characters are verboten, as are meta
         * controls.  Meta-printables are accepted, since they may be
         * intended as PC graphics (but don't expect them to dump right
         * from here because I don't decode them.  Maybe someday).
         */
		if (c < 8 || (c > 13 && c < 26) || (c > 27 && c < 32) ||
		    (c > 126 && c < 174) || c > 223)
			nl = 1;
	}
	if (nl) {
		ascii_dump (fp, bpp);
		return;
	}
	esc = 0;
	nl = 1;
#ifdef CTRACE
	if (use_ctrace)
		traceprintf (fp, "%s%s", (SYSback == 7)? Ablack
		 : Awhite, (SYSback == 7)? Aboldoff : Abold);
#endif
	while ((c = PULLCHAR (bpp)) != -1) {
		if (c == 0x1B)
			esc = !esc;
		else if (esc == 1 && c == '[')
			esc = 2;
		else if (esc == 1)
			esc = 0;
		else if (esc == 2 && c != ';' && !isdigit (c)) {
			/* handle some common cases? */
			esc = 0;
		} else if (esc == 0 && c == '\r') {
			traceprintf (fp, "\n");
			nl = 1;
		}
		/* safe programming: not everyone *always* agrees on isprint */
		else if (esc == 0 && c != '\n' && (isprint (c) || c == '\t')) {
			traceprintf (fp, "%c", c);
			nl = 0;
		}
	}
	if (!nl)
		traceprintf (fp, "\n");
#ifdef CTRACE
	if (use_ctrace)
		traceprintf (fp, Aboldoff);
#endif
}

#endif


/* Modify or displace interface trace flags */
int
dotrace (int argc, char *argv[], void *p OPTIONAL)
{
struct iface *ifp;
struct tracecmd *tp;

	if (argc < 2) {
		for (ifp = Ifaces; ifp != NULLIF; ifp = ifp->next)
			showtrace (ifp);
		return 0;
	}
	if ((ifp = if_lookup (argv[1])) == NULLIF) {
		tprintf (Badinterface, argv[1]);
		return 1;
	}
#if 0
	if (ifp->port) {
		tprintf ("No trace on this interface - use master.\n");
		return 1;
	}
#endif
	if (argc == 2) {
		showtrace (ifp);
		return 0;
	}
	/* MODIFY THIS TO HANDLE MULTIPLE OPTIONS */
	if (argc >= 3) {
		for (tp = Tracecmd; tp->name != NULLCHAR; tp++)
			if (strncmp (tp->name, argv[2], strlen (argv[2])) == 0)
				break;
		if (tp->name != NULLCHAR)
			ifp->trace = (int16) ((ifp->trace & (~(unsigned)tp->mask)) | (unsigned)tp->val);
		else
			ifp->trace = (int16) htoi (argv[2]);
	}
	/* Always default to stdout unless trace file is given */
	if (ifp->trsock != -1)
		close_s (ifp->trsock);
	ifp->trsock = -1;
	if (ifp->trfp != NULLFILE && ifp->trfp != stdout)
		(void) fclose (ifp->trfp);
	ifp->trfp = stdout;
	if (ifp->trfile != NULLCHAR)
		free (ifp->trfile);
	ifp->trfile = NULLCHAR;

	if (argc >= 4) {
		if (argv[3][0] == '!') {
			/* trace to the current output socket ! */
			ifp->trsock = Curproc->output;
			/* make sure stopping trace doesn't kill connection */
			(void) usesock (ifp->trsock);
		} else if ((ifp->trfp = fopen (argv[3], APPEND_TEXT)) == NULLFILE) {
			tprintf ("Can't write to %s\n", argv[3]);
			ifp->trfp = stdout;
		} else {
			ifp->trfile = strdup (argv[3]);
			if (argc >= 5 && tolower(argv[4][0]) == 'u')
				setbuf (ifp->trfp, NULL);
		}
	}
	showtrace (ifp);
	return 0;
}


/* Display the trace flags for a particular interface */
static void
showtrace (register struct iface *ifp)
{
	if (ifp == NULLIF)
		return;
	tprintf ("%s:", ifp->name);
#if 0
	if (ifp->port) {
		tprintf (" trace on master interface only.\n");
		return;
	}
#endif
	if (ifp->trace & (IF_TRACE_IN | IF_TRACE_OUT | IF_TRACE_RAW)) {
		if (ifp->trace & IF_TRACE_IN)
			tprintf (" input");
		if (ifp->trace & IF_TRACE_OUT)
			tprintf (" output");

		if (ifp->trace & IF_TRACE_NOBC)
			tprintf (" - no broadcasts");

#ifdef MONITOR
		if ((ifp->trace & IF_TRACE_PLAIN) == IF_TRACE_PLAIN)
			tprintf (" (Monitoring)");
		else
#endif
		if (ifp->trace & IF_TRACE_HEX)
			tprintf (" (Hex/ASCII dump)");
		else if (ifp->trace & IF_TRACE_ASCII)
			tprintf (" (ASCII dump)");
		else
			tprintf (" (headers only)");

		if (ifp->trace & IF_TRACE_RAW)
			tprintf (" Raw output");

#ifdef CTRACE
		if ((ifp->trace & IF_TRACE_COLOR) == IF_TRACE_COLOR) {
			(void) Avar_color(SYSfore);
			tprintf (" (%sColor%s)", Acyan, Avar);
		}
#endif
		if (ifp->trfile != NULLCHAR)
			tprintf (" trace file: %s", ifp->trfile);
		else if (ifp->trsock >= SOCKBASE)
			tprintf (" tracing to socket");
		tprintf ("\n");
	} else
		tprintf (" tracing off\n");
}


/* shut down all trace files */
void
shuttrace ()
{
struct iface *ifp;

	for (ifp = Ifaces; ifp != NULLIF; ifp = ifp->next) {
		if (ifp->trsock != -1)
			close_s (ifp->trsock);
		if (ifp->trfp != NULLFILE && ifp->trfp != stdout)
			(void) fclose (ifp->trfp);
		if (ifp->trfile != NULLCHAR)
			free (ifp->trfile);
		ifp->trfile = NULLCHAR;
		ifp->trfp = NULLFILE;
		ifp->trsock = -1;
	}
}

#endif /*TRACE*/


#ifdef PPP
/* Log messages of the form
 * Tue Jan 31 00:00:00 1987 44.64.0.7:1003 open FTP
 */
#if defined(SCREENSAVER) && defined(UNIX)
extern int16 intrace;
#endif

void
trace_log (struct iface *ifp, const char *fmt,...)
{
va_list ap;
char *cp;
time_t t;

	if (ifp->trfp == NULLFILE)
		return;

	(void) time (&t);
	cp = ctime (&t);
	rip (cp);
	traceprintf (ifp->trfp, "%s", cp);

	traceprintf (ifp->trfp, " - ");
	va_start (ap, fmt);			/*lint !e718 !e746 */
#if defined(SCREENSAVER) && defined(UNIX)
	intrace = 1;
#endif
	if (ifp->trfp == stdout)
		(void) usvprintf (tracesock, fmt, ap);
	else
		(void) vfprintf (ifp->trfp, fmt, ap);
#if defined(SCREENSAVER) && defined(UNIX)
	intrace = 0;
#endif
	va_end (ap);
	traceprintf (ifp->trfp, "\n");
}

#endif /* PPP */
