/************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2002 */ /* See the file NOTICE for conditions of use and distribution. */ /* Spamassassin in local_scan by Marc MERLIN - 2002/04/15 2002/04/16 - v0.9 Pre-release Home page: http://marc.merlins.org/linux/exim/sa.html For all this to work correctly, you global spamassassin config should have: # disable web bugs and other potentially dangerous attachements defang_mime 1 # Put report in the headers report_header 1 use_terse_report 1 # Do not rewrite the subject line with "****SPAM****" by default rewrite_subject 0 */ #include #include #include #include #include #include #include "local_scan.h" static const int samaxbody=256*1048576; static const char spamc[]="/usr/bin/spamc"; static const char nospamstatus[]=""; static char *buffera[4096]; static char *buffer=(char *)buffera; static char *reportbuffera[4096]; static char *reportbuffer=(char *)reportbuffera; static char *where; static int line; static char *panicerror; static char *panicvalue; #define CHECKERR(mret, mwhere, mline) \ if (mret < 0) \ { \ where=mwhere; \ line=mline; \ goto errexit; \ } \ #define PANIC(merror, mvalue) \ panicerror=merror; \ panicvalue=mvalue; \ goto panicexit; int local_scan(int fd, uschar **return_text) { header_line *hl; int ret; int pid; int writefd[2]; int readfd[2]; FILE *readfh; int isspam=0; int gotsa=0; int chunk; int spamstatus=NULL; char *contenttype=NULL; char *xspamstatus=NULL; char *xspamflag=NULL; char *xspamcheckerversion=NULL; char *xspamprevcontenttype=NULL; char *xspamreport=NULL; /* What to do before sending the message to SA */ /* We won't scan local messages. I think exim bypasses local_scan for a * bounce generated after a locally submitted message, but better be safe */ if (!sender_host_address) { return LOCAL_SCAN_ACCEPT; } /* Fork off spamc, and get ready to talk to it */ ret=pipe(writefd); CHECKERR(ret,"pipe 1",__LINE__); ret=pipe(readfd); CHECKERR(ret,"pipe 2",__LINE__); if ((pid=fork()) < 0) { CHECKERR(pid, "fork", __LINE__ - 1); } if (pid == 0) { close(readfd[0]); close(writefd[1]); ret=dup2(writefd[0],0); CHECKERR(ret,"dup2 stdin",__LINE__); ret=dup2(readfd[1],1); CHECKERR(ret,"dup2 stdout",__LINE__); ret=dup2(readfd[1],2); CHECKERR(ret,"dup2 stderr",__LINE__); /* * If you are really smart, you'd implement the spamc protocol and * talk to spamd directly instead of forking spamc, but considering * the overhead spent in spamd, forking off spamc seemed acceptable * rather than re-implementing and tracking the spamc/spamd protocol */ ret=execl(spamc, "spamc", NULL); CHECKERR(ret,"exec",__LINE__); } ret=close(readfd[1]); CHECKERR(ret,"close r",__LINE__); ret=close(writefd[0]); CHECKERR(ret,"close w",__LINE__); readfh=fdopen(readfd[0], "r"); /* Ok, we're ready for spewing the mail at spamc */ /* First we need to get the header lines from exim, and then we can read the body from fd */ hl=header_list; while (hl != NULL) { if (hl->type == '*') { hl=hl->next; continue; } ret=write(writefd[1],hl->text,strlen(hl->text)); CHECKERR(ret,"header line write",__LINE__); /* We are replacing the Content-Type header by the one we get from SA */ if (strstr((char *)hl->text, "Content-Type: ") == (char *)hl->text) { hl->type = '*'; } hl=hl->next; } ret=write(writefd[1],"\n",1); CHECKERR(ret,"header separation write",__LINE__); /* Now read the body and send it to SA */ /* Let's not send more body data than SA is going to process though */ chunk=(samaxbody / sizeof(buffera)); while ((ret=read(fd, buffer, sizeof(buffera))) > 0 && chunk-- > 0) { ret=write(writefd[1], buffer, ret); CHECKERR(ret,"body write",__LINE__); } CHECKERR(ret, "read body", __LINE__ - 4); close(writefd[1]); /* Let's see what SA has to tell us about this mail and store the headers */ while ((fgets((char *)buffer,sizeof(buffera),readfh)) != NULL) { if (!strcmp(buffer, "\n")) { if (gotsa == 0) { goto exit; } } if (strstr(buffer, "Content-Type: ") == buffer) { contenttype=string_copy(buffer); header_add(' ', contenttype); continue; } if (strstr(buffer, "X-Spam") != buffer) { continue; } if (strstr(buffer, "X-Spam-Status: ") == buffer) { char *start; char *end; gotsa=1; xspamstatus=string_copy(buffer); header_add(' ', xspamstatus); start=strstr(xspamstatus, "hits="); end=strstr(xspamstatus, "tests="); if (start!=NULL && end!=NULL) { /* I'm hoping exim frees this, if I do it, I get a SEGV... */ spamstatus=string_copyn(start, end-start); } continue; } if (strstr(buffer, "X-Spam-Flag: ") == buffer) { xspamflag=string_copy(buffer); header_add(' ', xspamflag); if (buffer[13] == 'Y') { isspam=1; } continue; } if (strstr(buffer, "X-Spam-Checker-Version: ") == buffer) { xspamcheckerversion=string_copy(buffer); header_add(' ', xspamcheckerversion); continue; } if (strstr(buffer, "X-Spam-Prev-Content-Type: ") == buffer) { xspamprevcontenttype=string_copy(buffer); header_add(' ', xspamprevcontenttype); continue; } if (strstr(buffer, "X-Spam-Report: ") == buffer) { const int rbufsize=sizeof(reportbuffera); strncpy(reportbuffer, buffer, rbufsize); reportbuffer[rbufsize-1]=0; while ((fgets((char *)buffer,sizeof(buffera),readfh)) != NULL) { if (strcmp(buffer, "\n")) { if (strstr(buffer, " *") != buffer) { PANIC("Unexpected string while parsing X-Spam-Report", buffer); } strncat(reportbuffer,buffer,rbufsize-strlen(reportbuffer)); reportbuffer[rbufsize-1]=0; } else { xspamreport=string_copy(reportbuffer); header_add(' ', xspamreport); break; } } } } exit: fclose(readfh); wait(&ret); if (ret) { sprintf(buffer, "%d", ret); PANIC("wait on spamc child yielded", buffer); } if (gotsa == 0) { log_write(0, LOG_MAIN, "SA wasn't successfully run against message"); return LOCAL_SCAN_ACCEPT; } if (spamstatus == NULL) { spamstatus = nospamstatus; } if (isspam) { *return_text=string_sprintf("Heuristics guessed that this message was spam: %s", spamstatus); return LOCAL_SCAN_REJECT; } else { log_write(0, LOG_MAIN, "SA score: %s", spamstatus); return LOCAL_SCAN_ACCEPT; } errexit: *return_text=string_sprintf("Temporary local call error while processing message, please contact postmaster"); log_write(0, LOG_REJECT, "local_scan SA: Unexpected error on %s, file "__FILE__", line %d: %s", where, line-1, strerror(errno)); return LOCAL_SCAN_TEMPREJECT; panicexit: *return_text=string_sprintf("Temporary local processing error while processing message, please contact postmaster"); log_write(0, LOG_REJECT, "local_scan SA: %s %s", panicerror, panicvalue); return LOCAL_SCAN_TEMPREJECT; } /* End of local_scan.c */