diff options
Diffstat (limited to 'grammar')
-rw-r--r-- | grammar/Makefile.am | 19 | ||||
-rw-r--r-- | grammar/conf-fmt | 145 | ||||
-rw-r--r-- | grammar/debian.conf | 132 | ||||
-rw-r--r-- | grammar/debian.new | 165 | ||||
-rw-r--r-- | grammar/grammar.y | 186 | ||||
-rw-r--r-- | grammar/lexer.l | 316 | ||||
-rw-r--r-- | grammar/makefile.stand-alone | 14 | ||||
-rw-r--r-- | grammar/mini.samp | 33 | ||||
-rw-r--r-- | grammar/parserif.h | 23 | ||||
-rw-r--r-- | grammar/rainerscript.c | 1614 | ||||
-rw-r--r-- | grammar/rainerscript.h | 255 | ||||
-rw-r--r-- | grammar/samp | 11 | ||||
-rw-r--r-- | grammar/testdriver.c | 109 |
13 files changed, 3022 insertions, 0 deletions
diff --git a/grammar/Makefile.am b/grammar/Makefile.am new file mode 100644 index 00000000..5911f443 --- /dev/null +++ b/grammar/Makefile.am @@ -0,0 +1,19 @@ +BUILT_SOURCES = grammar.h +CLEANFILES = grammar.h grammar.c +AM_YFLAGS = -d +noinst_LTLIBRARIES = libgrammar.la +#bin_PROGRAMS = testdriver # TODO: make this conditional + +libgrammar_la_SOURCES = \ + grammar.y \ + lexer.l \ + rainerscript.c \ + rainerscript.h \ + parserif.h \ + grammar.h +libgrammar_la_CPPFLAGS = $(RSRT_CFLAGS) + +testdriver_SOURCES = testdriver.c libgrammar.la +testdriver_CPPFLAGS = $(RSRT_CFLAGS) +testdriver_LDADD = libgrammar.la +testdriver_LDFLAGS = -lestr diff --git a/grammar/conf-fmt b/grammar/conf-fmt new file mode 100644 index 00000000..e34ab784 --- /dev/null +++ b/grammar/conf-fmt @@ -0,0 +1,145 @@ +PRI filter: + +- facility and severity may be numeric (but discouraged) +- format: facility "." priority [";" next-selector] (no whitespace) +- facility: + * auth, authpriv, cron, daemon, kern, lpr, mail, mark, news, security + (same as auth), syslog, user, uucp and local0 through local7 + * multiple +- "priority" (actually severity): + * debug, info, notice, warning, warn (same as warning), + err, error (same as err), crit, alert, emerg, panic (same as + emerg). The keywords error, warn and panic are deprecated and + should not be used anymore. + * "=" in front of sev --> exactly this + * "!" in front of sev --> ignore this priority + * "=" and "!" can be combined +- * => all fac/severities +- a '\' at end of line means that the following line f is a + continuation line. If so, leading whitespace is stripped from + f and then f as appended to the end of the current line, replacing + the backslash and all whitespace following it. + This makes it somewhat easier to grab selectors from an old-style + config stream. + '\' [WHITESPACE]* LF + + +DEBIAN SAMPLE +This probably includes everything that is problematic... + +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +#$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +user.* -/var/log/user.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +mail.info -/var/log/mail.info +mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# +# Logging for INN news system. +# +news.crit /var/log/news/news.crit +news.err /var/log/news/news.err +news.notice -/var/log/news/news.notice + +# +# Some "catch-all" log files. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole diff --git a/grammar/debian.conf b/grammar/debian.conf new file mode 100644 index 00000000..ff7708c5 --- /dev/null +++ b/grammar/debian.conf @@ -0,0 +1,132 @@ +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +#$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +#$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +user.* -/var/log/user.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +mail.info -/var/log/mail.info +mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# +# Logging for INN news system. +# +news.crit /var/log/news/news.crit +news.err /var/log/news/news.err +news.notice -/var/log/news/news.notice + +# +# Some "catch-all" log files. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +!ThisTag ++host1 +-host2 ++* +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole +$cfs 21 +$cfs 22 +$cfs 23 +# samples added to get full "flavor" of what we need to support... +:msg, contains, "error" /var/log/somelog +$cfs 11 +$cfs 12 +$cfs 13 +module() +$cfs 1 +$cfs 2 +$cfs 3 diff --git a/grammar/debian.new b/grammar/debian.new new file mode 100644 index 00000000..4dbb5907 --- /dev/null +++ b/grammar/debian.new @@ -0,0 +1,165 @@ +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +module( + name="imuxsock" # provides support for local system logging + ) +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 +module(name="imudp") +input(type="imudp" port="514") + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +#$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +#$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log + +# +# Some "catch-all" log files. +# +*.=debug;\ + auth,authpriv.none;\ + news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole + +global (dnscache="yes" arg1="1 2" arg2 = "1 2" arg3 ="1=2\"3") +# samples added to get full "flavor" of what we need to support... +:msg, contains, "error" /var/log/somelog +action(type="omfile" target="/var/log/mail/log") +*.* /* comment */ * # test +*.info :ommysql:, tra, la , la # comment (comment to be part of old style line!) + +# from SUSE: +if ( \ + /* kernel up to warning except of firewall */ \ + ($syslogfacility-text == 'kern') and \ + ($syslogseverity <= 4 /* warning */ ) and not \ + ($msg contains 'IN=' and $msg contains 'OUT=') \ + ) or ( \ + /* up to errors except of facility authpriv */ \ + ($syslogseverity <= 3 /* errors */ ) and not \ + ($syslogfacility-text == 'authpriv') \ + ) \ +then /dev/tty10 +& |/dev/xconsole +# +# slightly modified to not use continuation lines +if ( /* kernel up to warning except of firewall */ + ($syslogfacility-text == 'kern') and + ($syslogseverity <= 4 /* warning */ ) and not + ($msg contains 'IN=' and $msg contains 'OUT=') + ) or ( + /* up to errors except of facility authpriv */ + ($syslogseverity <= 3 /* errors */ ) and not + ($syslogfacility-text == 'authpriv') + ) +then /dev/tty10 +& |/dev/xconsole + +*.* rger # write to user (ugly...) +#ruleset name + +# FEDORA, a bit more complex config +# ### begin forwarding rule ### +# The statement between the begin ... end define a SINGLE forwarding +# rule. They belong together, do NOT split them. If you create multiple +# forwarding rules, duplicate the whole block! +# Remote Logging (we use TCP for reliable delivery) +# +# An on-disk queue is created for this action. If the remote host is +# down, messages are spooled to disk and sent when it is up again. +#$WorkDirectory /var/spppl/rsyslog # where to place spool files +#$ActionQueueFileName fwdRule1 # unique name prefix for spool files +#$ActionQueueMaxDiskSpace 1g # 1gb space limit (use as much as possible) +#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown +#$ActionQueueType LinkedList # run asynchronously +#$ActionResumeRetryCount -1 # infinite retries if host is down +# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional +#*.* @@remote-host:514 +# ### end of the forwarding rule ### +if $msg contains "error" then { + action(type="omfwd" protocol="tcp" target="10.0.0.1:514" + action.retryCount="-1" + queue.type="linkedList" queue.fileName="fwdRule" queue.maxDiskSpace="1g" + queue.saveOnShutdown="on" + ) + action(type="omfile" target="/var/log/somelog.log") + action(type="omuser" target="all") +} diff --git a/grammar/grammar.y b/grammar/grammar.y new file mode 100644 index 00000000..402b1a57 --- /dev/null +++ b/grammar/grammar.y @@ -0,0 +1,186 @@ + /* Bison file for rsyslog config format v2 (RainerScript). + * Please note: this file introduces the new config format, but maintains + * backward compatibility. In order to do so, the grammar is not 100% clean, + * but IMHO still sufficiently easy both to understand for programmers + * maitaining the code as well as users writing the config file. Users are, + * of course, encouraged to use new constructs only. But it needs to be noted + * that some of the legacy constructs (specifically the in-front-of-action + * PRI filter) are very hard to beat in ease of use, at least for simpler + * cases. So while we hope that cfsysline support can be dropped some time in + * the future, we will probably keep these useful constructs. + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +%{ +#include <stdio.h> +#include <libestr.h> +#include "rainerscript.h" +#include "parserif.h" +#define YYDEBUG 1 +extern int yylineno; + +/* keep compile rule cleam of errors */ +extern int yylex(void); +extern int yyerror(char*); +%} + +%union { + char *s; + long long n; + es_str_t *estr; + enum cnfobjType objType; + struct cnfobj *obj; + struct nvlst *nvlst; + struct cnfactlst *actlst; + struct cnfexpr *expr; + struct cnfrule *rule; + struct cnffunc *func; + struct cnffparamlst *fparams; +} + +%token <estr> NAME +%token <estr> VALUE +%token <estr> FUNC +%token <objType> BEGINOBJ +%token ENDOBJ +%token <s> CFSYSLINE +%token BEGIN_ACTION +%token STOP +%token <s> LEGACY_ACTION +%token <s> PRIFILT +%token <s> PROPFILT +%token <s> BSD_TAG_SELECTOR +%token <s> BSD_HOST_SELECTOR +%token IF +%token THEN +%token OR +%token AND +%token NOT +%token <s> VAR +%token <estr> STRING +%token <n> NUMBER +%token CMP_EQ +%token CMP_NE +%token CMP_LE +%token CMP_GE +%token CMP_LT +%token CMP_GT +%token CMP_CONTAINS +%token CMP_CONTAINSI +%token CMP_STARTSWITH +%token CMP_STARTSWITHI + +%type <nvlst> nv nvlst +%type <obj> obj +%type <actlst> actlst +%type <actlst> act +%type <s> cfsysline +%type <actlst> block +%type <expr> expr +%type <rule> rule +%type <rule> scriptfilt +%type <fparams> fparams + +%left AND OR +%left CMP_EQ CMP_NE CMP_LE CMP_GE CMP_LT CMP_GT CMP_CONTAINS CMP_CONTAINSI CMP_STARTSWITH CMP_STARTSWITHI +%left '+' '-' +%left '*' '/' '%' +%nonassoc UMINUS NOT + +%expect 3 +/* these shift/reduce conflicts are created by the CFSYSLINE construct, which we + * unfortunately can not avoid. The problem is that CFSYSLINE can occur both in + * global context as well as within an action. It's not permitted somewhere else, + * but this is suficient for conflicts. The "dangling else" built-in resolution + * works well to solve this issue, so we accept it (it's a wonder that our + * old style grammar doesn't work at all, so we better do not complain...). + * Use "bison -v rscript.y" if more conflicts arise and check rscript.out for + * were exactly these conflicts exits. + */ +%% +/* note: we use left recursion below, because that saves stack space AND + * offers the right sequence so that we can submit the top-layer objects + * one by one. + */ +conf: /* empty (to end recursion) */ + | conf obj { cnfDoObj($2); } + | conf rule { cnfDoRule($2); } + | conf cfsysline { cnfDoCfsysline($2); } + | conf BSD_TAG_SELECTOR { cnfDoBSDTag($2); } + | conf BSD_HOST_SELECTOR { cnfDoBSDHost($2); } +obj: BEGINOBJ nvlst ENDOBJ { $$ = cnfobjNew($1, $2); } + | BEGIN_ACTION nvlst ENDOBJ { $$ = cnfobjNew(CNFOBJ_ACTION, $2); } +cfsysline: CFSYSLINE { $$ = $1; } +nvlst: { $$ = NULL; } + | nvlst nv { $2->next = $1; $$ = $2; } +nv: NAME '=' VALUE { $$ = nvlstNew($1, $3); } +rule: PRIFILT actlst { $$ = cnfruleNew(CNFFILT_PRI, $2); $$->filt.s = $1; } + | PROPFILT actlst { $$ = cnfruleNew(CNFFILT_PROP, $2); $$->filt.s = $1; } + | scriptfilt { $$ = $1; } + +scriptfilt: IF expr THEN actlst { $$ = cnfruleNew(CNFFILT_SCRIPT, $4); + $$->filt.expr = $2; } +block: actlst { $$ = $1; } + | block actlst { $2->next = $1; $$ = $2; } + /* v7: | actlst + v7: | block rule */ /* v7 extensions require new rule engine capabilities! */ +actlst: act { $$=$1; } + | actlst '&' act { $3->next = $1; $$ = $3; } + | actlst cfsysline { $$ = cnfactlstAddSysline($1, $2); } + | '{' block '}' { $$ = $2; } +act: BEGIN_ACTION nvlst ENDOBJ { $$ = cnfactlstNew(CNFACT_V2, $2, NULL); } + | LEGACY_ACTION { $$ = cnfactlstNew(CNFACT_LEGACY, NULL, $1); } +expr: expr AND expr { $$ = cnfexprNew(AND, $1, $3); } + | expr OR expr { $$ = cnfexprNew(OR, $1, $3); } + | NOT expr { $$ = cnfexprNew(NOT, NULL, $2); } + | expr CMP_EQ expr { $$ = cnfexprNew(CMP_EQ, $1, $3); } + | expr CMP_NE expr { $$ = cnfexprNew(CMP_NE, $1, $3); } + | expr CMP_LE expr { $$ = cnfexprNew(CMP_LE, $1, $3); } + | expr CMP_GE expr { $$ = cnfexprNew(CMP_GE, $1, $3); } + | expr CMP_LT expr { $$ = cnfexprNew(CMP_LT, $1, $3); } + | expr CMP_GT expr { $$ = cnfexprNew(CMP_GT, $1, $3); } + | expr CMP_CONTAINS expr { $$ = cnfexprNew(CMP_CONTAINS, $1, $3); } + | expr CMP_CONTAINSI expr { $$ = cnfexprNew(CMP_CONTAINSI, $1, $3); } + | expr CMP_STARTSWITH expr { $$ = cnfexprNew(CMP_STARTSWITH, $1, $3); } + | expr CMP_STARTSWITHI expr { $$ = cnfexprNew(CMP_STARTSWITHI, $1, $3); } + | expr '+' expr { $$ = cnfexprNew('+', $1, $3); } + | expr '-' expr { $$ = cnfexprNew('-', $1, $3); } + | expr '*' expr { $$ = cnfexprNew('*', $1, $3); } + | expr '/' expr { $$ = cnfexprNew('/', $1, $3); } + | expr '%' expr { $$ = cnfexprNew('%', $1, $3); } + | '(' expr ')' { $$ = $2; } + | '-' expr %prec UMINUS { $$ = cnfexprNew('M', NULL, $2); } + | FUNC '(' ')' { $$ = (struct cnfexpr*) cnffuncNew($1, NULL); } + | FUNC '(' fparams ')' { $$ = (struct cnfexpr*) cnffuncNew($1, $3); } + | NUMBER { $$ = (struct cnfexpr*) cnfnumvalNew($1); } + | STRING { $$ = (struct cnfexpr*) cnfstringvalNew($1); } + | VAR { $$ = (struct cnfexpr*) cnfvarNew($1); } +fparams: expr { $$ = cnffparamlstNew($1, NULL); } + | expr ',' fparams { $$ = cnffparamlstNew($1, $3); } + +%% +/* +int yyerror(char *s) +{ + printf("parse failure on or before line %d: %s\n", yylineno, s); + return 0; +} +*/ diff --git a/grammar/lexer.l b/grammar/lexer.l new file mode 100644 index 00000000..a3f8bedd --- /dev/null +++ b/grammar/lexer.l @@ -0,0 +1,316 @@ + /* Lex file for rsyslog config format v2 (RainerScript). + * Please note: this file introduces the new config format, but maintains + * backward compatibility. In order to do so, the grammar is not 100% clean, + * but IMHO still sufficiently easy both to understand for programmers + * maitaining the code as well as users writing the config file. Users are, + * of course, encouraged to use new constructs only. But it needs to be noted + * that some of the legacy constructs (specifically the in-front-of-action + * PRI filter) are very hard to beat in ease of use, at least for simpler + * cases. So while we hope that cfsysline support can be dropped some time in + * the future, we will probably keep these useful constructs. + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +%option noyywrap nodefault case-insensitive yylineno + /*%option noyywrap nodefault case-insensitive */ + +/* avoid compiler warning: `yyunput' defined but not used */ +%option nounput noinput + + +%x INOBJ + /* INOBJ is selected if we are inside an object (name/value pairs!) */ +%x COMMENT + /* COMMENT is "the usual trick" to handle C-style comments */ +%x INCL + /* INCL is in $IncludeConfig processing (skip to include file) */ +%x LINENO + /* LINENO: support for setting the linenumber */ +%x EXPR + /* EXPR is a bit ugly, but we need it to support pre v6-syntax. The problem + * is that cfsysline statement start with $..., the same like variables in + * an expression. However, cfsysline statements can never appear inside an + * expression. So we create a specific expr mode, which is turned on after + * we lexed a keyword that needs to be followed by an expression (using + * knowledge from the upper layer...). In expr mode, we strictly do + * expression-based parsing. Expr mode is stopped when we reach a token + * that can not be part of an expression (currently only "then"). As I + * wrote this ugly, but the price needed to pay in order to remain + * compatible to the previous format. + */ +%{ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <libestr.h> +#include "rainerscript.h" +#include "parserif.h" +#include "grammar.h" +static int preCommentState; /* save for lex state before a comment */ + +struct bufstack { + struct bufstack *prev; + YY_BUFFER_STATE bs; + int lineno; + char *fn; + es_str_t *estr; +} *currbs = NULL; + +char *cnfcurrfn; /* name of currently processed file */ + +int popfile(void); +int cnfSetLexFile(char *fname); + +extern int yydebug; + +/* somehow, I need these prototype even though the headers are + * included. I guess that's some autotools magic I don't understand... + */ +//char *strdup(char*); +int fileno(FILE *stream); + +%} + +%% + + /* keywords */ +"if" { BEGIN EXPR; return IF; } +<EXPR>"then" { BEGIN INITIAL; return THEN; } +<EXPR>"or" { return OR; } +<EXPR>"and" { return AND; } +<EXPR>"not" { return NOT; } +<EXPR>"," | +<EXPR>"*" | +<EXPR>"/" | +<EXPR>"%" | +<EXPR>"+" | +<EXPR>"-" | +<EXPR>"(" | +<EXPR>")" { return yytext[0]; } +<EXPR>"==" { return CMP_EQ; } +<EXPR>"<=" { return CMP_LE; } +<EXPR>">=" { return CMP_GE; } +<EXPR>"!=" | +<EXPR>"<>" { return CMP_NE; } +<EXPR>"<" { return CMP_LT; } +<EXPR>">" { return CMP_GT; } +<EXPR>"contains" { return CMP_CONTAINS; } +<EXPR>"contains_i" { return CMP_CONTAINSI; } +<EXPR>"startswith" { return CMP_STARTSWITH; } +<EXPR>"startswith_i" { return CMP_STARTSWITHI; } +<EXPR>0[0-7]+ | /* octal number */ +<EXPR>0x[0-7a-f] | /* hex number, following rule is dec; strtoll handles all! */ +<EXPR>([1-9][0-9]*|0) { yylval.n = strtoll(yytext, NULL, 0); return NUMBER; } +<EXPR>\$[$!]{0,1}[a-z][a-z0-9\-_\.]* { yylval.s = strdup(yytext); return VAR; } +<EXPR>\'([^'\\]|\\['])*\' { yylval.estr = es_newStrFromBuf(yytext+1, yyleng-2); + return STRING; } +<EXPR>\"([^"\\]|\\["])*\" { yylval.estr = es_newStrFromBuf(yytext+1, yyleng-2); + return STRING; } +<EXPR>[ \t\n] +<EXPR>[a-z][a-z0-9_]* { yylval.estr = es_newStrFromCStr(yytext, yyleng); + return FUNC; } +<EXPR>. { dbgprintf("invalid char in expr: %s\n", yytext); } +"&" { return '&'; } +"{" { return '{'; } +"}" { return '}'; } +"ruleset" { dbgprintf("RULESET\n"); } + /* line number support because the "preprocessor" combines lines and so needs + * to tell us the real source line. + */ +"stop" { dbgprintf("STOP\n"); return STOP; } +"preprocfilelinenumber(" { BEGIN LINENO; } +<LINENO>[0-9]+ { yylineno = atoi(yytext) - 1; } +<LINENO>")" { BEGIN INITIAL; } +<LINENO>.|\n + /* $IncludeConfig must be detected as part of CFSYSLINE, because this is + * always the longest match :-( + */ +<INCL>.|\n +<INCL>[^ \t\n]+ { if(cnfDoInclude(yytext) != 0) + yyterminate(); + BEGIN INITIAL; } +"global"[ \n\t]*"(" { yylval.objType = CNFOBJ_GLOBAL; + BEGIN INOBJ; return BEGINOBJ; } +"input"[ \n\t]*"(" { yylval.objType = CNFOBJ_INPUT; + BEGIN INOBJ; return BEGINOBJ; } +"module"[ \n\t]*"(" { yylval.objType = CNFOBJ_MODULE; + BEGIN INOBJ; return BEGINOBJ; } +"action"[ \n\t]*"(" { BEGIN INOBJ; return BEGIN_ACTION; } +^[ \t]*:\$?[a-z\-]+[ ]*,[ ]*!?[a-z]+[ ]*,[ ]*\".*\" { + yylval.s = strdup(yytext); return PROPFILT; } +^[ \t]*[\*a-z][,\*a-z]*[0-7]*\.[,!=;\.\*a-z0-7]+ { yylval.s = strdup(yytext); return PRIFILT; } +"~" | +"*" | +\-\/[^*][^\n]* | +\/[^*][^\n]* | +:[a-z0-9]+:[^\n]* | +[\|\.\-\@\^?~>][^\n]+ | +[a-z0-9_][a-z0-9_\-\+,;]* { yylval.s = strdup(yytext); + // printf("lex: LEGA ACT: '%s'\n", yytext); + return LEGACY_ACTION; } +<INOBJ>")" { BEGIN INITIAL; return ENDOBJ; } +<INOBJ>[a-z][a-z0-9_\.]* { yylval.estr = es_newStrFromCStr(yytext, yyleng); + return NAME; } +<INOBJ>"=" { return(yytext[0]); } +<INOBJ>\"([^"\\]|\\['"?\\abfnrtv]|\\[0-7]{1,3})*\" { + yylval.estr = es_newStrFromBuf(yytext+1, yyleng-2); + return VALUE; } +"/*" { preCommentState = YY_START; BEGIN COMMENT; } +<EXPR>"/*" { preCommentState = YY_START; BEGIN COMMENT; } +<COMMENT>"*/" { BEGIN preCommentState; } +<COMMENT>([^*]|\n)+|. +<INOBJ>#.*$ /* skip comments in input */ +<INOBJ>[ \n\t] +<INOBJ>. { dbgprintf("INOBJ: invalid char '%s'\n", yytext); } +\$[a-z]+.*$ { /* see comment on $IncludeConfig above */ + if(!strncasecmp(yytext, "$includeconfig ", 14)) { + yyless(14); + BEGIN INCL; + } else { + yylval.s = strdup(yytext); + return CFSYSLINE; + } + } +![^ \t\n]+[ \t]*$ { yylval.s = strdup(yytext); return BSD_TAG_SELECTOR; } +[+-]\*[ \t\n]*#.*$ { yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } +[+-]\*[ \t\n]*$ { yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } +^[ \t]*[+-][a-z0-9.:-]+[ \t]*$ { yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } +\#.*\n /* skip comments in input */ +[\n\t ] /* drop whitespace */ +. { dbgprintf("invalid char: %s\n", yytext); + } +<<EOF>> { if(popfile() != 0) yyterminate(); } + +%% +int +cnfParseBuffer(char *buf, unsigned lenBuf) +{ + struct bufstack *bs; + int r = 0; + yydebug = 1; + BEGIN INITIAL; + /* maintain stack */ + if((bs = malloc(sizeof(struct bufstack))) == NULL) { + r = 1; + goto done; + } + + if(currbs != NULL) + currbs->lineno = yylineno; + bs->prev = currbs; + bs->fn = strdup("*buffer*"); + bs->bs = yy_scan_buffer(buf, lenBuf); + bs->estr = NULL; + currbs = bs; + cnfcurrfn = bs->fn; + yylineno = 1; +done: return r; +} + +/* set a new buffers. Returns 0 on success, something else otherwise. */ +int +cnfSetLexFile(char *fname) +{ + es_str_t *str = NULL; + FILE *fp; + int r = 0; + struct bufstack *bs; + + if(fname == NULL) { + fp = stdin; + } else { + if((fp = fopen(fname, "r")) == NULL) { + r = 1; + goto done; + } + } + readConfFile(fp, &str); + if(fp != stdin) + fclose(fp); + + /* maintain stack */ + if((bs = malloc(sizeof(struct bufstack))) == NULL) { + r = 1; + goto done; + } + + if(currbs != NULL) + currbs->lineno = yylineno; + bs->prev = currbs; + bs->fn = strdup(fname == NULL ? "stdin" : fname); + bs->bs = yy_scan_buffer((char*)es_getBufAddr(str), es_strlen(str)); + bs->estr = str; /* needed so we can free it later */ + currbs = bs; + cnfcurrfn = bs->fn; + yylineno = 1; + +done: + if(r != 0) { + if(str != NULL) + es_deleteStr(str); + } + return r; +} + + +/* returns 0 on success, something else otherwise */ +int +popfile(void) +{ + struct bufstack *bs = currbs; + + if(bs == NULL) + return 1; + + /* delete current entry. But we must not free the file name if + * this is the top-level file, because then it may still be used + * in error messages for other processing steps. + * TODO: change this to another method which stores the file + * name inside the config objects. In the longer term, this is + * necessary, as otherwise we may provide wrong file name information + * at the end of include files as well. -- rgerhards, 2011-07-22 + */ + yy_delete_buffer(bs->bs); + if(bs->prev != NULL) + free(bs->fn); + free(bs->estr); + + /* switch back to previous */ + currbs = bs->prev; + free(bs); + + if(currbs == NULL) + return 1; /* all processed */ + + yy_switch_to_buffer(currbs->bs); + yylineno = currbs->lineno; + cnfcurrfn = currbs->fn; + return 0; +} + +void +tellLexEndParsing(void) +{ + free(cnfcurrfn); + cnfcurrfn= NULL; +} diff --git a/grammar/makefile.stand-alone b/grammar/makefile.stand-alone new file mode 100644 index 00000000..b998a39d --- /dev/null +++ b/grammar/makefile.stand-alone @@ -0,0 +1,14 @@ +rscript: lex.yy.c utils.o rscript.tab.h utils.h + gcc -DSTAND_ALONE -g -o rscript lex.yy.c rscript.tab.c utils.o -lestr + +lex.yy.c: rscript.l rscript.tab.h + flex rscript.l + +rscript.tab.h: rscript.y + bison -d rscript.y + +utils.o: utils.c utils.h + gcc -g -DSTAND_ALONE -Wall -c utils.c + +clean: + rm *.o diff --git a/grammar/mini.samp b/grammar/mini.samp new file mode 100644 index 00000000..3bb0de44 --- /dev/null +++ b/grammar/mini.samp @@ -0,0 +1,33 @@ +#global (dnscache="yes" arg1="1 2" arg2 = "1 2" arg3 ="1=2\"3") +action(type="omuser" target="all" target="all2") +global (dnscache="no" b="2") +$FileOwner root +*.* * +$action somelog 1 +& /var/log/somelog +$action log2 1 +$action log2 2 +$action log2 3 +& action(type="fwd" target="10.1.1.2") +& /var/log/log2 + +if 1 then /var/log/log3 +/* sample bwlow is v7 +if 1 then { /var/log/log3 + if 2 then /var/log/log4 + *.* /var/log/log4b +} +*/ +*.* { /var/log/log5 + /var/log/log6 + $port 514 + @@fwd + rger + } +if not (1==0) and 2*4/-5--(10-3)>7/*pri("*.*")*/ then { + action(type="omfile" taget="/var/log/log5") + action(type="omfile" taget="/var/log/log6") + action(type="omfwd" taget="10.0.0.1" port="514") + action(type="omwusr" taget="rger" taget="rger2") +} +if getenv("user") == "test" then /var/log/testlog diff --git a/grammar/parserif.h b/grammar/parserif.h new file mode 100644 index 00000000..597cfe40 --- /dev/null +++ b/grammar/parserif.h @@ -0,0 +1,23 @@ +#ifndef PARSERIF_H_DEFINED +#define PARSERIF_H_DEFINED +#include "rainerscript.h" +int cnfSetLexFile(char*); +int yyparse(); +char *cnfcurrfn; +void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2))); +void parser_errmsg(char *fmt, ...) __attribute__((format(printf, 1, 2))); +void tellLexEndParsing(void); +extern int yydebug; +extern int yylineno; + +/* entry points to be called after the parser has processed the + * element in question. Actual processing must than be done inside + * these functions. + */ +void cnfDoObj(struct cnfobj *o); +void cnfDoRule(struct cnfrule *rule); +void cnfDoCfsysline(char *ln); +void cnfDoBSDTag(char *ln); +void cnfDoBSDHost(char *ln); +es_str_t *cnfGetVar(char *name, void *usrptr); +#endif diff --git a/grammar/rainerscript.c b/grammar/rainerscript.c new file mode 100644 index 00000000..22a90cb7 --- /dev/null +++ b/grammar/rainerscript.c @@ -0,0 +1,1614 @@ +/* rainerscript.c - routines to support RainerScript config language + * + * Module begun 2011-07-01 by Rainer Gerhards + * + * Copyright 2011 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <glob.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <libestr.h> +#include "rsyslog.h" +#include "rainerscript.h" +#include "parserif.h" +#include "grammar.h" +#include "queue.h" +#include "srUtils.h" + +void +readConfFile(FILE *fp, es_str_t **str) +{ + char ln[10240]; + char buf[512]; + int lenBuf; + int bWriteLineno = 0; + int len, i; + int start; /* start index of to be submitted text */ + int bContLine = 0; + int lineno = 0; + + *str = es_newStr(4096); + + while(fgets(ln, sizeof(ln), fp) != NULL) { + ++lineno; + if(bWriteLineno) { + bWriteLineno = 0; + lenBuf = sprintf(buf, "PreprocFileLineNumber(%d)\n", lineno); + es_addBuf(str, buf, lenBuf); + } + len = strlen(ln); + /* if we are continuation line, we need to drop leading WS */ + if(bContLine) { + for(start = 0 ; start < len && isspace(ln[start]) ; ++start) + /* JUST SCAN */; + } else { + start = 0; + } + for(i = len - 1 ; i >= start && isspace(ln[i]) ; --i) + /* JUST SCAN */; + if(i >= 0) { + if(ln[i] == '\\') { + --i; + bContLine = 1; + } else { + if(bContLine) /* write line number if we had cont line */ + bWriteLineno = 1; + bContLine = 0; + } + /* add relevant data to buffer */ + es_addBuf(str, ln+start, i+1 - start); + } + if(!bContLine) + es_addChar(str, '\n'); + } + /* indicate end of buffer to flex */ + es_addChar(str, '\0'); + es_addChar(str, '\0'); +} + +struct nvlst* +nvlstNew(es_str_t *name, es_str_t *value) +{ + struct nvlst *lst; + + if((lst = malloc(sizeof(struct nvlst))) != NULL) { + lst->next = NULL; + lst->name = name; + lst->val.datatype = 'S'; + lst->val.d.estr = value; + lst->bUsed = 0; + } + + return lst; +} + +void +nvlstDestruct(struct nvlst *lst) +{ + struct nvlst *toDel; + + while(lst != NULL) { + toDel = lst; + lst = lst->next; + es_deleteStr(toDel->name); + if(toDel->val.datatype == 'S') + es_deleteStr(toDel->val.d.estr); + free(toDel); + } +} + +void +nvlstPrint(struct nvlst *lst) +{ + char *name, *value; + dbgprintf("nvlst %p:\n", lst); + while(lst != NULL) { + name = es_str2cstr(lst->name, NULL); + // TODO: support for non-string types + value = es_str2cstr(lst->val.d.estr, NULL); + dbgprintf("\tname: '%s', value '%s'\n", name, value); + free(name); + free(value); + lst = lst->next; + } +} + +/* find a name starting at node lst. Returns node with this + * name or NULL, if none found. + */ +struct nvlst* +nvlstFindName(struct nvlst *lst, es_str_t *name) +{ + while(lst != NULL && es_strcmp(lst->name, name)) + lst = lst->next; + return lst; +} + + +/* find a name starting at node lst. Same as nvlstFindName, but + * for classical C strings. This is useful because the config system + * uses C string constants. + */ +static inline struct nvlst* +nvlstFindNameCStr(struct nvlst *lst, char *name) +{ + es_size_t lenName = strlen(name); + while(lst != NULL && es_strcasebufcmp(lst->name, (uchar*)name, lenName)) + lst = lst->next; + return lst; +} + + +/* check if there are duplicate names inside a nvlst and emit + * an error message, if so. + */ +static inline void +nvlstChkDupes(struct nvlst *lst) +{ + char *cstr; + + while(lst != NULL) { + if(nvlstFindName(lst->next, lst->name) != NULL) { + cstr = es_str2cstr(lst->name, NULL); + parser_errmsg("duplicate parameter '%s' -- " + "interpretation is ambigious, one value " + "will be randomly selected. Fix this problem.", + cstr); + free(cstr); + } + lst = lst->next; + } +} + + +/* check for unused params and emit error message is found. This must + * be called after all config params have been pulled from the object + * (otherwise the flags are not correctly set). + */ +void +nvlstChkUnused(struct nvlst *lst) +{ + char *cstr; + + while(lst != NULL) { + if(!lst->bUsed) { + cstr = es_str2cstr(lst->name, NULL); + parser_errmsg("parameter '%s' not known -- " + "typo in config file?", + cstr); + free(cstr); + } + lst = lst->next; + } +} + + +static inline void +doGetSize(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + unsigned char *c; + es_size_t i; + long long n; + c = es_getBufAddr(valnode->val.d.estr); + n = 0; + i = 0; + while(i < es_strlen(valnode->val.d.estr) && isdigit(*c)) { + n = 10 * n + *c - '0'; + ++i; + ++c; + } + if(i < es_strlen(valnode->val.d.estr)) { + ++i; + switch(*c) { + /* traditional binary-based definitions */ + case 'k': n *= 1024; break; + case 'm': n *= 1024 * 1024; break; + case 'g': n *= 1024 * 1024 * 1024; break; + case 't': n *= (int64) 1024 * 1024 * 1024 * 1024; break; /* tera */ + case 'p': n *= (int64) 1024 * 1024 * 1024 * 1024 * 1024; break; /* peta */ + case 'e': n *= (int64) 1024 * 1024 * 1024 * 1024 * 1024 * 1024; break; /* exa */ + /* and now the "new" 1000-based definitions */ + case 'K': n *= 1000; break; + case 'M': n *= 1000000; break; + case 'G': n *= 1000000000; break; + /* we need to use the multiplication below because otherwise + * the compiler gets an error during constant parsing */ + case 'T': n *= (int64) 1000 * 1000000000; break; /* tera */ + case 'P': n *= (int64) 1000000 * 1000000000; break; /* peta */ + case 'E': n *= (int64) 1000000000 * 1000000000; break; /* exa */ + default: --i; break; /* indicates error */ + } + } + if(i == es_strlen(valnode->val.d.estr)) { + val->val.datatype = 'N'; + val->val.d.n = n; + } else { + parser_errmsg("parameter '%s' does not contain a valid size", + param->name); + } +} + + +static inline void +doGetBinary(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + val->val.datatype = 'N'; + if(!es_strbufcmp(valnode->val.d.estr, (unsigned char*) "on", 2)) { + val->val.d.n = 1; + } else if(!es_strbufcmp(valnode->val.d.estr, (unsigned char*) "off", 3)) { + val->val.d.n = 0; + } else { + parser_errmsg("parameter '%s' must be \"on\" or \"off\" but " + "is neither. Results unpredictable.", param->name); + val->val.d.n = 0; + } +} + +static inline void +doGetQueueType(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + char *cstr; + if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"fixedarray", 10)) { + val->val.d.n = QUEUETYPE_FIXED_ARRAY; + } else if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"linkedlist", 10)) { + val->val.d.n = QUEUETYPE_LINKEDLIST; + } else if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"disk", 4)) { + val->val.d.n = QUEUETYPE_DISK; + } else if(!es_strcasebufcmp(valnode->val.d.estr, (uchar*)"direct", 6)) { + val->val.d.n = QUEUETYPE_DIRECT; + } else { + cstr = es_str2cstr(valnode->val.d.estr, NULL); + parser_errmsg("param '%s': unknown queue type: '%s'", + param->name, cstr); + free(cstr); + } +dbgprintf("XXXXX: queue type: %d\n", (int)val->val.d.n); + val->val.datatype = 'N'; +} + + +/* A file create-mode must be a four-digit octal number + * starting with '0'. + */ +static inline void +doGetFileCreateMode(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + int fmtOK = 0; + char *cstr; + uchar *c; + + if(es_strlen(valnode->val.d.estr) == 4) { + c = es_getBufAddr(valnode->val.d.estr); + if(!( (c[0] == '0') + && (c[1] >= '0' && c[1] <= '7') + && (c[2] >= '0' && c[2] <= '7') + && (c[3] >= '0' && c[3] <= '7') ) ) { + fmtOK = 1; + } + } + + if(fmtOK) { + val->val.datatype = 'N'; + val->val.d.n = (c[1]-'0') * 64 + (c[2]-'0') * 8 + (c[3]-'0');; + } else { + cstr = es_str2cstr(valnode->val.d.estr, NULL); + parser_errmsg("file modes need to be specified as " + "4-digit octal numbers starting with '0' -" + "parameter '%s=\"%s\"' is not a file mode", + param->name, cstr); + free(cstr); + } +} + +static inline void +doGetGID(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + char *cstr; + struct group *resultBuf; + struct group wrkBuf; + char stringBuf[2048]; /* 2048 has been proven to be large enough */ + + cstr = es_str2cstr(valnode->val.d.estr, NULL); + getgrnam_r(cstr, &wrkBuf, stringBuf, sizeof(stringBuf), &resultBuf); + if(resultBuf == NULL) { + parser_errmsg("parameter '%s': ID for group %s could not " + "be found", param->name, cstr); + } else { + val->val.datatype = 'N'; + val->val.d.n = resultBuf->gr_gid; + dbgprintf("param '%s': uid %d obtained for group '%s'\n", + param->name, (int) resultBuf->gr_gid, cstr); + } + free(cstr); +} + +static inline void +doGetUID(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + char *cstr; + struct passwd *resultBuf; + struct passwd wrkBuf; + char stringBuf[2048]; /* 2048 has been proven to be large enough */ + + cstr = es_str2cstr(valnode->val.d.estr, NULL); + getpwnam_r(cstr, &wrkBuf, stringBuf, sizeof(stringBuf), &resultBuf); + if(resultBuf == NULL) { + parser_errmsg("parameter '%s': ID for user %s could not " + "be found", param->name, cstr); + } else { + val->val.datatype = 'N'; + val->val.d.n = resultBuf->pw_uid; + dbgprintf("param '%s': uid %d obtained for user '%s'\n", + param->name, (int) resultBuf->pw_uid, cstr); + } + free(cstr); +} + +/* note: we support all integer formats that es_str2num support, + * so hex and octal representations are also valid. + */ +static inline void +doGetInt(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + long long n; + int bSuccess; + + n = es_str2num(valnode->val.d.estr, &bSuccess); + if(!bSuccess) { + parser_errmsg("parameter '%s' is not a proper number", + param->name); + } + val->val.datatype = 'N'; + val->val.d.n = n; +} + +static inline void +doGetWord(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + es_size_t i; + unsigned char *c; + val->val.datatype = 'S'; + val->val.d.estr = es_newStr(32); + c = es_getBufAddr(valnode->val.d.estr); + for(i = 0 ; i < es_strlen(valnode->val.d.estr) && !isspace(c[i]) ; ++i) { + es_addChar(&val->val.d.estr, c[i]); + } + if(i != es_strlen(valnode->val.d.estr)) { + parser_errmsg("parameter '%s' contains whitespace, which is not " + "permitted - data after first whitespace ignored", + param->name); + } +} + +static inline void +doGetChar(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + if(es_strlen(valnode->val.d.estr) != 1) { + parser_errmsg("parameter '%s' must contain exactly one character " + "but contains %d - cannot be processed", + param->name, es_strlen(valnode->val.d.estr)); + } + val->val.datatype = 'S'; + val->val.d.estr = es_strdup(valnode->val.d.estr); +} + +/* get a single parameter according to its definition. Helper to + * nvlstGetParams. + */ +static inline void +nvlstGetParam(struct nvlst *valnode, struct cnfparamdescr *param, + struct cnfparamvals *val) +{ + uchar *cstr; + + dbgprintf("XXXX: in nvlstGetParam, name '%s', type %d, valnode->bUsed %d\n", + param->name, (int) param->type, valnode->bUsed); + valnode->bUsed = 1; + val->bUsed = 1; + switch(param->type) { + case eCmdHdlrQueueType: + doGetQueueType(valnode, param, val); + break; + case eCmdHdlrUID: + doGetUID(valnode, param, val); + break; + case eCmdHdlrGID: + doGetGID(valnode, param, val); + break; + case eCmdHdlrBinary: + doGetBinary(valnode, param, val); + break; + case eCmdHdlrFileCreateMode: + doGetFileCreateMode(valnode, param, val); + break; + case eCmdHdlrInt: + doGetInt(valnode, param, val); + break; + case eCmdHdlrSize: + doGetSize(valnode, param, val); + break; + case eCmdHdlrGetChar: + doGetChar(valnode, param, val); + break; + case eCmdHdlrFacility: + cstr = (uchar*) es_str2cstr(valnode->val.d.estr, NULL); + val->val.datatype = 'N'; + val->val.d.n = decodeSyslogName(cstr, syslogFacNames); + free(cstr); + break; + case eCmdHdlrSeverity: + cstr = (uchar*) es_str2cstr(valnode->val.d.estr, NULL); + val->val.datatype = 'N'; + val->val.d.n = decodeSyslogName(cstr, syslogPriNames); + free(cstr); + break; + case eCmdHdlrGetWord: + doGetWord(valnode, param, val); + break; + case eCmdHdlrString: + val->val.datatype = 'S'; + val->val.d.estr = es_strdup(valnode->val.d.estr); + break; + case eCmdHdlrGoneAway: + parser_errmsg("parameter '%s' is no longer supported", + param->name); + break; + default: + dbgprintf("error: invalid param type\n"); + break; + } +} + + +/* obtain conf params from an nvlst and emit error messages if + * necessary. If an already-existing param value is passed, that is + * used. If NULL is passed instead, a new one is allocated. In that case, + * it is the caller's duty to free it when no longer needed. + * NULL is returned on error, otherwise a pointer to the vals array. + */ +struct cnfparamvals* +nvlstGetParams(struct nvlst *lst, struct cnfparamblk *params, + struct cnfparamvals *vals) +{ + int i; + struct nvlst *valnode; + struct cnfparamdescr *param; + + if(params->version != CNFPARAMBLK_VERSION) { + dbgprintf("nvlstGetParams: invalid param block version " + "%d, expected %d\n", + params->version, CNFPARAMBLK_VERSION); + return NULL; + } + + if(vals == NULL) { + if((vals = calloc(params->nParams, + sizeof(struct cnfparamvals))) == NULL) + return NULL; + } + + for(i = 0 ; i < params->nParams ; ++i) { + param = params->descr + i; + if((valnode = nvlstFindNameCStr(lst, param->name)) == NULL) + continue; + if(vals[i].bUsed) { + parser_errmsg("parameter '%s' specified more than once - " + "one instance is ignored. Fix config", param->name); + continue; + } + nvlstGetParam(valnode, param, vals + i); + } + return vals; +} + + +void +cnfparamsPrint(struct cnfparamblk *params, struct cnfparamvals *vals) +{ + int i; + char *cstr; + + for(i = 0 ; i < params->nParams ; ++i) { + dbgprintf("%s: ", params->descr[i].name); + if(vals[i].bUsed) { + // TODO: other types! + switch(vals[i].val.datatype) { + case 'S': + cstr = es_str2cstr(vals[i].val.d.estr, NULL); + dbgprintf(" '%s'", cstr); + free(cstr); + break; + case 'N': + dbgprintf("%lld", vals[i].val.d.n); + break; + default: + dbgprintf("(unsupported datatype %c)", + vals[i].val.datatype); + } + } else { + dbgprintf("(unset)"); + } + dbgprintf("\n"); + } +} + +struct cnfobj* +cnfobjNew(enum cnfobjType objType, struct nvlst *lst) +{ + struct cnfobj *o; + + if((o = malloc(sizeof(struct nvlst))) != NULL) { + nvlstChkDupes(lst); + o->objType = objType; + o->nvlst = lst; + } + + return o; +} + +void +cnfobjDestruct(struct cnfobj *o) +{ + if(o != NULL) { + nvlstDestruct(o->nvlst); + free(o); + } +} + +void +cnfobjPrint(struct cnfobj *o) +{ + dbgprintf("obj: '%s'\n", cnfobjType2str(o->objType)); + nvlstPrint(o->nvlst); +} + + +struct cnfactlst* +cnfactlstNew(enum cnfactType actType, struct nvlst *lst, char *actLine) +{ + struct cnfactlst *actlst; + + if((actlst = malloc(sizeof(struct cnfactlst))) != NULL) { + actlst->next = NULL; + actlst->syslines = NULL; + actlst->actType = actType; + actlst->lineno = yylineno; + actlst->cnfFile = strdup(cnfcurrfn); + if(actType == CNFACT_V2) + actlst->data.lst = lst; + else + actlst->data.legActLine = actLine; + } + return actlst; +} + +struct cnfactlst* +cnfactlstAddSysline(struct cnfactlst* actlst, char *line) +{ + struct cnfcfsyslinelst *cflst; + + if((cflst = malloc(sizeof(struct cnfcfsyslinelst))) != NULL) { + cflst->next = NULL; + cflst->line = line; + if(actlst->syslines == NULL) { + actlst->syslines = cflst; + } else { + cflst->next = actlst->syslines; + actlst->syslines = cflst; + } + } + return actlst; +} + + +void +cnfactlstDestruct(struct cnfactlst *actlst) +{ + struct cnfactlst *toDel; + + while(actlst != NULL) { + toDel = actlst; + actlst = actlst->next; + free(toDel->cnfFile); + cnfcfsyslinelstDestruct(toDel->syslines); + if(toDel->actType == CNFACT_V2) + nvlstDestruct(toDel->data.lst); + else + free(toDel->data.legActLine); + free(toDel); + } + +} + +static inline struct cnfcfsyslinelst* +cnfcfsyslinelstReverse(struct cnfcfsyslinelst *lst) +{ + struct cnfcfsyslinelst *curr, *prev; + if(lst == NULL) + return NULL; + prev = NULL; + while(lst != NULL) { + curr = lst; + lst = lst->next; + curr->next = prev; + prev = curr; + } + return prev; +} + +struct cnfactlst* +cnfactlstReverse(struct cnfactlst *actlst) +{ + struct cnfactlst *curr, *prev; + + prev = NULL; + while(actlst != NULL) { + //dbgprintf("reversing: %s\n", actlst->data.legActLine); + curr = actlst; + actlst = actlst->next; + curr->syslines = cnfcfsyslinelstReverse(curr->syslines); + curr->next = prev; + prev = curr; + } + return prev; +} + +void +cnfactlstPrint(struct cnfactlst *actlst) +{ + struct cnfcfsyslinelst *cflst; + + while(actlst != NULL) { + dbgprintf("aclst %p: ", actlst); + if(actlst->actType == CNFACT_V2) { + dbgprintf("V2 action type: "); + nvlstPrint(actlst->data.lst); + } else { + dbgprintf("legacy action line: '%s'\n", + actlst->data.legActLine); + } + for( cflst = actlst->syslines + ; cflst != NULL ; cflst = cflst->next) { + dbgprintf("action:cfsysline: '%s'\n", cflst->line); + } + actlst = actlst->next; + } +} + +struct cnfexpr* +cnfexprNew(unsigned nodetype, struct cnfexpr *l, struct cnfexpr *r) +{ + struct cnfexpr *expr; + + /* optimize some constructs during parsing */ + if(nodetype == 'M' && r->nodetype == 'N') { + ((struct cnfnumval*)r)->val *= -1; + expr = r; + goto done; + } + + if((expr = malloc(sizeof(struct cnfexpr))) != NULL) { + expr->nodetype = nodetype; + expr->l = l; + expr->r = r; + } +done: + return expr; +} + + +/* ensure that retval is a number; if string is no number, + * try to convert it to one. The semantics from es_str2num() + * are used (bSuccess tells if the conversion went well or not). + */ +static inline long long +var2Number(struct var *r, int *bSuccess) +{ + long long n; + if(r->datatype == 'S') { + n = es_str2num(r->d.estr, bSuccess); + } else { + *bSuccess = 1; + } + return r->d.n; +} + +/* ensure that retval is a string; if string is no number, + * emit error message and set number to 0. + */ +static inline es_str_t * +var2String(struct var *r, int *bMustFree) +{ + if(r->datatype == 'N') { + *bMustFree = 1; + return es_newStrFromNumber(r->d.n); + } + *bMustFree = 0; + return r->d.estr; +} + +/* Perform a function call. This has been moved out of cnfExprEval in order + * to keep the code small and easier to maintain. + */ +static inline void +doFuncCall(struct cnffunc *func, struct var *ret, void* usrptr) +{ + char *fname; + char *envvar; + int bMustFree; + es_str_t *estr; + char *str; + struct var r[CNFFUNC_MAX_ARGS]; + + dbgprintf("rainerscript: executing function id %d\n", func->fID); + switch(func->fID) { + case CNFFUNC_STRLEN: + if(func->expr[0]->nodetype == 'S') { + /* if we already have a string, we do not need to + * do one more recursive call. + */ + ret->d.n = es_strlen(((struct cnfstringval*) func->expr[0])->estr); + } else { + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + ret->d.n = es_strlen(estr); + if(bMustFree) es_deleteStr(estr); + } + ret->datatype = 'N'; + break; + case CNFFUNC_GETENV: + /* note: the optimizer shall have replaced calls to getenv() + * with a constant argument to a single string (once obtained via + * getenv()). So we do NOT need to check if there is just a + * string following. + */ + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + str = (char*) es_str2cstr(estr, NULL); + envvar = getenv(str); + ret->datatype = 'S'; + ret->d.estr = es_newStrFromCStr(envvar, strlen(envvar)); + if(bMustFree) es_deleteStr(estr); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + free(str); + break; + case CNFFUNC_TOLOWER: + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + if(!bMustFree) /* let caller handle that M) */ + estr = es_strdup(estr); + es_tolower(estr); + ret->datatype = 'S'; + ret->d.estr = estr; + break; + case CNFFUNC_CSTR: + cnfexprEval(func->expr[0], &r[0], usrptr); + estr = var2String(&r[0], &bMustFree); + if(!bMustFree) /* let caller handle that M) */ + estr = es_strdup(estr); + ret->datatype = 'S'; + ret->d.estr = estr; + break; + case CNFFUNC_CNUM: + if(func->expr[0]->nodetype == 'N') { + ret->d.n = ((struct cnfnumval*)func->expr[0])->val; + } else if(func->expr[0]->nodetype == 'S') { + ret->d.n = es_str2num(((struct cnfstringval*) func->expr[0])->estr, + NULL); + } else { + cnfexprEval(func->expr[0], &r[0], usrptr); + ret->d.n = var2Number(&r[0], NULL); + if(r[0].datatype == 'S') es_deleteStr(r[0].d.estr); + } + ret->datatype = 'N'; + break; + default: + if(Debug) { + fname = es_str2cstr(func->fname, NULL); + dbgprintf("rainerscript: invalid function id %u (name '%s')\n", + (unsigned) func->fID, fname); + free(fname); + } + ret->datatype = 'N'; + ret->d.n = 0; + } +} + +#define FREE_BOTH_RET \ + if(r.datatype == 'S') es_deleteStr(r.d.estr); \ + if(l.datatype == 'S') es_deleteStr(l.d.estr) + +#define COMP_NUM_BINOP(x) \ + cnfexprEval(expr->l, &l, usrptr); \ + cnfexprEval(expr->r, &r, usrptr); \ + ret->datatype = 'N'; \ + ret->d.n = var2Number(&l, &convok_l) x var2Number(&r, &convok_r); \ + FREE_BOTH_RET + +#define PREP_TWO_STRINGS \ + cnfexprEval(expr->l, &l, usrptr); \ + estr_l = var2String(&l, &bMustFree2); \ + cnfexprEval(expr->r, &r, usrptr); \ + estr_r = var2String(&r, &bMustFree) + +#define FREE_TWO_STRINGS \ + if(bMustFree) es_deleteStr(estr_r); \ + if(bMustFree2) es_deleteStr(estr_l); \ + FREE_BOTH_RET + +/* evaluate an expression. + * Note that we try to avoid malloc whenever possible (because of + * the large overhead it has, especially on highly threaded programs). + * As such, the each caller level must provide buffer space for the + * result on its stack during recursion. This permits the callee to store + * the return value without malloc. As the value is a somewhat larger + * struct, we could otherwise not return it without malloc. + * Note that we implement boolean shortcut operations. For our needs, there + * simply is no case where full evaluation would make any sense at all. + */ +void +cnfexprEval(struct cnfexpr *expr, struct var *ret, void* usrptr) +{ + struct var r, l; /* memory for subexpression results */ + es_str_t *estr_r, *estr_l; + int convok_r, convok_l; + int bMustFree, bMustFree2; + long long n_r, n_l; + + //dbgprintf("eval expr %p, type '%c'(%u)\n", expr, expr->nodetype, expr->nodetype); + switch(expr->nodetype) { + /* note: comparison operations are extremely similar. The code can be copyied, only + * places flagged with "CMP" need to be changed. + */ + case CMP_EQ: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = !es_strcmp(l.d.estr, r.d.estr); /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l == r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = !es_strcmp(l.d.estr, estr_r); /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n == n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = !es_strcmp(r.d.estr, estr_l); /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n == r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_NE: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr); /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l != r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r); /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n != n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l); /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n != r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_LE: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) <= 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l <= r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) <= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n <= n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) <= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n <= r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_GE: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) >= 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l >= r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) >= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n >= n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) >= 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n >= r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_LT: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) < 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l < r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) < 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n < n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) < 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n < r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_GT: + cnfexprEval(expr->l, &l, usrptr); + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + if(l.datatype == 'S') { + if(r.datatype == 'S') { + ret->d.n = es_strcmp(l.d.estr, r.d.estr) > 0; /*CMP*/ + } else { + n_l = var2Number(&l, &convok_l); + if(convok_l) { + ret->d.n = (n_l > r.d.n); /*CMP*/ + } else { + estr_r = var2String(&r, &bMustFree); + ret->d.n = es_strcmp(l.d.estr, estr_r) > 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_r); + } + } + } else { + if(r.datatype == 'S') { + n_r = var2Number(&r, &convok_r); + if(convok_r) { + ret->d.n = (l.d.n > n_r); /*CMP*/ + } else { + estr_l = var2String(&l, &bMustFree); + ret->d.n = es_strcmp(r.d.estr, estr_l) > 0; /*CMP*/ + if(bMustFree) es_deleteStr(estr_l); + } + } else { + ret->d.n = (l.d.n > r.d.n); /*CMP*/ + } + } + FREE_BOTH_RET; + break; + case CMP_STARTSWITH: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + ret->d.n = es_strncmp(estr_l, estr_r, estr_r->lenStr) == 0; + FREE_TWO_STRINGS; + break; + case CMP_STARTSWITHI: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + ret->d.n = es_strncasecmp(estr_l, estr_r, estr_r->lenStr) == 0; + FREE_TWO_STRINGS; + break; + case CMP_CONTAINS: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + ret->d.n = es_strContains(estr_l, estr_r) != -1; + FREE_TWO_STRINGS; + break; + case CMP_CONTAINSI: + PREP_TWO_STRINGS; + ret->datatype = 'N'; + ret->d.n = es_strCaseContains(estr_l, estr_r) != -1; + FREE_TWO_STRINGS; + break; + case OR: + cnfexprEval(expr->l, &l, usrptr); + ret->datatype = 'N'; + if(var2Number(&l, &convok_l)) { + ret->d.n = 1ll; + } else { + cnfexprEval(expr->r, &r, usrptr); + if(var2Number(&r, &convok_r)) + ret->d.n = 1ll; + else + ret->d.n = 0ll; + } + FREE_BOTH_RET; + break; + case AND: + cnfexprEval(expr->l, &l, usrptr); + ret->datatype = 'N'; + if(var2Number(&l, &convok_l)) { + cnfexprEval(expr->r, &r, usrptr); + if(var2Number(&r, &convok_r)) + ret->d.n = 1ll; + else + ret->d.n = 0ll; + } else { + ret->d.n = 0ll; + } + FREE_BOTH_RET; + break; + case NOT: + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + ret->d.n = !var2Number(&r, &convok_r); + if(r.datatype == 'S') es_deleteStr(r.d.estr); + break; + case 'N': + ret->datatype = 'N'; + ret->d.n = ((struct cnfnumval*)expr)->val; + break; + case 'S': + ret->datatype = 'S'; + ret->d.estr = es_strdup(((struct cnfstringval*)expr)->estr); + break; + case 'V': + ret->datatype = 'S'; + ret->d.estr = cnfGetVar(((struct cnfvar*)expr)->name, usrptr); + break; + case '+': + COMP_NUM_BINOP(+); + break; + case '-': + COMP_NUM_BINOP(-); + break; + case '*': + COMP_NUM_BINOP(*); + break; + case '/': + COMP_NUM_BINOP(/); + break; + case '%': + COMP_NUM_BINOP(%); + break; + case 'M': + cnfexprEval(expr->r, &r, usrptr); + ret->datatype = 'N'; + ret->d.n = -var2Number(&r, &convok_r); + if(r.datatype == 'S') es_deleteStr(r.d.estr); + break; + case 'F': + doFuncCall((struct cnffunc*) expr, ret, usrptr); + break; + default: + ret->datatype = 'N'; + ret->d.n = 0ll; + dbgprintf("eval error: unknown nodetype %u['%c']\n", + (unsigned) expr->nodetype, (char) expr->nodetype); + break; + } +} + +/* Evaluate an expression as a bool. This is added because expressions are + * mostly used inside filters, and so this function is quite common and + * important. + */ +int +cnfexprEvalBool(struct cnfexpr *expr, void *usrptr) +{ + int convok; + struct var ret; + cnfexprEval(expr, &ret, usrptr); + return var2Number(&ret, &convok); +} + +inline static void +doIndent(int indent) +{ + int i; + for(i = 0 ; i < indent ; ++i) + dbgprintf(" "); +} +void +cnfexprPrint(struct cnfexpr *expr, int indent) +{ + struct cnffunc *func; + int i; + + //dbgprintf("expr %p, indent %d, type '%c'\n", expr, indent, expr->nodetype); + switch(expr->nodetype) { + case CMP_EQ: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("==\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_NE: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("!=\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_LE: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("<=\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_GE: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf(">=\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_LT: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("<\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_GT: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf(">\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_CONTAINS: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("CONTAINS\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_CONTAINSI: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("CONTAINS_I\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_STARTSWITH: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("STARTSWITH\n"); + cnfexprPrint(expr->r, indent+1); + break; + case CMP_STARTSWITHI: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("STARTSWITH_I\n"); + cnfexprPrint(expr->r, indent+1); + break; + case OR: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("OR\n"); + cnfexprPrint(expr->r, indent+1); + break; + case AND: + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("AND\n"); + cnfexprPrint(expr->r, indent+1); + break; + case NOT: + doIndent(indent); + dbgprintf("NOT\n"); + cnfexprPrint(expr->r, indent+1); + break; + case 'S': + doIndent(indent); + cstrPrint("string '", ((struct cnfstringval*)expr)->estr); + dbgprintf("'\n"); + break; + case 'N': + doIndent(indent); + dbgprintf("%lld\n", ((struct cnfnumval*)expr)->val); + break; + case 'V': + doIndent(indent); + dbgprintf("var '%s'\n", ((struct cnfvar*)expr)->name); + break; + case 'F': + doIndent(indent); + func = (struct cnffunc*) expr; + cstrPrint("function '", func->fname); + dbgprintf("' (id:%d, params:%hu)\n", func->fID, func->nParams); + for(i = 0 ; i < func->nParams ; ++i) { + cnfexprPrint(func->expr[i], indent+1); + } + break; + case '+': + case '-': + case '*': + case '/': + case '%': + case 'M': + if(expr->l != NULL) + cnfexprPrint(expr->l, indent+1); + doIndent(indent); + dbgprintf("%c\n", (char) expr->nodetype); + cnfexprPrint(expr->r, indent+1); + break; + default: + dbgprintf("error: unknown nodetype %u\n", + (unsigned) expr->nodetype); + break; + } +} + +struct cnfnumval* +cnfnumvalNew(long long val) +{ + struct cnfnumval *numval; + if((numval = malloc(sizeof(struct cnfnumval))) != NULL) { + numval->nodetype = 'N'; + numval->val = val; + } + return numval; +} + +struct cnfstringval* +cnfstringvalNew(es_str_t *estr) +{ + struct cnfstringval *strval; + if((strval = malloc(sizeof(struct cnfstringval))) != NULL) { + strval->nodetype = 'S'; + strval->estr = estr; + } + return strval; +} + +struct cnfvar* +cnfvarNew(char *name) +{ + struct cnfvar *var; + if((var = malloc(sizeof(struct cnfvar))) != NULL) { + var->nodetype = 'V'; + var->name = name; + } + return var; +} + +struct cnfrule * +cnfruleNew(enum cnfFiltType filttype, struct cnfactlst *actlst) +{ + struct cnfrule* cnfrule; + if((cnfrule = malloc(sizeof(struct cnfrule))) != NULL) { + cnfrule->nodetype = 'R'; + cnfrule->filttype = filttype; + cnfrule->actlst = cnfactlstReverse(actlst); + } + return cnfrule; +} + +void +cnfrulePrint(struct cnfrule *rule) +{ + dbgprintf("------ start rule %p:\n", rule); + dbgprintf("%s: ", cnfFiltType2str(rule->filttype)); + switch(rule->filttype) { + case CNFFILT_NONE: + break; + case CNFFILT_PRI: + case CNFFILT_PROP: + dbgprintf("%s\n", rule->filt.s); + break; + case CNFFILT_SCRIPT: + dbgprintf("\n"); + cnfexprPrint(rule->filt.expr, 0); + break; + } + cnfactlstPrint(rule->actlst); + dbgprintf("------ end rule %p\n", rule); +} + +void +cnfcfsyslinelstDestruct(struct cnfcfsyslinelst *cfslst) +{ + struct cnfcfsyslinelst *toDel; + while(cfslst != NULL) { + toDel = cfslst; + cfslst = cfslst->next; + free(toDel->line); + free(toDel); + } +} + +void +cnfruleDestruct(struct cnfrule *rule) +{ + if( rule->filttype == CNFFILT_PRI + || rule->filttype == CNFFILT_PROP) + free(rule->filt.s); + cnfactlstDestruct(rule->actlst); + free(rule); +} + +struct cnffparamlst * +cnffparamlstNew(struct cnfexpr *expr, struct cnffparamlst *next) +{ + struct cnffparamlst* lst; + if((lst = malloc(sizeof(struct cnffparamlst))) != NULL) { + lst->nodetype = 'P'; + lst->expr = expr; + lst->next = next; + } + return lst; +} + +/* Obtain function id from name AND number of params. Issues the + * relevant error messages if errors are detected. + */ +static inline enum cnffuncid +funcName2ID(es_str_t *fname, unsigned short nParams) +{ + if(!es_strbufcmp(fname, (unsigned char*)"strlen", sizeof("strlen") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for strlen() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_STRLEN; + } else if(!es_strbufcmp(fname, (unsigned char*)"getenv", sizeof("getenv") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for getenv() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_GETENV; + } else if(!es_strbufcmp(fname, (unsigned char*)"tolower", sizeof("tolower") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for tolower() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_TOLOWER; + } else if(!es_strbufcmp(fname, (unsigned char*)"cstr", sizeof("cstr") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for cstr() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_CSTR; + } else if(!es_strbufcmp(fname, (unsigned char*)"cnum", sizeof("cnum") - 1)) { + if(nParams != 1) { + parser_errmsg("number of parameters for cnum() must be one " + "but is %d.", nParams); + return CNFFUNC_INVALID; + } + return CNFFUNC_CNUM; + } else { + return CNFFUNC_INVALID; + } +} + +struct cnffunc * +cnffuncNew(es_str_t *fname, struct cnffparamlst* paramlst) +{ + struct cnffunc* func; + struct cnffparamlst *param, *toDel; + unsigned short i; + unsigned short nParams; + + /* we first need to find out how many params we have */ + nParams = 0; + for(param = paramlst ; param != NULL ; param = param->next) + ++nParams; + if((func = malloc(sizeof(struct cnffunc) + (nParams * sizeof(struct cnfexp*)))) + != NULL) { + func->nodetype = 'F'; + func->fname = fname; + func->nParams = nParams; + func->fID = funcName2ID(fname, nParams); + /* shuffle params over to array (access speed!) */ + param = paramlst; + for(i = 0 ; i < nParams ; ++i) { + func->expr[i] = param->expr; + toDel = param; + param = param->next; + free(toDel); + } + } + return func; +} + +int +cnfDoInclude(char *name) +{ + char *cfgFile; + unsigned i; + int result; + glob_t cfgFiles; + struct stat fileInfo; + + /* Use GLOB_MARK to append a trailing slash for directories. + * Required by doIncludeDirectory(). + */ + result = glob(name, GLOB_MARK, NULL, &cfgFiles); + if(result == GLOB_NOSPACE || result == GLOB_ABORTED) { +#if 0 + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + errmsg.LogError(0, RS_RET_FILE_NOT_FOUND, "error accessing config file or directory '%s': %s", + pattern, errStr); + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); +#endif + dbgprintf("includeconfig glob error %d\n", errno); + return 1; + } + + for(i = 0; i < cfgFiles.gl_pathc; i++) { + cfgFile = cfgFiles.gl_pathv[i]; + + if(stat(cfgFile, &fileInfo) != 0) + continue; /* continue with the next file if we can't stat() the file */ + + if(S_ISREG(fileInfo.st_mode)) { /* config file */ + dbgprintf("requested to include config file '%s'\n", cfgFile); + cnfSetLexFile(cfgFile); + } else if(S_ISDIR(fileInfo.st_mode)) { /* config directory */ + if(strcmp(name, cfgFile)) { + /* do not include ourselves! */ + dbgprintf("requested to include directory '%s'\n", cfgFile); + cnfDoInclude(cfgFile); + } + } else { + dbgprintf("warning: unable to process IncludeConfig directive '%s'\n", cfgFile); + } + } + + globfree(&cfgFiles); + return 0; +} + +void +varDelete(struct var *v) +{ + if(v->datatype == 'S') + es_deleteStr(v->d.estr); +} + +void +cnfparamvalsDestruct(struct cnfparamvals *paramvals, struct cnfparamblk *blk) +{ + int i; + for(i = 0 ; i < blk->nParams ; ++i) { + varDelete(¶mvals[i].val); + } + free(paramvals); +} + +/* find the index (or -1!) for a config param by name. This is used to + * address the parameter array. Of course, we could use with static + * indices, but that would create some extra bug potential. So we + * resort to names. As we do this only during the initial config parsing + * stage the (considerable!) extra overhead is OK. -- rgerhards, 2011-07-19 + */ +int +cnfparamGetIdx(struct cnfparamblk *params, char *name) +{ + int i; + for(i = 0 ; i < params->nParams ; ++i) + if(!strcmp(params->descr[i].name, name)) + break; + if(i == params->nParams) + i = -1; /* not found */ + return i; +} + + +void +cstrPrint(char *text, es_str_t *estr) +{ + char *str; + str = es_str2cstr(estr, NULL); + dbgprintf("%s%s", text, str); + free(str); +} diff --git a/grammar/rainerscript.h b/grammar/rainerscript.h new file mode 100644 index 00000000..e11ae62f --- /dev/null +++ b/grammar/rainerscript.h @@ -0,0 +1,255 @@ +#ifndef INC_UTILS_H +#define INC_UTILS_H +#include <stdio.h> +#include <libestr.h> +#include <typedefs.h> + +#define CNFFUNC_MAX_ARGS 32 + /**< maximum number of arguments that any function can have (among + * others, this is used to size data structures). + */ + +extern int Debug; /* 1 if in debug mode, 0 otherwise -- to be enhanced */ + +enum cnfobjType { + CNFOBJ_ACTION, + CNFOBJ_GLOBAL, + CNFOBJ_INPUT, + CNFOBJ_MODULE, + CNFOBJ_INVALID = 0 +}; + +static inline char* +cnfobjType2str(enum cnfobjType ot) +{ + switch(ot) { + case CNFOBJ_ACTION: + return "action"; + break; + case CNFOBJ_GLOBAL: + return "global"; + break; + case CNFOBJ_INPUT: + return "input"; + break; + case CNFOBJ_MODULE: + return "module"; + break; + default:return "error: invalid cnfobjType"; + } +} + +enum cnfactType { CNFACT_V2, CNFACT_LEGACY }; + +/* a variant type, for example used for expression evaluation + * 2011-07-15/rger: note that there exists a "legacy" object var_t, + * which implements the same idea, but in a suboptimal manner. I have + * stipped this down as much as possible, but will keep it for a while + * to avoid unnecessary complexity during development. TODO: in the long + * term, var_t shall be replaced by struct var. + */ +struct var { + union { + es_str_t *estr; + struct cnfexpr *expr; + long long n; + } d; + char datatype; /* 'N' number, 'S' string, 'E' expression */ +}; + +struct cnfobj { + enum cnfobjType objType; + struct nvlst *nvlst; +}; + +struct nvlst { + struct nvlst *next; + es_str_t *name; + struct var val; + unsigned char bUsed; + /**< was this node used during config processing? If not, this + * indicates an error. After all, the user specified a setting + * that the software does not know. + */ +}; + +struct cnfcfsyslinelst { + struct cnfcfsyslinelst *next; + char *line; +}; + +struct cnfactlst { + struct cnfactlst *next; + struct cnfcfsyslinelst *syslines; + enum cnfactType actType; + union { + struct nvlst *lst; + char *legActLine; + } data; + char *cnfFile; + int lineno; +}; + +/* the following structures support expressions, and may (very much later + * be the sole foundation for the AST. + * + * nodetypes (list not yet complete) + * F - function + * N - number + * P - fparamlst + * R - rule + * S - string + * V - var + */ +enum cnfFiltType { CNFFILT_NONE, CNFFILT_PRI, CNFFILT_PROP, CNFFILT_SCRIPT }; +static inline char* +cnfFiltType2str(enum cnfFiltType filttype) +{ + switch(filttype) { + case CNFFILT_NONE: + return("filter:none"); + case CNFFILT_PRI: + return("filter:pri"); + case CNFFILT_PROP: + return("filter:prop"); + case CNFFILT_SCRIPT: + return("filter:script"); + } + return("error:invalid_filter_type"); /* should never be reached */ +} + + +struct cnfrule { + unsigned nodetype; + enum cnfFiltType filttype; + union { + char *s; + struct cnfexpr *expr; + } filt; + struct cnfactlst *actlst; +}; + +struct cnfexpr { + unsigned nodetype; + struct cnfexpr *l; + struct cnfexpr *r; +}; + +struct cnfnumval { + unsigned nodetype; + long long val; +}; + +struct cnfstringval { + unsigned nodetype; + es_str_t *estr; +}; + +struct cnfvar { + unsigned nodetype; + char *name; +}; + +struct cnffparamlst { + unsigned nodetype; /* P */ + struct cnffparamlst *next; + struct cnfexpr *expr; +}; + +enum cnffuncid { + CNFFUNC_INVALID = 0, /**< defunct entry, do not use (should normally not be present) */ + CNFFUNC_NAME = 1, /**< use name to call function (for future use) */ + CNFFUNC_STRLEN, + CNFFUNC_GETENV, + CNFFUNC_TOLOWER, + CNFFUNC_CSTR, + CNFFUNC_CNUM +}; + +struct cnffunc { + unsigned nodetype; + es_str_t *fname; + unsigned short nParams; + enum cnffuncid fID; /* function ID for built-ins, 0 means use name */ + struct cnfexpr *expr[]; +}; + +/* future extensions +struct x { + int nodetype; +}; +*/ + + +/* the following defines describe the parameter block for puling + * config parameters. Note that the focus is on ease and saveness of + * use, not performance. For example, we address parameters by name + * instead of index, because the former is less error-prone. The (severe) + * performance hit does not matter, as it is a one-time hit during config + * load but never during actual processing. So there is really no reason + * to care. + */ +struct cnfparamdescr { /* first the param description */ + char *name; /**< not a es_str_t to ease definition in code */ + ecslCmdHdrlType type; + unsigned flags; +}; +/* flags for cnfparamdescr: */ +#define CNFPARAM_REQUIRED 0x0001 + +struct cnfparamblk { /* now the actual param block use in API calls */ + unsigned short version; + unsigned short nParams; + struct cnfparamdescr *descr; +}; +#define CNFPARAMBLK_VERSION 1 + /**< caller must have same version as engine -- else things may + * be messed up. But note that we may support multiple versions + * inside the engine, if at some later stage we want to do + * that. -- rgerhards, 2011-07-15 + */ +struct cnfparamvals { /* the values we obtained for param descr. */ + struct var val; + unsigned char bUsed; +}; + + +int cnfParseBuffer(char *buf, unsigned lenBuf); +void readConfFile(FILE *fp, es_str_t **str); +struct nvlst* nvlstNew(es_str_t *name, es_str_t *value); +void nvlstDestruct(struct nvlst *lst); +void nvlstPrint(struct nvlst *lst); +void nvlstChkUnused(struct nvlst *lst); +struct nvlst* nvlstFindName(struct nvlst *lst, es_str_t *name); +struct cnfobj* cnfobjNew(enum cnfobjType objType, struct nvlst *lst); +void cnfobjDestruct(struct cnfobj *o); +void cnfobjPrint(struct cnfobj *o); +struct cnfactlst* cnfactlstNew(enum cnfactType actType, struct nvlst *lst, char *actLine); +void cnfactlstDestruct(struct cnfactlst *actlst); +void cnfactlstPrint(struct cnfactlst *actlst); +struct cnfactlst* cnfactlstAddSysline(struct cnfactlst* actlst, char *line); +struct cnfactlst* cnfactlstReverse(struct cnfactlst *actlst); +struct cnfexpr* cnfexprNew(unsigned nodetype, struct cnfexpr *l, struct cnfexpr *r); +void cnfexprPrint(struct cnfexpr *expr, int indent); +void cnfexprEval(struct cnfexpr *expr, struct var *ret, void *pusr); +int cnfexprEvalBool(struct cnfexpr *expr, void *usrptr); +struct cnfnumval* cnfnumvalNew(long long val); +struct cnfstringval* cnfstringvalNew(es_str_t *estr); +struct cnfrule * cnfruleNew(enum cnfFiltType filttype, struct cnfactlst *actlst); +void cnfruleDestruct(struct cnfrule *rule); +void cnfrulePrint(struct cnfrule *rule); +struct cnfvar* cnfvarNew(char *name); +struct cnffunc * cnffuncNew(es_str_t *fname, struct cnffparamlst* paramlst); +struct cnffparamlst * cnffparamlstNew(struct cnfexpr *expr, struct cnffparamlst *next); +int cnfDoInclude(char *name); +int cnfparamGetIdx(struct cnfparamblk *params, char *name); +struct cnfparamvals* nvlstGetParams(struct nvlst *lst, struct cnfparamblk *params, + struct cnfparamvals *vals); +void cnfparamsPrint(struct cnfparamblk *params, struct cnfparamvals *vals); +void varDelete(struct var *v); +void cnfparamvalsDestruct(struct cnfparamvals *paramvals, struct cnfparamblk *blk); +void cnfcfsyslinelstDestruct(struct cnfcfsyslinelst *cfslst); + +/* debug helper */ +void cstrPrint(char *text, es_str_t *estr); +#endif diff --git a/grammar/samp b/grammar/samp new file mode 100644 index 00000000..91d475b0 --- /dev/null +++ b/grammar/samp @@ -0,0 +1,11 @@ +daemon.*;mail.*;\ + news.err;\ + *.=debug;*.=info;\ + *.=notice;*.=warn |/dev/xconsole +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron,daemon.none;\ + mail,news.none -/var/log/messages + +mail.info -/var/log/mail.info + diff --git a/grammar/testdriver.c b/grammar/testdriver.c new file mode 100644 index 00000000..b29626d4 --- /dev/null +++ b/grammar/testdriver.c @@ -0,0 +1,109 @@ +/* This is a stand-alone test driver for grammar processing. We try to + * keep this separate as it simplyfies grammer development. + * + * Copyright 2011 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <libestr.h> +#include "rainerscript.h" +#include "parserif.h" + +extern int yylineno; +int Debug = 1; + +void +parser_errmsg(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + printf("error on or before line %d: ", yylineno); + vprintf(fmt, ap); + printf("\n"); + va_end(ap); +} + +int +yyerror(char *s) +{ + parser_errmsg("%s", s); + return 0; +} + +void +dbgprintf(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); +} + +void cnfDoObj(struct cnfobj *o) +{ + dbgprintf("global:obj: "); + cnfobjPrint(o); + cnfobjDestruct(o); +} + +void cnfDoRule(struct cnfrule *rule) +{ + dbgprintf("global:rule processed\n"); + cnfrulePrint(rule); +} + +void cnfDoCfsysline(char *ln) +{ + dbgprintf("global:cfsysline: %s\n", ln); +} + +void cnfDoBSDTag(char *ln) +{ + dbgprintf("global:BSD tag: %s\n", ln); +} + +void cnfDoBSDHost(char *ln) +{ + dbgprintf("global:BSD host: %s\n", ln); +} + +es_str_t* +cnfGetVar(char __attribute__((unused)) *name, + void __attribute__((unused)) *usrptr) +{ + es_str_t *estr; + estr = es_newStrFromCStr("", 1); + return estr; +} + +int +main(int argc, char *argv[]) +{ + int r; + + cnfSetLexFile(argc == 1 ? NULL : argv[1]); + yydebug = 0; + r = yyparse(); + printf("yyparse() returned %d\n", r); + return r; +} |