/* 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 */
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;
/* 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_status_count];
prog_counter_t catch[mf_status_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, unsigned 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;
}
#define STACK_EXPAND_BLOCK 64
static int
expand_dataseg(eval_environ_t env, size_t count, const char *errtext)
{
STKVAL *newds;
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;
}
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]);
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];
}
const char *
get_literal(eval_environ_t env, unsigned n)
{
return (char*)(env->dataseg + (size_t) get_immediate(env, n));
}
STKVAL
get_arg(eval_environ_t env, unsigned n)
{
return env->dataseg[env->tos + n + 1];
}
char *
get_string_arg(eval_environ_t env, unsigned n)
{
return (char*) (env->dataseg + (size_t) get_arg(env, n));
}
size_t
get_numeric_arg(eval_environ_t env, unsigned n)
{
return (size_t) 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.file = get_literal(env, 0);
env->locus.line = (size_t) get_immediate(env, 1);
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);
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 = get_string_arg(env, 0);
char *p;
long v = strtol(s, &p, 0);
adjust_stack(env, 1);
if (PROG_TRACE_ENGINE)
prog_trace(env, "STON %s", s);
if (*p)
env_throw(env, mf_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_numeric_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_numeric_arg(env, 1);
long r = (long) get_numeric_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 = (char *) get_literal(env, 0);
char *s = env->getsym(env->data, symbol);
if (PROG_TRACE_ENGINE)
prog_trace(env, "SYMBOL %s", symbol);
if (!s)
env_throw(env, mf_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_numeric_arg(env, 1);
long b = (long) get_numeric_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 = get_string_arg(env, 1);
char *b = get_string_arg(env, 0);
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_numeric_arg(env, 1);
long b = (long) get_numeric_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 = get_string_arg(env, 1);
char *b = get_string_arg(env, 0);
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_numeric_arg(env, 1);
long b = (long) get_numeric_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 = get_string_arg(env, 1);
char *b = get_string_arg(env, 0);
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_numeric_arg(env, 1);
long b = (long) get_numeric_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 = get_string_arg(env, 1);
char *b = get_string_arg(env, 0);
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_numeric_arg(env, 1);
long b = (long) get_numeric_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 = get_string_arg(env, 1);
char *b = get_string_arg(env, 0);
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_numeric_arg(env, 1);
long b = (long) get_numeric_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 = get_string_arg(env, 1);
char *b = get_string_arg(env, 0);
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_numeric_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_numeric_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_numeric_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_numeric_arg(env, 1);
unsigned long b = (unsigned long) get_numeric_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_numeric_arg(env, 1);
unsigned long b = (unsigned long) get_numeric_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_numeric_arg(env, 1);
unsigned long b = (unsigned long) get_numeric_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_numeric_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_numeric_arg(env, 1);
long b = (long) get_numeric_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_numeric_arg(env, 1);
long b = (long) get_numeric_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_numeric_arg(env, 1);
long b = (long) get_numeric_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_numeric_arg(env, 1);
long b = (long) get_numeric_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, mf_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_numeric_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_numeric_arg(env, 0);
regex_t *re = ®tab[index].re;
char *string = get_string_arg(env, 1);
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 = get_string_arg(env, 0);
size_t index = (size_t) get_immediate(env, 0);
struct rt_regex *rtx = ®tab[index];
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, mf_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 = get_string_arg(env, 1);
char *pattern = get_string_arg(env, 0);
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 = get_string_arg(env, 1);
char *pattern = get_string_arg(env, 0);
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_numeric_arg(env, 0);
regex_t *re = ®tab[index].re;
char *string = get_string_arg(env, 1);
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 = (char *) get_literal(env, 1);
char *xcode = (char *) get_literal(env, 2);
char *message = get_string_arg(env, 0);
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));
hdr->opcode = (enum header_opcode) get_immediate(env, 0);
hdr->name = get_literal(env, 1);
hdr->value = strdup(get_string_arg(env, 0));
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 = get_literal(env, 0);
void (*handler)(eval_environ_t) = get_immediate(env, 1);
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 = get_string_arg(env, 1);
char *right = get_string_arg(env, 0);
size_t off = heap_reserve(env, strlen(left) + strlen(right) + 1);
char *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 = get_numeric_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_status_count; i++)
if (mask & EXMASK(i)) {
if (PROG_TRACE_ENGINE)
prog_trace(env, "CATCH TARGET: %s",
mf_status_str((mf_status)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_status_count; i++)
if (mask & EXMASK(i))
printf("%s ", mf_status_str((mf_status)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_status_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_status_str((mf_status) 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_status_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_status_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 = get_literal(env, 0);
prog_counter_t pc = (prog_counter_t) get_immediate(env, 1);
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;
/* 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);
}
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_status 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_status 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_status 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);
}