#  Copyright © 1997—1999 Thomas Boutell <boutell@boutell.com>
#                        and Boutell.Com, Inc.
#            © 2003—2021 Sam Hocevar <sam@hocevar.net>
#
#  This software is released for free use under the terms of
#  the GNU Public License, version 2 or higher. NO WARRANTY
#  IS EXPRESSED OR IMPLIED. USE THIS SOFTWARE AT YOUR OWN RISK. */

%{
#if HAVE_CONFIG_H
#	include <config.h>
#endif

#include "net.h"
#include "types.h"
#include "rinetd.h"
#include "parse.h"

#define YY_CTX_LOCAL 1
#define YY_CTX_MEMBERS \
	FILE *fp; \
	int currentLine; \
	int isAuthAllow; \
	char *tmpPort, *bindPort, *connectPort; \
	int tmpProto, bindProto, connectProto; \
	int serverTimeout; \
	char *bindAddress, *connectAddress, *sourceAddress;
#define YY_INPUT(yyctx, buf, result, max_size) \
{ \
	int yyc = fgetc(yyctx->fp); \
	result = (EOF == yyc) ? 0 : (*(buf) = yyc, 1); \
}
#define PARSE_ERROR exit(1);
#define MEMORY_ERROR { \
		logError("could not allocate memory when parsing configuration.\n"); \
		exit(1); \
	}

#if defined __clang__
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wunused-label"
#elif defined __GNUC__
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-label"
#endif

%}

file     =  (sol (line eol | invalid-syntax))*
line     =  -? (command -?)? comment?
command  =  (server-rule | auth-rule | logfile | pidlogfile | logcommon)
comment  =  "#" (!eol .)*

server-rule  =  bind-address - bind-port - connect-address - connect-port (-? server-options)?
{
	addServer(yy->bindAddress, yy->bindPort, yy->bindProto,
		yy->connectAddress, yy->connectPort, yy->connectProto,
		yy->serverTimeout > 0 ? yy->serverTimeout : RINETD_DEFAULT_UDP_TIMEOUT,
		yy->sourceAddress);
	free(yy->bindAddress);
	free(yy->connectAddress);
	free(yy->sourceAddress);
	yy->bindAddress = yy->connectAddress = yy->sourceAddress = NULL;
	free(yy->bindPort);
	free(yy->connectPort);
	yy->bindPort = yy->connectPort = NULL;
	yy->serverTimeout = 0;
}

bind-address     =  < address > { yy->bindAddress = strdup(yytext); }
connect-address  =  < address > { yy->connectAddress = strdup(yytext); }
bind-port        =  full-port { yy->bindPort = yy->tmpPort; yy->bindProto = yy->tmpProto; }
connect-port     =  full-port { yy->connectPort = yy->tmpPort; yy->connectProto = yy->tmpProto; }
server-options   =  "[" -? option-list -? "]"

option-list     =  option (-? ',' -? option-list)?
option          =  (option-timeout | option-source)
option-timeout  =  "timeout" -? "=" -? < number >    { yy->serverTimeout = atoi(yytext); }
option-source   =  "src"     -? "=" -? < address >   { yy->sourceAddress = strdup(yytext); }

full-port  =  port proto

port   = < (number | service) >   { yy->tmpPort = strdup(yytext); }
proto  = '/tcp'                   { yy->tmpProto = IPPROTO_TCP; }
       | '/udp'                   { yy->tmpProto = IPPROTO_UDP; }
       | ''                       { yy->tmpProto = IPPROTO_TCP; }

auth-rule  =  auth-key - < pattern >
{
	allRules = (Rule *)
		realloc(allRules, sizeof(Rule) * (allRulesCount + 1));
	if (!allRules) {
		MEMORY_ERROR;
	}
	allRules[allRulesCount].pattern = strdup(yytext);
	if (!allRules[allRulesCount].pattern) {
		MEMORY_ERROR;
	}
	allRules[allRulesCount].type = yy->isAuthAllow ? allowRule : denyRule;
	if (seTotal > 0) {
		if (seInfo[seTotal - 1].rulesStart == 0) {
			seInfo[seTotal - 1].rulesStart = allRulesCount;
		}
		++seInfo[seTotal - 1].rulesCount;
	} else {
		++globalRulesCount;
	}
	++allRulesCount;
}

auth-key = < ("allow" | "deny") >         { yy->isAuthAllow = (yytext[0] == 'a'); }

logfile  =  "logfile" - < filename >
{
	logFileName = strdup(yytext);
	if (!logFileName) {
		MEMORY_ERROR;
	}
}

pidlogfile  =  "pidlogfile" - < filename >
{
	pidLogFileName = strdup(yytext);
	if (!pidLogFileName) {
		MEMORY_ERROR;
	}
}

logcommon  =  "logcommon"
{
	logFormatCommon = 1;
}

invalid_syntax  =  < (!eol .)+ > eol
{
	logError("invalid syntax at line %d: %s\n",
	        yy->currentLine, yytext);
	PARSE_ERROR; /* FIXME */
}

service  =  name
address  =  ipv4 | ipv6 | hostname
pattern  =  (hexdigit | '[' | ']' | ':' | '.' | [*?] )+
number   =  digit+

ipv4      =  number '.' number '.' number '.' number | '0'
ipv6      = '[' bare_ipv6 ']' | bare_ipv6
bare_ipv6 =  hexdigit* ':' (hexdigit | ':')* # Must have at least one ':'
hostname  =  (label '.')* name '.'?


name     =  id ('-' | id | digit)*
label    =  (id | digit) ('-' | id | digit)* # DNS labels may have digits
filename =  '"' [^"]+ '"'
         |  [^ \t\r\n]+

-        =  [ \t]+
digit    =  [0-9]
hexdigit =  [0-9a-fA-F]
id       =  [a-zA-Z_]
sol      =  { ++yy->currentLine; }
eol      =  '\r'? '\n' | eof
eof      =  '\0'

%%

void parseConfiguration(char const *file)
{
	FILE *in = fopen(file, "r");
	if (!in) {
		logError("could not open configuration file %s.\n", file);
		exit(1);
	}

	yycontext ctx;
	memset(&ctx, 0, sizeof(yycontext));
	ctx.fp = in;
	if (!yyparse(&ctx)) {
		logError("invalid syntax in file %s, line %d.\n", file, -1);
		exit(1);
	}
	yyrelease(&ctx);

	fclose(in);

	/* Avoid warnings for these unused functions */
	(void)yySet; (void)yyPush; (void)yyPop; (void)yyAccept;
}

