/* * Copyright (c) 2006 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_SA #include "mailprocd.h" char saVersion[64]; SV *saFactory = NULL; SV *saAddrListFactory = NULL; #undef SA_ASSERT #define SA_ASSERT(expr) if (!(expr)) { return (-1); } #define SA_ASSERT_SP(expr) if (!(expr)) { goto fail; } /* Return the SpamAssassin version into a fixed-size buffer. */ int SA_GetVersion(char *buf, size_t buf_len) { dSP; int n; SV *sv; char *s; STRLEN slen; ENTER; SAVETMPS; PUSHMARK(SP); n = call_pv("Mail::SpamAssassin::Version", G_SCALAR); SPAGAIN; SA_ASSERT_SP(n == 1); sv = POPs; s = SvPV(sv, slen); Strlcpy(buf, s, buf_len); buf[slen] = '\0'; PUTBACK; FREETMPS; LEAVE; return (0); fail: PUTBACK; FREETMPS; LEAVE; return (-1); } /* Return the SpamAssassin factory. */ static __inline__ SV * SA_Factory(void) { if (saFactory != NULL) { return (saFactory); } if ((saFactory = get_sv("Assassin", 0)) == NULL || SvTYPE(saFactory) != SVt_RV) { Fatal("No Assassin!"); } return (saFactory); } /* Return the SpamAssassin address list factory. */ static __inline__ SV * SA_AddrListFactory(void) { if (saAddrListFactory != NULL) { return (saAddrListFactory); } if ((saAddrListFactory = get_sv("AddrListFactory", 0)) == NULL || SvTYPE(saAddrListFactory) != SVt_RV) { Fatal("No AddrListFactory!"); } return (saAddrListFactory); } /* Parse a message and return a new Mail::SpamAssassin::Message object. */ int SA_ParseMessage(MPD_Message *msg) { dSP; I32 n; SV *svText; SA_ASSERT(msg->text != NULL); svText = newSVpvn(msg->text, msg->text_len); SA_ASSERT(svText != NULL); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(sv_2mortal(svText)); PUTBACK; n = call_method("parse", G_SCALAR); SPAGAIN; SA_ASSERT_SP(n == 1); msg->svParsed = newSVsv(POPs); SA_ASSERT_SP(msg->svParsed != NULL); PUTBACK; FREETMPS; LEAVE; return (0); fail: PUTBACK; FREETMPS; LEAVE; return (-1); } /* Check a message and return its spam status. */ int SA_CheckMessage(MPD_Message *msg) { dSP; I32 n; SA_ASSERT(msg->svParsed != NULL); SA_ASSERT(msg->status.sv == NULL); /* Run check. */ ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(msg->svParsed); PUTBACK; n = call_method("check", G_SCALAR); SPAGAIN; SA_ASSERT_SP(n == 1); msg->status.sv = newSVsv(POPs); if (msg->status.sv == NULL) { goto fail_free; } PUTBACK; FREETMPS; LEAVE; /* Obtain score. */ ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(msg->status.sv); PUTBACK; n = call_method("get_score", G_SCALAR); SPAGAIN; if (n != 1) { goto fail_free; } msg->status.score = (float)POPn; PUTBACK; FREETMPS; LEAVE; /* Obtain user-configured spam threshold. */ ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(msg->status.sv); PUTBACK; n = call_method("get_required_score", G_SCALAR); SPAGAIN; if (n != 1) { goto fail_free; } msg->status.req_score = (float)POPn; PUTBACK; FREETMPS; LEAVE; msg->status.spam_status = (msg->status.score > msg->status.req_score); return (0); fail_free: if (msg->status.sv != NULL) { sv_free(msg->status.sv); msg->status.sv = NULL; } fail: PUTBACK; FREETMPS; LEAVE; return (-1); } /* Learn from a message. */ void SA_LearnMessage(MPD_Message *msg) { dSP; if (msg->svParsed == NULL) return; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(msg->svParsed); PUSHs(&PL_sv_undef); XPUSHs(sv_2mortal(newSViv(msg->status.spam_status))); PUSHs(&PL_sv_undef); PUTBACK; call_method("learn", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* Destroy any SA object associated with a message. */ void SA_FinishMessage(MPD_Message *msg) { if (msg->status.sv != NULL) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(msg->status.sv); PUTBACK; call_method("finish", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; SvREFCNT_dec(msg->status.sv); msg->status.sv = NULL; } if (msg->svParsed != NULL) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(msg->svParsed); PUTBACK; call_method("finish", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; SvREFCNT_dec(msg->svParsed); msg->svParsed = NULL; } if (msg->svRewrite != NULL) { SvREFCNT_dec(msg->svRewrite); msg->text = NULL; /* Was pointer to svRewrite */ } } /* Rewrite a message, replacing the original text. */ int SA_RewriteMessage(MPD_Message *msg) { dSP; I32 n; STRLEN nlen; char *msgNew, *s; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(msg->status.sv); PUTBACK; n = call_method("rewrite_mail", G_SCALAR); SPAGAIN; SA_ASSERT_SP(n == 1); msg->svRewrite = newSVsv(POPs); SA_ASSERT_SP(msg->svRewrite != NULL); s = SvPV(msg->svRewrite, nlen); if ((msgNew = malloc(nlen+1)) == NULL) { goto fail; } free(msg->text); msg->text = msgNew; memcpy(msg->text, s, nlen); msg->text[nlen] = '\0'; msg->text_len = (size_t)nlen-1; PUTBACK; FREETMPS; LEAVE; return (0); fail: PUTBACK; FREETMPS; LEAVE; return (-1); } /* Signal a change in users */ int SA_SignalUserChange(const char *user, const char *home, const char *saDir) { dSP; HV *hvArgs; SA_ASSERT(hvArgs = newHV()); hv_store(hvArgs, "username", 8, newSVpv(user,0), 0); hv_store(hvArgs, "user_dir", 8, newSVpv(home,0), 0); hv_store(hvArgs, "userstate_dir", 13, newSVpv(saDir,0), 0); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(sv_2mortal(newRV_noinc((SV *)hvArgs))); PUTBACK; call_method("signal_user_changed", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; return (0); } /* Add an address to the whitelist database. */ void SA_AddAddressToWhiteList(const char *addr) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(sv_2mortal(newSVpv(addr,0))); PUTBACK; call_method("add_address_to_whitelist", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* * Read user preferences from a file (scoring options, scores, whitelists, * blacklists, etc). If allow_user_rules is set, also read custom rules. */ void SA_ReadScoreOnlyConfig(const char *path) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(sv_2mortal(newSVpv(path,0))); PUTBACK; call_method("read_scoreonly_config", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* * Compile all patterns, load all configuration files and load all possibly * required Perl modules. */ void SA_CompileNow(int use_user_prefs, int keep_user_state) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(sv_2mortal(newSViv(use_user_prefs))); XPUSHs(sv_2mortal(newSViv(keep_user_state))); PUTBACK; call_method("compile_now", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* Destroy the SA object (must be called from a child process) */ void SA_Finish(void) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); PUTBACK; call_method("finish", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* Initialize learning. */ int SA_InitLearner(void) { dSP; HV *hvArgs; SA_ASSERT(hvArgs = newHV()); hv_store(hvArgs, "wait_for_lock", 13, newSViv(1), 0); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); XPUSHs(sv_2mortal(newRV_noinc((SV *)hvArgs))); PUTBACK; call_method("init_learner", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; return (0); } /* Rebuild cache databases. */ void SA_RebuildLearnerCaches(void) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); PUTBACK; call_method("rebuild_learner_caches", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* Finish learning */ void SA_FinishLearner(void) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_Factory()); PUTBACK; call_method("finish_learner", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } /* Destroy the persistent address list factory. */ void SA_FinishAddrListFactory(void) { dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(SA_AddrListFactory()); PUTBACK; call_method("finish", G_VOID); SPAGAIN; PUTBACK; FREETMPS; LEAVE; } #endif /* HAVE_SA */