%{
/*
 # $Id: gram.y,v 1.7 1998/04/10 10:27:18 fbm Exp fbm $
 # Copyright (C) 1997,1998 Farrell McKay
 # All rights reserved.
 #
 # This file is part of the Fortify distribution, a toolkit for
 # upgrading the cryptographic strength of the Netscape Navigator
 # web browser, authored by Farrell McKay.
 #
 # This toolkit is provided to the recipient under the
 # following terms and conditions:-
 #   1.  This copyright notice must not be removed or modified.
 #   2.  This toolkit may not be reproduced or included in any commercial
 #       media distribution, or commercial publication (for example CD-ROM,
 #       disk, book, magazine, journal) without first obtaining the author's
 #       express permission.
 #   3.  This toolkit, or any component of this toolkit, may not be
 #       commercially resold, redeveloped, rewritten, enhanced or otherwise
 #       used as the basis for commercial venture, without first obtaining
 #       the author's express permission.
 #   4.  Subject to the above conditions being observed (1-3), this toolkit
 #       may be freely reproduced or redistributed.
 #   5.  This software is provided "as-is", without express or implied
 #       warranty.  In no event shall the author be liable for any direct,
 #       indirect or consequential damages however caused.
 #   6.  Subject to the above conditions being observed (1-5),
 #       this toolkit may be used at no cost to the recipient.
 #
 # Farrell McKay
 # Wayfarer Systems Pty Ltd		contact@fortify.net
 */

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <sys/types.h>
#include "misc.h"
#include "morpher.h"

#define isoctal(a)	((a) >= '0' && (a) <= '7')

extern int		yyparse();
extern int		yylex();
#if defined(GNU_WIN32)
extern char		*yytext;
#else
extern char		yytext[];
#endif
extern int		yyleng;

static int		unescape(char *src, int len);

static morph_set	*newmorphset;
static morph_t		*newmorph;
static char		*newtoken;

static char		buff[1024];

%}

%token GRAMMAR TARGET TEXT_OFFSET DATA_OFFSET RODATA_OFFSET
%token TOKEN STRING NUM
%token INIT SEGMENT MSG GRADE BASE OFFSET OLD NEW CONTEXT
%token TEXT DATA RODATA
%token PRINT ASSERT PTR ADDR EQ NE LT LE GT GE

%union {
	enum verb_t	vt;
	enum op_t	ot;
	unsigned long	ul;
	morph_t		*pmorph;
	vec_t		*pvec;
	exp_t		*pexp;
}

%type <vt> verb
%type <ot> attr_name bool_op
%type <ul> seg_id num
%type <pmorph> morph
%type <pvec> vector
%type <pexp> exp

%%

start:		headers opt_morphs
		{
			morph_t	*p;

			p = find_morph(newmorphset, "_main");
			if (p)
				newmorphset->main = p->base + p->offset;

			/* if (newmorphset->main == ADDR_UNKN) {
				yyerror("No _main definition");
			} */
		}
		| error
		{
			yyerror("Parse error");
		};

headers:	init '{' grammar header_attrs '}'
		{
#if 0
			int	i;

			/* check for missing attributes in header */
			for (i = 0; i < NSEGS; i++) {
				if (newmorphset->offsets[i] == 0) {
					sprintf(buff, "Init section: No offset for %s segment.",
						seg_name(i));
					yyerror(buff);
				}
			}
#endif
		};

init:		INIT
		{
			newmorphset = _calloc(1, sizeof(morph_set));
		};

grammar:	GRAMMAR num '.' num
		{
			newmorphset->main = ADDR_UNKN;
			newmorphset->nmorphs = 0;
			newmorphset->grammar_major = $2;
			newmorphset->grammar_minor = $4;

			if (newmorphset->grammar_major > GRAMMAR_MAJOR ||
				(newmorphset->grammar_major == GRAMMAR_MAJOR &&
					newmorphset->grammar_minor > GRAMMAR_MINOR))
				yyerror("Unsupported grammar version.");
		};

header_attrs:	header_attrs header_attr
		| header_attr
		;

header_attr:	TARGET STRING
		{
			if (newmorphset->target != (char *)0)
				free(newmorphset->target);
			newmorphset->target = _strndup(yytext+1, yyleng-2);
		}
		| TEXT_OFFSET NUM
		{
			newmorphset->offsets[SEG_TEXT] = (long) yylval.ul;
		}
		| DATA_OFFSET NUM
		{
			newmorphset->offsets[SEG_DATA] = (long) yylval.ul;
		}
		| RODATA_OFFSET NUM
		{
			newmorphset->offsets[SEG_RODATA] = (long) yylval.ul;
		};

opt_morphs:	morphs
		| /* empty */
		;

morphs: 	morphs morph
		{
			int n = ++(newmorphset->morphs_sz);
			newmorphset->morphs =
				(morph_t **) _realloc((void *) newmorphset->morphs,
				(n * sizeof(morph_t *)));
			newmorphset->morphs[n-1] = $2;
		}
		| morph
		{
			newmorphset->morphs_sz = 1;
			newmorphset->morphs = (morph_t **) _malloc(sizeof(morph_t *));
			newmorphset->morphs[0] = $1;
		};

morph:		morph_name '{' morph_attrs '}'
		{
			/* check for missing attributes in morph definition */
			if (newmorph->seg == SEG_UNKN) {
				sprintf(buff, "Morph %s: No segment attribute", newmorph->name);
				yyerror(buff);
			}
			if (newmorph->base == ADDR_UNKN) {
				sprintf(buff, "Morph %s: No base address", newmorph->name);
				yyerror(buff);
			}
			if (newmorph->old.len <= 0) {
				sprintf(buff, "Morph %s: No old value", newmorph->name);
				yyerror(buff);
			}
			if (!newmorph->is_context && (newmorph->old.len != newmorph->new.len)) {
				sprintf(buff, "Morph %s: Old length != new length", newmorph->name);
				yyerror(buff);
			}
			if (!newmorph->is_context)
				++(newmorphset->nmorphs);
			$$ = newmorph;
		};

morph_name:	TOKEN
		{
			newmorph = (morph_t *) _calloc(1, sizeof(morph_t));
			newmorph->name = _strndup(yytext, yyleng);
			newmorph->seg = SEG_UNKN;
			newmorph->base = ADDR_UNKN;
			newmorph->offset = 0;
			newmorph->grade = DEFAULT_GRADE;
		};

morph_attrs:	morph_attrs morph_attr
		| morph_attr
		;

morph_attr:	SEGMENT seg_id
		{
			newmorph->seg = $2;
		}
		| MSG STRING
		{
			if (newmorph->msg != (char *)0)
				free(newmorph->msg);
			newmorph->msg = _strndup(yytext+1, yyleng-2);
		}
		| GRADE NUM
		{
			newmorph->grade = yylval.ul;
		}
		| BASE NUM
		{
			newmorph->base = yylval.ul;
		}
		| OFFSET NUM
		{
			newmorph->offset = (long) yylval.ul;
		}
		| OLD vector
		{
			if (newmorph->old.len != 0)
				free(newmorph->old.data);
			newmorph->old = *($2);
			free($2);
		}
		| NEW vector
		{
			if (newmorph->new.len != 0)
				free(newmorph->new.data);
			newmorph->new = *($2);
			free($2);
		}
		| CONTEXT vector
		{
			if (newmorph->old.len != 0)
				free(newmorph->old.data);
			newmorph->old = *($2);
			newmorph->is_context = 1;
			free($2);
		}
		| verb exp
		{
			cmd_t	*cp, *p;

			cp = (cmd_t *) _calloc(1, sizeof(cmd_t));
			cp->verb = $1;
			cp->exp = $2;
			if (newmorph->cmds == (cmd_t *)0)
				newmorph->cmds = cp;
			else {
				for (p = newmorph->cmds; p->next;)
					p = p->next;
				p->next = cp;
			}
		};

verb:		PRINT
		{
			$$ = print;
		}
		| ASSERT
		{
			$$ = assert;
		};

seg_id:		TEXT
		{
			$$ = SEG_TEXT;
		}
		| DATA
		{
			$$ = SEG_DATA;
		}
		| RODATA
		{
			$$ = SEG_RODATA;
		};

vector:		vector NUM
		{
			int n = ++(($1)->len);
			($1)->data = (char *) _realloc((void *) (($1)->data),
							(n * sizeof(unsigned char)));
			($1)->data[n-1] = (unsigned char) yylval.ul;
			$$ = $1;
		}
		| NUM
		{
			$$ = _malloc(sizeof(vec_t));
			($$)->format = VEC_NUM_FMT;
			($$)->len = 1;
			($$)->data = (unsigned char *) _malloc(sizeof(unsigned char));
			($$)->data[0] = (unsigned char) yylval.ul;
		}
		| STRING
		{
			$$ = _malloc(sizeof(vec_t));
			($$)->format = VEC_STR_FMT;
			($$)->data = (unsigned char *) _strndup(yytext+1, yyleng-2);
			($$)->len = yyleng - 2;
			($$)->len = unescape(($$)->data, ($$)->len);
		};

exp:		num
		{
			$$ = (exp_t *) _calloc(1, sizeof(exp_t));
			($$)->op = constant;
			($$)->nargs = 1;
			($$)->arg1.ul = $1;
		}
		| PTR '[' num ']'
		{
			$$ = (exp_t *) _calloc(1, sizeof(exp_t));
			($$)->op = ptr;
			($$)->nargs = 1;
			($$)->arg1.ul = $3;
		}
		| PTR '[' num ',' num ']'
		{
			if ($3 >= $5) {
				sprintf(buff, "Morph %s: bad 'ptr' args", newmorph->name);
				yyerror(buff);
			}
			$$ = (exp_t *) _calloc(1, sizeof(exp_t));
			($$)->op = ptr;
			($$)->nargs = 2;
			($$)->arg1.ul = $3;
			($$)->arg2.ul = $5;
		}
		| opt_token attr_name
		{
			$$ = (exp_t *) _calloc(1, sizeof(exp_t));
			($$)->op = $2;
			($$)->nargs = 1;
			($$)->arg1.cp = newtoken;
		}
		|
		exp bool_op exp
		{
			$$ = (exp_t *) _calloc(1, sizeof(exp_t));
			($$)->op = $2;
			($$)->nargs = 2;
			($$)->arg1.p = $1;
			($$)->arg2.p = $3;
		};

opt_token:	TOKEN
		{
			newtoken = _strndup(yytext, yyleng);
		} '.'
		| /* empty */
		{
			newtoken = NULL;
		};

attr_name:	BASE
		{
			$$ = base;
		}
		| ADDR
		{
			$$ = addr;
		}
		| OFFSET
		{
			$$ = offset;
		};

bool_op:	EQ
		{
			$$ = eq;
		}
		| NE
		{
			$$ = ne;
		}
		| LT
		{
			$$ = lt;
		}
		| LE
		{
			$$ = le;
		}
		| GT
		{
			$$ = gt;
		}
		| GE
		{
			$$ = ge;
		};

num:		NUM
		{
			$$ = (long) yylval.ul;
		};

%%

morph_set *
parse(FILE* fp)
{
	lex_file_input(fp);
	yyparse();
	return newmorphset;
}

morph_t *
find_morph(morph_set *ms, char *name)
{
	int	i;
	morph_t	*p = (morph_t *)0;

	for (i = 0; i < ms->morphs_sz; i++) {
		p = ms->morphs[i];
		if (strcmp(p->name, name) == 0)
			break;
	}
	return p;
}

static int
unescape(char *src, int len)
{
	register char	*ptr = src;
	char		*end = ptr + len;
	char		*tgt = ptr;
	char		ch;

	while (ptr < end) {
		if (*ptr != '\\')
			*tgt++ = *ptr++;
		else if (++ptr < end) {
			ch = *ptr++;
			if (ch == '\\') {
				*tgt++ = '\\';
			} else if (isoctal(ch)) {
				ch -= '0';
				if (ptr < end && isoctal(*ptr))
					ch = (ch << 3) + (*ptr++ - '0');
				if (ptr < end && isoctal(*ptr))
					ch = (ch << 3) + (*ptr++ - '0');
				*tgt++ = ch;
			}
		}
	}
	return (tgt - src);
}

static void
dump_vec(FILE *fp, unsigned char *src, int len, int perline, int format)
{
	int	i;

	if (format == VEC_NUM_FMT) {
		if (len == 0)
			fputs("---------------", fp);		/* Highlight empty vectors. */
		for (i = 0; i < len; i++) {
			if (i > 0 && (i % perline) == 0) {
				fputs("\n\t\t\t", fp);
			}
			fprintf(fp, "0x%02x ", src[i]);
		}
		fputs("\n", fp);
	} else if (format == VEC_STR_FMT) {
		fputc('"', fp);
		for (; len-- > 0; src++) {
			if (*src == '\\')
				fputs("\\\\", fp);
			else if (isprint(*src))
				fputc(*src, fp);
			else
				fprintf(fp, "\\%03o", (unsigned int) *src);
		}
		fputs("\"\n", fp);
	}
}

void
dump_bool_op(FILE *fp, enum op_t op)
{
	if (op == eq)
		fputs("==", fp);
	else if (op == ne)
		fputs("!=", fp);
	else if (op == lt)
		fputs("<", fp);
	else if (op == le)
		fputs("<=", fp);
	else if (op == gt)
		fputs(">", fp);
	else if (op == ge)
		fputs(">=", fp);
}

static void
dump_exp(FILE *fp, exp_t *ep)
{
	if (ep->op == constant) {
		fprintf(fp, "%ld", ep->arg1.ul);
	}
	else if (ep->op == ptr) {
		fprintf(fp, "ptr[%ld", ep->arg1.ul);
		if (ep->nargs == 2)
			fprintf(fp, ",%ld", ep->arg2.ul);
		fputs("]", fp);
	}
	else if (ep->op == base || ep->op == addr || ep->op == offset) {
		if (ep->arg1.cp)
			fprintf(fp, "%s.", ep->arg1.cp);
		if (ep->op == base)
			fputs("base", fp);
		else if (ep->op == addr)
			fputs("addr", fp);
		else if (ep->op == offset)
			fputs("offset", fp);
	}
	else if (is_bool_op(ep->op)) {
		dump_exp(fp, ep->arg1.p);
		fputs(" ", fp);
		dump_bool_op(fp, ep->op);
		fputs(" ", fp);
		dump_exp(fp, ep->arg2.p);
	}
}

static void
dump_cmd(FILE *fp, cmd_t *cp)
{
	if (cp->verb == print)
		fputs("\tprint\t\t", fp);
	else if (cp->verb == assert)
		fputs("\tassert\t\t", fp);
	else {
		return;
	}
	dump_exp(fp, cp->exp);
	fputs("\n", fp);
}

void
dump_morph(FILE *fp, morph_set *ms, morph_t *mp, void (*eval_fn)(), void *vp)
{
	cmd_t	*cp;

	fprintf(fp, "\n%s {\n", mp->name);
	fprintf(fp, "\tsegment\t\t%s\n", seg_name(mp->seg));
	if (mp->msg)
		fprintf(fp, "\tmsg\t\t\"%s\"\n", mp->msg);
	if (mp->grade != DEFAULT_GRADE)
		fprintf(fp, "\tgrade\t\t%d\n", mp->grade);
	fprintf(fp, "\tbase\t\t0x%lx\n", mp->base);
	if (mp->seg == SEG_TEXT || mp->offset != 0)
		fprintf(fp, "\toffset\t\t0x%lx\n", mp->offset);
	if (mp->is_context) {
		fputs("\tcontext\t\t", fp);
		dump_vec(fp, mp->old.data, mp->old.len, 4, mp->old.format);
	} else {
		fputs("\told_value\t", fp);
		dump_vec(fp, mp->old.data, mp->old.len, 4, mp->old.format);
		fputs("\tnew_value\t", fp);
		dump_vec(fp, mp->new.data, mp->new.len, 4, mp->new.format);
	}

	for (cp = mp->cmds; cp; cp = cp->next) {
		dump_cmd(fp, cp);
		if (eval_fn)
			(*eval_fn)(cp, ms, mp, fp, vp);
	}

	fputs("}\n", fp);
}

static void
dump_seg_offset(FILE *fp, char *label, long val)
{
	if (val < 0)
		fprintf(fp, "\t%s\t-0x%lx\n", label, -val);
	else
		fprintf(fp, "\t%s\t0x%lx\n", label, val);
}

void
dump_init(FILE *fp, morph_set *ms)
{
	time_t	tm;

	tm = time((time_t *)0);
	fprintf(fp, "\n; Morphs dump generated at %s", ctime(&tm));

	fputs("\ninit {\n", fp);
	fprintf(fp, "\tgrammar\t\t%d.%d\n", GRAMMAR_MAJOR, GRAMMAR_MINOR);
	fprintf(fp, "\ttarget\t\t\"%s\"\n", ms->target);
	dump_seg_offset(fp, "text_offset", ms->offsets[SEG_TEXT]);
	dump_seg_offset(fp, "data_offset", ms->offsets[SEG_DATA]);
	dump_seg_offset(fp, "rodata_offset", ms->offsets[SEG_RODATA]);
	/* fprintf(fp, "\tmain\t\t0x%x\n", ms->main); */
	fputs("}\n", fp);
}

void
dump_set(FILE *fp, morph_set *ms, void (*eval_fn)(), void *vp)
{
	int	i;

	dump_init(fp, ms);
	for (i = 0; i < ms->morphs_sz; i++)
		dump_morph(fp, ms, ms->morphs[i], eval_fn, vp);
}
