Implement a new parser using a PEG grammar.

This commit is contained in:
Sam Hocevar 2017-09-04 20:14:11 +02:00
parent b3550b8e77
commit 718f4cddf7
5 changed files with 1289 additions and 203 deletions

View File

@ -8,7 +8,13 @@ man_MANS = rinetd.8
sysconf_DATA = rinetd.conf
sbin_PROGRAMS = rinetd
rinetd_SOURCES = rinetd.c rinetd.h match.c match.h
rinetd_SOURCES = rinetd.c rinetd.h parse.h match.c match.h
GENERATED_SOURCES = parse.h
# If peg/leg is available, regenerate parse.h
parse.h: parse.peg
if which leg >/dev/null 2>&1; then leg -o $@ $^; else touch $@; fi
# _POSIX_C_SOURCE is for SA_RESTART and others
# _XOPEN_SOURCE is for struct sigaction

1088
parse.h Normal file

File diff suppressed because it is too large Load Diff

176
parse.peg Normal file
View File

@ -0,0 +1,176 @@
%{
#define YY_CTX_LOCAL 1
#define YY_CTX_MEMBERS \
FILE *fp; \
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 = (line | invalid_syntax)*
line = -? (rule | auth | logfile | pidlogfile | logcommon)? -? comment? eol
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 { 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]
eol = '\r'? '\n' | eof
eof = '\0'

217
rinetd.c
View File

@ -153,12 +153,14 @@ static void refuse(ConnectionInfo *cnx, int logCode);
static int readArgs (int argc, char **argv, RinetdOptions *options);
static int getConfLine(FILE *in, char *line, int space, int *lnum);
static void clearConfiguration(void);
static void readConfiguration(void);
static void readConfiguration(char const *file);
static void registerPID(void);
static void logEvent(ConnectionInfo const *cnx, ServerInfo const *srv, int result);
static struct tm *get_gmtoff(int *tz);
#include "parse.h"
/* Signal handlers */
#if !HAVE_SIGACTION && !_WIN32
static RETSIGTYPE plumber(int s);
@ -213,7 +215,7 @@ int main(int argc, char *argv[])
signal(SIGINT, quit);
signal(SIGTERM, quit);
readConfiguration();
readConfiguration(options.conf_file);
registerPID();
syslog(LOG_INFO, "Starting redirections...");
@ -258,210 +260,23 @@ static void clearConfiguration(void) {
pidLogFileName = NULL;
}
static void readConfiguration(void) {
static void readConfiguration(char const *file) {
/* Parse the configuration file. */
FILE *in = fopen(options.conf_file, "r");
FILE *in = fopen(file, "r");
if (!in) {
goto lowMemory;
}
for (int lnum = 0; ; ) {
char line[16384];
if (!getConfLine(in, line, sizeof(line), &lnum)) {
break;
}
char const *currentToken = strtok(line, " \t\r\n");
if (!currentToken) {
syslog(LOG_ERR, "no bind address specified "
"on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
if (!strcmp(currentToken, "allow")
|| !strcmp(currentToken, "deny")) {
char const *pattern = strtok(0, " \t\r\n");
if (!pattern) {
syslog(LOG_ERR, "nothing to %s "
"specified on file %s, line %d.\n", currentToken, options.conf_file, lnum);
continue;
}
int bad = 0;
for (char const *p = pattern; *p; ++p) {
if (!strchr("0123456789?*.", *p)) {
bad = 1;
break;
}
}
if (bad) {
syslog(LOG_ERR, "illegal allow or "
"deny pattern. Only digits, ., and\n"
"the ? and * wild cards are allowed. "
"For performance reasons, rinetd\n"
"does not look up complete "
"host names.\n");
continue;
}
allRules = (Rule *)
realloc(allRules, sizeof(Rule) * (allRulesCount + 1));
if (!allRules) {
goto lowMemory;
}
allRules[allRulesCount].pattern = strdup(pattern);
if (!allRules[allRulesCount].pattern) {
goto lowMemory;
}
allRules[allRulesCount].type = currentToken[0] == 'a' ? allowRule : denyRule;
if (seTotal > 0) {
if (seInfo[seTotal - 1].rulesStart == 0) {
seInfo[seTotal - 1].rulesStart = allRulesCount;
}
++seInfo[seTotal - 1].rulesCount;
} else {
++globalRulesCount;
}
++allRulesCount;
} else if (!strcmp(currentToken, "logfile")) {
char const *nt = strtok(0, " \t\r\n");
if (!nt) {
syslog(LOG_ERR, "no log file name "
"specified on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
logFileName = strdup(nt);
if (!logFileName) {
goto lowMemory;
}
} else if (!strcmp(currentToken, "pidlogfile")) {
char const *nt = strtok(0, " \t\r\n");
if (!nt) {
syslog(LOG_ERR, "no PID log file name "
"specified on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
pidLogFileName = strdup(nt);
if (!pidLogFileName) {
goto lowMemory;
}
} else if (!strcmp(currentToken, "logcommon")) {
logFormatCommon = 1;
} else {
/* A regular forwarding rule. */
char const *bindAddress = currentToken;
char const *bindPortS = strtok(0, " \t\r\n");
if (!bindPortS) {
syslog(LOG_ERR, "no bind port "
"specified on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
struct servent *bindService = getservbyname(bindPortS, "tcp");
unsigned int bindPort = bindService ? ntohs(bindService->s_port) : atoi(bindPortS);
if (bindPort == 0 || bindPort >= 65536) {
syslog(LOG_ERR, "bind port missing "
"or out of range on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
char const *connectAddress = strtok(0, " \t\r\n");
if (!connectAddress) {
syslog(LOG_ERR, "no connect address "
"specified on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
char const *connectPortS = strtok(0, " \t\r\n");
if (!connectPortS) {
syslog(LOG_ERR, "no connect port "
"specified on file %s, line %d.\n", options.conf_file, lnum);
continue;
}
struct servent *connectService = getservbyname(connectPortS, "tcp");
unsigned int connectPort = connectService ? ntohs(connectService->s_port) : atoi(connectPortS);
if (connectPort == 0 || connectPort >= 65536) {
syslog(LOG_ERR, "bind port missing "
"or out of range on file %s, %d.\n", options.conf_file, lnum);
continue;
}
/* Turn all of this stuff into reasonable addresses */
struct in_addr iaddr;
if (getAddress(bindAddress, &iaddr) < 0) {
fprintf(stderr, "rinetd: host %s could not be "
"resolved on line %d.\n",
bindAddress, lnum);
continue;
}
/* 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");
continue;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
memcpy(&saddr.sin_addr, &iaddr, sizeof(iaddr));
saddr.sin_port = htons(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",
bindAddress, bindPort);
closesocket(fd);
continue;
}
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",
bindAddress, bindPort);
closesocket(fd);
continue;
}
#if _WIN32
u_long ioctltmp;
#else
int ioctltmp;
#endif
ioctlsocket(fd, FIONBIO, &ioctltmp);
if (getAddress(connectAddress, &iaddr) < 0) {
/* Warn -- don't exit. */
syslog(LOG_ERR, "host %s could not be "
"resolved on file %s, line %d.\n",
bindAddress, options.conf_file, lnum);
closesocket(fd);
continue;
}
/* Allocate server info */
seInfo = (ServerInfo *)
realloc(seInfo, sizeof(ServerInfo) * (seTotal + 1));
if (!seInfo) {
goto lowMemory;
}
ServerInfo *srv = &seInfo[seTotal];
memset(srv, 0, sizeof(*srv));
srv->fd = fd;
srv->localAddr = iaddr;
srv->localPort = htons(connectPort);
srv->fromHost = strdup(bindAddress);
if (!srv->fromHost) {
goto lowMemory;
}
srv->fromPort = bindPort;
srv->toHost = strdup(connectAddress);
if (!srv->toHost) {
goto lowMemory;
}
srv->toPort = connectPort;
#ifndef _WIN32
if (fd > maxfd) {
maxfd = fd;
}
#endif
++seTotal;
}
yycontext ctx;
memset(&ctx, 0, sizeof(yycontext));
ctx.fp = in;
if (!yyparse(&ctx)) {
syslog(LOG_ERR, "invalid syntax "
"on file %s, line %d.\n", file, -1);
exit(1);
}
fclose(in);
/* Open the log file */
if (logFile) {
fclose(logFile);
@ -986,7 +801,7 @@ RETSIGTYPE hup(int s)
syslog(LOG_INFO, "Received SIGHUP, reloading configuration...");
/* Learn the new rules */
clearConfiguration();
readConfiguration();
readConfiguration(options.conf_file);
#if !HAVE_SIGACTION
/* And reinstall the signal handler */
signal(SIGHUP, hup);

View File

@ -16,7 +16,8 @@
# to apply to only that forwarding rule
#
# bindadress bindport connectaddress connectport
127.0.0.1 4000 127.0.0.1 22
#127.0.0.1 4000/udp 127.0.0.1 22
# logging information
logfile /var/log/rinetd.log