%{ /* This file is part of mailfrom filter. Copyright (C) 2005, 2006, 2007 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 #include "mailfrom.h" #include "prog.h" #define obstack_chunk_alloc malloc #define obstack_chunk_free free #include "obstack.h" static NODE *alloc_node(enum node_type type, const struct locus *locus); static void free_node(NODE *node); static void set_poll_arg(struct poll_data *poll, int kw, NODE *expr); static int time_multiplier(const char *, unsigned *, unsigned *); static int codegen(prog_counter_t *pc, NODE *node, int finalize, size_t nautos); static NODE *reverse(NODE *in); static NODE *function_call(struct function *function, size_t count, NODE *subtree); static data_type_t node_type(NODE *node); static NODE *cast_to(data_type_t type, NODE *node); static NODE *cast_arg_list(NODE *args, data_type_t *parmtype); static void add_xref(struct variable *var, const struct locus *locus); static struct variable *vardecl(char *name, data_type_t type, storage_class_t sc, struct locus *loc); struct variable *externdecl(char *name, data_type_t type, STKVAL val, struct locus *loc); static void register_auto(struct variable *var); static void unregister_auto(struct variable *var); static size_t forget_autos(size_t nparam); static void optimize(NODE *node); static NODE *root_node[smtp_state_count]; prog_counter_t entry_point[smtp_state_count]; int regex_flags; /* Should default to REG_NOSUB ? */ unsigned error_count; /* Number of detected errors */ size_t variable_count = 1; /* Offset of the next-to-be-defined variable in the data segment */ static enum smtp_state state_tag; /* State tag of the currently processed PROG */ static struct function *func; /* The currently compiled function */ static prog_counter_t jump_pc; /* Pointer to the chain of jmp instructions */ /* State handlers and their positional parameters */ struct state_parms { int cnt; /* Number or positional parameters */ data_type_t types[2]; /* Their data types */ } state_parms[] = { { 0, }, /* smtp_state_none */ { 0, }, /* smtp_state_connect */ { 1, { dtype_string } }, /* smtp_state_helo */ { 1, { dtype_string } }, /* smtp_state_envfrom */ { 1, { dtype_string } }, /* smtp_state_envrcpt */ { 2, { dtype_string, dtype_string } }, /* smtp_state_header */ { 0, }, /* smtp_state_eoh */ { 2, { dtype_string, dtype_number } }, /* smtp_state_body */ { 0, }, /* smtp_state_eom */ }; struct parmtype { struct parmtype *next; data_type_t type; }; #define synt_none 0 #define synt_handler 1 #define synt_catch 2 #define synt_function 3 static int stie_in; static int allow_unquoted_strings; static int parmcount_none() { return 0; } static data_type_t parmtype_none(int n) { return dtype_unspecified; } static int parmcount_handler() { return state_parms[state_tag].cnt; } static data_type_t parmtype_handler(int n) { return state_parms[state_tag].types[n-1]; } static int parmcount_catch() { return 1; } static data_type_t parmtype_catch(int n) { return dtype_number; } static int parmcount_function() { return func->parmcount; } static data_type_t parmtype_function(int n) { return func->parmtype[n-1]; } struct parminfo { int (*parmcount)(void); data_type_t (*parmtype)(int n); } parminfo[] = { { parmcount_none, parmtype_none }, { parmcount_handler, parmtype_handler }, { parmcount_catch, parmtype_catch }, { parmcount_function, parmtype_function } }; #define PARMCOUNT() parminfo[stie_in].parmcount() #define PARMTYPE(n) parminfo[stie_in].parmtype(n) data_type_t string_to_type(char *s) { if (strcmp(s, "n") == 0) return dtype_number; else if (strcmp(s, "s") == 0) return dtype_string; else return dtype_unspecified; } const char * type_to_string(data_type_t t) { switch (t) { case dtype_number: return "number"; case dtype_string: return "string"; case dtype_unspecified: return "unspecified"; default: abort(); } } static int check_func_usage(struct function *fp) { if (func) func->statemask |= fp->statemask; else if (fp->statemask && !(EXMASK(state_tag) & fp->statemask)) { parse_error("Function `%s' cannot be used in " "prog `%s'", fp->name, state_to_string(state_tag)); return 1; } return 0; } static int check_builtin(const struct builtin *bp) { if (func) func->statemask |= bp->statemask; else if (bp->statemask && !(EXMASK(state_tag) & bp->statemask)) { parse_error("Built-in function `%s' cannot be used in " "prog `%s'", bp->name, state_to_string(state_tag)); return 1; } if (bp->capture) capture_on(); return 0; } static void jump_fixup(prog_counter_t pos, prog_counter_t endpos); static NODE *create_on_node(NODE *sel, struct case_stmt *cases); static void register_macro(enum smtp_state tag, char *macro); %} %error-verbose %expect 24 %union { char *string; struct { NODE *head; NODE *tail; } stmtlist; NODE *node; struct return_node ret; struct poll_data poll; struct locus locus; struct pollarg { int kw; NODE *expr; } pollarg; struct arglist { NODE *head; NODE *tail; size_t count; } arglist; long number; const struct builtin *builtin; struct variable *var; enum smtp_state state; struct { struct locus locus; int qualifier; } matchtype; data_type_t type; struct parmtype *parm; struct parmlist { struct parmtype *head, *tail; int count; } parmlist; int tie_in; struct function *function; struct { struct valist *head, *tail; } valist_list; struct valist *valist; struct { struct case_stmt *head, *tail; } case_list ; struct case_stmt *case_stmt; }; %token ACT_ACCEPT ACT_REJECT ACT_TEMPFAIL ACT_CONTINUE ACT_DISCARD %token ADD REPLACE DELETE %token PROG IF FI ELSE ELIF %token ON HOST FOR FROM AS DO DONE POLL MATCHES FNMATCHES %token MXMATCHES MXFNMATCHES %token WHEN NEXT SET CATCH KW_ECHO RETURNS RETURN FUNC %token SWITCH CASE DEFAULT %token STRING CODE XCODE %token SYMBOL VARIABLE IDENTIFIER %token ARG NUMBER BACKREF %token BUILTIN BUILTIN_PROC BUILTIN_P %token OR AND EQ NE LT LE GT GE NOT LOGAND LOGOR LOGXOR LOGNOT %token FUNCTION FUNCTION_PROC FUNCTION_P %token TYPE %left CONCAT %left OR %left AND %left NOT %left LOGOR %left LOGXOR %left LOGAND %nonassoc EQ NE MATCHES FNMATCHES MXMATCHES MXFNMATCHES %nonassoc LT LE GT GE %left '+' '-' %left '*' '/' %left UMINUS %left BUILTIN FUNCTION FUNCTION_P BUILTIN_P %type stmt condition action if_cond else_cond on_cond atom funcall proccall expr common_expr simp_expr atom_expr asgn catch return case_cond autodcl %type stmtlist %type triplet maybe_triplet %type pollstmt pollarglist %type pollarg %type number %type arglist %type variable %type string %type state_ident %type matches fnmatches %type retdecl %type parmlist parmdecl %type parm %type fundecl %type value %type valist catchlist %type cond_branches branches %type cond_branch branch %type on %% input : decllist ; decllist : decl | decllist decl ; decl : PROG state_ident DO stmtlist DONE { size_t auto_count = forget_autos(PARMCOUNT()); root_node[$2] = $4.head; if (codegen(&entry_point[$2], $4.head, 1, auto_count) == 0) milter_enable_state($2); stie_in = synt_none; } | FUNC fundecl DO stmtlist DONE { size_t auto_count = forget_autos(PARMCOUNT()); prog_counter_t pc; func->node = $4.head; /* Assign the entry point early to properly handle recursive functions */ func->entry = code_get_counter(); /* Entry code */ if (func->exmask) { code_instr(instr_saveex); code_immediate((void*)func->exmask); } if (codegen(&pc, $4.head, 0, auto_count) == 0) { jump_fixup(jump_pc, code_get_counter()); /* Exit code */ if (func->exmask) code_instr(instr_restex); code_instr(instr_return); } stie_in = synt_none; func = NULL; } | vardecl ; vardecl : TYPE IDENTIFIER { if (!vardecl($2, $1, storage_extern, NULL)) YYERROR; } | SET IDENTIFIER expr { if (optimization_level) optimize($3); switch ($3->type) { case node_type_string: if (!externdecl($2, dtype_string, $3->v.string, &$1)) YYERROR; break; case node_type_number: if (!externdecl($2, dtype_number, (STKVAL) $3->v.number, &$1)) YYERROR; break; default: yyerror("initializer element is not constant"); YYERROR; } } ; fundecl : IDENTIFIER '(' parmdecl ')' retdecl { data_type_t *ptypes = NULL; if ($3.count) { int i; struct parmtype *p; ptypes = xmalloc($3.count * sizeof *ptypes); for (i = 0, p = $3.head; p; i++) { struct parmtype *next = p->next; ptypes[i] = p->type; free(p); p = next; } } $$ = func = function_install($1, $3.count, ptypes, $5, get_locus()); stie_in = synt_function; } ; parmdecl : /* empty */ { $$.count = 0; } | parmlist ; parmlist : parm { $$.count = 1; $$.head = $$.tail = $1; } | parmlist ',' parm { $1.count++; $1.tail->next = $3; $1.tail = $3; $$ = $1; } ; parm : TYPE IDENTIFIER { if (!vardecl($2, $1, storage_param, NULL)) YYERROR; $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->type = $1; } | TYPE { $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->type = $1; } | IDENTIFIER { $$ = xmalloc(sizeof *$$); $$->next = NULL; if (($$->type = string_to_type($1)) == dtype_unspecified) parse_error("unknown type specification: %s", $1); } ; retdecl : /* empty */ { $$ = dtype_unspecified; } | RETURNS TYPE { $$ = $2; } | RETURNS IDENTIFIER { if (($$ = string_to_type($2)) == dtype_unspecified) parse_error("unknown type specification: %s", $2); } ; state_ident: IDENTIFIER { $$ = string_to_state($1); if ($$ == smtp_state_none) parse_error("Unknown smtp state tag: %s", $1); state_tag = $$; stie_in = synt_handler; } ; stmtlist : stmt { if ($1) $1->next = NULL; $$.head = $$.tail = $1; } | stmtlist stmt { if ($2) { if ($1.tail) $1.tail->next = $2; else $1.head = $2; $1.tail = $2; } $$ = $1; } ; stmt : condition | action | asgn | autodcl | catch | return | proccall ; asgn : SET IDENTIFIER expr { struct variable *var; data_type_t t = node_type($3); if (t == dtype_unspecified) { parse_error("unspecified value not ignored as " "it should be"); YYERROR; } var = variable_lookup($2); if (!var) var = vardecl($2, t, storage_auto, &$1); if (!var) YYERROR; var->flags = VAR_VOLATILE; $$ = alloc_node(node_type_asgn, &$1); $$->v.asgn.var = var; $$->v.asgn.node = cast_to(var->type, $3); } ; autodcl : TYPE IDENTIFIER { if (!vardecl($2, $1, storage_auto, NULL)) YYERROR; $$ = NULL; } ; action : ACT_ACCEPT maybe_triplet { if ($2.code || $2.xcode || $2.message) parse_error("warning: arguments are ignored for accept"); $$ = alloc_node(node_type_result, &$1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_ACCEPT; } | ACT_REJECT maybe_triplet { if ($2.code && $2.code[0] != '5') parse_error("reject code should be 5xx"); if ($2.xcode && $2.xcode[0] != '5') parse_error("reject extended code should be 5.x.x"); $$ = alloc_node(node_type_result, &$1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_REJECT; } | ACT_TEMPFAIL maybe_triplet { if ($2.code && $2.code[0] != '4') parse_error("tempfail code should be 4xx"); if ($2.xcode && $2.xcode[0] != '4') parse_error("tempfail extended code should be 4.x.x"); $$ = alloc_node(node_type_result, &$1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_TEMPFAIL; } | ACT_CONTINUE { $$ = alloc_node(node_type_result, &$1); memset(&$$->v.ret, 0, sizeof $$->v.ret); $$->v.ret.stat = SMFIS_CONTINUE; } | ACT_DISCARD { $$ = alloc_node(node_type_result, &$1); memset(&$$->v.ret, 0, sizeof $$->v.ret); $$->v.ret.stat = SMFIS_DISCARD; } | NEXT { $$ = alloc_node(node_type_next, &$1); } | ADD string expr { $$ = alloc_node(node_type_header, &$1); $$->v.hdr.opcode = header_add; $$->v.hdr.name = $2; $$->v.hdr.value = $3; } | REPLACE string expr { $$ = alloc_node(node_type_header, &$1); $$->v.hdr.opcode = header_replace; $$->v.hdr.name = $2; $$->v.hdr.value = $3; } | DELETE string { $$ = alloc_node(node_type_header, &$1); $$->v.hdr.opcode = header_delete; $$->v.hdr.name = $2; } | KW_ECHO expr { $$ = alloc_node(node_type_echo, &$1); $$->v.node = cast_to(dtype_string, $2); } ; maybe_triplet: /* empty */ { memset(&$$, 0, sizeof $$); } | triplet ; triplet : CODE { $$.code = $1; $$.xcode = NULL; $$.message = NULL; } | CODE XCODE { $$.code = $1; $$.xcode = $2; $$.message = NULL; } | CODE XCODE expr { $$.code = $1; $$.xcode = $2; $$.message = cast_to(dtype_string, $3); } | CODE expr { $$.code = $1; $$.xcode = NULL; $$.message = cast_to(dtype_string, $2); } ; condition : if_cond | case_cond | on_cond ; if_cond : IF expr stmtlist else_cond FI { $$ = alloc_node(node_type_if, &$1); $$->v.cond.cond = $2; $$->v.cond.if_true = $3.head; $$->v.cond.if_false = $4; } ; else_cond : /* empty */ { $$ = NULL; } | ELIF expr stmtlist else_cond { $$ = alloc_node(node_type_if, &$1); $$->v.cond.cond = $2; $$->v.cond.if_true = $3.head; $$->v.cond.if_false = $4; } | ELSE stmtlist { $$ = $2.head; } ; case_cond : SWITCH expr DO cond_branches DONE { struct case_stmt *defcase = NULL, *pcase, *prev; $$ = alloc_node(node_type_switch, &$1); $$->v.switch_stmt.node = $2; /* Make sure there is only one default case and place it at the beginning of the list */ pcase = $4.head; if (!pcase->valist) { defcase = pcase; $4.head = $4.head->next; } prev = pcase; pcase = pcase->next; while (pcase) { if (!pcase->valist) { if (defcase) { parse_error_locus(&pcase->locus, "duplicate default statement"); parse_error_locus(&defcase->locus, "previously defined here"); YYERROR; } defcase = pcase; prev->next = pcase->next; } else prev = pcase; pcase = pcase->next; } if (!defcase) { defcase = xmalloc(sizeof *defcase); defcase->locus = *get_locus(); defcase->valist = NULL; defcase->node = alloc_node(node_type_next, &defcase->locus); } defcase->next = $4.head; $$->v.switch_stmt.cases = defcase; } ; cond_branches: cond_branch { $$.head = $$.tail = $1; } | cond_branches cond_branch { $$.tail->next = $2; $$.tail = $2; } ; cond_branch: CASE valist ':' stmtlist { $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->locus = $1; $$->valist = $2.head; $$->node = $4.head; } | DEFAULT ':' stmtlist { $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->locus = $1; $$->valist = NULL; $$->node = $3.head; } ; valist : value { $$.head = $$.tail = $1; } | valist OR value { $1.tail->next = $3; $1.tail = $3; $$ = $1; } ; value : string { $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->value.type = dtype_string; $$->value.v.string = $1; } | number { $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->value.type = dtype_number; $$->value.v.number = $1; } ; matches : MATCHES { $$.locus = $1; $$.qualifier = 0; } | MXMATCHES { $$.locus = $1; $$.qualifier = QUALIFIER_MX; } ; fnmatches : FNMATCHES { $$.locus = $1; $$.qualifier = 0; } | MXFNMATCHES { $$.locus = $1; $$.qualifier = QUALIFIER_MX; } ; number : NUMBER | CODE { char *p; $$ = strtol($1, &p, 10); if (*p) { /* should not happen */ parse_error("Invalid number (near `%s')", p); YYERROR; } } ; expr : NOT expr { $$ = alloc_node(node_type_un, &$1); $$->v.un.opcode = unary_not; $$->v.un.arg = cast_to(dtype_number, $2); } | expr EQ expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_eq; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr NE expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_ne; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr LT expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_lt; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr LE expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_le; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr GT expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_gt; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr GE expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_ge; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = cast_to(node_type($1), $3); } | expr matches expr %prec MATCHES { NODE *p; $$ = alloc_node(node_type_bin, &$2.locus); $$->v.bin.opcode = bin_match; $$->v.bin.qualifier = $2.qualifier | QUALIFIER_REGFREE; $$->v.bin.arg[0] = cast_to(dtype_string, $1); $$->v.bin.arg[1] = p = alloc_node(node_type_regcomp, &$2.locus); p->v.node = cast_to(dtype_string, $3); } | expr fnmatches expr %prec MATCHES { $$ = alloc_node(node_type_bin, &$2.locus); $$->v.bin.opcode = bin_fnmatch; $$->v.bin.qualifier = $2.qualifier; $$->v.bin.arg[0] = cast_to(dtype_string, $1); $$->v.bin.arg[1] = cast_to(dtype_string, $3); } | expr OR expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_or; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | expr AND expr { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_and; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | common_expr ; common_expr: simp_expr | common_expr simp_expr %prec CONCAT { $$ = alloc_node(node_type_concat, get_locus()); $$->v.concat.arg[0] = cast_to(dtype_string, $1); $$->v.concat.arg[1] = cast_to(dtype_string, $2); } ; simp_expr : atom_expr | simp_expr '+' simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_add; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '-' simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_sub; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '*' simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_mul; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr '/' simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_div; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr LOGAND simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_logand; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr LOGOR simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_logor; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } | simp_expr LOGXOR simp_expr { $$ = alloc_node(node_type_bin, get_locus()); $$->v.bin.opcode = bin_logxor; $$->v.bin.arg[0] = cast_to(dtype_number, $1); $$->v.bin.arg[1] = cast_to(dtype_number, $3); } ; atom_expr : funcall | '(' expr ')' { $$ = $2; } | atom | '-' simp_expr %prec UMINUS { $$ = alloc_node(node_type_un, get_locus()); $$->v.un.opcode = unary_minus; $$->v.un.arg = cast_to(dtype_number, $2); } | '+' simp_expr %prec UMINUS { $$ = $2; } | LOGNOT simp_expr %prec UMINUS { $$ = alloc_node(node_type_un, get_locus()); $$->v.un.opcode = unary_lognot; $$->v.un.arg = cast_to(dtype_number, $2); } ; atom : string { $$ = alloc_node(node_type_string, get_locus()); $$->v.string = $1; } | SYMBOL { register_macro(state_tag, $1); $$ = alloc_node(node_type_symbol, get_locus()); $$->v.string = $1; } | number { $$ = alloc_node(node_type_number, get_locus()); $$->v.number = $1; } | BACKREF { $$ = alloc_node(node_type_backref, get_locus()); $$->v.number = $1; } | variable { if (stie_in == synt_catch && $1->storage_class != storage_extern) { /* FIXME */ parse_error("storage class %s is not accessible from exception handler", storage_class_str($1->storage_class)); YYERROR; } $$ = alloc_node(node_type_variable, get_locus()); $$->v.variable = $1; } | ARG { if ($1 > PARMCOUNT()) parse_error("Argument number too high"); $$ = alloc_node(node_type_arg, get_locus()); $$->v.arg.data_type = PARMTYPE($1); $$->v.arg.number = $1; } ; funcall : BUILTIN '(' arglist ')' { if (check_builtin($1)) YYERROR; if ($3.count < $1->parmcount - $1->optcount) { parse_error("Too few arguments in call to `%s'", $1->name); YYERROR; } else if ($3.count > $1->parmcount) { parse_error("Too many arguments in call to `%s'", $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, get_locus()); $$->v.builtin.builtin = $1; $$->v.builtin.args = cast_arg_list($3.head, $1->parmtype); } } | BUILTIN '(' ')' { if (check_builtin($1)) YYERROR; if ($1->parmcount - $1->optcount) { parse_error("Too few arguments in call to `%s'", $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, get_locus()); $$->v.builtin.builtin = $1; $$->v.builtin.args = NULL; } } | BUILTIN_P expr { if (check_builtin($1)) YYERROR; if ($1->parmcount - $1->optcount > 1) { parse_error("Too few arguments in call to `%s'", $1->name); YYERROR; } else if ($1->parmcount == 0) { parse_error("Too many arguments in call to `%s'", $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, get_locus()); $$->v.builtin.builtin = $1; $$->v.builtin.args = cast_arg_list($2, $1->parmtype); } } | FUNCTION '(' arglist ')' { if (check_func_usage($1)) YYERROR; $$ = function_call($1, $3.count, $3.head); if (!$$) YYERROR; } | FUNCTION '(' ')' { if (check_func_usage($1)) YYERROR; $$ = function_call($1, 0, NULL); if (!$$) YYERROR; } | FUNCTION_P expr { if (check_func_usage($1)) YYERROR; $$ = function_call($1, 1, $2); if (!$$) YYERROR; } ; proccall : BUILTIN_PROC '(' arglist ')' { if (check_builtin($1)) YYERROR; if ($3.count < $1->parmcount - $1->optcount) { parse_error("Too few arguments in call to `%s'", $1->name); YYERROR; } else if ($3.count > $1->parmcount) { parse_error("Too many arguments in call to `%s'", $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, get_locus()); $$->v.builtin.builtin = $1; $$->v.builtin.args = cast_arg_list($3.head, $1->parmtype); } } | BUILTIN_PROC '(' ')' { if (check_builtin($1)) YYERROR; if ($1->parmcount - $1->optcount) { parse_error("Too few arguments in call to `%s'", $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, get_locus()); $$->v.builtin.builtin = $1; $$->v.builtin.args = NULL; } } | BUILTIN_PROC expr { if (check_builtin($1)) YYERROR; if ($1->parmcount - $1->optcount > 1) { parse_error("Too few arguments in call to `%s'", $1->name); YYERROR; } else if ($1->parmcount == 0) { parse_error("Too many arguments in call to `%s'", $1->name); YYERROR; } else { $$ = alloc_node(node_type_builtin, get_locus()); $$->v.builtin.builtin = $1; $$->v.builtin.args = cast_arg_list($2, $1->parmtype); } } | FUNCTION_PROC '(' arglist ')' { if (check_func_usage($1)) YYERROR; $$ = function_call($1, $3.count, $3.head); if (!$$) YYERROR; } | FUNCTION_PROC '(' ')' { if (check_func_usage($1)) YYERROR; $$ = function_call($1, 0, NULL); if (!$$) YYERROR; } | FUNCTION_PROC expr { if (check_func_usage($1)) YYERROR; $$ = function_call($1, 1, $2); if (!$$) YYERROR; } ; string : STRING | IDENTIFIER { if (!allow_unquoted_strings) parse_error("warning: unquoted identifier `%s'", $1); } ; arglist : expr { $1->next = NULL; $$.head = $$.tail = $1; $$.count = 1; } | arglist ',' expr { $1.tail->next = $3; $1.tail = $3; $1.count++; $$ = $1; } ; variable : VARIABLE { $$ = variable_lookup($1); if (!$$) { parse_error("Variable %s is not defined", $1); YYERROR; } add_xref($$, get_locus()); } ; catch : CATCH catchlist DO { $$ = stie_in; stie_in = synt_catch; } stmtlist DONE { int i; struct valist *p; stie_in = $4; $$ = alloc_node(node_type_catch, &$1); for (i = 0, p = $2.head; p; p = p->next) i++; $$->v.catch.count = i; $$->v.catch.exmask = 0; for (i = 0, p = $2.head; p; p = p->next, i++) { if (p->value.type != dtype_number) { parse_error_locus(&$1, "expected numeric value, but found `%s'", p->value.v.string); continue; } $$->v.catch.exmask |= EXMASK(p->value.v.number); if (stie_in == synt_function) func->exmask |= EXMASK(p->value.v.number); } $$->v.catch.node = $5.head; } ; catchlist : '*' { int i; $$.head = $$.tail = NULL; for (i = 0; i < mf_status_count; i++) { struct valist *p = xmalloc(sizeof *p); p->next = NULL; p->value.type = dtype_number; p->value.v.number = i; if (!$$.head) $$.head = $$.tail = p; else { $$.tail->next = p; $$.tail = p; } } } | valist ; return : RETURN { if (!func) parse_error("`return' outside of a function"); else if (func->rettype != dtype_unspecified) parse_error( "`return' with no value, in function " "returning non-void"); $$ = alloc_node(node_type_return, &$1); $$->v.node = NULL; } | RETURN expr { if (!func) parse_error("`return' outside of a function"); else if (func->rettype == dtype_unspecified) parse_error( "`return' with a value, in function " "returning void"); else { $$ = alloc_node(node_type_return, &$1); $$->v.node = cast_to(func->rettype, $2); } } ; /* *************************** */ /* ON statement */ /* *************************** */ on_cond : on pollstmt do branches DONE { NODE *sel, *np; NODE *head = NULL, *tail; const struct builtin *bi; /* Build argument list */ if ($2.client_addr) { head = tail = $2.client_addr; tail = $2.email; tail->next = NULL; head->next = tail; } else head = tail = $2.email; if ($2.ehlo) np = $2.ehlo; else { np = alloc_node(node_type_variable, get_locus()); np->v.variable = variable_lookup("ehlo_domain"); } tail->next = np; tail = np; if ($2.mailfrom) np = $2.mailfrom; else { np = alloc_node(node_type_variable, get_locus()); np->v.variable = variable_lookup("mailfrom_address"); } tail->next = np; tail = np; if ($2.client_addr) bi = builtin_lookup("strictpoll"); else bi = builtin_lookup("stdpoll"); sel = alloc_node(node_type_builtin, &$1); sel->v.builtin.builtin = bi; sel->v.builtin.args = head; $$ = create_on_node(sel, $4.head); } | on funcall do branches DONE { $$ = create_on_node($2, $4.head); } ; on : ON { onblock(1); } ; do : DO { onblock(0); } ; pollstmt : POLL expr { struct pollarg arg; arg.kw = FOR; arg.expr = $2; memset(&$$, 0, sizeof $$); set_poll_arg(&$$, arg.kw, arg.expr); } | POLL expr pollarglist { struct pollarg arg; arg.kw = FOR; arg.expr = $2; set_poll_arg(&$3, arg.kw, arg.expr); $$ = $3; } | POLL pollarglist { $$ = $2; } ; pollarglist: pollarg { memset(&$$, 0, sizeof $$); set_poll_arg(&$$, $1.kw, $1.expr); } | pollarglist pollarg { set_poll_arg(&$1, $2.kw, $2.expr); $$ = $1; } ; pollarg : FOR expr { $$.kw = FOR; $$.expr = $2; } | HOST expr { $$.kw = HOST; $$.expr = $2; } | AS expr { $$.kw = AS; $$.expr = $2; } | FROM expr { $$.kw = FROM; $$.expr = $2; } ; branches : branch { $$.head = $$.tail = $1; } | branches branch { $1.tail->next = $2; $1.tail = $2; $$ = $1; } ; branch : WHEN { allow_unquoted_strings = 1; } valist ':' { allow_unquoted_strings = 0; } stmtlist { struct valist *p; for (p = $3.head; p; p = p->next) { if (p->value.type == dtype_string) { mf_status status; if (string_to_stat(p->value.v.string, &status)) { parse_error("Unknown return code: %s", p->value.v.string); YYERROR; } p->value.type = dtype_number; p->value.v.number = status; } } $$ = xmalloc(sizeof *$$); $$->next = NULL; $$->locus = $1; $$->valist = $3.head; $$->node = $6.head; } ; %% int yyerror(char *s) { parse_error("%s", s); } int parse_program(char *name, int ydebug, int ldebug) { int rc; yydebug = ydebug; if (source(name, ldebug)) return -1; stie_in = synt_none; code_immediate(NULL); /* Reserve 0 slot */ rc = yyparse() + error_count; if (rc == 0) post_parse(); return rc; } NODE * alloc_node(enum node_type type, const struct locus *locus) { NODE *node = malloc(sizeof(*node)); if (!node) { yyerror("Not enough memory"); abort(); } node->type = type; node->locus = *locus; node->next = NULL; return node; } void free_node(NODE *node) { free(node); } void copy_node(NODE *dest, NODE *src) { dest->type = src->type; dest->locus = src->locus; dest->v = src->v; } void free_subtree(NODE *node) { /*FIXME*/ } static struct status_tab { mf_status status; char *name; } status_tab[] = { { mf_success, "success" }, { mf_not_found, "not_found" }, { mf_failure, "failure" }, { mf_temp_failure, "temp_failure" }, { mf_ston_conv, "ston_conv" }, { mf_divzero, "divzero" }, { mf_regcomp, "regcomp" }, { mf_invip, "invip" }, { mf_invcidr, "invcidr" }, { mf_invtime, "invtime" }, { mf_dbfailure, "dbfailure" }, { mf_range, "range" }, { mf_url, "url" }, { mf_ioerr, "ioerr" }, { 0, NULL } }; int string_to_stat(char *str, mf_status *status) { struct status_tab *sp; for (sp = status_tab; sp->name; sp++) if (strcmp(str, sp->name) == 0) { *status = sp->status; return 0; } return 1; } const char * mf_status_str(mf_status stat) { struct status_tab *sp; for (sp = status_tab; sp->name; sp++) if (sp->status == stat) return sp->name; return "UNKNOWN"; } /* Print parse tree */ static void print_node_list(NODE *node, int indent); static void print_node(NODE *node, int indent); void print_stat(sfsistat stat); static int dbg_setreply(void *data, char *code, char *xcode, char *message); static void dbg_setheader(void *data, struct old_header_node *hdr); static void print_level(int level) { level *= 2; printf("%*.*s", level, level, ""); } static void print_bin_op(enum bin_opcode opcode) { char *p; switch (opcode) { case bin_and: p = "AND"; break; case bin_or: p = "OR"; break; case bin_eq: p = "EQ"; break; case bin_ne: p = "NE"; break; case bin_lt: p = "LT"; break; case bin_le: p = "LE"; break; case bin_gt: p = "GT"; break; case bin_ge: p = "GE"; break; case bin_match: p = "MATCH"; break; case bin_fnmatch: p = "FNMATCH"; break; case bin_add: p = "ADD"; break; case bin_sub: p = "SUB"; break; case bin_mul: p = "MUL"; break; case bin_div: p = "DIV"; break; case bin_logand: p = "LOGAND"; break; case bin_logor: p = "LOGOR"; break; case bin_logxor: p = "LOGXOR"; break; default: p = "UNKNOWN_OP"; } printf("%s", p); } static void print_quoted_string(const char *str) { for (; *str; str++) { if (isprint(*str)) putchar(*str); else { putchar('\\'); switch (*str) { case '\a': putchar('a'); break; case '\b': putchar('b'); break; case '\f': putchar('f'); break; case '\n': putchar('n'); break; case '\r': putchar('r'); break; case '\t': putchar('t'); break; default: printf("%03o", *str); } } } } static void print_node(NODE *node, int level) { switch (node->type) { case node_type_noop: break; case node_type_string: print_level(level); printf("STRING: \""); print_quoted_string(node->v.string); printf("\"\n"); break; case node_type_symbol: print_level(level); printf("SYMBOL: %s\n", node->v.string); break; case node_type_number: print_level(level); printf("NUMBER: %ld\n", node->v.number); break; case node_type_backref: print_level(level); printf("BACKREF: %ld\n", node->v.number); break; case node_type_variable: print_level(level); printf("VARIABLE %s %s (%lu)\n", storage_class_str(node->v.asgn.var->storage_class), node->v.asgn.var->name, (unsigned long) node->v.asgn.var->off); break; case node_type_arg: print_level(level); printf("ARG %lu\n", node->v.arg.number); break; case node_type_asgn: print_level(level); printf("SET %s %s (%lu)\n", storage_class_str(node->v.asgn.var->storage_class), node->v.asgn.var->name, (unsigned long) node->v.asgn.var->off); print_node(node->v.asgn.node, level + 1); break; case node_type_if: print_level(level); printf("COND: \n"); print_node(node->v.cond.cond, level); print_level(level); printf("IFTRUE\n"); print_node_list(node->v.cond.if_true, level+1); print_level(level); printf("IFFALSE\n"); print_node_list(node->v.cond.if_false, level+1); break; case node_type_switch: { struct case_stmt *pcase; print_level(level); printf("SWITCH: \n"); print_node(node->v.switch_stmt.node, level+1); for (pcase = node->v.switch_stmt.cases; pcase; pcase = pcase->next) { print_level(level+1); if (pcase->valist) { struct valist *vp; printf("CASE "); for (vp = pcase->valist; vp; vp = vp->next) switch (vp->value.type) { case dtype_string: printf("\"%s\" ", vp->value.v.string); break; case dtype_number: printf("%ld ", vp->value.v.number); break; default: abort(); } } else { printf("DEFAULT"); } putchar('\n'); print_level(level+1); printf("ACTION\n"); print_node_list(pcase->node, level+2); } break; } case node_type_bin: print_level(level); print_bin_op(node->v.bin.opcode); if (node->v.bin.opcode == bin_match || node->v.bin.opcode == bin_fnmatch) { if (node->v.bin.qualifier & QUALIFIER_MX) printf(",MX"); if (node->v.bin.qualifier & QUALIFIER_REGFREE) printf(",REGFREE"); } printf("\n"); print_node(node->v.bin.arg[0], level+1); print_node(node->v.bin.arg[1], level+1); break; case node_type_un: print_level(level); switch (node->v.un.opcode) { case unary_not: printf("NOT\n"); break; case unary_minus: printf("NEG\n"); break; default: abort(); } print_node(node->v.un.arg, level+1); break; case node_type_next: print_level(level); printf("next\n"); break; case node_type_result: if (node->v.ret.code) { char *s = NULL; int expr = 0; print_level(level); if (node->v.ret.message) { if (node->v.ret.message->type == node_type_string) s = node->v.ret.message->v.string; else { expr = 1; s = "(expression)"; } } dbg_setreply(NULL, node->v.ret.code, node->v.ret.xcode, s); if (expr) print_node(node->v.ret.message, level+1); } print_level(level); print_stat(node->v.ret.stat); printf("\n"); break; case node_type_header: print_level(level); printf("%s %s: \n", header_command_str(node->v.hdr.opcode), node->v.hdr.name); print_node_list(node->v.hdr.value, level+1); break; case node_type_builtin: { print_level(level); printf("BUILTIN %s\n", node->v.builtin.builtin->name); print_node_list(node->v.builtin.args, level+1); break; } case node_type_concat: print_level(level); printf("CONCAT:\n"); print_node(node->v.concat.arg[0], level+1); print_node(node->v.concat.arg[1], level+1); break; case node_type_push: print_level(level); printf("PUSH %p:\n", node->v.val); break; case node_type_regcomp: print_level(level); printf("REGCOMP:\n"); print_node(node->v.node, level+1); break; case node_type_catch: { int i; print_level(level); printf("CATCH "); for (i = 0; i < mf_status_count; i++) if (node->v.catch.exmask & EXMASK(i)) printf("%s ", mf_status_str(i)); printf("\n"); print_node_list(node->v.catch.node, level+1); printf("END CATCH\n"); break; } case node_type_echo: print_level(level); printf("ECHO:\n"); print_node(node->v.node, level+1); break; case node_type_return: print_level(level); if (node->v.node) { printf("RETURN:\n"); print_node(node->v.node, level+1); } break; case node_type_call: print_level(level); printf("CALL %s\n", node->v.call.func->name); print_node_list(node->v.call.args, level+1); break; case node_type_cast: print_level(level); printf("CAST %s\n", type_to_string(node->v.cast.data_type)); print_node_list(node->v.cast.node, level+1); break; default: abort(); } } static void print_node_list(NODE *node, int level) { for (; node; node = node->next) print_node(node, level); } int function_enumerator(void *sym, void *data) { int i; struct function *f = sym; printf("function %s (", f->name); for (i = 0; i < f->parmcount; i++) { printf("%s", type_to_string(f->parmtype[i])); if (i < f->parmcount-1) putchar(','); } putchar(')'); if (f->rettype != dtype_unspecified) printf(" returns %s", type_to_string(f->rettype)); printf(":\n"); print_node_list(f->node, 0); printf("END function %s\n", f->name); return 0; } void print_syntax_tree() { enum smtp_state tag; printf("State handlers:\n"); printf("---------------\n"); for (tag = smtp_state_first; tag < smtp_state_count; tag++) { if (root_node[tag]) { printf("%s:\n", state_to_string(tag)); print_node_list(root_node[tag], 0); putchar('\n'); } } printf("User functions:\n"); printf("---------------\n"); symbol_enumerate(SYM_FUNC, function_enumerator, NULL); } /* Cross-reference support */ struct collect_data { struct obstack stk; size_t count; }; static int variable_enumerator(void *item, void *data) { struct variable *var = item; struct collect_data *p = data; obstack_grow(&p->stk, &var, sizeof var); p->count++; return 0; } int print_locus(void *item, void *data) { struct locus *loc = item; struct locus **prev = data; int c; if (!*prev) { *prev = loc; printf("%s", loc->file); c = ':'; } else if (strcmp((*prev)->file, loc->file)) { *prev = loc; printf(", %s", loc->file); c = ':'; } else c = ','; printf("%c%lu", c, (unsigned long) loc->line); return 0; } void print_xref_var(struct variable *var) { struct locus *prev = NULL; printf("%-32.32s %6s %lu ", var->name, type_to_string(var->type), (unsigned long)var->off); mu_list_do(var->xref, print_locus, &prev); printf("\n"); } int vp_comp(const void *a, const void *b) { struct variable * const *va = a, * const *vb = b; return strcmp((*va)->name, (*vb)->name); } void print_xref() { struct collect_data cd; struct variable **vp; size_t i; obstack_init(&cd.stk); cd.count = 0; symbol_enumerate(SYM_VARIABLE, variable_enumerator, &cd); printf("Cross-references:\n"); printf("-----------------\n"); vp = obstack_finish(&cd.stk); qsort(vp, cd.count, sizeof *vp, vp_comp); for (i = 0; i < cd.count; i++, vp++) print_xref_var(*vp); obstack_free(&cd.stk, NULL); } static mu_list_t macro_list[smtp_state_count]; void register_macro(enum smtp_state tag, char *macro) { if (!script_dump_macros) return; if (!macro_list[tag]) { mu_list_create(¯o_list[tag]); mu_list_set_comparator(macro_list[tag], compare_string); } if (mu_list_locate(macro_list[tag], macro, NULL)) mu_list_append(macro_list[tag], macro); } static int print_macro(void *item, void *data) { int *p = data; if (*p) { printf(" "); *p = 0; } else printf(", "); printf("%s", item); return 0; } void print_used_macros() { enum smtp_state tag; for (tag = smtp_state_first; tag < smtp_state_count; tag++) { if (macro_list[tag]) { int n = 1; printf("%s", state_to_string(tag)); mu_list_do(macro_list[tag], print_macro, &n); printf("\n"); } } } /* Code generation */ #define __code_cat3__(a,b,c) a ## b ## c #define CODE_BINARY(op, node) do { \ switch (node_type(node->v.bin.arg[0])) { \ case dtype_number: \ code_instr(__code_cat3__(instr_,op,n)); \ break; \ case dtype_string: \ code_instr(__code_cat3__(instr_,op,s)); \ break; \ default: \ parse_error_locus(&node->locus, \ "Invalid argument type in binary operation"); \ break; \ } \ } while (0) int code_cast(struct locus *locus, data_type_t fromtype, data_type_t totype) { if (fromtype == totype) return 0; switch (fromtype) { case dtype_unspecified: parse_error_locus(locus, "Expression evaluates to unspecified data type"); return 1; case dtype_string: code_instr(instr_ston); break; case dtype_number: code_instr(instr_ntos); break; } return 0; } static void traverse_tree(NODE *node); static void code_node(NODE *node); static void jump_fixup(prog_counter_t pos, prog_counter_t endpos) { while (pos) { prog_counter_t next = (prog_counter_t)code_peek(pos); code_put(pos, (void*)(endpos - pos - 1)); pos = next; } } /* The following code is generated for switch statements: popreg ; Pop the result of the previous instruction into the reg xlat ; Look up in the table and replace reg with the new value ; If the switch selector is of string type, xlats is ; coded instead N ; Number of elements in the xlat table key1 ; First key off1 ; Corresponding jump offset, relative to L0 key2 ; Second key off2 ; Corresponding jump offset, relative to L0 . . . keyN offN bnz L1 ; If xlat failed, jump to the default case L0: jreg ; Jump to the selected branch L1: ... ; Default case L0+off1: ... ; First branch jmp L2 L0+off2: ... ; Second branch jmp L2 . . . L0+offN: ... ; Nth branch L2: */ static void code_switch_branches(NODE *node, data_type_t type) { prog_counter_t start, refpos, jmppos; struct case_stmt *pcase; size_t tabsize = 0; /* Compute the table size and convert all key values to type */ for (pcase = node->v.switch_stmt.cases; pcase; pcase = pcase->next) { struct valist *vp; long v; char *p; for (vp = pcase->valist; vp; vp = vp->next) { tabsize++; if (vp->value.type != type) { char buf[NUMERIC_BUFSIZE_BOUND]; /* FIXME: The following code is very crude */ switch (type) { case dtype_number: v = strtol(vp->value.v.string, &p, 0); if (*p) { parse_error_locus(&pcase->locus, "cannot convert %s to number", vp->value.v.string); return; } vp->value.v.number = v; break; case dtype_string: snprintf(buf, sizeof buf, "%ld", vp->value.v.number); vp->value.v.string = strdup(buf); break; default: abort(); } } } } code_instr(instr_popreg); code_instr(type == dtype_number ? instr_xlat : instr_xlats); start = code_reserve(2*tabsize+1); code_instr(instr_bnz); code_immediate((void*)1); refpos = code_instr(instr_jreg); /* Generate code for the branches */ jmppos = 0; code_put(start, (void*)tabsize); start++; for (pcase = node->v.switch_stmt.cases; pcase; pcase = pcase->next) { struct valist *vp; prog_counter_t pos; pos = code_get_counter() - refpos; traverse_tree(pcase->node); if (pcase->next) { code_instr(instr_jmp); jmppos = code_immediate((void*)jmppos); } for (vp = pcase->valist; vp; vp = vp->next) { code_put(start, (void*)vp->value.v.string); code_put(start+1, (void*)pos); start += 2; } } /* Fix up jump offsets */ jump_fixup(jmppos, code_get_counter()); } static void code_switch(NODE *node) { code_node(node->v.switch_stmt.node); code_switch_branches(node, node_type(node->v.switch_stmt.node)); } #define LOCUS_EQ(a,b) \ (strcmp((a)->file, (b)->file) == 0 && (a)->line == (b)->line) static void code_node(NODE *node) { prog_counter_t pos1, pos2, endpos; static struct locus *old_locus; if (!node) { error_count++; return; } #define MARK_LOCUS() \ do if (!old_locus || !LOCUS_EQ(old_locus, &node->locus)) { \ old_locus = &node->locus; \ code_instr(instr_locus); \ code_immediate(node->locus.file); \ code_immediate((void*)node->locus.line); \ } while (0) switch (node->type) { case node_type_noop: MARK_LOCUS(); break; case node_type_string: MARK_LOCUS(); code_instr(instr_push_immediate); code_immediate(node->v.string); break; case node_type_number: MARK_LOCUS(); code_instr(instr_push_immediate); code_immediate((void*)node->v.number); break; case node_type_symbol: MARK_LOCUS(); code_instr(instr_symbol); code_immediate(node->v.string); break; case node_type_variable: MARK_LOCUS(); switch (node->v.variable->storage_class) { case storage_extern: code_instr(instr_variable); break; case storage_auto: code_instr(instr_auto); break; case storage_param: code_instr(instr_arg); break; } code_immediate((void*)node->v.variable->off); if (node->v.variable->type == dtype_string) code_instr(instr_svalidate); break; case node_type_backref: MARK_LOCUS(); code_instr(instr_backref); code_immediate((void*)node->v.number); break; case node_type_arg: MARK_LOCUS(); code_instr(instr_arg); code_immediate((void*)node->v.arg.number); break; case node_type_asgn: code_node(node->v.asgn.node); node->v.variable->type = node_type(node->v.asgn.node); code_instr(instr_asgn); code_immediate((void*)node->v.variable->storage_class); code_immediate((void*)node->v.variable->off); break; case node_type_if: code_node(node->v.cond.cond); MARK_LOCUS(); code_instr(instr_bz); pos1 = code_immediate(NULL); traverse_tree(node->v.cond.if_true); if (node->v.cond.if_false) { code_instr(instr_jmp); pos2 = code_immediate(NULL); traverse_tree(node->v.cond.if_false); endpos = code_get_counter (); code_put(pos1, (void *)(pos2 - pos1)); code_put(pos2, (void *)(endpos - pos2 - 1)); } else code_put(pos1, (void *)(code_get_counter () - pos1 - 1)); break; case node_type_bin: code_node(node->v.bin.arg[0]); switch (node->v.bin.opcode) { case bin_and: MARK_LOCUS(); /* cond1 if not true goto X cond2 if true goto Y X: push 0 goto Z Y: push 1 Z: */ code_instr(instr_bz); pos1 = code_immediate(NULL); code_node(node->v.bin.arg[1]); code_instr(instr_bnz); pos2 = code_immediate((void *)4); code_instr(instr_push_immediate); code_immediate((void *)0); code_instr(instr_jmp); code_immediate((void *)2); code_instr(instr_push_immediate); code_immediate((void*)1); code_put(pos1, (void *)(pos2 - pos1)); break; case bin_or: MARK_LOCUS(); /* cond1 if true goto X cond2 if not true goto Y X: push 1 goto Z Y: push 0 Z: */ code_instr(instr_bnz); pos1 = code_immediate(NULL); code_node(node->v.bin.arg[1]); code_instr(instr_bz); pos2 = code_immediate((void *)4); code_instr(instr_push_immediate); code_immediate((void *)1); code_instr(instr_jmp); code_immediate((void *)2); code_instr(instr_push_immediate); code_immediate((void *)0); code_put(pos1, (void *)(pos2 - pos1)); break; case bin_eq: code_node(node->v.bin.arg[1]); CODE_BINARY(eq, node); break; case bin_ne: code_node(node->v.bin.arg[1]); CODE_BINARY(ne, node); break; case bin_lt: code_node(node->v.bin.arg[1]); CODE_BINARY(lt, node); break; case bin_le: code_node(node->v.bin.arg[1]); CODE_BINARY(le, node); break; case bin_gt: code_node(node->v.bin.arg[1]); CODE_BINARY(gt, node); break; case bin_ge: code_node(node->v.bin.arg[1]); CODE_BINARY(ge, node); break; case bin_match: code_node(node->v.bin.arg[1]); code_instr(instr_push_immediate); code_immediate((void*) (node->v.bin.qualifier & QUALIFIER_REGFREE)); MARK_LOCUS(); if (node->v.bin.qualifier & QUALIFIER_MX) code_instr(instr_regex_mx); else code_instr(instr_regex); break; case bin_fnmatch: code_node(node->v.bin.arg[1]); MARK_LOCUS(); if (node->v.bin.qualifier & QUALIFIER_MX) code_instr(instr_fnmatch_mx); else code_instr(instr_fnmatch); break; case bin_add: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_add); break; case bin_sub: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_sub); break; case bin_mul: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_mul); break; case bin_div: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_div); break; case bin_logand: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_logand); break; case bin_logor: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_logor); break; case bin_logxor: code_node(node->v.bin.arg[1]); MARK_LOCUS(); code_instr(instr_logxor); break; default: break; } break; case node_type_regcomp: code_node(node->v.node); code_instr(instr_regcomp); break; case node_type_push: code_instr(instr_push_immediate); code_immediate(node->v.val); break; case node_type_un: code_node(node->v.un.arg); MARK_LOCUS(); switch (node->v.un.opcode) { case unary_not: code_instr(instr_not); break; case unary_minus: code_instr(instr_neg); break; case unary_lognot: code_instr(instr_lognot); break; default: abort(); } break; case node_type_next: MARK_LOCUS(); code_instr(instr_next); break; case node_type_result: if (node->v.ret.message) code_node(node->v.ret.message); else { code_instr(instr_push_immediate); code_immediate(NULL); } MARK_LOCUS(); code_instr(instr_result); code_immediate((void*)node->v.ret.stat); code_immediate(node->v.ret.code); code_immediate(node->v.ret.xcode); code_instr(NULL); break; case node_type_header: MARK_LOCUS(); code_node(node->v.hdr.value); code_instr(instr_header); code_immediate((void*)node->v.hdr.opcode); code_immediate(node->v.hdr.name); break; case node_type_builtin: { NODE *p; int i; const struct builtin *bp = node->v.builtin.builtin; for (p = node->v.builtin.args, i = 0; p; p = p->next, i++) code_node(p); for (; i < bp->parmcount; i++) { code_instr(instr_push_immediate); code_immediate(0); } MARK_LOCUS(); code_instr(instr_builtin); code_immediate(node->v.builtin.builtin->name); code_immediate(node->v.builtin.builtin->handler); break; } case node_type_call: { NODE *p; for (p = node->v.call.args; p; p = p->next) code_node(p); MARK_LOCUS(); code_instr(instr_funcall); code_immediate(node->v.call.func->name); code_immediate((void*)node->v.call.func->entry); code_instr(instr_adjust); code_immediate((void*)node->v.call.func->parmcount); if (node->v.call.func->rettype != dtype_unspecified) code_instr(instr_pushreg); break; } case node_type_concat: code_node(node->v.concat.arg[0]); code_node(node->v.concat.arg[1]); code_instr(instr_concat); break; case node_type_catch: { int old_tie_in; prog_counter_t ctr; MARK_LOCUS(); code_instr(instr_catch); pos1 = code_immediate(NULL); code_immediate((void*)node->v.catch.count); code_immediate((void*)node->v.catch.exmask); old_tie_in = stie_in; stie_in = synt_catch; ctr = jump_pc; jump_pc = 0; traverse_tree(node->v.catch.node); jump_fixup(jump_pc, code_get_counter()); jump_pc = ctr; stie_in = old_tie_in; code_instr(instr_retcatch); if (stie_in == synt_function) { code_instr(instr_adjust); code_immediate((void*)1); code_instr(instr_jmp); jump_pc = code_immediate((void*)jump_pc); } else { code_instr(instr_push_immediate); code_immediate(NULL); code_instr(instr_result); code_immediate(SMFIS_CONTINUE); code_immediate(NULL); code_immediate(NULL); code_immediate(NULL); } endpos = code_get_counter (); code_put(pos1, (void *)(endpos - pos1)); break; } case node_type_echo: code_node(node->v.node); MARK_LOCUS(); code_instr(instr_echo); break; case node_type_return: if (func->rettype == dtype_unspecified) { MARK_LOCUS(); code_instr(instr_jmp); jump_pc = code_immediate((void*)jump_pc); } else { code_node(node->v.node); MARK_LOCUS(); code_instr(instr_popreg); code_instr(instr_jmp); jump_pc = code_immediate((void*)jump_pc); } break; case node_type_switch: code_switch(node); break; case node_type_cast: code_node(node->v.cast.node); code_cast(&node->v.cast.node->locus, node_type(node->v.cast.node), node->v.cast.data_type); break; default: abort(); } } static void traverse_tree(NODE *node) { for (; node; node = node->next) code_node(node); } static void optimize_switch(NODE *node) { struct case_stmt *pcase; data_type_t type; NODE *p, *bp = NULL; p = node->v.switch_stmt.node; optimize(p); type = node_type(p); for (pcase = node->v.switch_stmt.cases; pcase; pcase = pcase->next) { struct valist *vp; long v; char *s; for (vp = pcase->valist; vp; vp = vp->next) { if (vp->value.type != type) { char buf[NUMERIC_BUFSIZE_BOUND]; /* FIXME: The following code needs generalization */ switch (type) { case dtype_number: v = strtol(vp->value.v.string, &s, 0); if (*s) { parse_error_locus(&pcase->locus, "cannot convert %s to number", vp->value.v.string); return; } vp->value.v.number = v; break; case dtype_string: snprintf(buf, sizeof buf, "%ld", vp->value.v.number); vp->value.v.string = strdup(buf); break; default: abort(); } } vp->value.type = type; } optimize(pcase->node); } if (p->type == node_type_number || p->type == node_type_string) { for (pcase = node->v.switch_stmt.cases; pcase; pcase = pcase->next) { struct valist *vp; for (vp = pcase->valist; vp; vp = vp->next) { if (p->type == node_type_number ? (vp->value.v.number == p->v.number) : (strcmp(vp->value.v.string, p->v.string) == 0)) { bp = pcase->node; pcase->node = NULL; break; } } } /* If no node found, use the default one */ if (!bp) { bp = node->v.switch_stmt.cases->node; node->v.switch_stmt.cases->node = NULL; } if (bp) { NODE *tail = node->next; *node = *bp; free_node(bp); for (; node->next; node = node->next) ; node->next = tail; /* FIXME : free branches */ } } } static void optimize_arith(NODE *node) { NODE *arg0 = node->v.bin.arg[0]; NODE *arg1 = node->v.bin.arg[1]; if (arg0->type == node_type_number && arg1->type == node_type_number) { switch (node->v.bin.opcode) { case bin_add: node->v.number = arg0->v.number + arg1->v.number; break; case bin_sub: node->v.number = arg0->v.number - arg1->v.number; break; case bin_mul: node->v.number = arg0->v.number * arg1->v.number; break; case bin_div: if (arg1->v.number == 0) { parse_error_locus(&node->locus, "division by zero"); break; } node->v.number = arg0->v.number / arg1->v.number; break; case bin_logand: node->v.number = arg0->v.number & arg1->v.number; break; case bin_logor: node->v.number = arg0->v.number | arg1->v.number; break; case bin_logxor: node->v.number = arg0->v.number ^ arg1->v.number; break; default: return; } node->type = node_type_number; free_node(arg0); free_node(arg1); } else if (node_type(arg0) != dtype_number) { parse_error_locus(&arg0->locus, "left-hand side argument to the " "arithmetical operation is " "of wrong data type"); } else if (node_type(arg1) != dtype_number) { parse_error_locus(&arg1->locus, "right-hand side argument to the " "arithmetical operation is " "of wrong data type"); } else if (arg0->type == node_type_number) { switch (node->v.bin.opcode) { case bin_add: if (arg0->v.number == 0) { copy_node(node, arg1); free_node(arg0); free(arg1); } break; case bin_sub: if (arg0->v.number == 0) { NODE *n = alloc_node(node_type_un, &node->locus); n->v.un.opcode = unary_minus; n->v.un.arg = arg1; copy_node(node, n); free_node(arg0); free(n); } break; case bin_mul: if (arg0->v.number == 0) { node->type = node_type_number; node->v.number = 0; free_node(arg0); free_node(arg1); } else if (arg0->v.number == 1) { copy_node(node, arg1); free_node(arg0); } break; case bin_div: if (arg0->v.number == 0) { node->type = node_type_number; node->v.number = 0; free_node(arg0); free_node(arg1); } break; case bin_logand: if (arg0->v.number == 0) { node->type = node_type_number; node->v.number = 0; free_node(arg0); free_node(arg1); } else if (arg0->v.number == ~(unsigned long)0) { copy_node(node, arg1); free_node(arg0); } break; case bin_logor: if (arg0->v.number == 0) { copy_node(node, arg1); free_node(arg0); } else if (arg0->v.number == ~(unsigned long)0) { node->type = node_type_number; node->v.number = ~(unsigned long)0; free_node(arg0); free_node(arg1); } break; case bin_logxor: if (arg0->v.number == 0) { copy_node(node, arg1); free_node(arg0); } break; default: return; } } else if (arg1->type == node_type_number) { switch (node->v.bin.opcode) { case bin_add: case bin_sub: if (arg1->v.number == 0) { copy_node(node, arg0); free_node(arg1); free(arg0); } break; case bin_mul: if (arg1->v.number == 0) { node->type = node_type_number; node->v.number = 0; free_node(arg0); free_node(arg1); } else if (arg1->v.number == 1) { copy_node(node, arg0); free_node(arg1); } break; case bin_div: if (arg1->v.number == 0) { parse_error_locus(&node->locus, "division by zero"); } else if (arg1->v.number == 1) { copy_node(node, arg0); free_node(arg1); } break; case bin_logand: if (arg1->v.number == 0) { node->type = node_type_number; node->v.number = 0; free_node(arg0); free_node(arg1); } else if (arg1->v.number == ~(unsigned long)0) { copy_node(node, arg0); free_node(arg1); } break; case bin_logor: if (arg1->v.number == 0) { copy_node(node, arg0); free_node(arg1); } else if (arg1->v.number == ~(unsigned long)0) { node->type = node_type_number; node->v.number = ~(unsigned long)0; free_node(arg0); free_node(arg1); } break; case bin_logxor: if (arg1->v.number == 0) { copy_node(node, arg0); free_node(arg0); } break; default: return; } } } void optimize_relational(NODE *node) { NODE *arg0 = node->v.bin.arg[0]; NODE *arg1 = node->v.bin.arg[1]; if (arg0->type == node_type_number && arg1->type == node_type_number) { switch (node->v.bin.opcode) { case bin_and: node->v.number = arg0->v.number && arg1->v.number; break; case bin_or: node->v.number = arg0->v.number || arg1->v.number; break; case bin_eq: node->v.number = arg0->v.number == arg1->v.number; break; case bin_ne: node->v.number = arg0->v.number != arg1->v.number; break; case bin_lt: node->v.number = arg0->v.number < arg1->v.number; break; case bin_le: node->v.number = arg0->v.number <= arg1->v.number; break; case bin_gt: node->v.number = arg0->v.number > arg1->v.number; break; case bin_ge: node->v.number = arg0->v.number >= arg1->v.number; break; default: return; } } else if (arg0->type == node_type_string && arg1->type == node_type_string) { switch (node->v.bin.opcode) { case bin_and: node->v.number = arg0->v.string[0] != 0 && arg1->v.string[0] != 0; break; case bin_or: node->v.number = arg0->v.string[0] != 0 || arg1->v.string[0] != 0; break; case bin_eq: node->v.number = strcmp(arg0->v.string, arg1->v.string) == 0; break; case bin_ne: node->v.number = strcmp(arg0->v.string, arg1->v.string) != 0; break; case bin_lt: node->v.number = strcmp(arg0->v.string, arg1->v.string) < 0; break; case bin_le: node->v.number = strcmp(arg0->v.string, arg1->v.string) <= 0; break; case bin_gt: node->v.number = strcmp(arg0->v.string, arg1->v.string) > 0; break; case bin_ge: node->v.number = strcmp(arg0->v.string, arg1->v.string) >= 0; break; default: return; } } else return; node->type = node_type_number; free_node(arg0); free_node(arg1); } static void optimize_node(NODE *node) { int i; NODE *p, *arg0, *arg1; switch (node->type) { case node_type_bin: arg0 = node->v.bin.arg[0]; arg1 = node->v.bin.arg[1]; optimize(arg0); optimize(arg1); switch (node->v.bin.opcode) { case bin_and: case bin_or: case bin_eq: case bin_ne: case bin_lt: case bin_le: case bin_gt: case bin_ge: optimize_relational(node); break; case bin_add: case bin_sub: case bin_mul: case bin_div: case bin_logand: case bin_logor: case bin_logxor: optimize_arith(node); break; case bin_match: if (node_type(arg0) != dtype_string) { parse_error_locus(&arg0->locus, "left-hand side argument " "to match is " "of wrong data type"); } else if (arg1->type == node_type_push) { if (arg0->type == node_type_string) { node->v.number = regexec(arg1->v.val, arg0->v.string, 0, NULL, 0) == 0; node->type = node_type_number; free_node(arg0); regfree(arg1->v.val); free_node(arg1); } else node->v.bin.qualifier &= ~QUALIFIER_REGFREE; } else if (arg1->type != node_type_regcomp) { parse_error_locus(&arg1->locus, "right-hand side argument " "to match is " "of wrong data type " "(should not happen)"); } break; case bin_fnmatch: if (arg0->type == node_type_string && arg1->type == node_type_string) { node->v.number = fnmatch(arg1->v.string, arg0->v.string, 0) == 0; node->type = node_type_number; free_node(arg0); free_node(arg1); } else if (node_type(arg0) != dtype_string) { parse_error_locus(&arg0->locus, "left-hand side argument to fnmatch is of " "wrong data type"); } else if (node_type(arg1) != dtype_string) { parse_error_locus(&arg1->locus, "right-hand side argument to fnmatch is of " "wrong data type"); } break; } break; case node_type_regcomp: arg0 = node->v.node; optimize(arg0); if (arg0->type == node_type_string) { regex_t r, *rp; int rc; rc = regcomp(&r, arg0->v.string, regex_flags); if (rc) { char errbuf[512]; regerror(rc, &r, errbuf, sizeof(errbuf)); parse_error_locus(&node->locus, "Cannot compile regex: %s", errbuf); return; } node->type = node_type_push; rp = xmalloc(sizeof *rp); *rp = r; node->v.val = rp; } break; case node_type_if: p = node->v.cond.cond; optimize(p); optimize(node->v.cond.if_true); optimize(node->v.cond.if_false); if (p->type == node_type_number) { NODE *head; NODE *tail = node->next; if (p->v.number) { head = node->v.cond.if_true; free_subtree(node->v.cond.if_false); } else { head = node->v.cond.if_false; free_subtree(node->v.cond.if_true); } if (head) { *node = *head; free_node(head); for (; node->next; node = node->next) ; node->next = tail; } else node->type = node_type_noop; } else if (p->type == node_type_string) { NODE *head; NODE *tail = p->next; if (p->v.string && p->v.string[0]) { head = node->v.cond.if_true; free_subtree(node->v.cond.if_false); } else { head = node->v.cond.if_false; free_subtree(node->v.cond.if_true); } if (head) { *node = *head; free_node(head); for (; node->next; node = node->next) ; node->next = tail; } else node->type = node_type_noop; } break; case node_type_un: p = node->v.un.arg; optimize(p); if (p->type == node_type_number) { switch (node->v.un.opcode) { case unary_not: node->v.number = !p->v.number; break; case unary_minus: node->v.number = -p->v.number; break; case unary_lognot: node->v.number = ~p->v.number; break; } node->type = node_type_number; free_node(p); } break; case node_type_asgn: optimize(node->v.asgn.node); break; case node_type_result: optimize(node->v.ret.message); break; case node_type_builtin: for (i = 0, p = node->v.builtin.args; p; i++, p = p->next) optimize(p); if (strcmp(node->v.builtin.builtin->name, "interval") == 0) { if (node->v.builtin.args->type == node_type_string) { time_t t; char *endp; if (parse_time_interval( node->v.builtin.args->v.string, &t, &endp)) { parse_error_locus(&node->locus, "unrecognized time format (near `%s')", endp); break; } /* Replace this node */ node->type = node_type_number; node->v.number = t; } } break; case node_type_concat: optimize(node->v.concat.arg[0]); optimize(node->v.concat.arg[1]); arg0 = node->v.concat.arg[0]; arg1 = node->v.concat.arg[1]; if (arg0->type == node_type_string && arg1->type == node_type_string) { string_begin(); string_add(arg0->v.string, strlen(arg0->v.string)); string_add(arg1->v.string, strlen(arg1->v.string)); node->v.string = string_finish(); node->type = node_type_string; free_node(arg0); free_node(arg1); } else if (arg0->type == node_type_string && arg0->v.string[0] == 0) { copy_node(node, arg1); free_node(arg0); } else if (arg1->type == node_type_string && arg1->v.string[0] == 0) { copy_node(node, arg0); free_node(arg1); } break; case node_type_catch: optimize(node->v.catch.node); break; case node_type_echo: case node_type_return: optimize(node->v.node); break; case node_type_switch: optimize_switch(node); break; case node_type_header: optimize(node->v.hdr.value); break; case node_type_cast: optimize(node->v.cast.node); p = node->v.cast.node; if (p->type == node_type_string) { node->type = node_type_string; node->v = p->v; free_node(p); } else if (p->type == node_type_number) { char buf[NUMERIC_BUFSIZE_BOUND]; snprintf(buf, sizeof buf, "%ld", p->v.number); node->v.string = string_alloc(buf, strlen(buf)); node->type = node_type_string; } else if (node->v.cast.data_type == dtype_number && node_type(p) == dtype_number) { node->type = p->type; node->locus = p->locus; node->v = p->v; free_node(p); } break; default: break; } } static void optimize(NODE *node) { for (; node; node = node->next) optimize_node(node); } static int codegen(prog_counter_t *pc, NODE *node, int finalize, size_t nautos) { if (error_count == 0) { *pc = code_get_counter(); jump_pc = 0; if (optimization_level) { optimize(node); if (error_count) return error_count; } if (nautos) { code_instr(instr_stkalloc); code_immediate((void*)nautos); } traverse_tree(node); if (finalize) code_instr(NULL); } return error_count; } #define skip_ws(p) while (*(p) && isspace(*(p))) (p)++; static char * getword(char **p, char **buf) { int len; skip_ws(*p); if (!**p) return NULL; for (len = 0; (*p)[len] && !isspace((*p)[len]); len++) ; *buf = malloc(len+1); if (*buf) { memcpy(*buf, *p, len); (*buf)[len] = 0; } *p += len; return *buf; } enum regex_mode { regex_enable, regex_disable, regex_set }; static void pragma_regex(char *text) { char *p, *buf; int bit; enum regex_mode mode = regex_set; while (p = getword(&text, &buf)) { switch (p[0]) { case '+': mode = regex_enable; p++; break; case '-': mode = regex_disable; p++; break; case '=': mode = regex_set; p++; break; } if (strcmp (p, "extended") == 0) bit = REG_EXTENDED; else if (strcmp (p, "icase") == 0) bit = REG_ICASE; else if (strcmp (p, "newline") == 0) bit = REG_NEWLINE; else { parse_error("Unknown regexp flag: %s", p); free(buf); return; } switch (mode) { case regex_disable: regex_flags &= ~bit; break; case regex_enable: regex_flags |= bit; break; case regex_set: regex_flags = bit; break; } free(buf); } } static void pragma_option(char *text) { char *name; char *value; char *p; int len; if (!getword(&text, &name)) { parse_error("Invalid pragma: expected option name"); return; } #define EXPIRE_SUFFIX "expire-interval" #define EXPIRE_SUFFIX_LEN (sizeof(EXPIRE_SUFFIX)-1) #define TIMEOUT_SUFFIX "timeout" #define TIMEOUT_SUFFIX_LEN (sizeof(TIMEOUT_SUFFIX)-1) if (strcmp(name, "mailfrom") == 0 || (strlen(name) >= EXPIRE_SUFFIX_LEN && strcmp(name + strlen(name) - EXPIRE_SUFFIX_LEN, EXPIRE_SUFFIX) == 0) || (strlen(name) >= TIMEOUT_SUFFIX_LEN && strcmp(name + strlen(name) - TIMEOUT_SUFFIX_LEN, TIMEOUT_SUFFIX) == 0)) { /* Special handling for expire and timeout intervals */ value = strdup(text); text += strlen(text); } else if (!getword(&text, &value)) { parse_error("Invalid pragma: expected option value"); free(name); return; } skip_ws(text); if (*text) { parse_error("Invalid pragma: junk after the arguments"); /* Try to handle it anyway */ } p = value; skip_ws(p); len = strlen(p); if ((*p == '"' || *p == '\'') && p[len-1] == *p) { p[len-1] = 0; p++; } if (set_option(name, p, 0)) { if (errno == ENOENT) parse_error("Unknown pragma option"); else parse_error("Invalid pragma"); } free(name); free(value); } static void pragma_stacksize(char *text) { char *p; stack_size = strtoul(text, &p, 0); if (*p) { for (; *p; p++) if (!(isascii(*p) && isspace(*p))) { parse_error("Invalid pragma: junk after stack size"); return; } } } static int get_interval(char *text, time_t *pinterval) { char *endp; time_t interval; while (*text && isspace(*text)) text++; if (!*text) { parse_error("Invalid pragma: unexpected end of line"); return 1; } if (parse_time_interval(text, &interval, &endp)) { parse_error("Invalid pragma: unrecognized time format " "(near `%s')", endp); return 1; } *pinterval = interval; return 0; } /* #pragma database FMTNAME file FILENAME #pragma database FMTNAME expire-interval INTERVAL #pragma database cache positive-expire-interval INTERVAL #pragma database cache negative-expire-interval INTERVAL #pragma database dns negative-expire-interval INTERVAL */ static void pragma_database(char *text) { char *fmtname; char *kw; struct db_format *fmt; if (!getword(&text, &fmtname)) { parse_error("Invalid pragma: expected database name"); return; } if ((fmt = db_format_lookup(fmtname)) == NULL) { parse_error("unknown database format: %s", fmtname); return; } if (!getword(&text, &kw)) { parse_error("Invalid pragma: expected keyword"); return; } if (strcmp(kw, "file") == 0) { char *value; char *p; int len; if (!getword(&text, &value)) { parse_error("Invalid pragma: expected file name"); return; } p = value; len = strlen(p); if ((*p == '"' || *p == '\'') && p[len-1] == *p) { p[--len] = 0; p++; len--; } fmt->dbname = xstrdup(p); } else if (strcmp(kw, "expire-interval") == 0) { get_interval(text, &fmt->expire_interval); } else if (strcmp(kw, "positive-expire-interval") == 0) { if (fmt != cache_format) { parse_error("positive-expire-interval is valid only " "for cache database"); return; } get_interval(text, &fmt->expire_interval); } else if (strcmp(kw, "negative-expire-interval") == 0) { if (fmt == cache_format) get_interval(text, &negative_expire_interval); else if (fmt == dns_cache_format) get_interval(text, &fmt->expire_interval); else { parse_error("negative-expire-interval is valid only " "for cache and dns databases"); return; } } else { parse_error("Invalid pragma: unrecognized keyword: %s", kw); return; } } void parse_pragma(char *text) { char *buf; while (*text != '#') text++; ++text; skip_ws(text); text += 6; /* "pragma" */ if (!getword(&text, &buf)) parse_error("Empty pragma"); else if (strcmp(buf, "regex") == 0) pragma_regex(text); else if (strcmp(buf, "database") == 0) pragma_database(text); else if (strcmp(buf, "option") == 0) pragma_option(text); else if (strcmp(buf, "stacksize") == 0) pragma_stacksize(text); else parse_error("Unknown pragma"); free(buf); } /* Test run */ struct sfsistat_tab { char *name; sfsistat stat; } sfsistat_tab[] = { { "accept", SMFIS_ACCEPT }, { "continue", SMFIS_CONTINUE }, { "discard", SMFIS_DISCARD }, { "reject", SMFIS_REJECT }, { "tempfail", SMFIS_TEMPFAIL }, { NULL } }; const char * sfsistat_str(sfsistat stat) { struct sfsistat_tab *p; for (p = sfsistat_tab; p->name; p++) if (p->stat == stat) return p->name; return NULL; } void print_stat(sfsistat stat) { struct sfsistat_tab *p; for (p = sfsistat_tab; p->name; p++) if (p->stat == stat) { printf("%s", p->name); return; } printf("%d", stat); } const char * header_command_str(enum header_opcode opcode) { switch (opcode) { case header_add: return "ADD HEADER "; case header_replace: return "REPLACE HEADER "; break; case header_delete: return "DELETE HEADER "; break; } return "UNKNOWN HEADER COMMAND "; } static int dbg_setreply(void *data, char *code, char *xcode, char *message) { if (code) { printf("SET REPLY %s", code); if (xcode) printf(" %s", xcode); if (message) printf(" %s", message); printf("\n"); } return 0; } static void dbg_setheader(void *data, struct old_header_node *hdr) { printf("%s %s: %s\n", header_command_str(hdr->opcode), hdr->name, hdr->value); } void mailfromd_test(int argc, char **argv) { int i, rc; mu_list_t dict = NULL; eval_environ_t env; char *p, *end; long n; sfsistat status; char *args[9] = {0,0,0,0,0,0,0,0,0}; dict_init(&dict); env = create_environment(NULL, dict_getsym, dbg_setreply, dbg_setheader, dict); env_init(env); for (i = 0; i < argc; i++) { if (p = strchr(argv[i], '=')) { char *ident = argv[i]; *p++ = 0; if (*ident == '%') env_set_variable(env, ident+1, p); else if (isdigit(*ident) && *ident != 0 && ident[1] == 0) args[*ident - '0' - 1] = p; else dict_install(dict, argv[i], p); } } for (i = state_parms[test_state].cnt; i--; ) { switch (state_parms[test_state].types[i]) { case dtype_string: env_push_string(env, args[i] ? args[i] : ""); break; case dtype_number: n = strtol(p, &end, 0); if (*end) mu_error("$%d is not a number", i+1); env_push_number(env, n); break; default: abort(); } } env_make_frame(env); rc = xeval(env, test_state); env_leave_frame(env, state_parms[test_state].cnt); env_final_gc(env); status = environment_get_status(env); printf("State %s: ", state_to_string(test_state)); print_stat(status); printf("\n"); destroy_environment(env); } void set_poll_arg(struct poll_data *poll, int kw, NODE *expr) { switch (kw) { case FOR: poll->email = expr; break; case HOST: poll->client_addr = expr; break; case AS: poll->mailfrom = expr; break; case FROM: poll->ehlo = expr; break; default: abort(); } } static int time_multiplier(const char *str, unsigned *m, unsigned *plen) { static struct timetab { char *name; unsigned mul; } tab[] = { { "seconds", 1 }, { "minutes", 60 }, { "hours", 60*60 }, { "days", 24*60*60 }, { "weeks", 7*24*60*60 }, { "months", 31*7*24*60*60 }, { NULL } }; struct timetab *p; int slen; for (slen = 0; str[slen]; slen++) if (isspace(str[slen])) break; for (p = tab; p->name; p++) { if (p->name[0] == tolower(str[0])) { int nlen = strlen(p->name); if (nlen > slen) nlen = slen; if (strncasecmp(p->name, str, nlen) == 0) { *m = p->mul; if (plen) *plen = nlen; return 0; } } } return 1; } int parse_time_interval(char *str, time_t *pint, char **endp) { int rc = 0; time_t interval = 0; while (*str) { char *p; unsigned long n; unsigned mul, len; while (*str && isspace(*str)) str++; if (!isdigit(*str) && time_multiplier(str, &mul, &len) == 0) { n = 1; str += len; } else { n = strtoul(str, &p, 10); if (*p && !isspace(*p)) { str = p; rc = 1; break; } while (*p && isspace(*p)) p++; str = p; if (*str) { if (rc = time_multiplier(str, &mul, &len)) break; str += len; } else mul = 1; } interval += n*mul; } if (rc && endp) *endp = str; *pint = interval; return rc; } int convert_rate(const char *arg, double *rate) { double count; char *p; count = strtod(arg, &p); if (*p) { time_t div; char *endp; while (*p && isspace(*p)) p++; if (isalpha(*p)) { if (strcmp(p, "per") == 0 && isspace(p[3])) p += 3; } else if (ispunct(*p)) p++; if (parse_time_interval(p, &div, &endp)) { mu_error("unrecognized time format (near `%s')", endp); return 1; } count /= div; } *rate = count; return 0; } static struct tagtable { char *name; enum smtp_state tag; } tagtable[] = { { "none", smtp_state_none }, { "connect", smtp_state_connect }, { "helo", smtp_state_helo }, { "envfrom", smtp_state_envfrom }, { "envrcpt", smtp_state_envrcpt }, { "header", smtp_state_header }, { "eoh", smtp_state_eoh }, { "body", smtp_state_body }, { "eom", smtp_state_eom }, }; enum smtp_state string_to_state(char *name) { struct tagtable *p; for (p = tagtable; p < tagtable + sizeof tagtable/sizeof tagtable[0]; p++) if (strcasecmp (p->name, name) == 0) return p->tag; return smtp_state_none; } const char * state_to_string(enum smtp_state state) { if (state < sizeof tagtable/sizeof tagtable[0]) return tagtable[state].name; abort(); } struct variable * builtin_variable_install(const char *name, data_type_t type, unsigned flags) { struct variable *var = variable_install(name); var->type = type; var->off = variable_count++; return var; } static NODE * _reverse(NODE *list, NODE **root) { NODE *next; if (list->next == NULL) { *root = list; return list; } next = _reverse(list->next, root); next->next = list; list->next = NULL; return list; } NODE * reverse(NODE *in) { NODE *root; if (!in) return in; _reverse(in, &root); return root; } static NODE * create_on_node(NODE *sel, struct case_stmt *cases) { struct case_stmt *cur; unsigned mask = 0; char nbuf[NUMERIC_BUFSIZE_BOUND]; struct function *fp; const char *base_name; char *fname; size_t size; int count = 0; int argc; NODE *args; data_type_t *ptypes, rettype; NODE *call_node, *retnode; /* Create a list of exceptions to catch */ for (cur = cases; cur; cur = cur->next) { struct valist *vp; for (vp = cur->valist; vp; vp = vp->next) { mask |= EXMASK(vp->value.v.number); count++; } } /* Check if we already have a wrapper function */ snprintf(nbuf, sizeof nbuf, "%u", mask); switch (sel->type) { case node_type_builtin: base_name = sel->v.builtin.builtin->name; argc = sel->v.builtin.builtin->parmcount; args = reverse(sel->v.builtin.args); ptypes = sel->v.builtin.builtin->parmtype; rettype = sel->v.builtin.builtin->rettype; break; case node_type_call: base_name = sel->v.call.func->name; argc = sel->v.call.func->parmcount; args = sel->v.call.args; ptypes = sel->v.call.func->parmtype; rettype = sel->v.call.func->rettype; break; default: abort(); } if (rettype == dtype_unspecified) { parse_error_locus(&sel->locus, "function `%s' does not return any value", base_name); return NULL; } size = 2 + strlen(base_name) + 1 + strlen(nbuf) + 1; fname = xmalloc(size); strcpy(fname, "$"); strcat(fname, base_name); strcat(fname, "_"); strcat(fname, nbuf); strcat(fname, "$"); fp = function_lookup(fname); if (!fp) { /* Build and register the function */ NODE *np, *catch_node; NODE *head, *tail; prog_counter_t pc; int i; struct function *save_func = func; int save_stie_in = stie_in; prog_counter_t save_jump_pc = jump_pc; /* Create the catch statement */ np = alloc_node(node_type_catch, get_locus()); np->v.catch.count = count; np->v.catch.exmask = 0; for (cur = cases; cur; cur = cur->next) { struct valist *vp; for (i = 0, vp = cur->valist; vp; vp = vp->next, i++) np->v.catch.exmask |= EXMASK(vp->value.v.number); } catch_node = np; np = alloc_node(node_type_return, get_locus()); np->v.node = alloc_node(node_type_arg, get_locus()); np->v.node->v.arg.number = 1; np->v.node->v.arg.data_type = PARMTYPE(1); catch_node->v.catch.node = np; /* Build call arguments */ head = tail = NULL; for (i = 1; i <= argc; i++) { np = alloc_node(node_type_arg, get_locus()); /* FIXME: Verify if this is always correct */ np->v.arg.data_type = ptypes[i-1]; np->v.arg.number = i; if (!head) head = tail = np; else { tail->next = np; tail = np; } } np = alloc_node(node_type_return, get_locus()); np->v.node = sel; /* FIXME: create a copy */ switch (np->v.node->type) { case node_type_builtin: np->v.node->v.builtin.args = head; break; case node_type_call: np->v.node->v.call.args = reverse(head); break; default: abort(); } catch_node->next = np; fp = function_install(fname, argc, ptypes, rettype, get_locus()); fp->exmask = mask; fp->node = catch_node; fp->entry = code_get_counter(); /* Entry code */ code_instr(instr_saveex); code_immediate((void*)fp->exmask); func = fp; stie_in = synt_function; jump_pc = 0; if (codegen(&pc, fp->node, 0, fp->parmcount) == 0) { jump_fixup(jump_pc, code_get_counter()); /* Exit code */ code_instr(instr_restex); code_instr(instr_return); } func = save_func; stie_in = save_stie_in; jump_pc = save_jump_pc; } free(fname); call_node = alloc_node(node_type_call, get_locus()); call_node->v.call.func = fp; call_node->v.call.args = args; retnode = alloc_node(node_type_switch, get_locus()); retnode->v.switch_stmt.node = call_node; retnode->v.switch_stmt.cases = cases; return retnode; } NODE * function_call(struct function *function, size_t count, NODE *subtree) { NODE *np = NULL; if (count < function->parmcount) { parse_error("Too few arguments in call to `%s'", function->name); } else if (count > function->parmcount) { parse_error("Too many arguments in call to `%s'", function->name); } else { np = alloc_node(node_type_call, get_locus()); np->v.call.func = function; np->v.call.args = reverse(cast_arg_list(subtree, function->parmtype)); } return np; } data_type_t node_type(NODE *node) { switch (node->type) { case node_type_string: case node_type_symbol: return dtype_string; case node_type_number: return dtype_number; case node_type_if: return dtype_unspecified; case node_type_bin: return dtype_number; case node_type_un: return dtype_number; case node_type_builtin: return node->v.builtin.builtin->rettype; case node_type_concat: return dtype_string; case node_type_variable: return node->v.variable->type; case node_type_arg: return node->v.arg.data_type; case node_type_call: return node->v.call.func->rettype; case node_type_return: if (node->v.node) return node_type(node->v.node); break; case node_type_backref: return dtype_string; case node_type_cast: return node->v.cast.data_type; case node_type_next: case node_type_result: case node_type_header: case node_type_asgn: case node_type_push: case node_type_regcomp: case node_type_catch: case node_type_echo: case node_type_switch: break; } return dtype_unspecified; } NODE * cast_to(data_type_t type, NODE *node) { NODE *np; data_type_t ntype = node_type(node); switch (ntype) { case dtype_string: case dtype_number: if (type == ntype) return node; break; case dtype_unspecified: parse_error("cannot convert %s to %s", type_to_string(ntype), type_to_string(type)); return NULL; default: abort(); } np = alloc_node(node_type_cast, get_locus()); np->v.cast.data_type = type; np->v.cast.node = node; node->next = NULL; return np; } NODE * cast_arg_list(NODE *args, data_type_t *parmtype) { NODE *head = NULL, *tail = NULL; unsigned n = 1; while (args) { NODE *next = args->next; NODE *p = cast_to(*parmtype, args); if (head) tail->next = p; else head = p; tail = p; args = next; parmtype++; } return head; } void add_xref(struct variable *var, const struct locus *locus) { if (script_dump_xref) { struct locus *elt = xmalloc(sizeof *elt); if (!var->xref) mu_list_create(&var->xref); *elt = *locus; mu_list_append(var->xref, elt); } } struct variable * vardecl(char *name, data_type_t type, storage_class_t sc, struct locus *loc) { struct variable *var; if (type == dtype_unspecified) { parse_error("cannot define variable of unspecified type"); return NULL; } var = variable_install(name); if (var->type == dtype_unspecified) { /* the variable has just been added: go straight to initializing it */; } else if (sc != var->storage_class) { struct variable *vp; switch (sc) { case storage_extern: parse_error("INTERNAL ERROR at %s:%d, declaring %s %s", __FILE__, __LINE__, storage_class_str(sc), name); abort(); case storage_auto: if (var->storage_class == storage_param) { parse_error("warning: automatic variable `%s'" "is shadowing a parameter"); } else parse_error("warning: automatic variable `%s'" "is shadowing a global"); unregister_auto(var); break; case storage_param: parse_error("warning: parameter %s is shadowing a " "global", name); } /* Do the shadowing */ vp = xmalloc(sizeof *var); *vp = *var; var->shadowed = vp; } else { switch (sc) { case storage_extern: case storage_auto: if (var->type != type) { parse_error("redeclaring `%s' as different " "data type", name); parse_error_locus(&var->locus, "this is the location of the " "previous definition"); return NULL; } break; case storage_param: parse_error("duplicate parameter: %s", name); return NULL; } } var->type = type; var->storage_class = sc; switch (sc) { case storage_extern: add_xref(var, loc ? loc : get_locus()); var->off = variable_count++; break; case storage_auto: case storage_param: register_auto(var); } var->locus = *get_locus(); return var; } struct variable * externdecl(char *name, data_type_t type, STKVAL val, struct locus *loc) { struct variable *var = vardecl(name, type, storage_extern, loc); if (!var) return NULL; if (var->type != type) { char buf[NUMERIC_BUFSIZE_BOUND]; char *p; switch (var->type) { default: abort(); case dtype_string: snprintf(buf, sizeof buf, "%ld", (long) val); val = strdup(buf); break; case dtype_number: val = (STKVAL) strtol(val, &p, 10); if (*p) { parse_error("cannot convert `%s' to number", val); return NULL; } break; } } declare_variable(name, var->off, val); return var; } struct auto_rec { struct auto_rec *next; struct variable *var; }; static struct auto_rec *auto_list; static void register_auto(struct variable *var) { struct auto_rec *p = xmalloc(sizeof *p); p->var = var; p->next = auto_list; auto_list = p; } static void unregister_auto(struct variable *var) { struct auto_rec *p = auto_list, *prev = NULL; while (p) { struct auto_rec *next = p->next; if (p->var == var) { if (prev) prev->next = next; else auto_list = next; free(p); return; } prev = p; p = next; } } static size_t forget_autos(size_t nparam) { size_t auto_count = 0; size_t param_count = 0; struct auto_rec *p = auto_list; while (p) { struct auto_rec *next = p->next; struct variable *var = p->var; switch (var->storage_class) { case storage_auto: var->off = auto_count++; break; case storage_param: var->off = nparam - param_count++; break; default: abort(); } while (var->storage_class != storage_extern) { struct variable *shadowed = var->shadowed; if (shadowed) { *var = *shadowed; free(shadowed); } else { find_and_remove(SYM_VARIABLE, var->name); break; } } free(p); p = next; } auto_list = NULL; return auto_count; } const char * storage_class_str(storage_class_t sc) { switch (sc) { case storage_extern: return "extern"; case storage_auto: return "auto"; case storage_param: return "param"; } return "unknown?"; } const char * function_name() { switch (stie_in) { case synt_function: return func->name; case synt_handler: return state_to_string(state_tag); default: return ""; } }