%{ /* This file is part of mailfrom filter. Copyright (C) 2005, 2006 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #define obstack_chunk_alloc malloc #define obstack_chunk_free free #include #include "mailfrom.h" #include "gram.h" #include "prog.h" struct input_file_ident { ino_t i_node; dev_t device; }; static struct locus locus; /* Current input location */ static struct input_file_ident source_id; /* Identification of the input file */ static struct obstack string_stk; /* Obstack for constructing string values */ static char *multiline_delimiter; /* End of here-document delimiter */ static size_t multiline_delimiter_len; /* Length of multiline_delimiter_len */ static int multiline_unescape; /* Unescape here-document contents */ static int (*char_to_strip)(char); /* Strip matching characters of each here-document line */ static int is_tab(char c) { return c == '\t'; } static int is_space(char c) { return c == '\t' || c == ' '; } #define line_begin string_begin #define line_add string_add #define line_add_char string_add_char static void line_finish(void); static void string(char *str, size_t len); static int isemptystr(char *text); static int locus_var(const char *s); /* External interface to locus */ const struct locus * get_locus() { return &locus; } /* Auxiliary function for returning keyword tokens */ static int keyword(int kw) { yylval.locus = locus; return kw; } /* Input stack support */ #define xinput() (yyin ? getc(yyin) : EOF) #undef YY_INPUT #define YY_INPUT(buf,result,max_size) do { \ int i; \ for (i = 0; i < max_size; i++) { \ int ch = xinput(); \ if (ch == EOF) \ break; \ buf[i] = ch; \ } \ result = i; \ } while (0) #define LEX_BUFFER_STATE YY_BUFFER_STATE #define SET_BUFFER_STATE(s) do { \ (s) = YY_CURRENT_BUFFER; \ yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); \ } while (0) #define RESTORE_BUFFER_STATE(s) do { \ yy_delete_buffer(YY_CURRENT_BUFFER); \ yy_switch_to_buffer(s); \ } while (0) struct buffer_ctx { struct buffer_ctx *prev; struct locus locus; struct input_file_ident id; FILE *yyin; LEX_BUFFER_STATE state; }; static struct buffer_ctx *context_stack; #define STAT_ID_EQ(st,id) ((id).i_node == (st).st_ino \ && (id).device == (st).st_dev) struct buffer_ctx * ctx_lookup(struct stat *st) { struct buffer_ctx *ctx; for (ctx = context_stack; ctx; ctx = ctx->prev) if (STAT_ID_EQ(*st, ctx->id)) break; return ctx; } static mu_list_t include_path; static mu_list_t std_include_path; struct file_data { const char *name; size_t namelen; char *buf; size_t buflen; int found; }; static int find_include_file(void *item, void *data) { char *dir = item; struct file_data *dptr = data; size_t size = strlen(dir) + 1 + dptr->namelen + 1; if (size > dptr->buflen) { dptr->buflen = size; dptr->buf = xrealloc(dptr->buf, dptr->buflen); } strcpy(dptr->buf, dir); strcat(dptr->buf, "/"); strcat(dptr->buf, dptr->name); return dptr->found = access(dptr->buf, F_OK) == 0; } void lex_setup() { mu_list_create(&include_path); mu_list_create(&std_include_path); mu_list_append(std_include_path, "/usr/share/mailfromd/include"); mu_list_append(std_include_path, "/usr/local/share/mailfromd/include"); mu_list_append(std_include_path, DATAROOTDIR "/mailfromd/include"); } int push_source(const char *name) { FILE *fp; struct buffer_ctx *ctx; struct stat st; if (stat(name, &st)) { parse_error("cannot stat `%s': %s", name, mu_strerror(errno)); return 1; } if (locus.file && STAT_ID_EQ(st, source_id)) { parse_error("recursive inclusion"); return 1; } if ((ctx = ctx_lookup(&st))) { parse_error("recursive inclusion"); if (ctx->prev) parse_error_locus(&ctx->prev->locus, "`%s' already included here", name); else parse_error("`%s' already included at top level", name); return 1; } fp = fopen(name, "r"); if (!fp) { parse_error("cannot open `%s': %s", name, mu_strerror(errno)); return 1; } /* Push current context */ if (locus.file) { ctx = xmalloc (sizeof (*ctx)); ctx->locus = locus; ctx->id = source_id; ctx->yyin = yyin; ctx->prev = context_stack; context_stack = ctx; /* Switch to the new context */ yyin = fp; SET_BUFFER_STATE (ctx->state); } else { yyrestart (fp); } locus.file = strdup(name); locus.line = 1; source_id.i_node = st.st_ino; source_id.device = st.st_dev; if (yy_flex_debug) fprintf(stderr, "Processing file `%s'\n", name); return 0; } int pop_source() { struct buffer_ctx *ctx; if (yyin) fclose(yyin); if (!context_stack) { yyin = NULL; locus.file = NULL; return 1; } /* Restore previous context */ locus = context_stack->locus; locus.line++; /* #include rule did not increment it */ if (yy_flex_debug) fprintf(stderr, "Resuming file `%s' at line %lu\n", locus.file, (unsigned long) locus.line); source_id = context_stack->id; RESTORE_BUFFER_STATE(context_stack->state); ctx = context_stack->prev; free(context_stack); context_stack = ctx; return 0; } void parse_include() { int argc; char **argv; if (mu_argcv_get(yytext, "", NULL, &argc, &argv)) parse_error("cannot parse include line"); else if (argc > 2) parse_error("invalid include statement"); else { char *tmp = NULL; char *p = argv[1]; size_t len = strlen(p); int allow_cwd; static char *cwd = "."; if (p[0] == '<' && p[len - 1] == '>') { allow_cwd = 0; p[len - 1] = 0; p++; } else allow_cwd = 1; if (p[0] != '/') { struct file_data fd; fd.name = p; fd.namelen = strlen(p); fd.buf = NULL; fd.buflen = 0; fd.found = 0; if (allow_cwd) { mu_list_prepend(include_path, cwd); mu_list_do(include_path, find_include_file, &fd); mu_list_remove(std_include_path, cwd); } else mu_list_do(include_path, find_include_file, &fd); if (!fd.found) { mu_list_do(std_include_path, find_include_file, &fd); if (!fd.found) { parse_error("%s: No such file or directory", p); p = NULL; } } if (fd.found) p = tmp = fd.buf; } if (p) push_source(p); free(tmp); } mu_argcv_free(argc, argv); } %} /* Exclusive states: COMMENT Within a C-style comment; STR Processing a complex string; ML Within a multi-line aggregator. The line being built requires stripping leading whitespace (if requested). CML Continuation within a multi-line aggregator. The line being built does not require stripping leading whitespace. QML Quoted multi-line aggregator. No variable substitution and unquoting is needed. Inclusive states: ONBLOCK Lexical tie-in after an `on' keyword. In ONBLOCK state the strigns `as', `host', `for', `from', and `poll' are recognized as keywords. */ %x COMMENT STR ML CML QML %s ONBLOCK N [0-9][0-9]* P [1-9][0-9]* X [0-9a-fA-F] O [0-7] WS [ \t][ \t]* IDENT [a-zA-Z_][a-zA-Z_0-9]* LOCUS __file__|__line__|__function__ VMACRO __package__|__version__|__major__|__minor__|__patch__ MACRO {LOCUS}|{VMACRO} %% /* C-style comments */ "/*" BEGIN(COMMENT); [^*\n]* /* eat anything that's not a '*' */ "*"+[^*/\n]* /* eat up '*'s not followed by '/'s */ \n ++locus.line; "*"+"/" BEGIN(INITIAL); /* Configuration directives */ ^[ \t]*#[ \t]*pragma.*/\n parse_pragma(yytext); ^[ \t]*#[ \t]*include.*\n parse_include(); /* End-of-line comments */ #.*\n { locus.line++; } #.* /* end-of-file comment */; /* Reserved words */ accept return keyword(ACT_ACCEPT); reject return keyword(ACT_REJECT); tempfail return keyword(ACT_TEMPFAIL); continue return keyword(ACT_CONTINUE); discard return keyword(ACT_DISCARD); add return keyword(ADD); replace return keyword(REPLACE); delete return keyword(DELETE); if return keyword(IF); fi return keyword(FI); else return keyword(ELSE); elif return keyword(ELIF); on return keyword(ON); do return keyword(DO); done return keyword(DONE); matches return keyword(MATCHES); fnmatches return keyword(FNMATCHES); mx{WS}matches return keyword(MXMATCHES); mx{WS}fnmatches return keyword(MXFNMATCHES); when return keyword(WHEN); or return keyword(OR); and return keyword(AND); not return keyword(NOT); next return keyword(NEXT); prog return keyword(PROG); set return keyword(SET); catch return keyword(CATCH); echo return keyword(KW_ECHO); return return keyword(RETURN); returns return keyword(RETURNS); func return keyword(FUNC); switch return keyword(SWITCH); case return keyword(CASE); default return keyword(DEFAULT); string { yylval.type = dtype_string; return TYPE; } number { yylval.type = dtype_number; return TYPE; } {MACRO} { return locus_var(yytext); } poll return keyword(POLL); host return keyword(HOST); for return keyword(FOR); as return keyword(AS); from return keyword(FROM); /* Variables */ \%({MACRO}) { return locus_var(yytext+1); } \%{IDENT} { string(yytext + 1, yyleng - 1); return VARIABLE; } \%\{{IDENT}\} { string(yytext + 2, yyleng - 3); return VARIABLE; } /* Positional arguments */ \${P} { yylval.number = strtol(yytext + 1, NULL, 0); return ARG; } /* Sendmail variables */ \${IDENT} { if (yyleng == 2) string(yytext + 1, 1); else { line_begin(); line_add("{", 1); line_add(yytext + 1, yyleng - 1); line_add("}", 1); line_finish(); } return SYMBOL; } \$\{{IDENT}\} { string(yytext+1, yyleng - 1); return SYMBOL; } /* Exception conditions */ &{IDENT} { mf_status status; if (string_to_stat(yytext+1, &status)) { parse_error("Warning: Unknown exception code: %s", yytext); string(yytext+1, yyleng-1); return IDENTIFIER; } else { yylval.number = status; return NUMBER; } } /* Back-references */ \\{P} { yylval.number = strtoul(yytext+1, NULL, 0); return BACKREF; } /* Numeric strings */ {N}\.{N}\.{N} { string(yytext, yyleng); return XCODE; } [0-9]{3} { string(yytext, yyleng); return CODE; } 0[xX]{X}{X}* { yylval.number = strtoul(yytext, NULL, 16); return NUMBER; }; 0{O}{O}* { yylval.number = strtoul(yytext, NULL, 8); return NUMBER; }; 0|{P} { yylval.number = strtoul(yytext, NULL, 10); return NUMBER; }; /* Strings */ {IDENT}/"(" { if (yylval.builtin = builtin_lookup(yytext)) return yylval.builtin->rettype == dtype_unspecified ? BUILTIN_PROC : BUILTIN; else if (yylval.function = function_lookup(yytext)) return yylval.function->rettype == dtype_unspecified ? FUNCTION_PROC : FUNCTION; else { string(yytext, yyleng); return IDENTIFIER; } } {IDENT} { if (yylval.builtin = builtin_lookup(yytext)) return yylval.builtin->rettype == dtype_unspecified ? BUILTIN_PROC : BUILTIN_P; else if (yylval.function = function_lookup(yytext)) return yylval.function->rettype == dtype_unspecified ? FUNCTION_PROC : FUNCTION_P; else { string(yytext, yyleng); return IDENTIFIER; } } '[^\n']*' { string(yytext+1, yyleng-2); return STRING; } \"[^\\\"$%\n]*\" { string(yytext+1, yyleng-2); return STRING; } \"[^\\\"$%\n]*\\\n { ++locus.line; BEGIN(STR); line_begin(); line_add(yytext + 1, yyleng - 3); } \"[^\\\"$%\n]*/[\\$%] { BEGIN(STR); string(yytext+1, yyleng-1); line_begin(); return STRING; } \"\\x{X}{X}/[\\$%] { BEGIN(STR); line_add_char(strtoul(yytext + 3, NULL, 16)); line_finish(); return STRING; } \"\\x{X}{X} { BEGIN(STR); line_add_char(strtoul(yytext + 3, NULL, 16)); } \"\\0{O}{O}{O}/[\\$%] { BEGIN(STR); line_add_char(strtoul(yytext + 3, NULL, 8)); line_finish(); return STRING; } \"\\0{O}{O}{O} { BEGIN(STR); line_add_char(strtoul(yytext + 3, NULL, 8)); } \"\\[^1-9]/[\\$%] { BEGIN(STR); line_add_char(mu_argcv_unquote_char(yytext[2])); line_finish(); return STRING; } \"\\[^1-9] { BEGIN(STR); line_add_char(mu_argcv_unquote_char(yytext[2])); } [^\\\"$%\n]*\\\n { ++locus.line; line_add(yytext, yyleng - 2); } [^\\\"$%\n]*\" { BEGIN(INITIAL); if (yyleng > 1) line_add(yytext, yyleng - 1); line_finish(); return STRING; } [^\\\"$%\n]+/[\\$%] { line_add(yytext, yyleng); line_finish(); line_begin(); return STRING; } \\x{X}{X}/[\\$%] { line_add_char(strtoul(yytext + 2, NULL, 16)); line_finish(); line_begin(); return STRING; } \\x{X}{X} { line_add_char(strtoul(yytext + 2, NULL, 16)); } \\0{O}{O}{O}/[\\$%] { line_add_char(strtoul(yytext + 2, NULL, 8)); line_finish(); line_begin(); return STRING; } \\0{O}{O}{O} { line_add_char(strtoul(yytext + 2, NULL, 8)); } \\[^1-9]/[\\$%] { line_add_char(mu_argcv_unquote_char(yytext[1])); line_finish(); line_begin(); return STRING; } \\[^1-9] { line_add_char(mu_argcv_unquote_char(yytext[1])); } /* Multi-line strings */ "<<"(-" "?)?\\?{IDENT}[ \t]*.*\n | "<<"(-" "?)?'{IDENT}'[ \t]*.*\n { char *p; ++locus.line; char_to_strip = NULL; multiline_unescape = 1; line_begin(); p = yytext + 2; if (*p == '-') { ++p; if (*p == ' ') { ++p; char_to_strip = is_space; } else char_to_strip = is_tab; } if (*p == '\\') { p++; multiline_unescape = 0; } if (*p == '\'') { char *q; p++; multiline_unescape = 0; q = strchr(p, '\''); multiline_delimiter_len = q - p; } else multiline_delimiter_len = strcspn(p, " \t"); multiline_delimiter = xmalloc(multiline_delimiter_len + 1); memcpy(multiline_delimiter, p, multiline_delimiter_len); multiline_delimiter[multiline_delimiter_len] = 0; if (multiline_unescape) BEGIN(ML); else BEGIN(QML); } /* Quoted multilines */ [^\\\n]*\n { char *p; ++locus.line; p = yytext; if (char_to_strip) for (; char_to_strip (*p); p++) ; if (strlen(p) >= multiline_delimiter_len && memcmp(p, multiline_delimiter, multiline_delimiter_len) == 0 && isemptystr(p + multiline_delimiter_len)) { free (multiline_delimiter); multiline_delimiter = NULL; multiline_delimiter_len = 0; BEGIN(INITIAL); line_finish(); return STRING; } line_add(p, strlen(p)); } /* Unquoted multilines */ [^\\$%\n]+/[\\$%] { char *p = yytext; if (char_to_strip) for (; char_to_strip (*p); p++) ; line_add(p, strlen(p)); line_finish(); BEGIN(CML); return STRING; } [^\\$%\n]+/[\\$%] { line_add(yytext, yyleng); line_finish(); line_begin(); return STRING; } [$%]/[\\$%] { line_add(yytext, yyleng); line_finish(); return STRING; } [$%] { line_add(yytext, yyleng); } [^\\$%\n]*\n { char *p; ++locus.line; p = yytext; if (char_to_strip) for (; char_to_strip (*p); p++) ; if (strlen(p) >= multiline_delimiter_len && memcmp(p, multiline_delimiter, multiline_delimiter_len) == 0 && isemptystr(p + multiline_delimiter_len)) { free (multiline_delimiter); multiline_delimiter = NULL; multiline_delimiter_len = 0; BEGIN(INITIAL); line_finish(); return STRING; } line_add(p, strlen(p)); } [^\\$%\n]*\n { if (yyleng >= multiline_delimiter_len && memcmp(yytext, multiline_delimiter, multiline_delimiter_len) == 0 && isemptystr(yytext + multiline_delimiter_len)) { free (multiline_delimiter); multiline_delimiter = NULL; multiline_delimiter_len = 0; BEGIN(INITIAL); line_finish(); return STRING; } line_add(yytext, yyleng); BEGIN(ML); } /* Other tokens */ {WS} ; \n { locus.line++; } "=" return keyword(EQ); "!=" return keyword(NE); "<" return keyword(LT); "<=" return keyword(LE); ">" return keyword(GT); ">=" return keyword(GE); "&" return keyword(LOGAND); "|" return keyword(LOGOR); "^" return keyword(LOGXOR); "~" return keyword(LOGNOT); . return yytext[0]; %% static void string(char *str, size_t len) { line_begin(); line_add(str, len); line_finish(); } char * string_alloc(char *str, size_t len) { string_begin(); string_add(str, len); return string_finish(); } void string_begin() { /* nothing */ } char * string_finish() { obstack_1grow(&string_stk, 0); return obstack_finish(&string_stk); } static void line_finish() { yylval.string = string_finish(); if (yy_flex_debug) fprintf(stderr, "constructed line: %s\n",yylval.string); } void string_add(char *str, size_t len) { obstack_grow(&string_stk, str, len); } void string_add_char(unsigned char c) { obstack_1grow(&string_stk, c); } void parse_error(char *fmt, ...) { int n; va_list ap; char buf[256]; if (locus.file) n = snprintf(buf, sizeof buf, "%s:%lu: ", locus.file, (unsigned long) locus.line); else n = 0; va_start(ap, fmt); vsnprintf(buf + n, sizeof buf - n, fmt, ap); va_end(ap); mu_error(buf); if (strncasecmp(fmt, "warning:", 8)) error_count++; } void parse_error_locus(const struct locus *loc, char *fmt, ...) { int n; va_list ap; char buf[256]; n = snprintf(buf, sizeof buf, "%s:%lu: ", loc->file, (unsigned long) loc->line); va_start(ap, fmt); vsnprintf(buf + n, sizeof buf - n, fmt, ap); va_end(ap); mu_error(buf); if (strncmp(fmt, "warning:", 8)) error_count++; } int source(char *name, int ldebug) { yy_flex_debug = ldebug; obstack_init(&string_stk); return push_source(name); } int yywrap() { return pop_source(); } void add_include_dir(char *dir) { int rc; if (!include_path) { rc = mu_list_create(&include_path); if (rc) { mu_error("cannot create include path list: %s", mu_strerror(rc)); return; } } rc = mu_list_append(include_path, dir); if (rc) mu_error("cannot append to the include path list: %s", mu_strerror(rc)); } static int isemptystr(char *text) { for (; *text && isspace (*text); text++) ; return *text == 0; } void onblock(int enable) { if (enable) BEGIN(ONBLOCK); else BEGIN(INITIAL); } static int locus_var(const char *s) { if (strcmp(s, "__file__") == 0) { string(locus.file, strlen(locus.file)); return STRING; } else if (strcmp(s, "__line__") == 0) { yylval.number = locus.line; return NUMBER; } else if (strcmp(s, "__function__") == 0) { s = function_name(); string(s, strlen(s)); return STRING; } else if (strcmp(s, "__package__") == 0) { string(PACKAGE_TARNAME, sizeof PACKAGE_TARNAME - 1); return STRING; } else if (strcmp(s, "__version__") == 0) { string(PACKAGE_VERSION, sizeof PACKAGE_VERSION - 1); return STRING; } else if (strcmp(s, "__major__") == 0) { yylval.number = MAILFROMD_VERSION_MAJOR; return NUMBER; } else if (strcmp(s, "__minor__") == 0) { yylval.number = MAILFROMD_VERSION_MINOR; return NUMBER; } else if (strcmp(s, "__patch__") == 0) { yylval.number = MAILFROMD_VERSION_PATCH; return NUMBER; } abort(); } /* End of lex.l */