%{
#define YY_CTX_LOCAL 1
#define YY_CTX_MEMBERS \
    FILE *fp; \
    int currentLine; \
    int isAuthAllow; \
    unsigned int port, bindPort, connectPort; \
    char *bindAddress, *connectAddress;
#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);
%}

file     =  (sol (line eol | invalid_syntax))*
line     =  -? (rule | auth | logfile | pidlogfile | logcommon )? -? comment?

comment  =  "#" (!eol .)*

rule  =  bind_address - bind_port - connect_address - connect_port {
	/* Turn all of this stuff into reasonable addresses */
	struct in_addr iaddr;
	if (getAddress(yy->bindAddress, &iaddr) < 0) {
		fprintf(stderr, "rinetd: host %s could not be resolved.\n",
			yy->bindAddress);
		PARSE_ERROR;
	}
	/* Make a server socket */
	SOCKET fd = socket(PF_INET, SOCK_STREAM, 0);
	if (fd == INVALID_SOCKET) {
		syslog(LOG_ERR, "couldn't create "
			"server socket! (%m)\n");
		PARSE_ERROR;
	}
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	memcpy(&saddr.sin_addr, &iaddr, sizeof(iaddr));
	saddr.sin_port = htons(yy->bindPort);
	int tmp = 1;
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
		(const char *) &tmp, sizeof(tmp));
	if (bind(fd, (struct sockaddr *)
		&saddr, sizeof(saddr)) == SOCKET_ERROR)
	{
		/* Warn -- don't exit. */
		syslog(LOG_ERR, "couldn't bind to "
			"address %s port %d (%m)\n",
			yy->bindAddress, yy->bindPort);
		closesocket(fd);
		PARSE_ERROR;
	}
	if (listen(fd, RINETD_LISTEN_BACKLOG) == SOCKET_ERROR) {
		/* Warn -- don't exit. */
		syslog(LOG_ERR, "couldn't listen to "
			"address %s port %d (%m)\n",
			yy->bindAddress, yy->bindPort);
		closesocket(fd);
		PARSE_ERROR;
	}
#if _WIN32
	u_long ioctltmp;
#else
	int ioctltmp;
#endif
	ioctlsocket(fd, FIONBIO, &ioctltmp);
	if (getAddress(yy->connectAddress, &iaddr) < 0) {
		/* Warn -- don't exit. */
		syslog(LOG_ERR, "host %s could not be resolved.\n",
			yy->bindAddress);
		closesocket(fd);
		PARSE_ERROR;
	}
	/* Allocate server info */
	seInfo = (ServerInfo *)
		realloc(seInfo, sizeof(ServerInfo) * (seTotal + 1));
	if (!seInfo) {
		PARSE_ERROR;
	}
	ServerInfo *srv = &seInfo[seTotal];
	memset(srv, 0, sizeof(*srv));
	srv->fd = fd;
	srv->localAddr = iaddr;
	srv->localPort = htons(yy->connectPort);
	srv->fromHost = yy->bindAddress;
	if (!srv->fromHost) {
		PARSE_ERROR;
	}
	srv->fromPort = yy->bindPort;
	srv->toHost = yy->connectAddress;
	if (!srv->toHost) {
		PARSE_ERROR;
	}
	srv->toPort = yy->connectPort;
#ifndef _WIN32
	if (fd > maxfd) {
		maxfd = fd;
	}
#endif
	++seTotal;

	yy->bindAddress = yy->connectAddress = NULL;
}

bind_address     =  < ipv4 > { yy->bindAddress = strdup(yytext); }
connect_address  =  < ipv4 > { yy->connectAddress = strdup(yytext); }
bind_port        =  port { yy->bindPort = yy->port; }
connect_port     =  port { yy->connectPort = yy->port; }

port  =  < (number | service) > {
	struct servent *bindService = getservbyname(yytext, "tcp");
	yy->port = bindService ? ntohs(bindService->s_port) : atoi(yytext);
	if (yy->port == 0 || yy->port >= 65536) {
		syslog(LOG_ERR, "bind port missing or out of range\n");
		PARSE_ERROR;
	}
}

auth  =  auth_key - < pattern > {
	allRules = (Rule *)
		realloc(allRules, sizeof(Rule) * (allRulesCount + 1));
	if (!allRules) {
		PARSE_ERROR;
	}
	allRules[allRulesCount].pattern = strdup(yytext);
	if (!allRules[allRulesCount].pattern) {
		PARSE_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) {
		PARSE_ERROR;
	}
}

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

logcommon  =  "logcommon" {
	logFormatCommon = 1;
}

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

service  =  name
ipv4     =  number [.] number [.] number [.] number | '0'
pattern  =  [0-9*?]+ ('.' [0-9*?]+ ('.' [0-9*?]+ ('.' [0-9*?]+)?)?)?
number   =  digit+

name     =  [a-zA-Z][a-zA-Z0-9_]*
filename =  '"' [^"]+ '"'
         | [^ \t\r\n]+

-        =  [ \t]+
digit    =  [0-9]
sol      =  { ++yy->currentLine; }
eol      =  '\r'? '\n' | eof
eof      =  '\0'