#if !defined(lint) && !defined(SABER)
static const char rcsid[] = "$Id: ns_lexer.c,v 1.1.1.3 2001/01/31 03:59:48 zarzycki Exp $";
#endif /* not lint */

/*
 * Copyright (c) 1996-2000 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#include "port_before.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>

#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <stdarg.h>
#include <syslog.h>

#include <isc/eventlib.h>
#include <isc/logging.h>
#include <isc/memcluster.h>

#include "port_after.h"

#include "named.h"
#include "ns_parser.h"
#include "ns_parseutil.h"
#include "ns_lexer.h"

typedef enum lexer_state {
		scan, number, identifier, ipv4, quoted_string
} LexerState;

#define LEX_EOF			0x01
#define LEXER_MAX_PUSHBACK	2

typedef struct lexer_file_context {
	const char *	name;
	FILE *		stream;
	int		line_number;
	LexerState	state;
	u_int		flags;
        int		warnings;
        int		errors;
	u_int		pushback_count;
	char		pushback[LEXER_MAX_PUSHBACK];
	struct lexer_file_context *
			next;
} *LexerFileContext;

LexerFileContext current_file = NULL;

#define LEX_LAST_WAS_DOT	0x01
#define LEX_CONSECUTIVE_DOTS	0x02

typedef struct lexer_identifier {
	char buffer[LEX_MAX_IDENT_SIZE+1];
	int index;
	int num_dots;
	unsigned int flags;
} *LexerIdentifier;

static LexerIdentifier id;

static char special_chars[256];

#define whitespace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
#define domain_char(c) (isalnum((c)) || (c) == '.' || (c) == '-')
#define special_char(c) (special_chars[(c)] == 1)
#define identifier_char(c) (!whitespace(c) && !special_char(c))

static int last_token;
static YYSTYPE last_yylval;

static int lexer_initialized = 0;

/*
 * Problem Reporting
 */

static char *
token_to_text(int token, YYSTYPE lval) {
	static char buffer[LEX_MAX_IDENT_SIZE+50];

	if (token < 128) {
		if (token == 0)
			strcpy(buffer, "<end of file>");
		else
			sprintf(buffer, "'%c'", token);
	} else {
		switch (token) {
		case L_EOS:
			strcpy(buffer, ";");
			break;
		case L_STRING:
			sprintf(buffer, "'%s'", lval.cp);
			break;
		case L_QSTRING:
			sprintf(buffer, "\"%s\"", lval.cp);
			break;
		case L_IPADDR:
			sprintf(buffer, "%s", inet_ntoa(lval.ip_addr));
			break;
		case L_NUMBER:
			sprintf(buffer, "%ld", lval.num);
			break;	
		case L_END_INCLUDE:
			sprintf(buffer, "<end of include>");
			break;
		default:
			sprintf(buffer, "%s", lval.cp);
		}
	}

	return (buffer);
}

static char where[MAXPATHLEN + 100];
static char message[20480];

static void
parser_complain(int is_warning, int print_last_token, const char *format,
		va_list args)
{
	LexerFileContext lf;
	int severity;

	if (is_warning) {
		severity = log_warning;
	} else {
		severity = log_error;
	}

	INSIST(current_file != NULL);
	if (current_file->next != NULL) {
		for (lf = current_file; lf != NULL; lf = lf->next) {
			log_write(log_ctx, ns_log_parser, severity,
				  "%s '%s' line %d", 
				  (lf == current_file) ? 
				  "In" : "included from",
				  lf->name, lf->line_number);
		}
	}
	sprintf(where, "%s:%d: ", current_file->name,
		current_file->line_number);
	vsprintf(message, format, args);
	if (print_last_token)
		log_write(log_ctx, ns_log_parser, severity, "%s%s near %s",
			  where, message,
			  token_to_text(last_token, last_yylval));
	else
		log_write(log_ctx, ns_log_parser, severity,
			  "%s%s", where, message);
}

int
parser_warning(int print_last_token, const char *format, ...) {
	va_list args;

	va_start(args, format);
	parser_complain(1, print_last_token, format, args);
	va_end(args);
	current_file->warnings++;
	return (1);
}

int
parser_error(int print_last_token, const char *format, ...) {
	va_list args;

	va_start(args, format);
	parser_complain(0, print_last_token, format, args);
	va_end(args);
	current_file->errors++;
	return (1);
}

void
yyerror(const char *message) {
	parser_error(1, message);
}

/*
 * Keywords
 */

struct keyword {
        char *name;
	int token;
};

/*
 * "keywords" is an array of the keywords which are the fixed syntactic
 * elements of the configuration file.  Each keyword has a string version
 * of the keyword and a token id, which should be an identifier which
 * matches that in a %token statement inside the parser.y file.
 */
static struct keyword keywords[] = {
	{"acl", T_ACL}, 
	{"address", T_ADDRESS},
	{"algorithm", T_ALGID},
	{"allow", T_ALLOW},
	{"allow-query", T_ALLOW_QUERY}, 
	{"allow-recursion", T_ALLOW_RECURSION},
	{"allow-transfer", T_ALLOW_TRANSFER},
	{"allow-update", T_ALLOW_UPDATE},
#ifdef BIND_NOTIFY
	{"also-notify", T_ALSO_NOTIFY},
#endif
	{"auth-nxdomain", T_AUTH_NXDOMAIN},
	{"blackhole", T_BLACKHOLE},
	{"bogus", T_BOGUS},
	{"category", T_CATEGORY},
	{"class", T_CLASS},
	{"channel", T_CHANNEL},
	{"check-names", T_CHECK_NAMES},
	{"cleaning-interval", T_CLEAN_INTERVAL},
	{"controls", T_CONTROLS},
	{"coresize", T_CORESIZE},
	{"datasize", T_DATASIZE},
	{"deallocate-on-exit", T_DEALLOC_ON_EXIT},
	{"debug", T_DEBUG},
	{"default", T_DEFAULT},
	{"dialup", T_DIALUP},
	{"directory", T_DIRECTORY}, 
	{"dump-file", T_DUMP_FILE},
	{"dynamic", T_DYNAMIC},
	{"fail", T_FAIL},
	{"fake-iquery", T_FAKE_IQUERY},
	{"false", T_FALSE},
	{"fetch-glue", T_FETCH_GLUE},
	{"file", T_FILE}, 
	{"files", T_FILES}, 
	{"first", T_FIRST}, 
	{"forward", T_FORWARD},
	{"forwarders", T_FORWARDERS},
	{"group", T_GROUP},
	{"has-old-clients", T_HAS_OLD_CLIENTS},
	{"heartbeat-interval", T_HEARTBEAT},
	{"hint", T_HINT},
#ifdef HITCOUNTS
	{"hit-count", T_HITCOUNT},
#endif /* HITCOUNTS */
	{"host-statistics", T_HOSTSTATS},
	{"host-statistics-max", T_HOSTSTATSMAX},
	{"if-no-answer", T_IF_NO_ANSWER},
	{"if-no-domain", T_IF_NO_DOMAIN},
	{"ignore", T_IGNORE},
	{"include", T_INCLUDE},
	{"inet", T_INET},
	{"interface-interval", T_INTERFACE_INTERVAL},
	{"ixfr-base", T_FILE_IXFR},
	{"ixfr-tmp-file", T_IXFR_TMP},
	{"key", T_SEC_KEY},
	{"keys", T_KEYS},
	{"lame-ttl", T_LAME_TTL},
	{"listen-on", T_LISTEN_ON},
	{"logging", T_LOGGING},
	{"maintain-ixfr-base", T_MAINTAIN_IXFR_BASE},
	{"many-answers", T_MANY_ANSWERS},
	{"master", T_MASTER},
	{"masters", T_MASTERS},
	{"max-ixfr-log-size", T_MAX_LOG_SIZE_IXFR},
	{"max-ncache-ttl", T_MAX_NCACHE_TTL},
	{"max-transfer-time-in", T_MAX_TRANSFER_TIME_IN},
	{"memstatistics-file", T_MEMSTATS_FILE},
	{"min-roots", T_MIN_ROOTS},
	{"multiple-cnames", T_MULTIPLE_CNAMES},
	{"name", T_NAME},
	{"named-xfer", T_NAMED_XFER},
	{"no", T_NO},
#ifdef BIND_NOTIFY
	{"notify", T_NOTIFY},
#endif
	{"null", T_NULL_OUTPUT},
	{"one-answer", T_ONE_ANSWER},
	{"only", T_ONLY},
	{"order", T_ORDER},
	{"options", T_OPTIONS},
	{"owner", T_OWNER},
	{"perm", T_PERM},
	{"pid-file", T_PIDFILE},
	{"port", T_PORT},
	{"print-category", T_PRINT_CATEGORY},
	{"print-severity", T_PRINT_SEVERITY},
	{"print-time", T_PRINT_TIME},
	{"pubkey", T_PUBKEY},
	{"query-source", T_QUERY_SOURCE},
	{"rfc2308-type1", T_RFC2308_TYPE1},
	{"rrset-order", T_RRSET_ORDER},
	{"recursion", T_RECURSION},
	{"response", T_RESPONSE},
	{"secret", T_SECRET},
	{"serial-queries", T_SERIAL_QUERIES},
	{"server", T_SERVER}, 
	{"severity", T_SEVERITY}, 
	{"size", T_SIZE}, 
	{"slave", T_SLAVE},
	{"sortlist", T_SORTLIST},
	{"stacksize", T_STACKSIZE},
	{"statistics-file", T_STATS_FILE},
	{"statistics-interval", T_STATS_INTERVAL},
	{"stub", T_STUB},
	{"support-ixfr", T_SUPPORT_IXFR},
	{"syslog", T_SYSLOG}, 
	{"topology", T_TOPOLOGY},
	{"transfer-format", T_TRANSFER_FORMAT}, 
	{"transfer-source", T_TRANSFER_SOURCE},
	{"transfers", T_TRANSFERS}, 
	{"transfers-in", T_TRANSFERS_IN}, 
	{"transfers-out", T_TRANSFERS_OUT}, 
	{"transfers-per-ns", T_TRANSFERS_PER_NS}, 
	{"treat-cr-as-space", T_TREAT_CR_AS_SPACE},
	{"true", T_TRUE}, 
	{"trusted-keys", T_TRUSTED_KEYS},
	{"type", T_TYPE},
	{"unix", T_UNIX},
	{"unlimited", T_UNLIMITED},
	{"use-id-pool", T_USE_ID_POOL},
	{"use-ixfr", T_USE_IXFR},
	{"version", T_VERSION},
	{"versions", T_VERSIONS}, 
	{"warn", T_WARN},
	{"yes", T_YES}, 
	{"zone", T_ZONE},
	{(char *) NULL, 0},
};

/*
 * The table size should be a prime chosen to minimize collisions.
 */
#define KEYWORD_TABLE_SIZE 461

static symbol_table keyword_table = NULL;

static void
init_keywords() {
	struct keyword *k;
	symbol_value value;

	if (keyword_table != NULL)
		free_symbol_table(keyword_table);
	keyword_table = new_symbol_table(KEYWORD_TABLE_SIZE, NULL);
	for (k = keywords; k->name != NULL; k++) {
		value.integer = k->token;
		define_symbol(keyword_table, k->name, 0, value, 0);
	}
	dprint_symbol_table(99, keyword_table);
}

/*
 * File Contexts
 */

void
lexer_begin_file(const char *filename, FILE *stream) {
	LexerFileContext lf;

	if (stream == NULL) {
		stream = fopen(filename, "r");
		if (stream == NULL) {
			parser_error(0, "couldn't open include file '%s'",
				     filename);
			return;
		}
	}
	lf = (LexerFileContext)memget(sizeof (struct lexer_file_context));
	if (lf == NULL)
		panic("memget failed in lexer_begin_file", NULL);
	INSIST(stream != NULL);
	lf->stream = stream;
	lf->name = filename;  /* note copy by reference */
	lf->line_number = 1;
	lf->state = scan;
	lf->flags = 0;
	lf->warnings = 0;
	lf->errors = 0;
	lf->pushback_count = 0;
	lf->next = current_file;
	current_file = lf;
}

void
lexer_end_file(void) {
	LexerFileContext lf;

	INSIST(current_file != NULL);
	lf = current_file;
	current_file = lf->next;
	fclose(lf->stream);
	memput(lf, sizeof *lf);
}

/*
 * Character Input
 */

#define LEXER_GETC(c, cf) \
	do { \
		if ((cf)->pushback_count > 0) { \
			(cf)->pushback_count--; \
			(c) = (cf)->pushback[(cf)->pushback_count]; \
		} else \
			(c) = getc((cf)->stream); \
	} while (0);

#define LEXER_UNGETC(c, cf) \
	do { \
		INSIST((cf)->pushback_count < LEXER_MAX_PUSHBACK); \
		(cf)->pushback[(cf)->pushback_count++] = (c); \
	} while (0);

static void
scan_to_comment_end(int c_plus_plus_style) {
	int c;
	int done = 0;
	int prev_was_star = 0;

	while (!done) {
		LEXER_GETC(c, current_file);
		switch (c) {
		case EOF:
			if (!c_plus_plus_style)
				parser_error(0, "EOF in comment");
			current_file->flags |= LEX_EOF;
			done = 1;
			break;
		case '*':
			prev_was_star = 1;
			break;
		case '/':
			if (prev_was_star && !c_plus_plus_style)
				done = 1;
			prev_was_star = 0;
			break;
		case '\n':
			if (c_plus_plus_style) {
				/* don't consume the newline because
				   we want it to be a delimiter for
				   anything before the comment
				   started */
				LEXER_UNGETC(c, current_file);
				done = 1;
			} else {
				current_file->line_number++;
			}
			prev_was_star = 0;
			break;
		default:
			prev_was_star = 0;
		}
	}
}

int
get_next_char(int comment_ok) {
	int c, nc;

	if (current_file->flags & LEX_EOF)
		return (EOF);

	LEXER_GETC(c, current_file);

	if (comment_ok) {
		while (c == '/' || c == '#') {
			if (c == '#') {
				scan_to_comment_end(1);
				if (current_file->flags & LEX_EOF)
					return (EOF);
				LEXER_GETC(c, current_file);
			} else {
				LEXER_GETC(nc, current_file);
				switch (nc) {
				case EOF:
					current_file->flags |= LEX_EOF;
					return ('/');
				case '*':
				case '/':
					scan_to_comment_end((nc == '/'));
					if (current_file->flags & LEX_EOF)
						return (EOF);
					LEXER_GETC(c, current_file);
					break;
				default:
					LEXER_UNGETC(nc, current_file);
					return ('/');
				}
			}
		}
	}

	if (c == EOF)
		current_file->flags |= LEX_EOF;
	else if (c == '\n')
		current_file->line_number++;
	return (c);
}

void
put_back_char(int c) {
	if (c == EOF)
		current_file->flags |= LEX_EOF;
	else {
		LEXER_UNGETC(c, current_file);
		if (c == '\n')
			current_file->line_number--;
	}
}


/*
 * Identifiers
 */

static void
clear_identifier(LexerIdentifier id) {
	INSIST(id != NULL);
	id->index = 0;
	id->num_dots = 0;
	id->flags = 0;
}

static char *
dup_identifier(LexerIdentifier id) {
	char *duplicate;

	INSIST(id != NULL);
	duplicate = savestr(id->buffer, 1);
	return (duplicate);
}

static void
finish_identifier(LexerIdentifier id) {
	INSIST(id != NULL && id->index < LEX_MAX_IDENT_SIZE);
	id->buffer[id->index] = '\0';
}

static void
add_to_identifier(LexerIdentifier id, int c) {
	INSIST(id != NULL);
	id->buffer[id->index] = c;
	id->index++;
	if (id->index >= LEX_MAX_IDENT_SIZE) {
		parser_error(0, "identifier too long");
		current_file->state = scan;
		/* discard chars until we hit a non-identifier char */
		while (c != EOF && identifier_char(c)) {
			c = get_next_char(1);
		}
		put_back_char(c);
		clear_identifier(id);
	} else {
		if (c == '.') {
			if (id->flags & LEX_LAST_WAS_DOT)
				id->flags |= LEX_CONSECUTIVE_DOTS;
			id->flags |= LEX_LAST_WAS_DOT;
			id->num_dots++;
		} else {
			id->flags &= ~LEX_LAST_WAS_DOT;
		}
	}
}

/*
 * yylex() -- return the next token from the current input stream
 */
int
yylex() {
	int c;
	int comment_ok = 1;
	int token = -1;
	symbol_value value;

	while (token < 0) {
		c = get_next_char(comment_ok);
		switch(current_file->state) {
		case scan:
			if (c == EOF) {
				if (current_file->next == NULL)
					/*
					 * We don't want to call
					 * lexer_end_file() here because we
					 * want to keep the toplevel file
					 * context to log errors against.
					 */
					token = 0;
				else {
					lexer_end_file();
					token = L_END_INCLUDE;
				}
				break;
			}
			if (whitespace(c))
				break;
			if (identifier_char(c)) {
				if (isdigit(c))
					current_file->state = number;
				else
					current_file->state = identifier;
				clear_identifier(id);
				add_to_identifier(id, c);
			} else
				if (special_char(c)) {
					if (c == ';') {
						token = L_EOS; 
						break;
					}
					if (c == '"') {
						clear_identifier(id);
						current_file->state =
							quoted_string;
						comment_ok = 0;
						break;
					}
					token = c;
				} else {
					parser_error(0,
						     "invalid character '%c'",
						     c);
				}
			break;

		case number:
			if (c != EOF && identifier_char(c)) {
				if (!isdigit(c))
					current_file->state =
						(c == '.') ? ipv4 : identifier;
				add_to_identifier(id, c);
			} else {
				put_back_char(c);
				current_file->state = scan;
				finish_identifier(id);
				yylval.num = strtol(id->buffer, (char**)0, 0);
				token = L_NUMBER;
			}
			break;

		case identifier:
			if (c != EOF && identifier_char(c)) {
				add_to_identifier(id, c);
			} else {
				put_back_char(c);
				current_file->state = scan;
				finish_identifier(id);
				/* is it a keyword? */
				if (lookup_symbol(keyword_table, id->buffer,
						  0, &value)) {
					yylval.cp = id->buffer;
					token = value.integer;
				} else {
					yylval.cp = dup_identifier(id);
					token = L_STRING;
				}
			}
			break;

		case ipv4:
			if (c != EOF && identifier_char(c)) {
				if (!isdigit(c)) {
					if (c != '.' ||
					    (id->flags & LEX_CONSECUTIVE_DOTS))
						current_file->state =
							identifier;
				}
				add_to_identifier(id, c);
			} else {
				put_back_char(c);
				if (id->num_dots > 3 ||
				    (id->flags & LEX_LAST_WAS_DOT))
					current_file->state = identifier;
				else {
					if (id->num_dots == 1) {
						add_to_identifier(id, '.');
						add_to_identifier(id, '0');
						add_to_identifier(id, '.');
						add_to_identifier(id, '0');
					} else if (id->num_dots == 2) {
						add_to_identifier(id, '.');
						add_to_identifier(id, '0');
					}
					current_file->state = scan;
					finish_identifier(id);
					token = L_IPADDR;
					if (inet_aton(id->buffer,
						      &(yylval.ip_addr))==0) {
						yylval.cp = dup_identifier(id);
						token = L_STRING;
					}
				}
			}
			break;

		case quoted_string:
			if (c == EOF) {
				parser_error(0, "EOF in quoted string");
				return 0;
			} else {
				if (c == '"') {
					comment_ok = 1;
					current_file->state = scan;
					finish_identifier(id);
					yylval.cp = dup_identifier(id);
					token = L_QSTRING;
				} else {
					/* XXX add backslash escapes here */
					add_to_identifier(id, c);
				}
			}
			break;
			
		default:
			panic("unhandled state in yylex", NULL);
		}
	}

	last_token = token;
	last_yylval = yylval;
	return (token);
}

/*
 * Initialization
 */

symbol_table constants;

static void
import_constants(const struct ns_sym *s, int type) {
	symbol_value value;
	for ((void)NULL; s != NULL && s->name != NULL; s++) {
		value.integer = s->number;
		define_symbol(constants, s->name, type, value, 0);
	}
}	

static void
import_res_constants(const struct res_sym *r, int type) {
	symbol_value value;
	for ((void)NULL; r != NULL && r->name != NULL; r++) {
		value.integer = r->number;
		define_symbol(constants, r->name, type, value, 0);
	}
}	

#define CONSTANTS_TABLE_SIZE 397	/* should be prime */

static void
import_all_constants() {
	constants = new_symbol_table(CONSTANTS_TABLE_SIZE, NULL);
	import_res_constants(__p_class_syms, SYM_CLASS);
	import_constants(category_constants, SYM_CATEGORY);
	import_constants(logging_constants, SYM_LOGGING);
	import_constants(syslog_constants, SYM_SYSLOG);
}

void
lexer_initialize() {
	memset(special_chars, 0, sizeof special_chars);
	special_chars[';'] = 1;
	special_chars['{'] = 1;
	special_chars['}'] = 1;
	special_chars['!'] = 1;
	special_chars['/'] = 1;
	special_chars['"'] = 1;
	special_chars['*'] = 1;
	id = (LexerIdentifier)memget(sizeof (struct lexer_identifier));
	if (id == NULL)
		panic("memget failed in lexer_initialize", NULL);
	init_keywords();
	import_all_constants();
	lexer_initialized = 1;
}

void
lexer_setup(void) {
	REQUIRE(lexer_initialized);

	current_file = NULL;    /* XXX should we INSIST(current_file==NULL)? */
	INSIST(id != NULL);
}

void
lexer_shutdown(void) {
	REQUIRE(lexer_initialized);

	free_symbol_table(keyword_table);
	free_symbol_table(constants);
	memput(id, sizeof (struct lexer_identifier));
	id = NULL;
	lexer_initialized = 0;
}
