%{ /* 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 #include #include "mailfrom.h" NODE *alloc_node(enum node_type type, const struct locus *locus); int string_to_stat(char *, mf_status *); void set_poll_arg(struct poll_node *poll, int kw, NODE *var); static int time_multiplier(const char *, unsigned *, unsigned *); static NODE *root_node; static int regex_flags; /* Should default to REG_NOSUB ? */ static int parse_error_count; %} %union { char *string; struct { NODE *head; NODE *tail; } stmtlist; NODE *node; struct return_node ret; struct poll_node poll; struct { struct poll_action *head, *tail; } actionlist; struct locus locus; struct { int kw; NODE *var; } arg; double number; }; %token ACT_ACCEPT ACT_REJECT ACT_TEMPFAIL ACT_CONTINUE ACT_DISCARD %token ADD REPLACE DELETE KW_FUNCTION CALL %token IF FI ELSE ELIF %token ON HOST FOR FROM AS DO DONE POLL MATCHES FNMATCHES LISTENS %token WHEN HOSTNAME RELAYED RATE NEXT VALIDUSER DBMAP %token STRING CODE XCODE %token SYMBOL %token OR AND EQ NE NOT %left OR %left AND %left EQ NE %left NOT HOSTNAME %type stmt condition action if_cond else_cond on_cond cond var call %type stmtlist %type triplet maybe_triplet %type pollstmt arglist %type values branches branch %type pollarg %type number factor divisor timespec time %% input : stmtlist { root_node = $1.head; } | function stmtlist { root_node = $2.head; } ; stmtlist : stmt { $1->next = NULL; $$.head = $$.tail = $1; } | stmtlist stmt { $1.tail->next = $2; $1.tail = $2; $$ = $1; } | stmtlist function ; stmt : condition | action ; action : ACT_ACCEPT maybe_triplet { $$ = alloc_node(node_type_return, &$1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_ACCEPT; } | ACT_REJECT maybe_triplet { $$ = alloc_node(node_type_return, &$1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_REJECT; } | ACT_TEMPFAIL maybe_triplet { $$ = alloc_node(node_type_return, &$1); $$->v.ret = $2; $$->v.ret.stat = SMFIS_TEMPFAIL; } | ACT_CONTINUE { $$ = alloc_node(node_type_return, &$1); $$->v.ret.stat = SMFIS_CONTINUE; } | ACT_DISCARD { $$ = alloc_node(node_type_return, &$1); $$->v.ret.stat = SMFIS_DISCARD; } | NEXT { $$ = alloc_node(node_type_next, &$1); } | ADD STRING STRING { $$ = alloc_node(node_type_header, &$1); $$->v.hdr.opcode = header_add; $$->v.hdr.name = $2; $$->v.hdr.value = $3; } | REPLACE STRING STRING { $$ = 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; } | call ; 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 STRING { $$.code = $1; $$.xcode = $2; $$.message = $3; } | CODE STRING { $$.code = $1; $$.xcode = NULL; $$.message = $2; } ; condition : if_cond | on_cond ; if_cond : IF cond 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 cond 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; } ; cond : var EQ var { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_eq; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = $3; } | var NE var { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_ne; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = $3; } | var MATCHES var { $$ = alloc_node(node_type_bin, &$2); if ($3->type == node_type_string) { int rc; $$->v.re.opcode = bin_regex; $$->v.re.arg = $1; rc = regcomp(&$$->v.re.regex, $3->v.string, regex_flags); if (rc) { char errbuf[512]; regerror(rc, &$$->v.re.regex, errbuf, sizeof(errbuf)); parse_error("Cannot compile regex: %s", errbuf); YYERROR; } } else { $$->v.bin.opcode = bin_match; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = $3; } } | var FNMATCHES var { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_fnmatch; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = $3; } | cond OR cond { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_or; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = $3; } | cond AND cond { $$ = alloc_node(node_type_bin, &$2); $$->v.bin.opcode = bin_and; $$->v.bin.arg[0] = $1; $$->v.bin.arg[1] = $3; } | NOT cond { $$ = alloc_node(node_type_un, &$1); $$->v.un.opcode = unary_not; $$->v.un.arg = $2; } | '(' cond ')' { $$ = $2; } | RELAYED var { $$ = alloc_node(node_type_relayed_domain, &$1); $$->v.node = $2; } | RATE var number divisor { $$ = alloc_node(node_type_rate, get_locus()); $$->v.rate.email = $2; $$->v.rate.limit = $3 / $4; } | LISTENS var { $$ = alloc_node(node_type_listens, &$1); $$->v.node = $2; } | VALIDUSER var { $$ = alloc_node(node_type_validuser, &$1); $$->v.node = $2; } | DBMAP STRING var { $$ = alloc_node(node_type_dbmap, &$1); $$->v.map.name = $2; $$->v.map.key = $3; #ifndef USE_DBM parse_error("DBMAP is not available"); parse_error_count++; #endif } ; divisor : /* empty */ { $$ = 1; } | '/' number { $$ = $2; } | '/' timespec { $$ = $2; } ; timespec : time | timespec time { $$ = $1 + $2; } ; time : factor STRING { unsigned m; if (time_multiplier($2, &m, NULL)) { parse_error("Invalid time unit: %s", $2); YYERROR; } $$ = $1 * m; } ; factor : /* empty */ { $$ = 1; } | number ; number : CODE { char *p; $$ = strtod($1, &p); if (*p) { /* should not happen */ parse_error("Invalid number (near `%s')", p); YYERROR; } } ; on_cond : ON pollstmt DO branches DONE { $$ = alloc_node(node_type_poll, &$1); $$->v.poll = $2; $$->v.poll.actions = $4.head; } ; pollstmt : POLL arglist { if ($2.email == NULL) { parse_error_locus(&$1, "poll statement missing email"); YYERROR; } $$ = $2; } ; arglist : pollarg { memset(&$$, 0, sizeof $$); set_poll_arg(&$$, $1.kw, $1.var); } | arglist pollarg { set_poll_arg(&$1, $2.kw, $2.var); $$ = $1; } ; pollarg : var { $$.kw = FOR; $$.var = $1; } | FOR var { $$.kw = FOR; $$.var = $2; } | HOST var { $$.kw = HOST; $$.var = $2; } | AS var { $$.kw = AS; $$.var = $2; } | FROM var { $$.kw = FROM; $$.var = $2; } ; branches : branch | branches branch { $1.tail->next = $2.head; $1.tail = $2.tail; $$ = $1; } ; branch : WHEN values ':' stmtlist { struct poll_action *act; for (act = $2.head; act; act = act->next) { act->locus = $1; act->node = $4.head; } $$ = $2; } ; values : STRING { mf_status status; if (string_to_stat($1, &status)) { parse_error("Unknown return code: %s", $1); YYERROR; } else { struct poll_action *act = malloc(sizeof *act); if (!act) { parse_error("Not enough memory"); abort(); } act->next = NULL; act->status = status; $$.head = $$.tail = act; } } | values OR STRING { mf_status status; if (string_to_stat($3, &status)) { parse_error("Unknown return code: %s", $1); YYERROR; } else { struct poll_action *act = malloc(sizeof *act); if (!act) { parse_error("Not enough memory"); abort(); } act->next = NULL; act->status = status; $1.tail->next = act; $1.tail = act; $$ = $1; } } ; var : STRING { $$ = alloc_node(node_type_string, get_locus()); $$->v.string = $1; } | SYMBOL { $$ = alloc_node(node_type_symbol, get_locus()); $$->v.string = $1; } | HOSTNAME var { $$ = alloc_node(node_type_hostname, get_locus()); $$->v.node = $2; } ; function : KW_FUNCTION STRING DO stmt DONE { function_install($2, $4, &$1); } ; call : CALL STRING { $$ = alloc_node(node_type_funcall, get_locus()); if (!($$->v.node = function_lookup($2))) { parse_error("Call to undefined function %s", $2); parse_error_count++; } } ; %% int yyerror(char *s) { parse_error("%s", s); } int parse_config(char *name, int ydebug, int ldebug) { yydebug = ydebug; if (source(name, ldebug)) return -1; return yyparse() || parse_error_count; } 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; } int string_to_stat(char *str, mf_status *status) { if (strcmp(str, "success") == 0) *status = mf_success; else if (strcmp(str, "not_found") == 0) *status = mf_not_found; else if (strcmp(str, "failure") == 0) *status = mf_failure; else if (strcmp(str, "temp_failure") == 0) *status = mf_temp_failure; else return 1; return 0; } /* 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 header_node *hdr); static char * mf_status_str(mf_status stat) { switch (stat) { case mf_success: return "success"; case mf_not_found: return "not_found"; case mf_failure: return "failure"; case mf_temp_failure: return "temp_failure"; } return "UNKNOWN"; } static void print_level(int level) { level *= 2; printf("%*.*s", level, level, ""); } static void print_poll(struct poll_node *poll, int level) { struct poll_action *act; print_level(level); printf("POLL\n"); print_level(level+1); printf("FOR:\n"); print_node(poll->email, level+2); if (poll->client_addr) { print_level(level+1); printf("HOST:\n"); print_node(poll->client_addr, level+2); } if (poll->mailfrom) { print_level(level+1); printf("MAIL FROM:\n"); print_node(poll->mailfrom, level+2); } if (poll->ehlo) { print_level(level+1); printf("EHLO:\n"); print_node(poll->ehlo, level+2); } for (act = poll->actions; act; act = act->next) { print_level(level+1); printf("when %s:\n", mf_status_str(act->status)); print_node_list(act->node, level+2); } } 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_match: p = "MATCH"; break; case bin_fnmatch: p = "FNMATCH"; break; case bin_regex: p = "REGEX"; break; default: p = "UNKNOWN_OP"; } printf("%s", p); } static void print_node(NODE *node, int level) { switch (node->type) { case node_type_string: print_level(level); printf("CONSTANT: %s\n", node->v.string); break; case node_type_symbol: print_level(level); printf("SYMBOL: %s\n", node->v.string); 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_poll: print_poll(&node->v.poll, level); break; case node_type_bin: print_level(level); print_bin_op(node->v.bin.opcode); printf("\n"); print_node(node->v.bin.arg[0], level+1); if (node->v.bin.opcode != bin_regex) print_node(node->v.bin.arg[1], level+1); else { print_level(level+1); printf("/* COMPILED REGEX */\n"); } break; case node_type_un: print_level(level); printf("NOT "); print_node(node->v.un.arg, 0); break; case node_type_next: print_level(level); printf("next\n"); break; case node_type_return: if (node->v.ret.code) { print_level(level); dbg_setreply(NULL, node->v.ret.code, node->v.ret.xcode, node->v.ret.message); } print_level(level); print_stat(node->v.ret.stat); printf("\n"); break; case node_type_header: print_level(level); dbg_setheader(NULL, &node->v.hdr); break; case node_type_hostname: print_level(level); printf("HOSTNAME\n"); print_node(node->v.node, level+1); break; case node_type_relayed_domain: print_level(level); printf("RELAYED\n"); print_node(node->v.node, level+1); break; case node_type_rate: print_level(level); printf("RATE %g\n", node->v.rate.limit); print_node(node->v.rate.email, level+1); break; case node_type_listens: print_level(level); printf("LISTENS\n"); print_node(node->v.node, level+1); break; case node_type_funcall: print_level(level); printf("CALL\n"); print_node(node->v.node, level+1); break; case node_type_validuser: print_level(level); printf("VALIDUSER\n"); print_node(node->v.node, level+1); break; case node_type_dbmap: print_level(level); printf("DBMAP"); print_level(level+1); printf("%s\n", node->v.map.name); print_node(node->v.map.key, level+1); break; default: abort(); } } static void print_node_list(NODE *node, int level) { for (; node; node = node->next) print_node(node, level); } void print_config() { print_node_list(root_node, 0); } #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; } 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; 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); } 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, "option") == 0) pragma_option(text); else parse_error("Unknown pragma"); free(buf); } /* Rudimentary dictionary support */ struct dict_entry { char *name; char *value; }; static int name_comp(const void *item, const void *data) { const struct dict_entry *ent = item; const struct dict_entry *p = data; return strcmp(ent->name, p->name); } void name_destroy(void *item) { const struct dict_entry *ent = item; free(ent->name); free(ent->value); } void dict_init(mu_list_t *dict) { mu_list_create(dict); mu_list_set_comparator(*dict, name_comp); mu_list_set_destroy_item (*dict, name_destroy); } char * dict_install(mu_list_t dict, char *name, char *value) { struct dict_entry ent, *p; ent.name = name; if (mu_list_locate(dict, &ent, (void**)&p) == 0) { free(p->value); p->value = strdup(value); } else { p = malloc(sizeof(*p)); if (!p) abort(); p->name = strdup(name); p->value = strdup(value); mu_list_append(dict, p); } return p->value; } void dict_destroy(mu_list_t *dict) { mu_list_destroy(dict); } char * dict_getsym(void *data, char *str) { struct dict_entry ent, *p; char *tmp = NULL; char *val = NULL; if (str[0] == '{') { int len = strlen(str); if (str[len-1] == '}') { tmp = malloc(len-1); if (!tmp) abort(); memcpy(tmp, str+1, len-2); tmp[len-2] = 0; str = tmp; } } ent.name = str; if (mu_list_locate(data, &ent, (void**)&p) == 0) val = p->value; free(tmp); return val; } static int dbmap_lookup_p(const char *dbname, const char *email) { #ifdef USE_DBM int rc; char *name; char *p; DBM_FILE db; DBM_DATUM key; DBM_DATUM contents; if (mu_dbm_open(dbname, &db, MU_STREAM_RDWR, 0)) { mu_error("mu_dbm_open(%s) failed: %s", dbname, mu_strerror(errno)); return 0; } name = strdup(email); p = strchr(name, '@'); if (p) *p = 0; memset (&key, 0, sizeof key); memset (&contents, 0, sizeof contents); MU_DATUM_PTR (key) = name; MU_DATUM_SIZE (key) = strlen(name)+1; rc = mu_dbm_fetch(db, key, &contents) == 0; debug2(10, "Checking alias %s: %s", name, rc ? "true" : "false"); mu_dbm_datum_free(&contents); mu_dbm_close(db); return rc; #else mu_error("dbmap_lookup_p called where it should not"); abort(); #endif } static int valid_user_p(const char *email) { int rc; struct mu_auth_data *auth_data; char *name = strdup(email); char *p = strchr(name, '@'); if (p) *p = 0; auth_data = mu_get_auth_by_name(name); rc = auth_data != NULL; mu_auth_data_free(auth_data); debug2(10, "Checking user %s: %s", name, rc ? "true" : "false"); free (name); return rc; } /* Runtime support */ union value { int bool; char *string; }; struct eval_environ { void *data; SMFICTX *ctx; char *(*getsym)(void *data, char *str); int (*setreply)(void *data, char *code, char *xcode, char *message); void (*setheader)(void *data, struct header_node *hdr); mu_list_t dict; /* Exit information */ sfsistat status; jmp_buf x; }; void eval_node_list(NODE *node, struct eval_environ *env); void eval_node(NODE *node, struct eval_environ *env, union value *val); void eval_poll(NODE *node, struct eval_environ *env) { struct poll_node *poll = &node->v.poll; mf_status rc; struct poll_action *act; union value vemail, vaddr, vehlo, vmailfrom; eval_node(poll->email, env, &vemail); if (poll->ehlo) eval_node(poll->ehlo, env, &vehlo); else vehlo.string = smtp_domain; if (poll->mailfrom) eval_node(poll->mailfrom, env, &vmailfrom); else vmailfrom.string = postmaster_email; if (poll->client_addr) { union value vaddr; eval_node(poll->client_addr, env, &vaddr); trace("%s:%lu: STRICT POLL FOR %s HOST %s FROM %s AS <%s>", node->locus.file, node->locus.line, vemail.string, vaddr.string, vehlo.string, vmailfrom.string); rc = method_strict(env->ctx, vemail.string, vaddr.string, vehlo.string, vmailfrom.string); } else { trace("%s:%lu: STANDARD POLL FOR %s FROM %s AS <%s>", node->locus.file, node->locus.line, vemail.string, vehlo.string, vmailfrom.string); rc = method_standard(env->ctx, vemail.string, vehlo.string, vmailfrom.string); } for (act = poll->actions; act; act = act->next) { if (act->status == rc) eval_node_list(act->node, env); } } void eval_hostname(struct eval_environ *env, union value *val) { char *s = dict_getsym(env->dict, val->string); if (!s) { char hbuf[NI_MAXHOST]; if (resolve_ipstr(val->string, hbuf, sizeof(hbuf)) == 0) { s = dict_install(env->dict, val->string, hbuf); } else return; /* val is not changed */ } val->string = s; } void eval_rate(NODE *node, struct eval_environ *env, union value *email, double limit, union value *res) { char *p; double rate; if (get_rate(email->string, &rate) != mf_success) { mu_error("%s:%lu: runtime error: cannot get rate for %s", node->locus.file, node->locus.line, email->string); env->status = SMFIS_TEMPFAIL; longjmp(env->x, 1); } res->bool = rate > limit; } void eval_node(NODE *node, struct eval_environ *env, union value *val) { union value vtmp; switch (node->type) { case node_type_string: val->string = node->v.string; break; case node_type_symbol: val->string = env->getsym(env->data, node->v.string); if (!val->string) { mu_error("Symbol `%s' is not defined", node->v.string); env->status = SMFIS_TEMPFAIL; longjmp(env->x, 1); } break; case node_type_if: eval_node(node->v.cond.cond, env, &vtmp); if (vtmp.bool) { trace("%s:%lu: MATCHED CONDITION", node->locus.file, node->locus.line); eval_node_list(node->v.cond.if_true, env); } else eval_node_list(node->v.cond.if_false, env); break; case node_type_poll: eval_poll(node, env); break; case node_type_bin: eval_node(node->v.bin.arg[0], env, &vtmp); switch (node->v.bin.opcode) { case bin_and: if (!vtmp.bool) { val->bool = vtmp.bool; break; } eval_node(node->v.bin.arg[1], env, val); val->bool &= vtmp.bool; break; case bin_or: if (vtmp.bool) { val->bool = vtmp.bool; break; } eval_node(node->v.bin.arg[1], env, val); val->bool |= vtmp.bool; break; case bin_eq: eval_node(node->v.bin.arg[1], env, val); val->bool = strcasecmp(vtmp.string, val->string) == 0; break; case bin_ne: eval_node(node->v.bin.arg[1], env, val); val->bool = strcasecmp(vtmp.string, val->string) != 0; break; case bin_regex: val->bool = regexec(&node->v.re.regex, vtmp.string, 0, NULL, 0) == 0; break; case bin_match: { int rc; regex_t re; eval_node(node->v.bin.arg[1], env, val); rc = regcomp(&re, val->string, regex_flags); if (rc) { char errbuf[512]; regerror(rc, &re, errbuf, sizeof(errbuf)); mu_error("runtime error at %s:%lu, compiling regex `%s': %s", val->string, node->locus.file, node->locus.line, errbuf); env->status = SMFIS_TEMPFAIL; longjmp(env->x, 1); } val->bool = regexec(&re, vtmp.string, 0, NULL, 0) == 0; regfree(&re); break; } case bin_fnmatch: eval_node(node->v.bin.arg[1], env, val); val->bool = fnmatch (val->string, vtmp.string, 0) == 0; break; } break; case node_type_un: eval_node(node->v.un.arg, env, val); val->bool = !val->bool; break; case node_type_next: trace("%s:%lu: next", node->locus.file, node->locus.line); break; case node_type_return: trace("%s:%lu: %s %s %s %s", node->locus.file, node->locus.line, sfsistat_str(node->v.ret.stat), node->v.ret.code ? node->v.ret.code : "", node->v.ret.xcode ? node->v.ret.xcode : "", node->v.ret.message ? node->v.ret.message : ""); env->setreply(env->data, node->v.ret.code, node->v.ret.xcode, node->v.ret.message); env->status = node->v.ret.stat; longjmp(env->x, 1); break; case node_type_header: trace("%s:%lu: %s %s %s", node->locus.file, node->locus.line, header_command_str(node->v.hdr.opcode), node->v.hdr.name, node->v.hdr.value); env->setheader(env->data, &node->v.hdr); break; case node_type_hostname: eval_node(node->v.node, env, val); eval_hostname(env, val); break; case node_type_relayed_domain: eval_node(node->v.node, env, val); val->bool = relayed_domain_p(val->string); break; case node_type_rate: eval_node(node->v.rate.email, env, &vtmp); eval_rate(node, env, &vtmp, node->v.rate.limit, val); break; case node_type_listens: eval_node(node->v.node, env, &vtmp); val->bool = listens_on (vtmp.string, 25) == mf_success; break; case node_type_funcall: eval_node(node->v.node, env, &vtmp); break; case node_type_validuser: eval_node(node->v.node, env, &vtmp); val->bool = valid_user_p(vtmp.string); break; case node_type_dbmap: eval_node(node->v.map.key, env, &vtmp); val->bool = dbmap_lookup_p(node->v.map.name, vtmp.string); break; default: abort(); } } void eval_node_list(NODE *node, struct eval_environ *env) { union value v; /* trace("%s:%lu: CONTINUING", node->locus.file, node->locus.line);*/ for (; node; node = node->next) eval_node(node, env, &v); } /* Run-time execution */ static char * ctx_getsym(void *data, char *str) { return smfi_getsymval(data, str); } static int ctx_setreply(void *data, char *code, char *xcode, char *message) { if (code) return smfi_setreply(data, code, xcode, message); return 0; } static void ctx_setheader(void *data, struct header_node *hdr) { priv_store_header_command(data, hdr); } sfsistat run_program(SMFICTX *ctx) { struct eval_environ env; env.data = env.ctx = ctx; env.getsym = ctx_getsym; env.setreply = ctx_setreply; env.setheader = ctx_setheader; env.status = SMFIS_CONTINUE; dict_init(&env.dict); if (setjmp(env.x) == 0) eval_node_list(root_node, &env); dict_destroy(&env.dict); return env.status; } /* 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) return 0; printf("SET REPLY %s", code); if (xcode) printf(" %s", xcode); if (message) printf(" %s", message); printf("\n"); } static void dbg_setheader(void *data, struct header_node *hdr) { printf("SET HEADER %s: %s\n", hdr->name, hdr->value); } sfsistat test_program(mu_list_t dict) { struct eval_environ env; env.data = dict; env.ctx = NULL; env.getsym = dict_getsym; env.setreply = dbg_setreply; env.setheader = dbg_setheader; env.status = SMFIS_CONTINUE; dict_init(&env.dict); if (setjmp(env.x) == 0) eval_node_list(root_node, &env); dict_destroy(&env.dict); print_stat(env.status); printf("\n"); return env.status; } void set_poll_arg(struct poll_node *poll, int kw, NODE *var) { switch (kw) { case FOR: poll->email = var; break; case HOST: poll->client_addr = var; break; case AS: poll->mailfrom = var; break; case FROM: poll->ehlo = var; 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; for (p = tab; p->name; p++) { if (p->name[0] == tolower(str[0])) { int nlen = strlen(p->name); int slen = strlen(str); if (slen > nlen) continue; if (strncasecmp(p->name, str, slen) == 0) { *m = p->mul; if (plen) *plen = slen; return 0; } } } return 1; } int convert_rate(const char *arg, double *rate) { double count; char *p; count = strtod(arg, &p); if (*p) { char *q; double div; while (*p && isspace(*p)) p++; switch (*p) { case 0: break; case '/': div = 0; p++; while (*p) { unsigned n; unsigned m, len; while (*p && isspace(*p)) p++; if (isdigit(*p)) n = strtoul(p, &q, 10); else { n = 1; q = p; } while (*q && isspace(*q)) q++; if (time_multiplier(q, &m, &len)) { mu_error("Invalid interval (near `%s')", q); return 1; } div += n*m; p = q + len; } break; default: mu_error("Invalid interval (near `%s')", p); return 1; } count /= div; } *rate = count; return 0; }