/* This file is part of mailfromd. Copyright (C) 2006, 2007, 2008 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 3, 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, see . */ #define MF_SOURCE_NAME MF_SOURCE_PROG #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "mailfromd.h" #include "prog.h" #include "optab.h" #define DEFINE_BUILTIN_MODULE #include "builtin.h" #define obstack_chunk_alloc malloc #define obstack_chunk_free free #include #define SP(s) ((s) ? (s) : "''") /* Code generation support */ #define CODE_INITIAL 128 #define CODE_INCREMENT 128 static prog_counter_t pc, pmax; instr_t *prog; size_t stack_size = 4096; /* Data segment */ STKVAL *dataseg; size_t datasize; /* Table of relocatable entries in the data segment */ size_t *dataseg_reloc; size_t dataseg_reloc_count; /* Regular expressions */ struct rt_regex *regtab; size_t regcount; struct obstack regstk; void code_init() { pmax = CODE_INITIAL; prog = calloc(pmax, sizeof prog[0]); if (!prog) { mu_error(_("Not enough memory to compile the program")); exit(1); } } prog_counter_t code_get_counter() { return pc; } prog_counter_t code_reserve(size_t count) { prog_counter_t ret = pc; if (pc + count > pmax) { size_t incr = (pc + count - pmax + CODE_INCREMENT - 1) / CODE_INCREMENT; pmax += incr * CODE_INCREMENT; prog = realloc(prog, pmax*sizeof prog[0]); if (!prog) { mu_error(_("Not enough memory to compile the program")); exit(1); } } pc += count; return ret; } prog_counter_t code_instr(const instr_t ptr) { if (pc >= pmax) { pmax += CODE_INCREMENT; prog = realloc(prog, pmax*sizeof prog[0]); if (!prog) { mu_error(_("Not enough memory to compile the program")); exit(1); } } prog[pc] = ptr; return pc++; } prog_counter_t code_op(unsigned code) { return code_instr((instr_t)code); } prog_counter_t code_immediate(const void *ptr) { return code_instr(ptr); } void code_put(prog_counter_t pos, void *ptr) { assert(pos < pmax); prog[pos] = (instr_t) ptr; } instr_t code_peek(prog_counter_t pos) { assert(pos < pmax); return prog[pos]; } /* Regexps*/ void register_regex(struct sym_regex *rp) { struct rt_regex r; if (regcount == 0) obstack_init(®stk); r.compiled = 0; /* Will be compiled later */ r.expr = rp->lit ? rp->lit->off : 0; r.regflags = rp->regflags; rp->index = regcount++; obstack_grow(®stk, &r, sizeof r); } void finalize_regex() { if (regcount) regtab = obstack_finish(®stk); } unsigned long prog_trace_option = 0; #define PROG_TRACE_ENGINE (prog_trace_option & BUILTIN_MASK_prog) void enable_module_trace(char *name) { struct builtin_module *mod; for (mod = builtin_module; mod->name; mod++) if (strcmp(mod->name, name) == 0) { prog_trace_option |= mod->mask; return; } mu_error(_("No such module: %s"), name); } void disable_module_trace(char *name) { struct builtin_module *mod; for (mod = builtin_module; mod->name; mod++) if (strcmp(mod->name, name) == 0) { prog_trace_option &= ~mod->mask; return; } mu_error(_("No such module: %s"), name); } void enable_prog_trace(const char *modlist) { if (strcmp(modlist, "all") == 0) { prog_trace_option = ULONG_MAX; return; } else { char *modcopy, *s; modcopy = xstrdup(modlist); for (s = strtok(modcopy,","); s; s = strtok(NULL, ",")) enable_module_trace(s); free(modcopy); } } void disable_prog_trace(const char *modlist) { if (strcmp(modlist, "all") == 0) { prog_trace_option = 0; return; } else { char *modcopy, *s; modcopy = xstrdup(modlist); for (s = strtok(modcopy,","); s; s = strtok(NULL, ",")) disable_module_trace(s); free(modcopy); } } /* ======================================================================== Drzewa w górę, rzeki w dół. Jacek Kaczmarski. "Upadek Ikara" ======================================================================== */ /* Run-time evaluation */ /* Max. number of C locals to save in struct eval_environ for eventual fixups. See comment to env_fixup_autos, below. */ #define MAX_AUTO_PTR 128 struct eval_environ { prog_counter_t pc; /* Program counter */ prog_counter_t tos; /* Top of stack: toh <= tos <= datasize + stack_size */ prog_counter_t toh; /* Top of heap: datasize <= toh <= tos */ prog_counter_t base; /* Base pointer */ STKVAL reg; /* General purpose register */ STKVAL *dataseg; /* Data space */ size_t stack_size; /* Size of allocated stack + heap */ struct locus locus; /* Program locus corresponding to PC */ /* Temporary heap space */ size_t temp_start; size_t temp_size; STKVAL *auto_ptr[MAX_AUTO_PTR]; /* Pointers to C automatic variables referring to dataseg. */ size_t numautos; /* Number of entries in auto_ptr. */ /* Sendmail interaction data: */ SMFICTX *ctx; /* Milter Context */ void *data; /* MTA symbol table */ /* methods to access the latter */ char *(*getsym)(void *data, char *str); int (*setreply)(void *data, char *code, char *xcode, char *message); void (*setheader)(void *data, struct old_header_node *hdr); /* Regular expression matching */ regmatch_t *matches; /* Match map */ size_t matchcount; /* Number of used entries in matches */ size_t matchsize; /* Total number of entries in matches */ char *string; /* Last matched string */ mu_stream_t stream; /* Capture stream */ /* Non-local exits */ prog_counter_t defcatch[mf_exception_count]; prog_counter_t catch[mf_exception_count]; /* Built-in private data */ void **bi_priv_array; /* Exit information */ sfsistat status; /* Program exit status */ jmp_buf x_jmp; /* Return point for runtime errors */ jmp_buf catch_jmp; /* Return point for throws */ }; void advance_pc(eval_environ_t env, long cnt) { env->pc += cnt; } void adjust_stack(eval_environ_t env, unsigned cnt) { env->tos += cnt; } void unroll_stack(eval_environ_t env, unsigned cnt) { env->tos -= cnt; } size_t env_base(eval_environ_t env, size_t frame) { size_t base = env->base; for (; frame; frame--) base = (size_t) env->dataseg[base + 1] + base + 1; return base; } void env_var_inc(eval_environ_t env, size_t off) { ++*env_data_ref(env, off); } const struct locus * env_get_locus(eval_environ_t env) { return &env->locus; } mu_stream_t env_get_stream(eval_environ_t env) { return env->stream; } /* A call to expand_dataseg (see below) invalidates any C variables that pointed to dataseg before the call. To avoid dereferencing invalid memory pointers, the addresses of such C variables are stored in env->auto_ptr using env_register_auto (it is done by get_string_arg). When expand_dataseg is called, it calls env_fixup_autos and passes it the offset of new dataseg from old one. env_fixup_autos adds this value to every address in auto_ptr, thereby fixing them. The auto_ptr array is cleared (by calling env_unregister_autos) after executing each instruction (see eval_environment). */ void env_register_auto(eval_environ_t env, void *ptr) { if (env->numautos == MAX_AUTO_PTR) runtime_error(env, "INTERNAL ERROR at %s:%d, please report", __FILE__, __LINE__); env->auto_ptr[env->numautos++] = ptr; } void env_unregister_autos(eval_environ_t env) { env->numautos = 0; } void env_fixup_autos(eval_environ_t env, ptrdiff_t offset) { int i; for (i = 0; i < env->numautos; i++) { STKVAL *pptr = env->auto_ptr[i]; *pptr += offset; } } #define STACK_EXPAND_BLOCK 64 static int expand_dataseg(eval_environ_t env, size_t count, const char *errtext) { STKVAL *newds; ptrdiff_t offset; count = ((count + STACK_EXPAND_BLOCK - 1) / STACK_EXPAND_BLOCK) * STACK_EXPAND_BLOCK; newds = realloc(env->dataseg, (env->stack_size + datasize + count) * sizeof env->dataseg[0]); if (!newds) { if (errtext) runtime_error(env, errtext); else return 1; } offset = (char*)newds - (char*)env->dataseg; env->dataseg = newds; env->stack_size += count; env->tos += count; env->base += count; memmove(newds + env->tos, newds + env->tos - count, (datasize + env->stack_size - env->tos) * sizeof newds[0]); env_fixup_autos(env, offset); mu_error(_("Warning: stack segment expanded, new size=%lu"), (unsigned long) env->stack_size); return 0; } void prog_trace(eval_environ_t env, const char *fmt, ...) { char buf[512]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof buf, fmt, ap); va_end(ap); logmsg(LOG_DEBUG, "%4lu: %s:%lu: %s", (unsigned long) env->pc, env->locus.file, (unsigned long) env->locus.line, buf); } void instr_funcall(eval_environ_t env); void instr_locus(eval_environ_t env); void runtime_stack_trace(eval_environ_t env) { size_t base; mu_error(_("Stack trace:")); for (base = env->base; base < datasize + env->stack_size - 4; base = (size_t) env->dataseg[base + 1] + base + 1) { int i; prog_counter_t pc = (prog_counter_t)env->dataseg[base + 2] - 1; char *name; struct locus *ploc = NULL, loc; if (prog[pc-2] == instr_funcall) { name = (char*)(env->dataseg + (size_t) prog[pc-1]); pc -= 2; } else { name = "(in catch)"; pc -= 3; } for (i = 0; i < 10; i++) { if (pc > i + 3 && prog[pc - i - 3] == instr_locus) { loc.file = (char*)(env->dataseg + (size_t) prog[pc - i - 2]); loc.line = (size_t) prog[pc - i - 1]; ploc = &loc; break; } } if (ploc) mu_error("%04lu: %s:%lu: %s", (unsigned long) pc, ploc->file, (unsigned long) ploc->line, name); else mu_error("%04lu: %s", (unsigned long) pc, name); } mu_error(_("Stack trace finishes")); } void runtime_warning(eval_environ_t env, const char *fmt, ...) { int n; va_list ap; char buf[512]; n = snprintf(buf, sizeof buf, _("RUNTIME WARNING near %s:%lu: "), env->locus.file, (unsigned long) env->locus.line); va_start(ap, fmt); vsnprintf(buf + n, sizeof buf - n, fmt, ap); va_end(ap); mu_error(buf); } void runtime_error(eval_environ_t env, const char *fmt, ...) { int n; va_list ap; char buf[512]; n = snprintf(buf, sizeof buf, _("RUNTIME ERROR near %s:%lu: "), env->locus.file, (unsigned long) env->locus.line); va_start(ap, fmt); vsnprintf(buf + n, sizeof buf - n, fmt, ap); va_end(ap); mu_error(buf); if (stack_trace_option) runtime_stack_trace(env); env->status = SMFIS_TEMPFAIL; /* FIXME */ longjmp(env->x_jmp, 1); } void * get_immediate(eval_environ_t env, unsigned n) { return prog[env->pc + n + 1]; } void get_literal(eval_environ_t env, unsigned n, const char **p) { *p = (char*)(env->dataseg + (size_t) get_immediate(env, n)); env_register_auto(env, p); } STKVAL get_arg(eval_environ_t env, unsigned n) { return env->dataseg[env->tos + n + 1]; } void get_string_arg(eval_environ_t env, unsigned n, char **p) { *p = (char*) (env->dataseg + (size_t) get_arg(env, n)); env_register_auto(env, p); } void get_numeric_arg(eval_environ_t env, unsigned n, long *np) { *np = (long) get_arg(env, n); } void get_pointer_arg(eval_environ_t env, unsigned n, void **p) { *p = (void*) get_arg(env, n); } void push(eval_environ_t env, STKVAL val) { if (env->tos < env->toh) runtime_error(env, "INTERNAL ERROR at %s:%d, please report", __FILE__, __LINE__); if (env->tos == env->toh) { debug2(100, "tos=%lu, toh=%lu", (unsigned long) env->tos, (unsigned long) env->toh); expand_dataseg(env, 1, _("Out of stack space; increase #pragma stacksize")); } env->dataseg[env->tos--] = val; } STKVAL pop(eval_environ_t env) { if (env->tos == datasize + env->stack_size - 1) runtime_error(env, _("Stack underflow")); return env->dataseg[++env->tos]; } size_t heap_reserve_words(eval_environ_t env, size_t words) { size_t off = env->toh; if (env->toh + words > env->tos) expand_dataseg(env, words, _("Heap overrun; increase #pragma stacksize")); env->toh += words; return off; } size_t heap_reserve(eval_environ_t env, size_t size) { return heap_reserve_words(env, B2STACK(size)); } STKVAL heap_tempspace(eval_environ_t env, size_t size) { size_t words = B2STACK(size); if (env->toh + words > env->tos) expand_dataseg(env, words, _("Heap overrun; increase #pragma stacksize")); return env->dataseg + env->toh; } void heap_obstack_begin(eval_environ_t env) { env->temp_start = env->toh; env->temp_size = 0; } STKVAL heap_obstack_finish(eval_environ_t env) { size_t ret = env->temp_start; env->temp_start = 0; env->temp_size = 0; return (STKVAL) ret; } void * heap_obstack_grow(eval_environ_t env, void *ptr, size_t size) { size_t words = B2STACK(size); char *ret; if (env->tos - env->toh < words + B2STACK(env->temp_size)) expand_dataseg(env, words, _("Memory chunk too big to fit into heap")); ret = (char*) env_data_ref(env, env->temp_start) + env->temp_size; if (ptr) memmove(ret, ptr, size); env->temp_size += size; env->toh += words; return ret; } STKVAL * env_data_ref(eval_environ_t env, size_t off) { return env->dataseg + off; } void pushs(eval_environ_t env, char *s) { size_t off = heap_reserve(env, strlen(s) + 1); strcpy((char*) env_data_ref(env, off), s); push(env, (STKVAL) off); } /* Auxiliary instructions */ void instr_locus(eval_environ_t env) { env->locus.line = (size_t) get_immediate(env, 1); get_literal(env, 0, &env->locus.file); if (PROG_TRACE_ENGINE) prog_trace(env, "LOCUS"); advance_pc(env, 2); } void dump_locus(prog_counter_t i) { printf("\"%s\" %lu", (char*) (dataseg + (size_t)prog[i]), (unsigned long) (size_t) prog[i+1]); } /* Stack manipulation instructions */ void instr_xchg(eval_environ_t env) { prog_counter_t p = env->tos + 1; STKVAL tmp = env->dataseg[p]; env->dataseg[p] = env->dataseg[p + 1]; env->dataseg[p + 1] = tmp; if (PROG_TRACE_ENGINE) prog_trace(env, "XCHG"); } void instr_dup(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "DUP"); push(env, get_arg(env, 0)); } void instr_pop(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "POP"); pop(env); } void instr_push(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "PUSH %x", get_immediate(env, 0)); push(env, get_immediate(env, 0)); advance_pc(env, 1); } void dump_push(prog_counter_t i) { printf("%lx", (unsigned long) prog[i]); } void instr_memstk(eval_environ_t env) { size_t frame = (size_t) get_immediate(env, 0); long off = (long) get_immediate(env, 1); size_t val = env_base(env, frame) + off; advance_pc(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "MEMSTK %lu(%ld)=%lu", (unsigned long) frame, off, (unsigned long) val); push(env, (STKVAL) val); } void dump_memstk(prog_counter_t i) { printf("%lu(%ld)", (unsigned long) prog[i], (long) prog[i+1]); } void instr_deref(eval_environ_t env) { size_t off = (size_t) get_arg(env, 0); STKVAL val = env->dataseg[off]; adjust_stack(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "DEREF %lu=%lu (%p)", (unsigned long) off, (unsigned long) val, val); push(env, val); } void instr_stkalloc(eval_environ_t env) { unsigned n = (unsigned) get_immediate(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "STKALLOC %p", n); if (env->tos - n < env->toh) { debug3(100, "tos=%lu, toh=%lu, delta=%u", (unsigned long) env->tos, (unsigned long) env->toh, n); expand_dataseg(env, env->toh - (env->tos - n), _("Out of stack space; increase #pragma stacksize")); } env->tos -= n; advance_pc(env, 1); } void dump_stkalloc(prog_counter_t i) { printf("%u", (unsigned)prog[i]); } void instr_backref(eval_environ_t env) { unsigned n = (unsigned) get_immediate(env, 0); size_t matchlen; if (PROG_TRACE_ENGINE) prog_trace(env, "BACKREF %u", (unsigned)get_immediate(env, 0)); advance_pc(env, 1); if (!env->matches || !env->string) { /* FIXME: Try to throw exception first: env_throw(env, mf_no_regex); */ runtime_error(env, _("No previous regular expression")); } if (n > env->matchcount) { /* FIXME: See above */ runtime_error(env, _("Invalid back-reference number")); } if (env->matches[n].rm_so == -1) { push(env, 0); } else { char *s; size_t off; matchlen = env->matches[n].rm_eo - env->matches[n].rm_so; off = heap_reserve(env, matchlen + 1); s = (char*) env_data_ref(env, off); memcpy(s, env->string + env->matches[n].rm_so, matchlen); s[matchlen] = 0; push(env, (STKVAL) off); } } void dump_backref(prog_counter_t i) { printf("%u", (unsigned) prog[i]); } /* Type conversion instructions */ void instr_ston(eval_environ_t env) { char *s; char *p; long v; get_string_arg(env, 0, &s); v = strtol(s, &p, 0); adjust_stack(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "STON %s", s); if (*p) env_throw(env, mfe_ston_conv, "Cannot convert stack value to number (stopped at %8.8s)", p); push(env, (STKVAL) v); } void instr_ntos(eval_environ_t env) { long v = (long) get_arg(env, 0); char buf[NUMERIC_BUFSIZE_BOUND]; adjust_stack(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "NTOS %ld", v); snprintf(buf, sizeof buf, "%ld", v); pushs(env, buf); } /* Evaluation instructions */ void instr_cmp(eval_environ_t env) { long l = (long) get_arg(env, 1); long r = (long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "CMP %ld %ld", l, r); push(env, (STKVAL) (l == r)); } void instr_symbol(eval_environ_t env) { char *symbol, *s; get_literal(env, 0, (const char **)&symbol); s = env->getsym(env->data, symbol); if (PROG_TRACE_ENGINE) prog_trace(env, "SYMBOL %s", symbol); if (!s) env_throw(env, mfe_macroundef, _("Macro not defined: %s"), symbol); if (PROG_TRACE_ENGINE) prog_trace(env, "%s dereferenced to %s", symbol, s); advance_pc(env, 1); pushs(env, s); } void dump_symbol(prog_counter_t i) { printf("%08lx %s", (unsigned long) prog[i], (char *) (dataseg + (size_t) prog[i])); } /* Comparation instructions */ void instr_eqn(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "EQN %ld %ld", a, b); adjust_stack(env, 2); push(env, (STKVAL) (a == b)); } void instr_eqs(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "EQS %s %s", a, b); adjust_stack(env, 2); push(env, (STKVAL) (strcmp(a, b) == 0)); } void instr_nen(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "NEN %ld %ld", a, b); adjust_stack(env, 2); push(env, (STKVAL) (a != b)); } void instr_nes(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "NES %s %s", a, b); adjust_stack(env, 2); push(env, (STKVAL) strcmp(a, b)); } void instr_ltn(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "LTN %ld %ld", a, b); adjust_stack(env, 2); push(env, (STKVAL) (a < b)); } void instr_lts(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "LTS %s %s", a, b); adjust_stack(env, 2); push(env, (STKVAL) (strcmp(a, b) < 0)); } void instr_len(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "LEN %ld %ld", a, b); adjust_stack(env, 2); push(env, (STKVAL) (a <= b)); } void instr_les(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "LES %s %s", a, b); adjust_stack(env, 2); push(env, (STKVAL) (strcmp(a, b) <= 0)); } void instr_gtn(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "GTN %ld %ld", a, b); adjust_stack(env, 2); push(env, (STKVAL) (a > b)); } void instr_gts(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "GTS %s %s", a, b); adjust_stack(env, 2); push(env, (STKVAL) (strcmp(a, b) > 0)); } void instr_gen(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "GEN %ld %ld", a, b); adjust_stack(env, 2); push(env, (STKVAL) (a >= b)); } void instr_ges(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "GES %s %s", a, b); adjust_stack(env, 2); push(env, (STKVAL) (strcmp(a, b) >= 0)); } /* Jump instructions */ void instr_bz(eval_environ_t env) { long v = (long) get_arg(env, 0); long off = (long) get_immediate(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "BZ %ld (%ld)", off, v); adjust_stack(env, 1); if (v == 0) advance_pc(env, off); advance_pc(env, 1); } void dump_branch (prog_counter_t i) { printf("%ld (%ld)", (long) prog[i], i + (long) prog[i] + 1); } void instr_bnz(eval_environ_t env) { long v = (long) get_arg(env, 0); long off = (long) get_immediate(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "BNZ %ld (%ld)", off, v); adjust_stack(env, 1); if (v != 0) advance_pc(env, off); advance_pc(env, 1); } void instr_jmp(eval_environ_t env) { long off = (long) get_immediate(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "JMP %ld", off); advance_pc(env, off+1); } /* Boolean instructions */ void instr_not(eval_environ_t env) { long v = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "NOT %ld", v); adjust_stack(env, 1); push(env, (STKVAL) !v); } /* Bitwise arithmetics */ void instr_logand(eval_environ_t env) { unsigned long a = (unsigned long) get_arg(env, 1); unsigned long b = (unsigned long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGAND %lu %lu", a, b); push(env, (STKVAL) (a & b)); } void instr_logor(eval_environ_t env) { unsigned long a = (unsigned long) get_arg(env, 1); unsigned long b = (unsigned long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGOR %lu %lu", a, b); push(env, (STKVAL) (a | b)); } void instr_logxor(eval_environ_t env) { unsigned long a = (unsigned long) get_arg(env, 1); unsigned long b = (unsigned long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGXOR %lu %lu", a, b); push(env, (STKVAL) (a ^ b)); } void instr_lognot(eval_environ_t env) { unsigned long v = (unsigned long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGNOT %ld", v); adjust_stack(env, 1); push(env, (STKVAL) ~v); } /* Arithmetic instructions */ void instr_add(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "ADD %ld %ld", a, b); push(env, (STKVAL) (a + b)); } void instr_sub(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "SUB %ld %ld", a, b); push(env, (STKVAL) (a - b)); } void instr_mul(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "MUL %ld %ld", a, b); push(env, (STKVAL) (a * b)); } void instr_div(eval_environ_t env) { long a = (long) get_arg(env, 1); long b = (long) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "DIV %ld %ld", a, b); if (b == 0) env_throw(env, mfe_divzero, "Division by zero at %08lx", (unsigned long)env->pc); push(env, (STKVAL) (a / b)); } void instr_neg(eval_environ_t env) { long v = (long) get_arg(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "NEG %ld", v); adjust_stack(env, 1); push(env, (STKVAL) -v); } /* Matching and Regular expression instructions */ /* REGEX: Basically it is useful only for debugging. Its effect is the same as that of instr_push */ void instr_regex(eval_environ_t env) { char buffer[REGEX_STRING_BUFSIZE]; size_t index = (size_t) get_immediate(env, 0); advance_pc(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "REGEX (%s) %s", regex_flags_to_string(regtab[index].regflags, buffer, sizeof buffer), (char*) env_data_ref(env, regtab[index].expr)); push(env, (STKVAL) index); } void dump_regex(prog_counter_t i) { size_t index = (size_t) prog[i]; char buffer[REGEX_STRING_BUFSIZE]; printf("(%s) %s", regex_flags_to_string(regtab[index].regflags, buffer, sizeof buffer), (char*)(dataseg + regtab[index].expr)); } void instr_regmatch(eval_environ_t env) { int v; size_t index = (size_t)get_arg(env, 0); regex_t *re = ®tab[index].re; char *string; get_string_arg(env, 1, &string); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "REGMATCH %s %s", env_data_ref(env, regtab[index].expr), string); env->matchcount = re->re_nsub; if (env->matchsize < env->matchcount + 1) { void *p = realloc(env->matches, sizeof(env->matches[0]) * (env->matchcount + 1)); if (!p) runtime_error(env, _("Not enough memory")); env->matches = p; env->matchsize = env->matchcount + 1; } v = regexec(re, string, env->matchcount + 1, env->matches, 0); env->string = string; push(env, (STKVAL) (v == 0)); } void instr_regcomp(eval_environ_t env) { int v; char buffer[REGEX_STRING_BUFSIZE]; size_t expr_off = (size_t)get_arg(env, 0); char *expr; size_t index = (size_t) get_immediate(env, 0); struct rt_regex *rtx = ®tab[index]; get_string_arg(env, 0, &expr); advance_pc(env, 1); adjust_stack(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "REGCOMP %s %s", regex_flags_to_string(rtx->regflags, buffer, sizeof buffer), expr); if (rtx->compiled) { regfree(&rtx->re); rtx->compiled = 0; } v = regcomp(&rtx->re, expr, rtx->regflags); if (v) { char errbuf[512]; regerror(v, &rtx->re, errbuf, sizeof(errbuf)); env_throw(env, mfe_regcomp, "compiling regex `%s': %s", expr, errbuf); } else { rtx->compiled = 1; rtx->expr = expr_off; } push(env, (STKVAL) index); } void dump_regcomp(prog_counter_t i) { size_t index = (size_t) prog[i]; struct rt_regex *rtx = ®tab[index]; char buffer[REGEX_STRING_BUFSIZE]; printf("%s", regex_flags_to_string(rtx->regflags, buffer, sizeof buffer)); } void instr_fnmatch(eval_environ_t env) { char *string, *pattern; get_string_arg(env, 1, &string); get_string_arg(env, 0, &pattern); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "FNMATCH %s %s", string, pattern); push(env, (STKVAL) (fnmatch (pattern, string, 0) == 0)); } static int mx_match(eval_environ_t env, char *string, int (*matcher)(const char *name, void *data), void *data) { int rc = 0; mxbuf_t mxbuf; mf_status mxstat; char *p = strchr(string, '@'); if (p) p++; else p = string; mxstat = getmx(p, mxbuf); rc = 0; if (mxstat == mf_success) { int i; for (i = 0; i < MAXMXCOUNT && mxbuf[i]; i++) { if (matcher(mxbuf[i], data)) { rc = 1; break; } } dns_freemx(mxbuf); } else if (mxstat != mf_not_found) env_throw(env, mxstat, "cannot get MXs for %s", p); return rc; } static int fn_matcher(const char *string, void *data) { return fnmatch (data, string, 0) == 0; } void instr_fnmatch_mx(eval_environ_t env) { char *string, *pattern; get_string_arg(env, 1, &string); get_string_arg(env, 0, &pattern); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "FNMATCH,MX %s %s", string, pattern); push(env, (STKVAL) mx_match(env, string, fn_matcher, pattern)); } static int regex_matcher(const char *string, void *data) { return regexec(data, string, 0, NULL, 0) == 0; } void instr_regmatch_mx(eval_environ_t env) { int rc; size_t index = (size_t)get_arg(env, 0); regex_t *re = ®tab[index].re; char *string; get_string_arg(env, 1, &string); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "REGMATCH,MX %s %s", regtab[index].expr, string); rc = mx_match(env, string, regex_matcher, re); push(env, (STKVAL) rc); } /* Mail filter specific instructions */ void instr_next(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "NEXT", env->locus.file, env->locus.line); trace("%s%s:%lu: next", mailfromd_msgid(env->ctx), env->locus.file, env->locus.line); } void instr_result(eval_environ_t env) { sfsistat status = (sfsistat) get_immediate(env, 0); char *code, *xcode; char *message; get_string_arg(env, 0, &message); get_literal(env, 1, (const char**)&code); get_literal(env, 2, (const char**)&xcode); if (PROG_TRACE_ENGINE) prog_trace(env, "RESULT %d %s %s %s", status, SP(code), SP(xcode), SP(message)); trace("%s%s:%lu: %s %s %s %s", mailfromd_msgid(env->ctx), env->locus.file, env->locus.line, sfsistat_str(status), SP(code), SP(xcode), SP(message)); if (code[0] == 0) code = NULL; if (xcode[0] == 0) xcode = NULL; env->status = status; env->setreply(env->data, code, xcode, message); advance_pc(env, 3); adjust_stack(env, 1); } void dump_result(prog_counter_t i) { printf("%s %s %s", sfsistat_str((sfsistat)prog[i]), (char*)(dataseg + (size_t) prog[i+1]), (char*)(dataseg + (size_t) prog[i+2])); } void instr_header(eval_environ_t env) { struct old_header_node *hdr = xmalloc (sizeof(*hdr)); char *value; hdr->opcode = (enum header_opcode) get_immediate(env, 0); get_literal(env, 1, &hdr->name); get_string_arg(env, 0, &value); hdr->value = strdup(value); advance_pc(env, 2); adjust_stack(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "HEADER %d %s %s", hdr->opcode, hdr->name, hdr->value); trace("%s%s:%lu: %s %s %s", mailfromd_msgid(env->ctx), env->locus.file, env->locus.line, header_command_str(hdr->opcode), hdr->name, hdr->value); env->setheader(env->data, hdr); } void dump_header(prog_counter_t i) { printf("%s %s", header_command_str((enum header_opcode) prog[i]), (char*)(dataseg + (size_t)prog[i+1])); } void instr_builtin(eval_environ_t env) { const char *name; void (*handler)(eval_environ_t) = get_immediate(env, 1); get_literal(env, 0, &name); if (PROG_TRACE_ENGINE) prog_trace(env, "BUILTIN %s", name); advance_pc(env, 2); handler(env); } void dump_builtin(prog_counter_t i) { printf("%s ", (char*)(dataseg + (size_t) prog[i])); } void instr_concat(eval_environ_t env) { char *left, *right; size_t off; char *res; get_string_arg(env, 1, &left); get_string_arg(env, 0, &right); off = heap_reserve(env, strlen(left) + strlen(right) + 1); res = (char*) env_data_ref(env, off); strcat(strcpy(res, left), right); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "CONCAT ('%s','%s')='%s'", left, right, res); push(env, (STKVAL) off); } void dump_adjust(prog_counter_t i) { printf("%lu ", (unsigned long) prog[i]); } void instr_asgn(eval_environ_t env) { STKVAL val = get_arg(env, 1); size_t dest = (size_t) get_arg(env, 0); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "ASGN %lu=%lu", (unsigned long) dest, (unsigned long) val); env->dataseg[dest] = val; } void instr_catch(eval_environ_t env) { long off = (long) get_immediate(env, 0); /* int count = (long) get_immediate(env, 1); -- Count is not used */ unsigned mask = (unsigned) get_immediate(env, 2); int i; prog_counter_t entry = env->pc + 4; if (PROG_TRACE_ENGINE) prog_trace(env, "CATCH %ld, %ld", entry, off); for (i = 0; i < mf_exception_count; i++) if (mask & EXMASK(i)) { if (PROG_TRACE_ENGINE) prog_trace(env, "CATCH TARGET: %s", mf_exception_str((mf_exception)i)); env->catch[i] = entry; } advance_pc(env, off); } void dump_catch(prog_counter_t i) { unsigned mask = (unsigned) prog[i+2]; printf("%ld (%ld)", (long) prog[i], i + (long) prog[i]); printf("; Targets: "); for (i = 0; i < mf_exception_count; i++) if (mask & EXMASK(i)) printf("%s ", mf_exception_str((mf_exception)i)); } void instr_throw(eval_environ_t env) { unsigned long n = (unsigned long) get_immediate(env, 0); size_t off = (size_t) get_arg(env, 0); advance_pc(env, 1); adjust_stack(env, 1); if (n > mf_exception_count) runtime_error(env, _("Invalid exception number: %lu"), n); env_throw_0(env, (mf_status) n, off); } void dump_throw(prog_counter_t i) { printf("%s", mf_exception_str((mf_exception) prog[i])); } void instr_echo(eval_environ_t env) { char *str = (char*) env_data_ref(env, (size_t) pop(env)); logmsg(LOG_INFO, "%s", str); } void instr_return(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "RETURN"); env_leave_frame(env, 0); env->pc--; } void instr_retcatch(eval_environ_t env) { prog_counter_t pc = env->pc; if (PROG_TRACE_ENGINE) prog_trace(env, "RETCATCH"); env_leave_frame(env, 1); env->pc = pc; } void instr_saveex(eval_environ_t env) { unsigned exmask = (unsigned) get_immediate(env, 0); int i; advance_pc(env, 1); if (PROG_TRACE_ENGINE) prog_trace(env, "SAVEEX %x", exmask); push(env, (STKVAL) exmask); for (i = 0; i < mf_exception_count; i++) { if (EXMASK(i) & exmask) push(env, (STKVAL) env->catch[i]); } } void dump_saveex(prog_counter_t i) { printf("%x", (unsigned)prog[i]); } void instr_restex(eval_environ_t env) { unsigned exmask = (unsigned)env->dataseg[env->base]; int i; if (PROG_TRACE_ENGINE) prog_trace(env, "RESTEX %x", exmask); for (i = 0; i < mf_exception_count; i++) { if (EXMASK(i) & exmask) env->catch[i] = (prog_counter_t) env->dataseg[env->base-i-1]; } } void instr_adjust(eval_environ_t env) { long nargs = (long) get_immediate(env, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "ADJUST %ld", nargs); adjust_stack(env, nargs); advance_pc(env, 1); } void instr_popreg(eval_environ_t env) { env->reg = pop(env); if (PROG_TRACE_ENGINE) prog_trace(env, "POPREG %p", env->reg); } void instr_pushreg(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "PUSHREG %p", env->reg); push(env, env->reg); } void instr_funcall(eval_environ_t env) { const char *name; prog_counter_t pc = (prog_counter_t) get_immediate(env, 1); get_literal(env, 0, &name); advance_pc(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "FUNCALL %s (%lu)", name, (unsigned long)pc); env_make_frame(env); env->pc = pc-1; } void dump_funcall(prog_counter_t i) { printf("%s (%lu)", (char*)(dataseg + (size_t)prog[i]), (unsigned long)prog[i+1]); } /* Opcode: xlat N OFF Synopsis: Scan the table until xI==REG is found or the table is exhausted. If found, replace REG with yI and return 0. Otherwise, return 1 */ void instr_xlat(eval_environ_t env) { unsigned long i; unsigned long count = (unsigned long) get_immediate(env, 0); size_t off = (size_t) get_immediate(env, 1); STKVAL *tab = (STKVAL *) (env->dataseg + off); if (PROG_TRACE_ENGINE) prog_trace(env, "XLAT %lu %lu", count, (unsigned long) off); advance_pc(env, 2); for (i = 0; i < count; i += 2) { if ((long) tab[i] == (long) env->reg) { env->reg = (STKVAL) tab[i+1]; push(env, (STKVAL)0); return; } } push(env, (STKVAL)1); } void instr_xlats(eval_environ_t env) { unsigned long i; unsigned long count = (unsigned long) get_immediate(env, 0); size_t off = (size_t) get_immediate(env, 1); STKVAL *tab = (STKVAL *) (env->dataseg + off); char *str = (char*) env_data_ref(env, (size_t) env->reg); if (PROG_TRACE_ENGINE) prog_trace(env, "XLATS %lu %lu", count, (unsigned long) off); advance_pc(env, 2); for (i = 0; i < count; i += 2) { if (strcmp((char*)(env->dataseg + (size_t)tab[i]), str) == 0) { env->reg = (STKVAL) tab[i+1]; push(env, (STKVAL)0); return; } } push(env, (STKVAL)1); } void dump_xlat(prog_counter_t i) { unsigned long j; unsigned long count = (unsigned long) prog[i]; size_t off = (size_t) prog[i+1]; STKVAL *tab = (STKVAL *) (dataseg + off); printf("%lu %lu ", count, (unsigned long) off); for (j = 0; j < count; j += 2) printf("(%ld %ld) ", (long)tab[j], (long)tab[j+1]); } void dump_xlats(prog_counter_t i) { unsigned long j; unsigned long count = (unsigned long) prog[i]; size_t off = (size_t) prog[i+1]; STKVAL *tab = (STKVAL *) (dataseg + off); printf("%lu %lu ", count, (unsigned long) off); for (j = 0; j < count; j += 2) printf("(%08lx %s %ld) ", (unsigned long)tab[j], (char*)(dataseg + (size_t) tab[j]), (long)tab[j+1]); } void instr_jreg(eval_environ_t env) { env->pc += (prog_counter_t)env->reg - 1; if (PROG_TRACE_ENGINE) prog_trace(env, "JREG %ld (%lu)", (long)((prog_counter_t)env->reg - 1), (unsigned long)env->pc); } static void _dumper(prog_counter_t pc, struct optab *op, void *data) { printf("%04lu: ", (unsigned long) pc); printf("%s ", op->name); if (op->dump) op->dump(pc + 1); putchar('\n'); } void dump_code(prog_counter_t start, prog_counter_t end) { if (end == 0) end = pc; scan_code(start, end, _dumper, NULL); } static void _fixup(prog_counter_t pc, struct optab *op, void *data) { struct locus *locus = data; enum instr_opcode opcode = (enum instr_opcode) prog[pc]; prog[pc] = op->instr; if (opcode == opcode_regex) { int rc; size_t index = (size_t) prog[pc+1]; if (index > regcount) { parse_error_locus(locus, "Invalid regexp index %lu", (unsigned long) index); return; } rc = regcomp(®tab[index].re, (char*) (dataseg + regtab[index].expr), regtab[index].regflags); if (rc) { char errbuf[512]; regerror(rc, ®tab[index].re, errbuf, sizeof(errbuf)); parse_error_locus(locus, "Cannot compile regex: %s", errbuf); } else regtab[index].compiled = 1; } else if (opcode == opcode_locus) { locus->file = (char*) (dataseg + (size_t) prog[pc+1]); locus->line = (size_t) prog[pc+2]; } } void fixup_code() { struct locus locus = { "", 0 }; scan_code(0, pc, _fixup, &locus); if (error_count) exit(EX_CONFIG); } void env_init(eval_environ_t env) { /* Initialize status and registers */ env->status = SMFIS_CONTINUE; env->tos = datasize + env->stack_size - 1; env->base = 0; env->reg = 0; env->matches = NULL; env->matchsize = 0; env->matchcount = 0; env->string = NULL; env->numautos = 0; /* Initialize catch functions */ memcpy (env->catch, env->defcatch, sizeof env->catch); env_final_gc(env); } void env_make_frame(eval_environ_t env) { push(env, (STKVAL) (env->pc + 1)); push(env, (STKVAL) (env->base - env->tos)); env->base = env->tos; } void env_leave_frame(eval_environ_t env, int nargs) { env->tos = env->base; env->base += (prog_counter_t) pop(env) + 1; env->pc = (prog_counter_t) pop(env); adjust_stack(env, nargs); } void env_push_string(eval_environ_t env, char *arg) { pushs(env, (STKVAL) arg); } void env_push_number(eval_environ_t env, long arg) { push(env, (STKVAL) arg); } int eval_environment(eval_environ_t env, prog_counter_t start) { if (setjmp(env->x_jmp)) return 1; for (env->pc = start; ; env->pc++) { if (env->pc >= pc) runtime_error(env, _("pc out of range")); if (!prog[env->pc]) break; if (setjmp(env->catch_jmp) == 0) { (*(prog[env->pc]))(env); env_unregister_autos(env); } } return 0; } static void env_vsprintf_error(const char *fmt, va_list ap) { mu_error(_("Out of memory while formatting error message:")); mu_verror(fmt, ap); } size_t env_vsprintf(eval_environ_t env, const char *biname, const char *fmt, va_list ap) { size_t n = 0, off; char *p, *s; while (1) { size_t k; size_t size; if (env->tos == env->toh) if (expand_dataseg(env, B2STACK(strlen(fmt)), NULL)) { env_vsprintf_error(fmt, ap); break; } size = (env->tos - env->toh - 1) * sizeof env->dataseg[0]; s = (char*) env_data_ref(env, env->toh); if (biname) { n = snprintf(s, size, "%s: ", biname); if (n >= size) { n += strlen(fmt); /* rough estimation */ if (expand_dataseg(env, B2STACK(n), NULL)) { env_vsprintf_error(fmt, ap); break; } continue; } } k = vsnprintf(s + n, size - n, fmt, ap); if (k >= size) { if (expand_dataseg(env, B2STACK(k), NULL)) { env_vsprintf_error(fmt, ap); break; } continue; } n += k; break; } p = (char*) env_data_ref(env, off = heap_reserve(env, n + 1)); memmove(p, s, n + 1); return off; } void env_throw_0(eval_environ_t env, mf_exception status, size_t off) { prog_counter_t pc = env->catch[status]; if (pc) { /* Reset the exception to avoid recursion. We will never have to restore it again, since the catch handler either returns from the function, in which case the exception mask will be restored, or stops the execution with an action, in which case exceptions are not relevant anymore. */ env->catch[status] = 0; /* Fixup the program counter */ env->pc = pc - 1; /* Generate normal entry frame */ push(env, (STKVAL) off); env_push_number(env, status); env_make_frame(env); longjmp(env->catch_jmp, 1); } runtime_error(env, "%s", (char*) env_data_ref(env, off)); } void env_throw(eval_environ_t env, mf_exception status, const char *fmt, ...) { va_list ap; size_t off; va_start(ap, fmt); off = env_vsprintf(env, NULL, fmt, ap); va_end(ap); env_throw_0(env, status, off); } void env_throw_bi(eval_environ_t env, mf_exception status, const char *biname, const char *fmt, ...) { va_list ap; size_t off; va_start(ap, fmt); off = env_vsprintf(env, biname, fmt, ap); va_end(ap); env_throw_0(env, status, off); } sfsistat environment_get_status(eval_environ_t env) { return env->status; } SMFICTX * env_get_context(eval_environ_t env) { return env->ctx; } /* Capturing support */ int env_capture_start(eval_environ_t env) { int rc = mu_temp_file_stream_create(&env->stream, NULL); if (rc) { mu_error(_("%sCannot create temporary stream: %s"), mailfromd_msgid(env->ctx), mu_strerror(rc)); return 1; } rc = mu_stream_open(env->stream); if (rc) { mu_error(_("%sCannot open temporary stream: %s"), mailfromd_msgid(env->ctx), mu_strerror(rc)); mu_stream_destroy(&env->stream, mu_stream_get_owner(env->stream)); return 1; } return 0; } int env_capture_write(eval_environ_t env, const char *buf, size_t size) { if (env->stream) { int rc = mu_stream_sequential_write(env->stream, buf, size); if (rc) { mu_error(_("%sTemporary stream write failed: %s"), mailfromd_msgid(env->ctx), mu_strerror(rc)); mu_stream_close(env->stream); mu_stream_destroy(&env->stream, mu_stream_get_owner(env->stream)); } return rc; } return 1; } int env_capture_write_args(eval_environ_t env, ...) { va_list ap; char *arg; int rc = 0; if (!env->stream) return 1; va_start(ap, env); while (arg = va_arg(ap, char*)) { if (rc = env_capture_write(env, arg, strlen(arg))) break; } va_end(ap); return rc; } struct builtin_priv { /* Built-in private data structure */ void *(*init)(); void (*destroy)(void*); }; static size_t builtin_priv_size; static size_t builtin_priv_count; static struct builtin_priv *bi_priv; static void builtin_priv_destroy(void *ptr) { free(ptr); } int builtin_priv_register(void *(*init)(), void (*destroy)(void*)) { struct builtin_priv *p; int desc; if (!init) return -1; if (builtin_priv_count == builtin_priv_size) { if (builtin_priv_size == 0) builtin_priv_size = 4; bi_priv = x2nrealloc(bi_priv, &builtin_priv_size, sizeof bi_priv[0]); } desc = builtin_priv_count; p = bi_priv + builtin_priv_count++; p->init = init; p->destroy = destroy ? destroy : builtin_priv_destroy; return desc; } void * env_get_builtin_priv(eval_environ_t env, int id) { if (id < 0 || id >= builtin_priv_count) runtime_error(env, _("Unknown built-in private data requested (%d)"), id); if (!env->bi_priv_array) { if (builtin_priv_count == 0) runtime_error(env, _("No built-in private data registered")); env->bi_priv_array = xcalloc(builtin_priv_count, sizeof env->bi_priv_array[0]); } if (env->bi_priv_array[id] == NULL) { env->bi_priv_array[id] = bi_priv[id].init(); if (!env->bi_priv_array[id]) runtime_error(env, _("Initial allocation for built-in " "private data #%d failed"), id); } return env->bi_priv_array[id]; } static void env_builtin_priv_destroy(eval_environ_t env) { if (env->bi_priv_array) { int i; for (i = 0; i < builtin_priv_count; i++) if (env->bi_priv_array[i]) bi_priv[i].destroy(env->bi_priv_array[i]); free(env->bi_priv_array); } } /* 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) { struct dict_entry *ent = item; free(ent->name); free(ent->value); free(ent); } 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; } /* Initialize the data segment and relocate string variables */ static void init_dataseg(STKVAL *dseg) { memcpy(dseg, dataseg, datasize * sizeof dataseg[0]); } eval_environ_t create_environment(SMFICTX *ctx, char *(*getsym)(void *data, char *str), int (*setreply)(void *data, char *code, char *xcode, char *message), void (*setheader)(void *data, struct old_header_node *hdr), void *data) { struct eval_environ *env = calloc(1, sizeof *env); if (!env) { mu_error(_("Not enough memory")); exit(1); } env->stack_size = stack_size; env->dataseg = calloc(stack_size + datasize, sizeof env->dataseg[0]); if (!env->dataseg) { mu_error(_("Not enough memory")); exit(1); } init_dataseg(env->dataseg); env->ctx = ctx; env->data = data; env->getsym = getsym; env->setreply = setreply; env->setheader = setheader; env->status = SMFIS_CONTINUE; /* FIXME: The only register that we initialize here. The rest is initialized in env_init. The top of heap should be retained across calls to handlers, since we store string variables there. This raises stack size requirements. */ env->toh = datasize; env->bi_priv_array = NULL; memset(env->defcatch, 0, sizeof env->defcatch); return env; } void destroy_environment(eval_environ_t env) { free(env->dataseg); free(env->matches); mu_stream_close(env->stream); mu_stream_destroy(&env->stream, mu_stream_get_owner(env->stream)); env_builtin_priv_destroy(env); free(env); } /* Builtins */ void builtin_setup () { struct builtin_module *mod; for (mod = builtin_module; mod->name; mod++) if (mod->init) mod->init(); } static int function_counter(void *sym, void *data) { size_t *p = data; ++*p; return 0; } struct entry_point { int ishandler; prog_counter_t pc; union { enum smtp_state tag; const char *name; } v; }; struct enum_data { int i; struct entry_point *base; }; static int function_lister(void *sym, void *data) { struct function *f = sym; struct enum_data *d = data; d->base[d->i].ishandler = 0; d->base[d->i].v.name = f->name; d->base[d->i].pc = f->entry; d->i++; return 0; } static int comp_pc(const void *a, const void *b) { const struct entry_point *ap = a; const struct entry_point *bp = b; if (ap->pc < bp->pc) return -1; else if (ap->pc > bp->pc) return 1; return 0; } static void print_dataseg() { char *p = (char*) dataseg; size_t s = datasize * sizeof(STKVAL); size_t off; printf("Data segment:\n"); printf("-------------\n"); for (off = 0; off < s; ) { char vbuf[GACOPYZ_VBUFSIZE]; size_t rd = gacopyz_format_vbuf(vbuf, p + off, s - off); printf("%08lx: %s\n", (unsigned long) off, vbuf); off += rd; } } void print_code() { enum smtp_state tag; struct entry_point *ep; /* Entry points */ size_t epcount = 0; /* Number of entry points */ size_t i; struct enum_data d; /* Count all available entry points: */ for (tag = smtp_state_first; tag < smtp_state_count; tag++) if (entry_point[tag]) epcount++; symbol_enumerate(SYM_FUNC, function_counter, &epcount); ep = xmalloc((epcount+1)*sizeof *ep); /* Fill in entry points array */ i = 0; for (tag = smtp_state_first; tag < smtp_state_count; tag++) if (entry_point[tag]) { ep[i].ishandler = 1; ep[i].pc = entry_point[tag]; ep[i].v.tag = tag; i++; } d.base = ep; d.i = i; symbol_enumerate(SYM_FUNC, function_lister, &d); /* Sort the array */ qsort(ep, epcount, sizeof ep[0], comp_pc); ep[epcount].pc = pc; /* Actually print the code */ for (i = 0; i < epcount; i++) { if (ep[i].ishandler) printf("HANDLER %s\n", state_to_string(ep[i].v.tag)); else printf("FUNCTION %s\n", ep[i].v.name); dump_code(ep[i].pc, ep[i+1].pc); } free(ep); print_dataseg(); } static eval_environ_t genv; static size_t *s_off; static size_t s_cnt; static int s_off_cmp(const void *a, const void *b) { char *pa = genv->dataseg[*(const size_t *) a]; char *pb = genv->dataseg[*(const size_t *) b]; if (pa < pb) return -1; else if (pa > pb) return 1; return 1; } void env_final_gc(eval_environ_t env) { size_t i; size_t top = datasize; size_t bot = env->toh; genv = env; /* Prepare s_off/s_count: remove any variables that are not in heap */ s_off = xcalloc(dataseg_reloc_count, sizeof s_off[0]); for (i = 0, s_cnt = 0; i < dataseg_reloc_count; i++) { size_t p = (size_t) env->dataseg[dataseg_reloc[i]]; if (top <= p && p < bot) s_off[s_cnt++] = dataseg_reloc[i]; } if (s_cnt) { qsort(s_off, s_cnt, sizeof s_off[0], s_off_cmp); /* Compact the variables */ env->toh = datasize; for (i = 0; i < s_cnt; i++) { size_t off = s_off[i]; char *p = (char*) env_data_ref(env, (size_t) env->dataseg[off]); size_t len = strlen(p) + 1; size_t q = heap_reserve(env, len); memmove(env_data_ref(env, q), p, len); env->dataseg[off] = (STKVAL) q; } free(s_off); } } void env_save_catches(eval_environ_t env) { memcpy(env->defcatch, env->catch, sizeof env->defcatch); }