/*---------------------------------------------------------------------------
 *		
 * NAME:	mctlFix - insert a patch around the super-user requirement
 *			  for the mctl() system call on SUNO/S 4.1.
 *
 * USAGE:	mctlFix <objfile>
 *
 * DESCRIPTION: This program patches the kernel object file containing the 
 *		first level code for the mctl() system call, to remove the
 *		superuser check which must normally succedd to allow a 
 *		user to lock memory.
 * 
 * ARGUMENTS:   <objfile> - the object file containing the code for the
 *		mctl() system call. This will normally be 
 *
 * 			   sys/sun4/OBJ/kern_mman.o
 *
 * NOTES:	This program works by spawing off 'adb' subprocesses.
 *
 * AUTHOR/DATE: John Lloyd, Fall 1990. Help given by Mike Parker.
 *
 * UPDATES:
 *
 *--------------------------------------------------------------------------*/

#include <stdio.h>
#include <signal.h>
#include <strings.h>
#include <varargs.h>
#include <fcntl.h>

#define RBUFSIZE	1024

char rbuf[RBUFSIZE];
char **argvec;

#define SYS	-1

FILE *wIn;		/* input from adb 'write' process */
FILE *wOut;		/* output to adb 'write' process */
FILE *sIn;		/* input from adb 'search' process */
FILE *sOut;		/* output to adb 'search' process */
int verbose = 0;	/* diagnostic output */

static fatal();
static output();
static input();

main(argc, argv)
int argc;
char **argv;
{
	/* The program works by creating two adb processes: 

	   One is used to write the patch into the object file;
	   the other is used to search /vmunix to find the proper
	   location at which to apply the patch.
	 */

	char iaddr[256];	/* address of patch location */
	int fds1[2];		/* file descriptors */
	int fds2[2];		/*   returned by pipe() */
	int wPid;		/* id for adb 'write' process */
	int sPid;		/* id for adb 'search' process */
	char *objfile = NULL;
	char *unixfile = NULL;
	int k;

	argvec = argv;

	argc--; argv++;
	while (argc)
	 { if (strcmp (*argv, "-v") == 0)
	    { verbose = 1;
	    }
	   else if (!objfile)
	    { objfile = *argv;
	    }
	   else if (!unixfile)
	    { unixfile = *argv;
	    }
	   else
	    { argc = -1;
	      break;
	    }
	   argc--; argv++;
	 }
	if (argc == -1)
	 { fprintf (stderr, 
"usage: %s [-v] <objectfile> <unixfile>\n", argvec[0]);
	   exit (1);
	 }
	/* start by trying to open the adb write process, which will
	   actually do the patching: */

	if (pipe(fds1) < 0 || pipe(fds2) < 0)
	 { fatal (SYS, "can't open pipes for adb write process\n");
	 }
	if ((wPid = fork()) == 0)
	 { int k;
	   if (dup2(fds1[0], fileno(stdin)) < 0 ||
	       dup2(fds2[1], fileno(stdout)) < 0)
	    { kill (SIGKILL, getppid());
	      fatal (SYS, "can't map pipes onto stdin and stdout\n");
	    }
	   for (k=getdtablesize()-1; k>2; k--) close(k);
	   if (execlp("adb", "adb", "-w", objfile, 0))
	    { kill (SIGKILL, getppid());
	      fatal (SYS, "can't exec 1st 'adb' to do patching\n");
	    }
	   /* not reached */
	 }
	/* we are the parent, connected to the child */
	if ((wIn = fdopen(fds2[0], "r")) == NULL ||
	    (wOut = fdopen(fds1[1], "w")) == NULL)
	 { fatal (SYS, "can't do fdopen() on pipe connections\n");
	 }

	/* poke the first adb and check that it's happy */
	output (wOut, "\n");
	input (wIn, rbuf, RBUFSIZE);
	flushInput (wIn);
	output (wOut, "_mctl+4?i\n");
	input (wIn, rbuf, RBUFSIZE);
	if (!match ("^_mctl", rbuf))
	 { fprintf (stderr, "%s", rbuf);
	   fatal (1, "adb write process is not happy\n");
	 }

	/* and now set up second adb process to search UNIX */

	if (pipe(fds1) < 0 || pipe(fds2) < 0)
	 { fatal (SYS, "can't open pipes for adb search process\n");
	 }
	if ((sPid = fork()) == 0)
	 { int k;
	   if (dup2(fds1[0], fileno(stdin)) < 0 ||
	       dup2(fds2[1], fileno(stdout)) < 0)
	    { kill (SIGKILL, getppid());
	      fatal (SYS, "can't map pipes onto stdin and stdout\n");
	    }
	   for (k=getdtablesize()-1; k>2; k--) close(k);
	   if (execlp("adb", "adb", unixfile, 0))
	    { kill (SIGKILL, getppid());
	      fatal (SYS, "can't exec 2nd 'adb' to do searching\n");
	    }
	   /* not reached */
	 }
	/* we are the parent, connected to the child */
	if ((sIn = fdopen(fds2[0], "r")) == NULL ||
	    (sOut = fdopen(fds1[1], "w")) == NULL)
	 { fatal (SYS, "can't do fdopen on pipe connections\n");
	 }

	output (sOut, "\n");
	input (sIn, rbuf, RBUFSIZE);
	flushInput (sIn);
	output (sOut, "_mctl+4?i\n");
	input (sIn, rbuf, RBUFSIZE);
	if (!match ("^_mctl", rbuf))
	 { fprintf (stderr, "%s", rbuf);
	   fatal (1, "adb search process is not happy\n");
	 }

	/* march along through all references to 'mctl' and see
	   if we find call to suser(). */

	k = 0;
	while (match("^_mctl", rbuf))
	 { if (match("call") && match("_suser"))
	    { if (++k == 1)
	       { strcpy (iaddr, rbuf);
	       }
	    }
	   output (sOut, "\n");
	   input (sIn, rbuf, RBUFSIZE);
	 }
	if (k == 0)
	 { fprintf (stderr, "No calls to suser() found in mctl()\n");
	   exit (1);
	 }
	if (k > 1)
	 { fatal (1, "%d calls to suser() found in mctl()\n", k);
	 }
	if (!index (iaddr, ':'))
	 { fprintf (stderr, "%s", iaddr);
	   fatal (1, "address for suser() call not colon terminated\n");
	 }
	*index(iaddr,':') = '\0';
	flushInput (sIn);

	output (sOut, "%s?i\n", iaddr);
	input (sIn, rbuf, RBUFSIZE);
	output (sOut, "\n");
	input (sIn, rbuf, RBUFSIZE);
	if (!match("nop", rbuf))
	 { fprintf (stderr, "%s", rbuf);
	   fatal (1, "call to suser() doesn't have nop in delay slot\n");
	 }

	output (wOut, "%s?W 90102001\n", iaddr);
	input (wIn, rbuf, RBUFSIZE);
	/* read back the result and check it */
	output (wOut, "%s?i\n", iaddr);
	input (wIn, rbuf, RBUFSIZE);
	if (!match("mov", rbuf) || !match("1, %o0", rbuf))
	 { fprintf (stderr, "%s", rbuf);
	   fatal (1, "patched instruction does not read back correctly\n");
	 }
	exit(0);
}

static fatal(va_alist)
va_dcl
{
	/* Terminate the program with suitable error messages. */

	va_list pvar;
	int code;
	char *fmt;

	extern char *sys_errlist[];
	extern int errno;

	va_start(pvar);
	code = va_arg(pvar,int);
	fmt = va_arg(pvar,char*);
	fprintf (stderr, "%s: ", argvec[0]);
	vfprintf (stderr, fmt, pvar);
	va_end(pvar);

	if (code == SYS)
	 { fprintf ("errno %d: %s\n", errno, sys_errlist[errno]);
	   exit (1);
	 }
	else
	 { exit (code);
	 }
}

static output(va_alist)
va_dcl
{
	/* wrapper for fprintf() */

	va_list pvar;
	FILE *fp;
	char *fmt;

	va_start(pvar);
	fp = va_arg(pvar,FILE*);
	fmt = va_arg(pvar,char*);
	vfprintf (fp, fmt, pvar);
	fflush (fp);
	if (verbose)
	 { printf ("%s> ", fp == wOut ? "W" : fp == sOut ? "S" : "?" );
	   vfprintf (stdout, fmt, pvar);
	 }
	va_end(pvar);
}

static input(fp, buf, size)
FILE *fp;
char *buf;
int size;
{
	/* wrapper for fgets() */

	fgets (buf, size, fp);
	if (verbose)
	 { printf ("%s< ", fp == wIn ? "W" : fp == sIn ? "S" : "?" );
	   printf ("%s", buf);
	 }
}

match(s1,s2)
char *s1, *s2;
{
	/* return true if string 's1' matches a substring inside 's2'.
	   A '^' character at the beginning of 's1' is used to match
	   the beginning of 's2'.
	 */
	register char *cp;
	register int l1, l2;

	if (*s1 == '^')
	 { return (strncmp(s1+1,s2,strlen(s1+1))==0);
	 }
	else
	 { l1 = strlen(s1);
	   cp = s2;
	   l2 = strlen(s2);
	   while (l2 >= l1)
	    { register char *p1, *p2;
	      p1 = s1;
	      p2 = cp;
	      while (*p1++ == *p2++)
	       { if (*p1 == '\0')
		  { return (1);
		  }
	       }
	      cp++;
	      l2--;
	    }	
	 }
	return(0);
}

flushInput(fp)
FILE *fp;
{
	char buf[1024];

	sleep(1);
	fcntl (fileno(fp), F_SETFL, fcntl(fileno(fp),F_GETFL,0) | FNDELAY);
	fread (buf, 1, sizeof(buf), fp);
	fcntl (fileno(fp), F_SETFL, fcntl(fileno(fp),F_GETFL,0) & ~FNDELAY);
}
