/* intercept.c -- spawn a process and intercept BIOS and DOS
 * SWI (software interrupt) calls, recording register data to
 * a file.
 * Demonstrates chaining interrupt handlers.
 *************************************************************
 * Solely for compilation under Borland's Turbo C.
 *************************************************************
 * Written 7/31/1987 by:
 *	 Ned Konz
 *	 210 Oleeta St.
 *	 Ormond Bch, FL 32074 (904)672-2431
 *	 BIX:nkonz  CIS:76046,223
 *
 * Released into the public domain by the author.
 *
 * Modified 4/11/1989 by:
 *	Russell Nelson
 *	11 Grant St.
 *	Potsdam, NY 13676 (215)265-5655
 *	CIS: 70441,205 GEnie: BH01 Internet: nelson@clutx.clarkson.edu
 *	BITNET: nelson@clutx  UUCP: uunet!clutx.clarkson.edu!nelson
 *
 * No copyright is claimed by Russell Nelson
 *
 **************************************************************/

/*************************************************************
 *	This program was written so I could find out quickly what
 *	an unknown program's interface with the outside world was.
 *	It does not currently handle hardware interrupts.
 *	What it does:
 *
 *	1. Runs the specified program, intercepting certain interrupts
 *		and recording data in memory.
 *	2. Writes an intermediate file filled with Swi_info structures (binary)
 *	3. Runs a program called "INTERPRE.EXE" to interpret the
 *		intermediate file (It's a separate program to keep
 *		this one small and so you can write your own.)
 *		It's called like this:
 *			interpre.exe [-l] infilename outfilename progname [args...]
 *			where -l denotes long output format.
 **************************************************************/

#include <dos.h>
#include <stdio.h>
#include <mem.h>
#include <alloc.h>
#include <process.h>
#include <io.h>
#include <string.h>
#include <dir.h>
#include "intercept.h"

char *usage =
"[-l] [-T tmpdir] [-s maxcalls] [-o outfile] [-d datafile]\n"
"\tprogram [args...]\n"
"	-l	sets long format output: explanation AND register values\n"
"	-T	sets temporary directory for intermediate file to \"tmpdir\"\n"
"		(will use TMP or TMPDIR environment vars. if found otherwise)\n"
" 	-s	sets the maximum number of SWI records to \"maxcalls\"\n"
"	-o	names the output filename to \"outfile\" rather than\n"
"		the default name (\"intercep.out\")\n"
"	-d	sets the interrupt data file to use, defaults to INTERPRE.DAT\n"
"	program		is the name of the program to monitor\n"
"	args		are any command-line arguments to be passed\n"
"			to the monitored program.\n";

char *logo = "INTERCEPT -- monitor DOS and BIOS calls.	By:\n"
	"	Ned Konz\n"
	"	210 Oleeta St.\n"
	"	Ormond Bch, FL 32074\n"
	"	BIX:nkonz  CIS:76046,223  (904)672-2431\n"
	"	08/02/1987\n"
	"	Datafile option and parsing added by Russell Nelson\n";

char switchar,	/* DOS parameter switch char (from int 0x21, fn 0x3700) */
	sepchar = '\\';		/* DOS filename separator */

void interrupt inthandler ( Regpack r, Intpack i );
int get_swi_list(unsigned);
void install(void);
void uninstall(void);

/* the 8086/8088 INT instruction */
#define SWI_INSTRUCTION	0xCD

/* int_table[] --
 * table containg numbers of interrupts we're catching
 * and their old handlers
 * should be sorted by most common interrupts first.
 * NO HARDWARE INTERRUPTS!!!
 * Note: some of these may be commented out because they tend to
 * quickly fill up the output file. Uncomment and re-compile
 * if you want them too.
 */
Intblock
int_table[MAX_INTS + 1];		/* leave room for a terminator */

/* swi_list[] --
 * area in memory into which we store data about each SWI
 */
Swi_info huge *swi_list = NULL;	/* beginning of swi_list */
Swi_info huge *swi_list_end = NULL;	/* just past end of swi_list */
volatile Swi_info huge *swi_next = NULL;	/* pointer to next block */

/* our single interrupt handler
 * which merely records our registers and interrupt number
 * in swi_list[]
 * and chains to old handler.
 */
void interrupt
inthandler ( Regpack r, Intpack i )
{
	/* the following variables are declared as static to get them
	 * off the caller's stack and to ensure that t[] is where
	 * we want it to be: from [BP-01] through [BP-06]
	 */
	static unsigned char far * caller;
	static unsigned char which_int;
	static IFP oldhandler;
	static Intblock *ibp;
	volatile unsigned t[3];	/* to move stack values down by 3 words into */

	/* point to next instruction */
	caller = (char far *)i.ipcs;

	if (caller[-2] != SWI_INSTRUCTION) {	/* was this a non-SWI? (uh-oh!) */
		uninstall();
		exit(-1);
	}

	which_int = caller[-1];	/* which SWI is this? */

	if (FP_SEG(i.ipcs) > _CS && FP_SEG(i.ipcs) < 0xA000
		&& swi_next < swi_list_end) {
		swi_next->regs = r;
		swi_next->caller = i;
		swi_next->intnum = which_int;
		swi_next++;
	}

	/* get old handler value */
	for (ibp=int_table; ibp->intnum>=0 && ibp->intnum!=which_int; ibp++)
		;

	if (ibp->intnum < 0)		/* can't happen... */
		return;			/* but if it does, just return "safely" */

	oldhandler = ibp->oldint;

	/* move all our registers down by 3 words on the stack */
	movedata(_SS, FP_OFF(&r), _SS, FP_OFF(t), sizeof(Regpack));

	/* supply a mock flag value with interrupts masked OFF */
	r.ovl.new.flags = i.flags & ~0x0200;

	/* get the address of the routine to chain to */
	r.ovl.new.ipcs = oldhandler;

	/* bump our frame pointer value down by 3 words --
	 * the stack pointer will be loaded from this new value next.
	 */
	_BP -= 6;

	/* unstack all registers and do an IRET */
	return;
}

int
get_swi_list(unsigned n)
{
	if (!(swi_list = farcalloc(n, sizeof(Swi_info))))
		return 0;

	swi_next = swi_list;
	swi_list_end = swi_list + n;
	return n;
}


/* install our handler for all the named interrupts
 */
void
install()
{
	Intblock *ibp;
	IFPP vp;

	for (ibp = int_table; ibp->intnum >= 0; ibp++) {
		vp = (IFPP) MK_FP(0, ibp->intnum*4);
		ibp->oldint = *vp;
		disable();
		*vp = inthandler;
		enable();
	}
}

/* un-install our handler for all the named interrupts
 */
void
uninstall()
{
	Intblock *ibp;
	IFPP vp;

	for (ibp = int_table; ibp->intnum >= 0; ibp++) {
		if (ibp->oldint != NULL) {
			vp = (IFPP) MK_FP(0, ibp->intnum*4);
			disable();
			*vp = ibp->oldint;
			enable();
		}
	}
}


/* Read template file into buffer, setting pointers to
 * beginning of lines.
 * Sets template_text, templates and ntemplates.
 * Pads out interrupt IDs to 6 characters.
 * Returns number of lines.
 */
int
read_template_file(char *filename)
{
	FILE *ifile;
	char inline[ 100 ];
	Intblock *ibp = int_table;
	int i;

	if (! (ifile = fopen(filename, "rt")))
		return 0;
	while (fgets(inline, sizeof(inline), ifile)) {
		if (ibp - int_table >= MAX_INTS)
			return 0;
		if (sscanf(inline, "%2x", &i) != 1)
			continue;
		if (ibp > int_table && i == (ibp-1)->intnum)
			continue;	/* same as the previous one */
		ibp->intnum = i;
		ibp->oldint = NULL;
		ibp++;
	}
	ibp->intnum = -1;		/* terminate the list */
	fclose(ifile);
	return 1;
}


/* output structures to intermediate file, call filter program
 * return child return code (zero if OK)
 */
int
output_file(char *tmpdir, char *outfilename, int longmode,
	char *progname, char *tfilename)
{
	Swi_info huge *swip;
	Swi_info outrec;
	FILE *ofp;
	char tempname[ 80 ];
	int rval = 0;

	sprintf(tempname, "%s%c%s", tmpdir, sepchar, "intercXXXXXX");

	if (! mktemp(tempname)) {
		fprintf(stderr, "%s: Bad temp file: %s\n", progname, tempname);
		return -1;
	}
	if (! (ofp = fopen(tempname, "wb"))) {
		fprintf(stderr, "%s: Can't open intermediate file %s\n",
			progname, tempname);
		return -1;
	}
#ifdef DEBUG
	fprintf(stderr, "%s: Using \"%s\" as intermediate file\n",
		progname, tempname);
#endif
	for (swip = swi_list; swip < (Swi_info huge *)swi_next; swip++) {
		outrec = *swip;
		if (fwrite(&outrec, sizeof(outrec), 1, ofp) != 1) {
			fprintf(stderr, "%s: Write error on file %s\n", progname, tempname);
			fclose(ofp);
			unlink(tempname);
			return -1;
		}
	}
	fclose(ofp);
#ifdef DEBUG
	fprintf(stderr, "Running: " OFILTER " %s %s %s %s %s\n",
		(longmode ? "-l" : " "), "-t", tfilename, tempname, outfilename
		);
#endif
	rval = spawnlp(P_WAIT, OFILTER, OFILTER,
		(longmode ? "-l" : " "), "-t", tfilename, tempname, outfilename
		, NULL);
#ifndef DEBUG
	unlink(tempname);
#endif
	return rval;
}

void
main(int argc, char *argv[])
{
	static unsigned nswi = MAX_INTERRUPTS;	/* how many to record? */
	static char *ofilename;	/* output file name */
	static char *tfilename;	/* template file name */
	static int childret;
	static int longmode = 0;
	static char tmpdir[ 64 ] = ".";
	char *progname;

	/* spit out logo */
	fprintf(stderr, "%s", logo);

	if ((switchar = getswitchar()) != '/')
		sepchar = '/';
	progname = strrchr(argv[0], sepchar) + 1;
	*strchr(progname, '.') = '\0';

	/* process cmdline arguments */
	if (argc < 2) {
		fprintf(stderr, "Usage: %s %s", progname, usage);
		exit(1);
	}

	/* get TMP or TMPDIR (use ofilename as tmp var) */
	if ((ofilename = getenv("TMPDIR")) || (ofilename = getenv("TMP")))
		strncpy(tmpdir, ofilename, sizeof(tmpdir));

	ofilename = OFILENAME;
	tfilename = TFILENAME;

	while (argv[1][0] == '-') {
		char *dummy;	/* is this needed? */
		switch (argv[1][1]) {
			case 's':	/* specify max swi calls */
			case 'S':
				nswi = (unsigned)strtol(argv[2], &dummy, 0);
				argv++;
				break;

			case 'o':	/* specify output filename */
			case 'O':
				ofilename = argv[2];
				argv++;
				break;

			case 'd':	/* specify data filename */
			case 'D':
				tfilename = argv[2];
				argv++;
				break;

			case 'l':	/* set long-mode output */
			case 'L':
				longmode++;
				break;

			case 't':
			case 'T':
				strncpy(tmpdir, argv[2], sizeof(tmpdir));
				argv++;
				break;

			default:
				fprintf(stderr, "%s: unknown option \"%s\"\n",progname,argv[1]);
				fprintf(stderr, "Usage: %s\t%s", progname, usage);
				exit(2);
				break;
		}
		argv++;
	}

	if (! read_template_file( searchpath(tfilename) )) {
		fprintf(stderr, "%s: error during read of \"%s\"\n",
			progname, tfilename);
		exit(5);
	}

	/* obtain far segment for recording calls */
	if (! get_swi_list(nswi)) {
		fprintf(stderr, "%s: Can't get enough memory for %u swi records\n",
			progname, nswi);
		exit(3);
	}
	else
		fprintf(stderr, "%s: Recording up to %u SWI records to file \"%s\"\n",
			progname, nswi, ofilename);

	/* install our interrupt handler for each interrupt in the list */
	install();

	/* now run the process */
	childret = spawnvp(P_WAIT, argv[1], argv+1);

	/* and restore our captured interrupts */
	uninstall();

	/* report on child return value */
	if (childret == -1) {
		fprintf(stderr, "%s: Spawn of \"%s\" failed: %s\n",
			progname, argv[1], strerror(NULL));
		exit(4);
	}
	if (childret != 0)
		fprintf(stderr, "%s: Child process \"%s\" exit value: %d\n",
			progname, argv[1], childret);

	/* output intermediate file and run output filter program */
	if (output_file(tmpdir, ofilename, longmode, progname, tfilename))
		fprintf(stderr, "%s: couldn't run output filter program " OFILTER "\n",
			progname);

	farfree(swi_list);

	exit(0);
}
