/* 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 <stdio.h>
#include <ctype.h>
#include <time.h>
#include "global.h"
#include "config.h"
#ifdef ANSIPROTO
#include <stdarg.h>
#endif
#include "mbuf.h"
#include "iface.h"
#include "pktdrvr.h"
#include "commands.h"
#include "session.h"
#include "trace.h"

#ifdef TRACE
static void ascii_dump __ARGS((FILE *fp,struct mbuf **bpp));
static void ctohex __ARGS((char *buf,int16 c));
static void fmtline __ARGS((FILE *fp,int16 addr,char *buf,int16 len));
static void hex_dump __ARGS((FILE *fp,struct mbuf **bpp));
static void showtrace __ARGS((struct iface *ifp));
static void plain_dump __ARGS((FILE *fp,register struct mbuf **bpp));
extern struct session *Current;
extern struct session *Command;
#ifdef MULTITASK
extern int Nokeys;
#endif

extern int Tracesession;
extern struct session *Trace;

#ifdef MONITOR
int Trace_compact_header = 0;
static char *kissname __ARGS((struct iface *ifp,struct mbuf *bp,unsigned type));

#include "slip.h"

static char *
#ifdef PROTOTYPES
kissname(struct iface *ifp, struct mbuf *bp, unsigned type)
#else
kissname(ifp, bp, type)
    struct iface *ifp;
    struct mbuf *bp;
    unsigned type;
#endif
{
    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(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    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";

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
	"off",		0,		0xffff,
	NULLCHAR,	0,		0
};

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

    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 (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 */
#ifdef MULTITASK
        if((Current != Trace || Nokeys) && (ifp->trfp == stdout))
#else
        if((Current != Trace) && (ifp->trfp == stdout))
#endif /* MULTITASK */
            return; /* Nothing to trace */
    } else {
        /* Disable trace on non-command sessions or when shelled out */
#ifdef MULTITASK
        if((Current != Command || Nokeys) && (ifp->trfp == stdout))
#else
        if((Current != Command) && (ifp->trfp == stdout))
#endif
            return; /* Nothing to trace */
    }
#endif

    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)
			fprintf(ifp->trfp, "(%s) ", kissname(ifp, bp, type));
		else
#endif
			fprintf(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)
			fprintf(ifp->trfp, "(%s) ", kissname(ifp, bp, type));
		else
#endif
			fprintf(ifp->trfp,"\n%s - %s sent:\n",cp,ifp->name);
		break;
	}
	if(bp == NULLBUF || (size = len_p(bp)) == 0){
		fprintf(ifp->trfp,"empty packet!!\n");
		return;
	}

	if(type < NCLASS)
		func = Tracef[type].tracef;
	else
		func = NULLVFP((FILE *,struct mbuf**,int));

	dup_p(&tbp,bp,0,size);
	if(tbp == NULLBUF){
		fprintf(ifp->trfp,nospace);
		return;
	}
#ifdef MONITOR
	Trace_compact_header = ((ifp->trace&IF_TRACE_PLAIN) == IF_TRACE_PLAIN);
#endif
	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);
		dup_p(&tbp,bp,0,len_p(bp));
		if(tbp != NULLBUF)
			hex_dump(ifp->trfp,&tbp);
		else
			fprintf(ifp->trfp,nospace);
	}
	free_p(tbp);
}

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

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

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

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

	address = 0;
#if 0
	pullup(bpp, buf, 1);	/* remove first zero byte */
#endif
	while((n = pullup(bpp,buf,sizeof(buf))) != 0){
		fmtline(fp,address,buf,n);
		address += n;
	}
}
/* Dump an mbuf in ascii */
static void
ascii_dump(fp,bpp)
FILE *fp;
register struct mbuf **bpp;
{
	int c;
	register int16 tot;

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

	tot = 0;
	while((c = PULLCHAR(bpp)) != -1){
		if((tot % 64) == 0)
			fprintf(fp,"%04x  ",tot);
		fprintf (fp, "%c", isprint(uchar(c)) ? c : '.');
		if((++tot % 64) == 0)
			fprintf(fp,"\n");
	}
	if((tot % 64) != 0)
		fprintf(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(fp,addr,buf,len)
FILE *fp;
int16 addr;
char *buf;
int16 len;
{
	char line[80];
	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;
	fprintf (fp, line);
}
/* Convert byte to two ascii-hex characters */
static void
ctohex(buf,c)
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(fp,bpp)
FILE *fp;
register struct mbuf **bpp;
{
    struct mbuf *tmp;
    int c, esc, nl;

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

    /* check for lots of non-ASCII, non-VT100 and ascii_dump instead? */
    dup_p(&tmp, *bpp, 0, len_p(*bpp));
    nl = 0;
    while ((c = PULLCHAR(&tmp)) != -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;
    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')
      {
          fprintf(fp, "\n");
          nl = 1;
      }
      /* safe programming: not everyone *always* agrees on isprint */
      else if (esc == 0 && c != '\n' && (isprint(c) || c == '\t'))
      {
          fprintf(fp, "%c", c);
          nl = 0;
      }
    }
    if (!nl)
      fprintf(fp, "\n");
}
#endif


/* Modify or displace interface trace flags */
int
dotrace(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	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(ifp->port){
		tprintf("No trace on this interface - use master.\n");
		return 1;
	}
	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 = (ifp->trace & ~tp->mask) | tp->val;
		else
			ifp->trace = htoi(argv[2]);
	}
	/* Always default to stdout unless trace file is given */
	if(ifp->trfp != NULLFILE && ifp->trfp != stdout)
		fclose(ifp->trfp);
	ifp->trfp = stdout;
	if(ifp->trfile != NULLCHAR)
		free(ifp->trfile);
	ifp->trfile = NULLCHAR;

	if(argc >= 4){
		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]);
		}
	}
	showtrace(ifp);
	return 0;
}
/* Display the trace flags for a particular interface */
static void
showtrace(ifp)
register struct iface *ifp;
{
	if(ifp == NULLIF)
		return;
	tprintf("%s:",ifp->name);
	if(ifp->port){
		tprintf(" trace on master interface only.\n");
		return;
	}
	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");

		if(ifp->trfile != NULLCHAR)
			tprintf(" trace file: %s",ifp->trfile);
		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->trfp != NULLFILE && ifp->trfp != stdout)
			fclose(ifp->trfp);
		if(ifp->trfile != NULLCHAR)
			free(ifp->trfile);
		ifp->trfile = NULLCHAR;
		ifp->trfp = NULLFILE;
	}
}

#ifdef PPP
/* Log messages of the form
 * Tue Jan 31 00:00:00 1987 44.64.0.7:1003 open FTP
 */
#if	defined(ANSIPROTO)
void
trace_log(struct iface *ifp,char *fmt, ...)
{
	va_list ap;
	char *cp;
	long t;

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

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

	fprintf(ifp->trfp," - ");
	va_start(ap,fmt);
	vfprintf(ifp->trfp,fmt,ap);
	va_end(ap);
	fprintf(ifp->trfp,"\n");
}
#else
/*VARARGS2*/
void
trace_log(ifp,fmt,arg1,arg2,arg3,arg4,arg5)
struct iface *ifp;
char *fmt;
int arg1,arg2,arg3,arg4,arg5;
{
	char *cp;
	long t;

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

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

	fprintf(ifp->trfp," - ");
	fprintf(ifp->trfp,fmt,arg1,arg2,arg3,arg4,arg5);
	fprintf(ifp->trfp,"\n");
}
#endif
#endif /* PPP */

#endif /*TRACE*/

