/* 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-2014 Rainer Gerhards and Adiscon GmbH. * * This file is part of the rsyslog runtime library. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * -or- * see COPYING.ASL20 in the source distribution * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ %top{ #ifndef __clang_analyzer__ /* this is not really our code */ #include "config.h" } %{ #include #include #include #include #include #include #include "rsyslog.h" #include "srUtils.h" #include "parserif.h" PRAGMA_IGNORE_Wsign_compare PRAGMA_IGNORE_Wmissing_noreturn FILE *fp_rs_full_conf_output = NULL; /* TODO: move this to a better modules, refactor -- rgerhards, 2018-01-22 */ static char * read_file(const char *const filename) { char *content = NULL; int fd = -1; struct stat sb; ssize_t nread; assert(filename != NULL); if((fd = open((const char*) filename, O_RDONLY)) == -1) { goto done; } if(fstat(fd, &sb) == -1) { goto done; } content = malloc(sb.st_size+1); if(content == NULL) { goto done; } nread = read(fd, content, sb.st_size); content[nread] = '\0'; if(nread != (ssize_t) sb.st_size) { free(content); content = NULL; goto done; } done: if(fd != -1) { close(fd); } return content; } static es_str_t* ATTR_NONNULL(1) expand_backticks_echo(const char *param) { assert(param != NULL); assert(strncmp(param, "echo ", sizeof("echo ")-1) == 0); char envvar[512]; int i_envvar = 0; int in_env = 0; es_str_t *estr; param += sizeof("echo ")-1; if((estr = es_newStr(strlen(param))) == NULL) { goto done; } while(*param) { if(in_env) { if(isspace(*param) || *param == '/') { envvar[i_envvar] = '\0'; const char *envval = getenv(envvar); if(envval != NULL) es_addBuf(&estr, envval, strlen(envval)); es_addChar(&estr, *param); /* curr char part of output! */ i_envvar = 0; in_env = 0; } else if(i_envvar > sizeof(envvar) - 1) { parser_errmsg("environment variable too long, begins with %s", envvar); goto done; } else { envvar[i_envvar++] = *param; } } else if (*param == '$') { in_env = 1; } else { es_addChar(&estr, *param); } ++param; } /* final check, we may be in env var name (very probable!) */ if(in_env) { envvar[i_envvar] = '\0'; const char *envval = getenv(envvar); if(envval != NULL) es_addBuf(&estr, envval, strlen(envval)); } done: return estr; } static es_str_t* ATTR_NONNULL(1) expand_backticks(char *const param) { es_str_t *estr; assert(param != NULL); if(strncmp(param, "echo ", sizeof("echo ")-1) == 0) { estr = expand_backticks_echo(param); } else if(strncmp(param, "cat ", sizeof("cat ")-1) == 0) { const char *val = read_file(param+4); if(val == NULL) { parser_errmsg("file could not be accessed for `%s`", param); const char *errmsg = "/* file cound not be accessed - see" "error messages */"; estr = es_newStrFromCStr(errmsg, strlen(errmsg)); } else { estr = es_newStrFromCStr(val, strlen(val)); } free((void*) val); } else { parser_errmsg("invalid backtick parameter `%s` - replaced by " "empty string (\"\")", param); estr = es_newStr(1); } return estr; } %} %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 INCALL /* INCALL: support for the call statement */ %x IN_PROCEDURE_CALL /* IN_PROCEDURE_CALL: support for the call statement */ %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 #include #include #include #include #include #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(const char *fname); static void cnfPrintToken(const char *token); 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... */ #if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) \ && !defined(__DragonflyBSD__) && !defined(_AIX) int fileno(FILE *stream); #endif %} %% /* keywords */ "if" { cnfPrintToken(yytext); BEGIN EXPR; return IF; } "foreach" { cnfPrintToken(yytext); BEGIN EXPR; return FOREACH; } "reload_lookup_table" { cnfPrintToken(yytext); BEGIN IN_PROCEDURE_CALL; return RELOAD_LOOKUP_TABLE_PROCEDURE; } "(" { cnfPrintToken(yytext); return yytext[0]; } \'([^'\\]|\\['"\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\' { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); return STRING; } \"([^"\\$]|\\["'\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\" { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); return STRING; } "," { cnfPrintToken(yytext); return yytext[0]; } ")" { cnfPrintToken(yytext); BEGIN INITIAL; return yytext[0]; } [ \t\n]* {cnfPrintToken(yytext); } . { cnfPrintToken(yytext); parser_errmsg("invalid character '%s' in expression " "- is there an invalid escape sequence somewhere?", yytext); } "(" { cnfPrintToken(yytext); BEGIN EXPR; return yytext[0]; } "then" { cnfPrintToken(yytext); BEGIN INITIAL; return THEN; } "do" { cnfPrintToken(yytext); BEGIN INITIAL; return DO; } ";" { cnfPrintToken(yytext); BEGIN INITIAL; return ';'; } "or" { cnfPrintToken(yytext); return OR; } "and" { cnfPrintToken(yytext); return AND; } "not" { cnfPrintToken(yytext); return NOT; } "=" | "," | "*" | "/" | "%" | "+" | "&" | "-" | "[" | "]" | ")" { cnfPrintToken(yytext); return yytext[0]; } "==" { cnfPrintToken(yytext); return CMP_EQ; } "<=" { cnfPrintToken(yytext); return CMP_LE; } ">=" { cnfPrintToken(yytext); return CMP_GE; } "!=" | "<>" { cnfPrintToken(yytext); return CMP_NE; } "<" { cnfPrintToken(yytext); return CMP_LT; } ">" { cnfPrintToken(yytext); return CMP_GT; } "contains" { cnfPrintToken(yytext); return CMP_CONTAINS; } "in" { cnfPrintToken(yytext); return ITERATOR_ASSIGNMENT; } "contains_i" { cnfPrintToken(yytext); return CMP_CONTAINSI; } "startswith" { cnfPrintToken(yytext); return CMP_STARTSWITH; } "startswith_i" { cnfPrintToken(yytext); return CMP_STARTSWITHI; } 0[0-7]+ | /* octal number */ 0x[0-9a-f]+ | /* hex number, following rule is dec; strtoll handles all! */ ([1-9][0-9]*|0) { cnfPrintToken(yytext); yylval.n = strtoll(yytext, NULL, 0); return NUMBER; } \$[$!./]{0,1}[@a-z_]*[!@a-z0-9\-_\.\[\]]* { cnfPrintToken(yytext); yylval.s = strdup(yytext+1); return VAR; } \'([^'\\]|\\['"\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\' { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); return STRING; } `([^`\\]|\\['`"\\bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*` { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = expand_backticks(yytext+1); return STRING; } \"([^"\\$]|\\["'\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\" { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); return STRING; } \"([^"\\]|\\["'\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\" { cnfPrintToken(yytext); parser_errmsg("$-sign in double quotes must be " "escaped, problem string is: %s", yytext); } [ \t\n] { cnfPrintToken(yytext); } "exists" { cnfPrintToken(yytext); return EXISTS; } /* special case function (see rainerscript.c) */ [a-z][a-z0-9_]* { cnfPrintToken(yytext); yylval.estr = es_newStrFromCStr(yytext, yyleng); return FUNC; } . { cnfPrintToken(yytext); parser_errmsg("invalid character '%s' in expression " "- is there an invalid escape sequence somewhere?", yytext); } [ \t\n] { cnfPrintToken(yytext); } . { cnfPrintToken(yytext); parser_errmsg("invalid character '%s' in 'call' statement" "- is there an invalid escape sequence somewhere?", yytext); } [a-zA-Z][a-zA-Z0-9\-_\.]* { cnfPrintToken(yytext); yylval.estr = es_newStrFromCStr(yytext, yyleng); BEGIN INITIAL; return NAME; } "&" { cnfPrintToken(yytext); return '&'; } "{" { cnfPrintToken(yytext); return '{'; } "}" { cnfPrintToken(yytext); return '}'; } "stop" { cnfPrintToken(yytext); return STOP; } "else" { cnfPrintToken(yytext); return ELSE; } "call" { cnfPrintToken(yytext); BEGIN INCALL; return CALL; } "call_indirect" { cnfPrintToken(yytext); BEGIN EXPR; return CALL_INDIRECT; } "set" { cnfPrintToken(yytext); BEGIN EXPR; return SET; } "reset" { cnfPrintToken(yytext); BEGIN EXPR; return RESET; } "unset" { cnfPrintToken(yytext); BEGIN EXPR; return UNSET; } "continue" { cnfPrintToken(yytext); return CONTINUE; } /* line number support because the "preprocessor" combines lines and so needs * to tell us the real source line. */ "preprocfilelinenumber(" { cnfPrintToken(yytext); BEGIN LINENO; } [0-9]+ { cnfPrintToken(yytext); yylineno = atoi(yytext) - 1; } ")" { cnfPrintToken(yytext); BEGIN INITIAL; } .|\n /* $IncludeConfig must be detected as part of CFSYSLINE, because this is * always the longest match :-( */ .|\n [^ \t\n]+ { cnfPrintToken(yytext); if(cnfDoInclude(yytext, 0) != 0) yyterminate(); BEGIN INITIAL; } "main_queue"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_MAINQ; BEGIN INOBJ; return BEGINOBJ; } "timezone"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_TIMEZONE; BEGIN INOBJ; return BEGINOBJ; } "parser"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_PARSER; BEGIN INOBJ; return BEGINOBJ; } "global"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_GLOBAL; BEGIN INOBJ; return BEGINOBJ; } "template"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_TPL; BEGIN INOBJ; return BEGIN_TPL; } "ruleset"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_RULESET; BEGIN INOBJ; return BEGIN_RULESET; } "property"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_PROPERTY; BEGIN INOBJ; return BEGIN_PROPERTY; } "constant"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_CONSTANT; BEGIN INOBJ; return BEGIN_CONSTANT; } "input"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_INPUT; BEGIN INOBJ; return BEGINOBJ; } "module"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_MODULE; BEGIN INOBJ; return BEGINOBJ; } "lookup_table"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_LOOKUP_TABLE; BEGIN INOBJ; return BEGINOBJ; } "dyn_stats"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_DYN_STATS; BEGIN INOBJ; return BEGINOBJ; } "percentile_stats"[ \n\t]*"(" { cnfPrintToken(yytext); yylval.objType = CNFOBJ_PERCTILE_STATS; BEGIN INOBJ; return BEGINOBJ; } "include"[ \n\t]*"(" { cnfPrintToken(yytext); BEGIN INOBJ; return BEGIN_INCLUDE; } "action"[ \n\t]*"(" { cnfPrintToken(yytext); BEGIN INOBJ; return BEGIN_ACTION; } ^[ \t]*:\$?[a-z\-]+[ ]*,[ ]*!?[a-z]+[ ]*,[ ]*\"(\\\"|[^\"])*\" { cnfPrintToken(yytext); yylval.s = strdup(rmLeadingSpace(yytext)); dbgprintf("lexer: propfilt is '%s'\n", yylval.s); return PROPFILT; } ^[ \t]*[\*a-z][\*a-z]*[0-7]*[\.,][,!=;\.\*a-z0-7]+ { cnfPrintToken(yytext); yylval.s = strdup(rmLeadingSpace(yytext)); return PRIFILT; } "~" | "*" | \-\/[^*][^\n]* | \/[^*][^\n]* | :[a-z0-9]+:[^\n]* | [\|\.\-\@\^?~>][^\n]+ | [a-z0-9_][a-z0-9_\-\+,;]* { cnfPrintToken(yytext); yylval.s = yytext; return LEGACY_ACTION; } ")" { cnfPrintToken(yytext); BEGIN INITIAL; return ENDOBJ; } [a-z][a-z0-9_\.]* { cnfPrintToken(yytext); yylval.estr = es_newStrFromCStr(yytext, yyleng); return NAME; } "," | "[" | "]" | "=" { cnfPrintToken(yytext); return(yytext[0]); } \"([^"\\]|\\['"?\\abfnrtv]|\\[0-7]{1,3})*\" { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = es_newStrFromBuf(yytext+1, strlen(yytext)-1); return STRING; } `([^`\\]|\\['`?\\abfnrtv]|\\[0-7]{1,3})*` { cnfPrintToken(yytext); yytext[yyleng-1] = '\0'; unescapeStr((uchar*)yytext+1, yyleng-2); yylval.estr = expand_backticks(yytext+1); return STRING; } /*yylval.estr = es_newStrFromBuf(yytext+1, yyleng-2); return VALUE; }*/ "/*" { cnfPrintToken(yytext); preCommentState = YY_START; BEGIN COMMENT; } "/*" { cnfPrintToken(yytext); preCommentState = YY_START; BEGIN COMMENT; } "/*" { cnfPrintToken(yytext); preCommentState = YY_START; BEGIN COMMENT; } "*/" { cnfPrintToken(yytext); BEGIN preCommentState; } ([^*]|\n)+|. #.*$ /* skip comments in input */ [ \n\t] { cnfPrintToken(yytext); } . { cnfPrintToken(yytext); parser_errmsg("invalid character '%s' in object definition " "- is there an invalid escape sequence somewhere?", yytext); } \$[a-z]+.*$ { cnfPrintToken(yytext); /* see comment on $IncludeConfig above */ if(!strncasecmp(yytext, "$includeconfig ", 14)) { yyless((yy_size_t)14); BEGIN INCL; } else if(!strncasecmp(yytext, "$ruleset ", 9)) { yylval.s = strdup(yytext); return LEGACY_RULESET; } else { cnfDoCfsysline(strdup(yytext)); } } ![^ \t\n]+[ \t]*$ { cnfPrintToken(yytext); yylval.s = strdup(yytext); return BSD_TAG_SELECTOR; } [+-]\*[ \t\n]*#.*$ { cnfPrintToken(yytext); yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } [+-]\*[ \t\n]*$ { cnfPrintToken(yytext); yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } ^[ \t]*[+-][a-z0-9.:-]+[ \t]*$ { cnfPrintToken(yytext); yylval.s = strdup(yytext); return BSD_HOST_SELECTOR; } \#.*\n {cnfPrintToken(yytext); }/* skip comments in input */ [\n\t ] {cnfPrintToken(yytext); }/* drop whitespace */ . { cnfPrintToken(yytext); parser_errmsg("invalid character '%s' " "- is there an invalid escape sequence somewhere?", yytext); } <> { if(popfile() != 0) yyterminate(); } %% static void cnfPrintToken(const char *token) { if(fp_rs_full_conf_output != NULL) { fprintf(fp_rs_full_conf_output, "%s", token); } } /* add config file or text the the stack of config objects to be * processed. * cnfobjname is either the file name or "text" if generated from * text ("text" can also be replaced by something more intelligent * by the caller. * The provided string is freed. */ int ATTR_NONNULL() cnfAddConfigBuffer(es_str_t *const str, const char *const cnfobj_name) { struct bufstack *bs; int r = 0; assert(str != NULL); assert(cnfobj_name != NULL); if((bs = malloc(sizeof(struct bufstack))) == NULL) { r = 1; goto done; } if(currbs != NULL) currbs->lineno = yylineno; bs->prev = currbs; bs->fn = strdup(cnfobj_name); yy_size_t lll = es_strlen(str); /* NOTE: yy_scan_buffer() does an automatic yy_switch_to_buffer to the new buffer */ bs->bs = yy_scan_buffer((char*)es_getBufAddr(str), lll); bs->estr = str; /* needed so we can free it later */ currbs = bs; cnfcurrfn = bs->fn; yylineno = 1; dbgprintf("config parser: pushed config fragment on top of stack: %s\n", cnfobj_name); if(fp_rs_full_conf_output != NULL) { fprintf(fp_rs_full_conf_output, "\n##### BEGIN CONFIG: %s (put on stack)\n", cnfcurrfn); } done: if(r != 0) { es_deleteStr(str); } return r; } /* set a new buffers. Returns 0 on success, 1 on error, 2 on file not exists. * note: in case of error, errno must be kept valid! */ int cnfSetLexFile(const char *const fname) { es_str_t *str = NULL; struct bufstack *bs; FILE *fp; int r = 0; /* check for invalid recursive include */ for(bs = currbs ; bs != NULL ; bs = bs->prev) { if(!strcmp(fname, bs->fn)) { parser_errmsg("trying to include file '%s', " "which is already included - ignored", fname); r = 1; goto done; } } if(fname == NULL) { fp = stdin; } else { if((fp = fopen(fname, "r")) == NULL) { r = 2; goto done; } } readConfFile(fp, &str); if(fp != stdin) fclose(fp); r = cnfAddConfigBuffer(str, ((fname == NULL) ? "stdin" : fname)); done: return r; } /* returns 0 on success, something else otherwise */ int popfile(void) { struct bufstack *bs = currbs; if(fp_rs_full_conf_output != NULL) { fprintf(fp_rs_full_conf_output, "\n##### END CONFIG: %s\n", cnfcurrfn); } 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 */ dbgprintf("config parser: reached end of file %s\n", bs->fn); 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) { dbgprintf("config parser: parsing completed\n"); return 1; /* all processed */ } yy_switch_to_buffer(currbs->bs); yylineno = currbs->lineno; cnfcurrfn = currbs->fn; dbgprintf("config parser: resume parsing of file %s at line %d\n", cnfcurrfn, yylineno); return 0; } void tellLexEndParsing(void) { free(cnfcurrfn); cnfcurrfn= NULL; } #endif // #ifndef __clang_analyzer__