1 /* Spamassassin in local_scan by Marc MERLIN <marc_soft@merlins.org> */
2 /* $Id: sa-exim.c,v 1.71 2005/03/08 20:39:51 marcmerlin Exp $ */
3 /*
4
5 The inline comments and minidocs were moved to the distribution tarball
6
7 You can get the up to date version of this file and full tarball here:
8 http://sa-exim.sourceforge.net/
9 http://marc.merlins.org/linux/exim/sa.html
10 The discussion list is here:
11 http://lists.merlins.org/lists/listinfo/sa-exim
12 */
13
14
15
16 #include <stdio.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <time.h>
23 #include <ctype.h>
24 #include <signal.h>
25 #include <setjmp.h>
26 #include <sys/wait.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include "sa-exim.h"
30
31 /* Exim includes */
32 #include <local_scan.h>
33
34 #ifdef DLOPEN_LOCAL_SCAN
35
36 /* Karsten Engelke <me@kaeng.org> says this is missing on openbsd */
37 #ifndef RTLD_NOW
38 #define RTLD_NOW 0x002
39 #endif
40
41 /* Return the verion of the local_scan ABI, if being compiled as a .so */
local_scan_version_major(void)42 int local_scan_version_major(void)
43 {
44 return LOCAL_SCAN_ABI_VERSION_MAJOR;
45 }
46
local_scan_version_minor(void)47 int local_scan_version_minor(void)
48 {
49 return LOCAL_SCAN_ABI_VERSION_MINOR;
50 }
51
52 /* Left over for compatilibility with old patched exims that didn't have
53 a version number with minor an major. Keep in mind that it will not work
54 with older exim4s (I think 4.11 is required) */
55 #ifdef DLOPEN_LOCAL_SCAN_OLD_API
local_scan_version(void)56 int local_scan_version(void)
57 {
58 return 1;
59 }
60 #endif
61 #endif
62
63 #ifndef SAFEMESGIDCHARS
64 #define SAFEMESGIDCHARS "!#%( )*+,-.0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~";
65 #endif
66
67
68 /******************************/
69 /* Compile time config values */
70 /******************************/
71 #ifndef SPAMC_LOCATION
72 #define SPAMC_LOCATION "/usr/bin/spamc"
73 #endif
74
75 #ifndef SPAMASSASSIN_CONF
76 #define SPAMASSASSIN_CONF "/etc/exim4/sa-exim.conf"
77 #endif
78 static const char conffile[]=SPAMASSASSIN_CONF;
79
80
81 /********************/
82 /* Code starts here */
83 /********************/
84 static const char nospamstatus[]="<error finding status>";
85
86 static char *buffera[4096];
87 static char *buffer=(char *)buffera;
88 static int SAEximDebug=0;
89 static int SAPrependArchiveWithFrom=1;
90 static jmp_buf jmp_env;
91
92 static char *where="Error handler called without error string";
93 static int line=-1;
94 static char *panicerror;
95
96 #define MIN(a,b) (a<b?a:b)
97
98 #define CHECKERR(mret, mwhere, mline) \
99 if (mret < 0) \
100 { \
101 where=mwhere; \
102 line=mline; \
103 goto errexit; \
104 }
105
106 #define PANIC(merror) \
107 panicerror=merror; \
108 goto panicexit;
109
110
alarm_handler(int sig)111 static void alarm_handler(int sig)
112 {
113 sig = sig; /* Keep picky compilers happy */
114 longjmp(jmp_env, 1);
115 }
116
117
118 /* Comparing header lines isn't fun, especially since the comparison has to
119 be caseless, so we offload this to this function
120 You can scan on partial headers, just give the root to scan for
121 Return 1 if the header was found, 0 otherwise */
compare_header(char * buffertocompare,char * referenceheader)122 static int compare_header(char *buffertocompare, char *referenceheader)
123 {
124 int idx;
125 int same=1;
126
127 for (idx=0; idx<strlen(referenceheader); idx++)
128 {
129 if ( tolower(referenceheader[idx]) != tolower(buffertocompare[idx]) )
130 {
131 same=0;
132 break;
133 }
134 }
135
136 if (SAEximDebug > 7)
137 {
138 if (same)
139 {
140 log_write(0, LOG_MAIN, "SA: Debug8: Found %s in %s", referenceheader, buffertocompare);
141 }
142 else if (SAEximDebug > 8)
143 {
144 log_write(0, LOG_MAIN, "SA: Debug9: Did not find %s in %s", referenceheader, buffertocompare);
145 }
146 }
147
148 return same;
149 }
150
151
152 /* returns a header from a buffer line */
get_header(char * buffer)153 static char *get_header(char *buffer)
154 {
155 char *start;
156 char *end;
157 char *header;
158
159 start=buffer;
160 end=strstr(buffer, ":");
161
162 header=string_copyn(start, end-start);
163
164 if (SAEximDebug>5)
165 {
166 log_write(0, LOG_MAIN, "SA: Debug6: Extracted header %s in buffer %s", header, buffer);
167 }
168
169 return header;
170 }
171
172
173 /* Rejected mails can be archived in a spool directory */
174 /* filename will contain a double / before the filename, I prefer two to none */
savemail(int readfd,off_t fdstart,char * dir,char * dirvarname,char * filename,int SAmaxarchivebody,char * condition)175 static int savemail(int readfd, off_t fdstart, char *dir, char *dirvarname,
176 char *filename, int SAmaxarchivebody, char *condition)
177 {
178 header_line *hl;
179 int writefd=0;
180 int ret;
181 ssize_t stret;
182 off_t otret;
183 char *expand;
184 char *fake_env_from;
185 int towrite;
186 int chunk;
187 struct stat bufst;
188
189 if (dir == NULL)
190 {
191 if (SAEximDebug>4)
192 {
193 log_write(0, LOG_MAIN, "SA: Debug5: Not saving message because %s in undefined", dirvarname);
194 }
195 return 0;
196 }
197
198 if (condition[0] != '1' || condition[1] != 0)
199 {
200 expand=expand_string(condition);
201 if (expand == NULL)
202 {
203 /* Can't use PANIC within this function :( */
204 CHECKERR(-1, string_sprintf("savemail condition expansion failure on %s", condition), __LINE__ - 1);
205 }
206
207 if (SAEximDebug > 2)
208 {
209 log_write(0, LOG_MAIN, "SA: Debug3: savemail condition expand returned: '%s'", expand);
210 }
211
212 if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
213 {
214 if (SAEximDebug > 1)
215 {
216 log_write(0, LOG_MAIN, "SA: Debug2: savemail condition expanded to false, not saving message to disk");
217 }
218 return 0;
219 }
220 }
221
222 if (SAEximDebug)
223 {
224 log_write(0, LOG_MAIN, "SA: Debug: Writing message to %s/new/%s", dir, filename);
225
226 }
227
228 if (stat(string_sprintf("%s/new/", dir), &bufst) == -1)
229 {
230 log_write(0, LOG_MAIN, "SA: Notice: creating maildir tree in %s", dir);
231 if (stat(dir, &bufst) == -1)
232 {
233 ret=mkdir (dir, 0770);
234 CHECKERR(ret,string_sprintf("mkdir %s", dir),__LINE__);
235 }
236 ret=mkdir (string_sprintf("%s/new", dir), 0770);
237 CHECKERR(ret,string_sprintf("mkdir %s/new/", dir),__LINE__);
238 ret=mkdir (string_sprintf("%s/cur", dir), 0770);
239 CHECKERR(ret,string_sprintf("mkdir %s/cur/", dir),__LINE__);
240 ret=mkdir (string_sprintf("%s/tmp", dir), 0770);
241 CHECKERR(ret,string_sprintf("mkdir %s/tmp/", dir),__LINE__);
242 }
243
244 /* Let's not worry about you receiving two spams at the same second
245 * with the same message ID. If you do, the second one will overwrite
246 * the first one */
247 writefd=creat(string_sprintf("%s/new/%s", dir, filename), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
248 CHECKERR(writefd, string_sprintf("creat %s/new/%s", dir, filename),__LINE__);
249
250 /* make the file look like a valid mbox -- idea from dman */
251 /* Although now that we use maildir format, this isn't really necessary */
252 /* Richard Lithvall made this an option */
253 if(SAPrependArchiveWithFrom == 1)
254 {
255 fake_env_from=string_sprintf("From %s Thu Jan 1 00:00:01 1970\n",sender_address);
256 stret=write(writefd, fake_env_from, strlen(fake_env_from));
257 CHECKERR(stret,string_sprintf("'From ' line write in %s", filename),__LINE__);
258 }
259
260 /* First we need to get the header lines from exim, and then we can read
261 the body from writefd */
262 hl=header_list;
263 while (hl != NULL)
264 {
265 /* type '*' means the header is internal, don't print it */
266 if (hl->type == '*')
267 {
268 hl=hl->next;
269 continue;
270 }
271 stret=write(writefd,hl->text,strlen(hl->text));
272 CHECKERR(stret,string_sprintf("header line write in %s", filename),__LINE__);
273 hl=hl->next;
274 }
275 stret=write(writefd,"\n",1);
276 CHECKERR(stret,string_sprintf("header separation write in %s", filename),__LINE__);
277
278 /* Now copy the body to the save file */
279 /* we already read from readfd, so we need to reset it */
280 otret=lseek(readfd, fdstart, SEEK_SET);
281 CHECKERR(otret, "lseek reset on spooled message", __LINE__);
282
283 if (SAEximDebug > 8)
284 {
285 log_write(0, LOG_MAIN, "SA: Debug9: Archive body write starts: writing up to %d bytes in %d byte blocks", SAmaxarchivebody, sizeof(buffera));
286 }
287
288 towrite=SAmaxarchivebody;
289 chunk=0;
290 while (towrite>0 && (stret=read(readfd, buffer, MIN(sizeof(buffera), towrite))) > 0)
291 {
292 chunk++;
293 if (SAEximDebug > 8)
294 {
295 log_write(0, LOG_MAIN, "SA: Debug9: Processing archive body chunk %d (read %.0f, and %.0f can still be written)", chunk, (double)stret, (double)towrite);
296 }
297 towrite-=stret;
298 stret=write(writefd, buffer, stret);
299 CHECKERR(stret,string_sprintf("body write in %s", filename),__LINE__);
300 }
301 CHECKERR(stret, "read body for archival", __LINE__ - 8);
302 ret=close(writefd);
303 CHECKERR(ret, "Closing spooled message",__LINE__);
304 return 0;
305
306 /* catch the global errexit, clean up, and return the error up */
307 errexit:
308 close(writefd);
309 return -1;
310 }
311
312 /*
313 * let's add the X-SA-Exim-Connect-IP, X-SA-Exim-Rcpt-To, and
314 * X-SA-Exim-Mail-From headers.
315 * Those are all required by the greylisting with SA implementation
316 * And From/Rcpt-To can also be used for personalized SA rules
317 */
AddSAEheaders(char * rcptlist,int SAmaxrcptlistlength)318 void AddSAEheaders(char *rcptlist, int SAmaxrcptlistlength)
319 {
320 if (sender_host_address)
321 {
322 header_add(' ', "X-SA-Exim-Connect-IP: %s\n", sender_host_address);
323 }
324 else
325 {
326 header_add(' ', "X-SA-Exim-Connect-IP: <locally generated>\n");
327 }
328
329 /* Create a mega envelope-to header with all the recipients */
330 /* Note, if you consider this a privacy violation, you can remove the header
331 * in exim's system filter.
332 * This is very useful to see who a message was really sent to, and can
333 * be used by Spamassassin to do additional scoring */
334 if (strlen(rcptlist) <= SAmaxrcptlistlength)
335 {
336 header_add(' ', "X-SA-Exim-Rcpt-To: %s\n", rcptlist);
337 }
338 /* Therefore SAmaxrcptlistlength set to 0 disables the header completely */
339 else if (SAmaxrcptlistlength)
340 {
341 header_add(' ', "X-SA-Exim-Rcpt-To: too long (recipient list exceeded maximum allowed size of %d bytes)\n", SAmaxrcptlistlength);
342 }
343
344 header_add(' ', "X-SA-Exim-Mail-From: %s\n", sender_address);
345 }
346
RemoveHeaders(char * headername)347 void RemoveHeaders(char *headername)
348 {
349 header_line *hl;
350
351 /* Remove headers that SA can set */
352 hl=header_list;
353 while (hl != NULL)
354 {
355
356 /* type '*' means the header is internal or deleted */
357 if (hl->type == '*')
358 {
359 hl=hl->next;
360 continue;
361 }
362
363 /* Strip all SA and SA-Exim headers on incoming mail */
364 if ( compare_header((char *)hl->text, headername) )
365 {
366 if (SAEximDebug > 2)
367 {
368 log_write(0, LOG_MAIN, "SA: Debug3: removing header %s on incoming mail '%s'", headername, (char *)hl->text);
369 }
370 hl->type = '*';
371 }
372 hl=hl->next;
373 }
374 }
375
376
377 /*
378 * Headers can be multi-line (in theory all of them can I think). Parsing them
379 * is a little more work than a simple line scan, so we're off-loading this to
380 * a function
381 */
parsemlheader(char * buffer,FILE * readfh,char * headername,char ** header)382 int parsemlheader(char *buffer, FILE *readfh, char *headername, char **header)
383 {
384 header_line *hl;
385 char *dummy;
386 char *foundheadername;
387
388 if (SAEximDebug > 4)
389 {
390 log_write(0, LOG_MAIN, "SA: Debug5: looking for header %s", headername);
391 }
392
393 if (header == NULL)
394 {
395 header=&dummy;
396 }
397
398 if (compare_header(buffer, string_sprintf("%s", headername)))
399 {
400 *header=string_copy(buffer);
401
402 /* Read the next line(s) in case this is a multi-line header */
403 while ((fgets((char *)buffer,sizeof(buffera),readfh)) != NULL)
404 {
405 /* Remove trailing newline */
406 if (buffer[strlen(buffer)-1] == '\n')
407 {
408 buffer[strlen(buffer)-1]=0;
409 /* and any carriage return */
410 if (buffer[strlen(buffer)-1] == '\r')
411 {
412 buffer[strlen(buffer)-1]=0;
413 }
414 }
415 if (SAEximDebug > 5)
416 {
417 log_write(0, LOG_MAIN, "SA: Debug6: while parsing header %s, read %s", headername, buffer);
418 }
419 /* concatenated lines only start with space or tab. right? */
420 if (buffer[0] != ' ' && buffer[0] != '\t')
421 {
422 break;
423 }
424
425 /* Guard against humongous header lines */
426 if (strlen(*header) < 8000)
427 {
428 /* Slight waste of memory here, oh well... */
429 *header=string_sprintf("%s\n%s", *header, buffer);
430 }
431 else
432 {
433 log_write(0, LOG_MAIN, "SA: Warning: while parsing header %s, ignoring the following trailing line due to header size overflow: %s", headername, buffer);
434
435 }
436 }
437 if (SAEximDebug > 5)
438 {
439 log_write(0, LOG_MAIN, "SA: Debug6: header pieced up %s as: '%s'", headername, *header);
440 }
441
442 /* Headers need a newline at the end before being handed out to exim */
443 /* Slight waste of memory here, oh well... */
444 *header=string_sprintf("%s\n", *header);
445
446 foundheadername=get_header(*header);
447
448 /* Mark the former header as deleted if it's already present */
449 /* Note that for X-Spam, it won't since we already deleted it earlier */
450 hl=header_list;
451 while (hl != NULL)
452 {
453 /* type '*' means the header is internal or deleted */
454 if (hl->type == '*')
455 {
456 hl=hl->next;
457 continue;
458 }
459
460 if ( compare_header((char *)hl->text, foundheadername) )
461 {
462 if (SAEximDebug > 5)
463 {
464 log_write(0, LOG_MAIN, "SA: Debug6: removing old copy of header '%s' and replacing with new one: '%s'", (char *)hl->text, *header);
465 }
466 hl->type = '*';
467 break;
468 }
469 hl=hl->next;
470 }
471
472 header_add(' ', "%s", *header);
473 return 1;
474 }
475 return 0;
476 }
477
478
cleanmsgid(char * msgid,char * SAsafemesgidchars)479 char *cleanmsgid(char *msgid, char *SAsafemesgidchars)
480 {
481 char *safemesgid;
482 char *ptr;
483
484 /* In case the message-Id is too long, let's truncate it */
485 safemesgid=string_copyn(msgid, 220);
486 ptr=safemesgid;
487
488 /* Clean Message-ID to make sure people can't write on our FS */
489 while (*ptr)
490 {
491 /* This might be more aggressive than you want, but since you
492 * potentially have shell programs dealing with the resulting filenames
493 * let's make it a bit safer */
494 if (strchr(SAsafemesgidchars, *ptr) == NULL)
495 {
496 *ptr='_';
497 }
498 ptr++;
499 }
500
501 if (SAEximDebug > 1)
502 {
503 log_write(0, LOG_MAIN, "SA: Debug2: Message-Id taken from Exim and cleaned from: %s to: %s", msgid, safemesgid);
504 }
505
506 return safemesgid;
507 }
508
509
510 /* Exim calls us here, feeds us a fd on the message body, and expects a return
511 message in *return_text */
local_scan(volatile int fd,uschar ** return_text)512 int local_scan(volatile int fd, uschar **return_text)
513 {
514 #warning you should not worry about the "might be clobbered by longjmp", see source
515 int ret;
516 ssize_t stret;
517 int pid;
518 int writefd[2];
519 int readfd[2];
520 char *spamc_argv[10];
521 int i;
522 /* These are the only values that we want working after the longjmp
523 * The automatic ones can be clobbered, but we don't really care */
524 volatile FILE *readfh;
525 volatile char *mesgfn=NULL;
526 volatile off_t fdsize;
527 volatile off_t scansize;
528 volatile off_t fdstart;
529 volatile char *rcptlist;
530 volatile void *old_sigchld;
531 char *safemesgid=NULL;
532 int isspam=0;
533 int gotsa=0;
534 int chunk;
535 off_t towrite;
536 char *mailinfo;
537 float spamvalue=0.0;
538 char *spamstatus=NULL;
539 time_t beforescan;
540 time_t afterscan;
541 time_t afterwait;
542 time_t scantime=0;
543 time_t fulltime=0;
544 struct stat stbuf;
545
546 uschar *expand;
547 header_line *hl;
548
549 static int readconffile=0;
550 static int wrotedebugenabled=0;
551
552 /* Options we read from /etc/exim4/sa-exim.conf */
553 static char *SAspamcpath=SPAMC_LOCATION;
554 static char *SAsafemesgidchars=SAFEMESGIDCHARS
555 static char *SAspamcSockPath=NULL;
556 static char *SAspamcPort=NULL;
557 static char *SAspamcHost=NULL;
558 static char *SAspamcUser=NULL;
559 static char *SAEximRunCond="0";
560 static char *SAEximRejCond="1";
561 static int SAmaxbody=250*1024;
562 static char *SATruncBodyCond="0";
563 static int SARewriteBody=0;
564 static int SAmaxarchivebody=20*1048576;
565 static int SAerrmaxarchivebody=1024*1048576;
566 static int SAmaxrcptlistlength=0;
567 static int SAaddSAEheaderBeforeSA=1;
568 static int SAtimeout=240;
569 static char *SAtimeoutsave=NULL;
570 static char *SAtimeoutSavCond="1";
571 static char *SAerrorsave=NULL;
572 static char *SAerrorSavCond="1";
573 static int SAtemprejectonerror=0;
574 static char *SAteergrube="1048576";
575 static float SAteergrubethreshold;
576 /* This is obsolete, since SAteergrube (now a condition) can do the same */
577 static char *SAteergrubecond="1";
578 static int SAteergrubetime=900;
579 static char *SAteergrubeSavCond="1";
580 static char *SAteergrubesave=NULL;
581 static int SAteergrubeoverwrite=1;
582 static char *SAdevnull="1048576";
583 static float SAdevnullthreshold;
584 static char *SAdevnullSavCond="1";
585 static char *SAdevnullsave=NULL;
586 static char *SApermreject="1048576";
587 static float SApermrejectthreshold;
588 static char *SApermrejectSavCond="1";
589 static char *SApermrejectsave=NULL;
590 static char *SAtempreject="1048576";
591 static float SAtemprejectthreshold;
592 static char *SAtemprejectSavCond="1";
593 static char *SAtemprejectsave=NULL;
594 static int SAtemprejectoverwrite=1;
595 static char *SAgreylistiswhitestr="GREYLIST_ISWHITE";
596 static float SAgreylistraisetempreject=3.0;
597 static char *SAspamacceptsave=NULL;
598 static char *SAspamacceptSavCond="0";
599 static char *SAnotspamsave=NULL;
600 static char *SAnotspamSavCond="0";
601 /* Those variables can take a %s to show the spam info */
602 static char *SAmsgteergrubewait="wait for more output";
603 static char *SAmsgteergruberej="Please try again later";
604 static char *SAmsgpermrej="Rejected";
605 static char *SAmsgtemprej="Please try again later";
606 /* Do not put a %s in there, or you'll segfault */
607 static char *SAmsgerror="Temporary local error while processing message, please contact postmaster";
608
609 /* This needs to be retrieved through expand_string in order
610 not to violate the API. */
611 uschar *primary_hostname=expand_string("$primary_hostname");
612
613 /* New values we read from spamassassin */
614 char *xspamstatus=NULL;
615 char *xspamflag=NULL;
616
617
618 /* Any error can write the faulty message to mesgfn, so we need to
619 give it a value right now. We'll set the real value later */
620 /* message_id here comes from Exim, it's an internal disk Mesg-Id format
621 which doesn't correlate to the actual message's Mesg-Id. We shouldn't
622 need to clean it, and besides, SAsafemesgidchars hasn't been read from
623 the config file yet, but eh, safety is always a good thing, right? */
624 safemesgid=cleanmsgid(message_id, SAsafemesgidchars);
625 mesgfn=string_sprintf("%d_%s", time(NULL), safemesgid);
626
627 /* We won't scan local messages. I think exim bypasses local_scan for a
628 * bounce generated after a locally submitted message, but better be safe */
629 /* This is commented out now, because you can control it with SAEximRunCond
630 if (!sender_host_address)
631 {
632 return LOCAL_SCAN_ACCEPT;
633 }
634 */
635
636 /* If you discard a mail with exim ACLs, we get 0 recipients, so let's just
637 * accept the mail, which won't matter either way since it'll get dropped
638 * (thanks to John Horne for reporting this corner case) */
639 if (recipients_count == 0)
640 {
641 return LOCAL_SCAN_ACCEPT;
642 }
643
644 /*
645 * We keep track of whether we've alrady read the config file, but since
646 * exim spawns itself, it will get read by exim children even though you
647 * didn't restart exim. That said, after you change the config file, you
648 * should restart exim to make sure all the instances pick up the new
649 * config file
650 */
651 if (!readconffile)
652 {
653 ret=open(conffile, 0);
654 CHECKERR(ret,string_sprintf("conf file open for %s", conffile),__LINE__);
655 readfh=fdopen(ret, "r");
656 CHECKERR(readfh,"fdopen",__LINE__);
657
658 while ((fgets((char *)buffer, sizeof(buffera), (FILE *)readfh)) != NULL)
659 {
660 if (*buffer == '#' || *buffer == '\n' )
661 {
662 continue;
663 }
664
665 if (*buffer != 'S' || *(buffer+1) != 'A')
666 {
667 log_write(0, LOG_MAIN, "SA: Warning: error while reading configuration file %s. Line does not begin with a SA directive: '%s', ignoring", conffile, buffer);
668 continue;
669 }
670
671 #define M_CHECKFORVAR(VAR, TYPE) \
672 if (strstr(buffer, #VAR ": ") == buffer) \
673 { \
674 if (sscanf(buffer, #VAR ": " TYPE, &VAR)) \
675 { \
676 if (SAEximDebug > 3) \
677 { \
678 if (SAEximDebug && ! wrotedebugenabled) \
679 { \
680 log_write(0, LOG_MAIN, "SA: Debug4: Debug enabled, reading config from file %s", conffile); \
681 wrotedebugenabled=1; \
682 } \
683 else \
684 { \
685 log_write(0, LOG_MAIN, "SA: Debug4: config read " #VAR " = " TYPE, VAR); \
686 }\
687 }\
688 } \
689 else \
690 { \
691 log_write(0, LOG_MAIN, "SA: Warning: error while reading configuration file %s. Can't parse value in: '%s', ignoring", conffile, buffer); \
692 } \
693 continue; \
694 }
695
696 #define M_CHECKFORSTR(VAR) \
697 if (strstr(buffer, #VAR ": ") == buffer) \
698 { \
699 VAR = strdup(buffer+strlen( #VAR )+2); \
700 if (VAR == NULL) \
701 { \
702 log_write(0, LOG_MAIN, "SA: PANIC: malloc failed, quitting..."); \
703 exit(-1); \
704 } \
705 \
706 if (VAR[strlen(VAR)-1] == '\n') \
707 { \
708 VAR[strlen(VAR)-1]=0; \
709 } \
710 if (SAEximDebug > 3) \
711 { \
712 log_write(0, LOG_MAIN, "SA: Debug4: config read " #VAR " = %s", VAR); \
713 } \
714 continue; \
715 }
716
717 M_CHECKFORVAR(SAEximDebug, "%d");
718 M_CHECKFORSTR(SAspamcpath);
719 M_CHECKFORSTR(SAsafemesgidchars);
720 M_CHECKFORSTR(SAspamcSockPath);
721 M_CHECKFORSTR(SAspamcPort);
722 M_CHECKFORSTR(SAspamcHost);
723 M_CHECKFORSTR(SAspamcUser);
724 M_CHECKFORSTR(SAEximRunCond);
725 M_CHECKFORSTR(SAEximRejCond);
726 M_CHECKFORVAR(SAmaxbody, "%d");
727 M_CHECKFORSTR(SATruncBodyCond);
728 M_CHECKFORVAR(SARewriteBody, "%d");
729 M_CHECKFORVAR(SAPrependArchiveWithFrom, "%d");
730 M_CHECKFORVAR(SAmaxarchivebody, "%d");
731 M_CHECKFORVAR(SAerrmaxarchivebody, "%d");
732 M_CHECKFORVAR(SAmaxrcptlistlength, "%d");
733 M_CHECKFORVAR(SAaddSAEheaderBeforeSA, "%d");
734 M_CHECKFORVAR(SAtimeout, "%d");
735 M_CHECKFORSTR(SAtimeoutsave);
736 M_CHECKFORSTR(SAtimeoutSavCond);
737 M_CHECKFORSTR(SAerrorsave);
738 M_CHECKFORSTR(SAerrorSavCond);
739 M_CHECKFORVAR(SAtemprejectonerror, "%d");
740 M_CHECKFORSTR(SAteergrube);
741 M_CHECKFORSTR(SAteergrubecond);
742 M_CHECKFORVAR(SAteergrubetime, "%d");
743 M_CHECKFORSTR(SAteergrubeSavCond);
744 M_CHECKFORSTR(SAteergrubesave);
745 M_CHECKFORVAR(SAteergrubeoverwrite, "%d");
746 M_CHECKFORSTR(SAdevnull);
747 M_CHECKFORSTR(SAdevnullSavCond);
748 M_CHECKFORSTR(SAdevnullsave);
749 M_CHECKFORSTR(SApermreject);
750 M_CHECKFORSTR(SApermrejectsave);
751 M_CHECKFORSTR(SApermrejectSavCond);
752 M_CHECKFORSTR(SAtempreject);
753 M_CHECKFORSTR(SAtemprejectSavCond);
754 M_CHECKFORSTR(SAtemprejectsave);
755 M_CHECKFORVAR(SAtemprejectoverwrite, "%d");
756 M_CHECKFORSTR(SAgreylistiswhitestr);
757 M_CHECKFORVAR(SAgreylistraisetempreject, "%f");
758 M_CHECKFORSTR(SAspamacceptsave);
759 M_CHECKFORSTR(SAspamacceptSavCond);
760 M_CHECKFORSTR(SAnotspamsave);
761 M_CHECKFORSTR(SAnotspamSavCond);
762 M_CHECKFORSTR(SAmsgteergrubewait);
763 M_CHECKFORSTR(SAmsgteergruberej);
764 M_CHECKFORSTR(SAmsgpermrej);
765 M_CHECKFORSTR(SAmsgtemprej);
766 M_CHECKFORSTR(SAmsgerror);
767
768
769 }
770
771 readconffile=1;
772 }
773
774 #define M_CONDTOFLOAT(VAR) \
775 if ((expand=expand_string( VAR )) == NULL) \
776 { \
777 PANIC(string_sprintf(#VAR " config expansion failure on %s", #VAR ));\
778 } \
779 sscanf(expand, "%f", &VAR ## threshold); \
780 if (SAEximDebug > 2) \
781 { \
782 log_write(0, LOG_MAIN, "SA: Debug3: expanded " #VAR " = %.2f", VAR ## threshold); \
783 }\
784
785 M_CONDTOFLOAT(SAteergrube);
786 M_CONDTOFLOAT(SAdevnull);
787 M_CONDTOFLOAT(SApermreject);
788 M_CONDTOFLOAT(SAtempreject);
789
790 /* Initialize the list of recipients here */
791 rcptlist=string_copy(recipients_list[0].address);
792 for (i=1; i < recipients_count && strlen((char *)rcptlist) < 7998 - strlen(recipients_list[i].address); i++)
793 {
794 rcptlist=string_sprintf("%s, %s", rcptlist, recipients_list[i].address);
795 }
796
797 if (sender_host_address != NULL)
798 {
799 mailinfo=string_sprintf("From <%s> (host=%s [%s]) for",
800 sender_address, sender_host_name, sender_host_address);
801 }
802 else
803 {
804 mailinfo=string_sprintf("From <%s> (local) for", sender_address);
805 }
806 mailinfo=string_sprintf("%s %s", mailinfo, rcptlist);
807
808
809 /* Remove SA-Exim headers that could have been set before we add ours*/
810 RemoveHeaders("X-SA-Exim-");
811
812 if(SAaddSAEheaderBeforeSA)
813 {
814 AddSAEheaders((char *)rcptlist, SAmaxrcptlistlength);
815 }
816
817 /* This is used later if we need to rewind and save the body elsewhere */
818 fdstart=lseek(fd, 0, SEEK_CUR);
819 CHECKERR(fdstart,"lseek SEEK_CUR",__LINE__);
820
821 ret=fstat(fd, &stbuf);
822 CHECKERR(ret,"fstat fd",__LINE__);
823 /* this is the body size plus a few bytes (exim msg ID) */
824 /* it should be 18 bytes, but I'll assume it could be more or less */
825 fdsize=stbuf.st_size;
826
827 if (SAEximDebug > 3)
828 {
829 log_write(0, LOG_MAIN, "SA: Debug4: Message body is about %.0f bytes and the initial offset is %.0f", (double)(fdsize-18), (double)fdstart);
830 }
831
832 if (fdsize > SAmaxbody)
833 {
834 if (SATruncBodyCond[0] != '1' || SATruncBodyCond[1] != 0)
835 {
836 expand=expand_string(SATruncBodyCond);
837 if (expand == NULL)
838 {
839 PANIC(string_sprintf("SATruncBodyCond expansion failure on %s", SATruncBodyCond));
840 }
841
842 if (SAEximDebug)
843 {
844 log_write(0, LOG_MAIN, "SA: Debug: SATruncBodyCond expand returned: '%s'", expand);
845 }
846
847 if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
848 {
849 log_write(0, LOG_MAIN, "SA: Action: check skipped due to message size (%.0f bytes) and SATruncBodyCond expanded to false (Message-Id: %s). %s", (double)(fdsize-18), safemesgid, mailinfo);
850 header_add(' ', "X-SA-Exim-Scanned: No (on %s); Message bigger than SAmaxbody (%d)\n", primary_hostname, SAmaxbody);
851 return LOCAL_SCAN_ACCEPT;
852 }
853 }
854
855 if (SAEximDebug > 1)
856 {
857 log_write(0, LOG_MAIN, "SA: Debug2: Message body is about %.0f bytes and SATruncBodyCond expanded to true, will feed a truncated body to SA", (double)(fdsize-18));
858 }
859
860 /* Let's feed exactly spamc will accept */
861 scansize=SAmaxbody;
862 header_add(' ', "X-SA-Exim-Scan-Truncated: Fed %.0f bytes of the body to SA instead of %.0f\n", (double)scansize, (double)fdsize);
863 }
864 else
865 {
866 scansize=fdsize;
867 }
868
869 expand=expand_string(SAEximRunCond);
870 if (expand == NULL)
871 {
872 PANIC(string_sprintf("SAEximRunCond expansion failure on %s", SAEximRunCond));
873 }
874
875 if (SAEximDebug)
876 {
877 log_write(0, LOG_MAIN, "SA: Debug: SAEximRunCond expand returned: '%s'", expand);
878 }
879
880
881 /* Bail from SA if the expansion string says so */
882 if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
883 {
884 log_write(0, LOG_MAIN, "SA: Action: Not running SA because SAEximRunCond expanded to false (Message-Id: %s). %s", safemesgid, mailinfo);
885 header_add(' ', "X-SA-Exim-Scanned: No (on %s); SAEximRunCond expanded to false\n", primary_hostname);
886 return LOCAL_SCAN_ACCEPT;
887 }
888
889 if (SAEximDebug)
890 {
891 log_write(0, LOG_MAIN, "SA: Debug: check succeeded, running spamc");
892 }
893
894 /* Ok, so now that we know we're running SA, we remove the X-Spam headers */
895 /* that might have been there */
896 RemoveHeaders("X-Spam-");
897
898
899 beforescan=time(NULL);
900 /* Fork off spamc, and get ready to talk to it */
901 ret=pipe(writefd);
902 CHECKERR(ret,"write pipe",__LINE__);
903 ret=pipe(readfd);
904 CHECKERR(ret,"read pipe",__LINE__);
905
906 /* Ensure that SIGCHLD isn't being ignored. */
907 old_sigchld = signal(SIGCHLD, SIG_DFL);
908
909 if ((pid=fork()) < 0)
910 {
911 CHECKERR(pid, "fork", __LINE__ - 1);
912 }
913
914 if (pid == 0)
915 {
916 close(readfd[0]);
917 close(writefd[1]);
918
919 ret=dup2(writefd[0],0);
920 CHECKERR(ret,"dup2 stdin",__LINE__);
921 ret=dup2(readfd[1],1);
922 CHECKERR(ret,"dup2 stdout",__LINE__);
923 ret=dup2(readfd[1],2);
924 CHECKERR(ret,"dup2 stderr",__LINE__);
925
926 i = 0;
927 spamc_argv[i++] = "spamc";
928 if (SAspamcUser && SAspamcUser[0])
929 {
930 expand=expand_string(SAspamcUser);
931 if (expand == NULL)
932 {
933 log_write(0, LOG_MAIN | LOG_PANIC, "SA: SAspamcUser expansion failure on %s, will run as Exim user instead.", SAspamcUser);
934 }
935 else if (expand[0] != '\0')
936 {
937 spamc_argv[i++] = "-u";
938 spamc_argv[i++] = expand;
939 }
940 }
941
942 /*
943 * I could implement the spamc protocol and talk to spamd directly
944 * instead of forking spamc, but considering the overhead spent
945 * in spamd, forking off spamc seemed acceptable rather than
946 * re-implementing and tracking the spamc/spamd protocol or linking
947 * with a possibly changing library
948 */
949 /* Ok, we cheat, spamc cares about how big the whole message is and
950 * we only know about the body size, so I'll give an extra 16K
951 * to account for any headers that can accompany the message */
952
953 spamc_argv[i++] = "-s";
954 spamc_argv[i++] = string_sprintf("%d", SAmaxbody+16384);
955
956 if(SAspamcSockPath)
957 {
958 spamc_argv[i++] = "-U";
959 spamc_argv[i++] = SAspamcSockPath;
960 }
961 else
962 {
963 if (SAspamcHost) {
964 spamc_argv[i++] = "-d";
965 spamc_argv[i++] = SAspamcHost;
966 }
967 if (SAspamcPort) {
968 spamc_argv[i++] = "-p";
969 spamc_argv[i++] = SAspamcPort;
970 }
971 }
972 spamc_argv[i++] = NULL;
973
974 ret=execv(SAspamcpath, spamc_argv);
975 CHECKERR(ret,string_sprintf("exec %s", SAspamcpath),__LINE__);
976 }
977
978 if (SAEximDebug > 8)
979 {
980 log_write(0, LOG_MAIN, "SA: Debug9: forked spamc");
981 }
982
983 ret=close(readfd[1]);
984 CHECKERR(ret,"close r",__LINE__);
985 ret=close(writefd[0]);
986 CHECKERR(ret,"close w",__LINE__);
987 readfh=fdopen(readfd[0], "r");
988
989 if (SAEximDebug > 8)
990 {
991 log_write(0, LOG_MAIN, "SA: Debug9: closed filehandles");
992 }
993
994 /* Ok, we're ready for spewing the mail at spamc */
995 /* First we need to get the header lines from exim, and then we can read
996 the body from fd */
997 hl=header_list;
998 while (hl != NULL)
999 {
1000 /* type '*' means the header is internal, don't print it */
1001 if (hl->type == '*')
1002 {
1003 hl=hl->next;
1004 continue;
1005 }
1006
1007 stret=write(writefd[1],hl->text,strlen(hl->text));
1008 CHECKERR(stret,"header line write",__LINE__);
1009
1010 hl=hl->next;
1011 }
1012 stret=write(writefd[1],"\n",1);
1013 CHECKERR(stret,"header separation write",__LINE__);
1014
1015 if (SAEximDebug > 6)
1016 {
1017 log_write(0, LOG_MAIN, "SA: Debug7: sent headers to spamc pipe. Sending body...");
1018 }
1019
1020 towrite=scansize;
1021 chunk=0;
1022 while (towrite>0 && (stret=read(fd, buffer, MIN(sizeof(buffera), towrite))) > 0)
1023 {
1024 chunk++;
1025 if (SAEximDebug > 8)
1026 {
1027 log_write(0, LOG_MAIN, "SA: Debug9: spamc body going to write chunk %d (read %.0f, %.0f left to write)", chunk, (double)stret, (double)towrite);
1028 }
1029 towrite-=stret;
1030 stret=write(writefd[1], buffer, stret);
1031 CHECKERR(stret,"body write in",__LINE__);
1032 if (SAEximDebug > 8)
1033 {
1034 log_write(0, LOG_MAIN, "SA: Debug9: Spamc body wrote chunk %d (wrote %.0f, %.0f left to write)", chunk, (double)stret, (double)towrite);
1035 }
1036 }
1037 CHECKERR(stret, "read body", __LINE__ - 14);
1038 close(writefd[1]);
1039
1040 if (SAEximDebug > 5)
1041 {
1042 log_write(0, LOG_MAIN, "SA: Debug6: fed spam to spamc, reading result");
1043 }
1044
1045 if (SAtimeout)
1046 {
1047 if (SAEximDebug > 2)
1048 {
1049 log_write(0, LOG_MAIN, "SA: Debug3: Setting timeout of %d secs before reading from spamc", SAtimeout);
1050 }
1051 /* SA can take very long to run for various reasons, let's not wait
1052 * forever, that's just bad at SMTP time */
1053 if (setjmp(jmp_env) == 0)
1054 {
1055 signal(SIGALRM, alarm_handler);
1056 alarm (SAtimeout);
1057 }
1058 else
1059 {
1060 /* Make sure that all your variables here are volatile or static */
1061 signal(SIGCHLD, old_sigchld);
1062 fclose((FILE *)readfh);
1063
1064 header_add(' ', "X-SA-Exim-Scanned: No (on %s); SA Timed out after %d secs\n", primary_hostname, SAtimeout);
1065
1066 /* We sent it to LOG_REJECT too so that we get a header dump */
1067 log_write(0, LOG_MAIN | LOG_REJECT, "SA: Action: spamd took more than %d secs to run, accepting message (scanned in %d/%d secs | Message-Id: %s). %s", SAtimeout, scantime, fulltime, safemesgid, mailinfo);
1068
1069 ret=savemail(fd, fdstart, SAtimeoutsave, "SAtimeoutsave", (char *)mesgfn, SAerrmaxarchivebody, SAtimeoutSavCond);
1070 CHECKERR(ret,where,line);
1071
1072 /* Make sure we kill spamc in case SIGPIPE from fclose didn't */
1073 kill(pid, SIGTERM);
1074 return LOCAL_SCAN_ACCEPT;
1075
1076 }
1077 }
1078
1079 /* Let's see what SA has to tell us about this mail and store the headers */
1080 while ((fgets((char *)buffer,sizeof(buffera),(FILE *) readfh)) != NULL)
1081 {
1082 /* Remove trailing newline */
1083 if (buffer[strlen(buffer)-1] == '\n')
1084 {
1085 buffer[strlen(buffer)-1]=0;
1086 /* and any carriage return */
1087 if (buffer[strlen(buffer)-1] == '\r')
1088 {
1089 buffer[strlen(buffer)-1]=0;
1090 }
1091 }
1092 restart:
1093 if (SAEximDebug > 5)
1094 {
1095 log_write(0, LOG_MAIN, "SA: Debug6: spamc read: %s", buffer);
1096 }
1097
1098 /* Let's handle special multi-line headers first, what a pain... */
1099 /* We feed the one line we read and the filehandle because we'll need
1100 to check whether more lines need to be concatenated */
1101 /* This is ugly, there is an order dependency so we return to the
1102 beginning of the loop without reading a new line since we already
1103 did that */
1104 if (parsemlheader(buffer, (FILE *)readfh, "Subject", NULL)) goto restart;
1105 if ((SARewriteBody == 1) && parsemlheader(buffer, (FILE *)readfh, "Content-Type", NULL)) goto restart;
1106 if ((SARewriteBody == 1) && parsemlheader(buffer, (FILE *)readfh, "Content-Transfer-Encoding", NULL)) goto restart;
1107
1108 if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-Flag", &xspamflag))
1109 {
1110 if (xspamflag[13] == 'Y')
1111 {
1112 isspam=1;
1113 }
1114 if (SAEximDebug > 2)
1115 {
1116 log_write(0, LOG_MAIN, "SA: Debug3: isspam read from X-Spam-Flag: %d", isspam);
1117 }
1118 goto restart;
1119 }
1120
1121 if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-Status", &xspamstatus))
1122 {
1123 char *start;
1124 char *end;
1125
1126 gotsa=1;
1127
1128 /* if we find the preconfigured greylist string (and it is defined
1129 * in sa-exim.conf), we can raise the threshold for tempreject just
1130 * for this mail, since it's been whitelisted */
1131 if (SAgreylistiswhitestr && strstr(xspamstatus, SAgreylistiswhitestr))
1132 {
1133 SAtemprejectthreshold+=SAgreylistraisetempreject;
1134 if (SAEximDebug > 2)
1135 {
1136 log_write(0, LOG_MAIN, "SA: Debug3: read %s string, SAtempreject is now changed to %f", SAgreylistiswhitestr, SAtemprejectthreshold);
1137 }
1138 }
1139 else
1140 {
1141 if (SAEximDebug > 2)
1142 {
1143 log_write(0, LOG_MAIN, "SA: Debug3: did not find read GREYLIST_ISWHITE string in X-Spam-Status");
1144 }
1145 }
1146
1147 start=strstr(xspamstatus, "hits=");
1148 /* Support SA 3.0 format */
1149 if (start == NULL)
1150 {
1151 start=strstr(xspamstatus, "score=");
1152 }
1153
1154 end=strstr(xspamstatus, " tests=");
1155 if (end == NULL)
1156 {
1157 if (SAEximDebug > 5)
1158 {
1159 log_write(0, LOG_MAIN, "SA: Debug6: Could not find old spamstatus format, trying new one...");
1160 }
1161 end=strstr(xspamstatus, "\n tests=");
1162 }
1163 if (start!=NULL && end!=NULL)
1164 {
1165 spamstatus=string_copyn(start, end-start);
1166 if (SAEximDebug > 2)
1167 {
1168 log_write(0, LOG_MAIN, "SA: Debug3: Read from X-Spam-Status: %s", spamstatus);
1169 }
1170 }
1171 else
1172 {
1173 PANIC(string_sprintf("SA: could not parse X-Spam-Status: to extract hits and required. Bad!. Got: '%s'", xspamstatus));
1174 }
1175
1176 start=strstr(spamstatus, "=");
1177 end=strstr(spamstatus, " ");
1178 if (start!=NULL && end!=NULL)
1179 {
1180 start++;
1181 sscanf(start, "%f", &spamvalue);
1182 }
1183 else
1184 {
1185 PANIC(string_sprintf("SA: spam value extract failed in '%s'. Bad!", xspamstatus));
1186 }
1187
1188 goto restart;
1189 }
1190
1191 if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-", NULL)) goto restart;
1192
1193 /* Ok, now we can do normal processing */
1194
1195 /* If no more headers here, we're done */
1196 if (buffer[0] == 0)
1197 {
1198 if (SAEximDebug > 5)
1199 {
1200 log_write(0, LOG_MAIN, "SA: Debug6: spamc read got newline, end of headers", buffer);
1201 }
1202 goto exit;
1203 }
1204
1205 if (compare_header(buffer, "Message-Id: "))
1206 {
1207 char *start;
1208 char *end;
1209 char *mesgid=NULL;
1210
1211 start=strchr(buffer, '<');
1212 end=strchr(buffer, '>');
1213
1214 if (start == NULL || end == NULL)
1215 {
1216 /* we keep the default mesgfn (unix date in seconds) */
1217 if (SAEximDebug)
1218 {
1219 log_write(0, LOG_MAIN, "SA: Debug: Could not get Message-Id from %s", buffer);
1220 }
1221 }
1222 else if ((mesgid=string_copyn(start+1,end-start-1)) && mesgid[0])
1223 {
1224 /* We replace the exim Message-ID with the one read from
1225 the message * as we use this to detect dupes when we
1226 send 45x and get the same * message multiple times */
1227 safemesgid=cleanmsgid(mesgid, SAsafemesgidchars);
1228 mesgfn=string_sprintf("%d_%s", time(NULL), safemesgid);
1229
1230 if (SAEximDebug > 5)
1231 {
1232 log_write(0, LOG_MAIN, "SA: Debug6: Message-Id received and cleaned as: %s", safemesgid);
1233 }
1234 }
1235 continue;
1236 }
1237 }
1238
1239 exit:
1240
1241
1242 if (isspam && SARewriteBody == 1)
1243 {
1244 int line;
1245
1246 if (SAEximDebug)
1247 {
1248 log_write(0, LOG_MAIN, "SA: Debug: SARewriteBody == 1, rewriting message body");
1249 }
1250
1251 /* already read from fd? Better reset it... */
1252 ret=lseek(fd, fdstart, SEEK_SET);
1253 CHECKERR(ret, "lseek reset on spooled message", __LINE__);
1254
1255 line=1;
1256 while ((fgets((char *)buffer,sizeof(buffera),(FILE *) readfh)) != NULL)
1257 {
1258 if (SAEximDebug > 8)
1259 {
1260 log_write(0, LOG_MAIN, "SA: Debug9: Read body from SA; line %d (read %d)", line, strlen(buffer));
1261 }
1262
1263 stret=write(fd, buffer, strlen(buffer));
1264 CHECKERR(stret,string_sprintf("%s", "SA body write to msg"),__LINE__);
1265 if (SAEximDebug > 8)
1266 {
1267 log_write(0, LOG_MAIN, "SA: Debug9: Wrote to msg; line %d (wrote %d)", line, ret);
1268 }
1269 if (buffer[strlen(buffer)-1] == '\n')
1270 {
1271 line++;
1272 }
1273 }
1274
1275
1276 if (SAEximDebug > 1)
1277 {
1278 log_write(0, LOG_MAIN, "SA: Debug2: body_linecount before SA: %d", body_linecount);
1279 }
1280
1281 /* update global variable $body_linecount to reflect the new body size*/
1282 if (body_linecount > 0) body_linecount = (line - 1); // Not updating if zero, indicating spool_wireformat
1283
1284 if (SAEximDebug > 1)
1285 {
1286 log_write(0, LOG_MAIN, "SA: Debug2: body_linecount after SA: %d", body_linecount);
1287 }
1288
1289 }
1290
1291 fclose((FILE *)readfh);
1292
1293 afterscan=time(NULL);
1294 scantime=afterscan-beforescan;
1295
1296 wait(&ret);
1297 signal(SIGCHLD, old_sigchld);
1298
1299 if (ret)
1300 {
1301 sprintf(buffer, "%d", ret);
1302 PANIC(string_sprintf("wait on spamc child yielded, %s", buffer));
1303 }
1304
1305 afterwait=time(NULL);
1306 fulltime=afterwait-beforescan;
1307
1308 if(!SAaddSAEheaderBeforeSA)
1309 {
1310 AddSAEheaders((char *)rcptlist, SAmaxrcptlistlength);
1311 }
1312
1313 header_add(' ', "X-SA-Exim-Version: %s\n",version);
1314
1315 if (gotsa == 0)
1316 {
1317 header_add(' ', "X-SA-Exim-Scanned: No (on %s); Unknown failure\n", primary_hostname);
1318 log_write(0, LOG_MAIN, "SA: Action: SA didn't successfully run against message, accepting (time: %d/%d secs | Message-Id: %s). %s", scantime, fulltime, safemesgid, mailinfo);
1319 return LOCAL_SCAN_ACCEPT;
1320 }
1321
1322 header_add(' ', "X-SA-Exim-Scanned: Yes (on %s)\n", primary_hostname);
1323
1324 if (spamstatus == NULL)
1325 {
1326 spamstatus = (char *) nospamstatus;
1327 }
1328 if (isspam)
1329 {
1330 int dorej=1;
1331 int doteergrube=0;
1332
1333 if (SAEximRejCond[0] != '1' || SAEximRejCond[1] != 0)
1334 {
1335 expand=expand_string(SAEximRejCond);
1336 if (expand == NULL)
1337 {
1338 PANIC(string_sprintf("SAEximRejCond expansion failure on %s", SAEximRejCond));
1339 }
1340
1341 if (SAEximDebug)
1342 {
1343 log_write(0, LOG_MAIN, "SA: Debug: SAEximRejCond expand returned: '%s'", expand);
1344 }
1345
1346 if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
1347 {
1348 log_write(0, LOG_MAIN, "SA: Notice: SAEximRejCond expanded to false, not applying reject rules");
1349 dorej=0;
1350 }
1351 }
1352
1353 if (dorej && spamvalue >= SAteergrubethreshold)
1354 {
1355 doteergrube=1;
1356 if (SAteergrubecond[0] != '1' || SAteergrubecond[1] != 0)
1357 {
1358 expand=expand_string(SAteergrubecond);
1359 if (expand == NULL)
1360 {
1361 PANIC(string_sprintf("SAteergrubecond expansion failure on %s", SAteergrubecond));
1362 }
1363
1364 if (SAEximDebug)
1365 {
1366 log_write(0, LOG_MAIN, "SA: Debug: SAteergrubecond expand returned: '%s'", expand);
1367 }
1368
1369 if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
1370 {
1371 log_write(0, LOG_MAIN, "SA: Notice: SAteergrubecond expanded to false, not teergrubing known peer");
1372 doteergrube=0;
1373 }
1374 }
1375 }
1376
1377 if (dorej && doteergrube)
1378 {
1379 char *teergrubewaitstr;
1380 teergrubewaitstr=string_sprintf(SAmsgteergrubewait, spamstatus);
1381
1382 /* By default, we'll only save temp bounces by message ID so
1383 * that when the same message is submitted several times, we
1384 * overwrite the same file on disk and not create a brand new
1385 * one every single time */
1386 if (SAteergrubeoverwrite)
1387 {
1388 ret=savemail(fd, fdstart, SAteergrubesave, "SAteergrubesave", safemesgid, SAmaxarchivebody, SAteergrubeSavCond);
1389 CHECKERR(ret,where,line);
1390 }
1391 else
1392 {
1393 ret=savemail(fd, fdstart, SAteergrubesave, "SAteergrubesave", (char *)mesgfn, SAmaxarchivebody, SAteergrubeSavCond);
1394 CHECKERR(ret,where,line);
1395 }
1396
1397 spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAteergrubethreshold);
1398 /* Exim might want to stop us if we run for too long, but that's
1399 * exactly what we're trying to do, so let's override that */
1400 alarm(0);
1401
1402 for (i=0;i<SAteergrubetime/10;i++)
1403 {
1404 smtp_printf("451-%s\r\n", teergrubewaitstr);
1405 ret=smtp_fflush();
1406 if (ret != 0)
1407 {
1408 log_write(0, LOG_MAIN | LOG_REJECT, "SA: Action: teergrubed sender for %d secs until it closed the connection: %s (scanned in %d/%d secs | Message-Id: %s). %s", i*10, spamstatus, scantime, fulltime, safemesgid, mailinfo);
1409 /* The other side closed the connection, nothing to print */
1410 *return_text="";
1411 return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1412 }
1413 sleep(10);
1414 }
1415
1416 log_write(0, LOG_MAIN | LOG_REJECT, "SA: Action: teergrubed sender until full configured duration of %d secs: %s (scanned in %d/%d secs | Message-Id: %s). %s", SAteergrubetime, spamstatus, scantime, fulltime, safemesgid, mailinfo);
1417 *return_text=string_sprintf(SAmsgteergruberej, spamstatus);
1418 return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1419 }
1420 else if (dorej && spamvalue >= SAdevnullthreshold)
1421 {
1422 ret=savemail(fd, fdstart, SAdevnullsave, "SAdevnullsave", (char *)mesgfn, SAmaxarchivebody, SAdevnullSavCond);
1423 CHECKERR(ret,where,line);
1424
1425 recipients_count=0;
1426 spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAdevnullthreshold);
1427 log_write(0, LOG_REJECT | LOG_MAIN, "SA: Action: silently tossed message: %s (scanned in %d/%d secs | Message-Id: %s). %s", spamstatus, scantime, fulltime, safemesgid, mailinfo);
1428 return LOCAL_SCAN_ACCEPT;
1429 }
1430 else if (dorej && spamvalue >= SApermrejectthreshold)
1431 {
1432 ret=savemail(fd, fdstart, SApermrejectsave, "SApermrejectsave", (char *)mesgfn, SAmaxarchivebody, SApermrejectSavCond);
1433 CHECKERR(ret,where,line);
1434
1435 spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SApermrejectthreshold);
1436 log_write(0, LOG_MAIN | LOG_REJECT, "SA: Action: permanently rejected message: %s (scanned in %d/%d secs | Message-Id: %s). %s", spamstatus, scantime, fulltime, safemesgid, mailinfo);
1437 *return_text=string_sprintf(SAmsgpermrej, spamstatus);
1438 return LOCAL_SCAN_REJECT_NOLOGHDR;
1439 }
1440 else if (dorej && spamvalue >= SAtemprejectthreshold)
1441 {
1442 /* Yeah, gotos are harmful, but that'd be a function with a lot
1443 * of options to send, so, here's a small shortcut */
1444 goto dotempreject;
1445 }
1446 else
1447 {
1448 ret=savemail(fd, fdstart, SAspamacceptsave, "SAspamacceptsave", (char *)mesgfn, SAmaxarchivebody, SAspamacceptSavCond);
1449 CHECKERR(ret,where,line);
1450 log_write(0, LOG_MAIN, "SA: Action: flagged as Spam but accepted: %s (scanned in %d/%d secs | Message-Id: %s). %s", spamstatus, scantime, fulltime, safemesgid, mailinfo);
1451 return LOCAL_SCAN_ACCEPT;
1452 }
1453 }
1454 else
1455 {
1456 /* This is an exception to the rule, for grey listing, we allow for
1457 * sending back a tempreject on SA scores that aren't considered as
1458 * spam (greylisting is now done directly in spamassassin though */
1459 if (spamvalue >= SAtemprejectthreshold)
1460 {
1461 dotempreject:
1462
1463 /* By default, we'll only save temp bounces by message ID so
1464 * that when the same message is submitted several times, we
1465 * overwrite the same file on disk and not create a brand new
1466 * one every single time */
1467 if (SAtemprejectoverwrite)
1468 {
1469 ret=savemail(fd, fdstart, SAtemprejectsave, "SAtemprejectsave", safemesgid, SAmaxarchivebody, SAtemprejectSavCond);
1470 CHECKERR(ret,where,line);
1471 }
1472 else
1473 {
1474 ret=savemail(fd, fdstart, SAtemprejectsave, "SAtemprejectsave", (char *)mesgfn, SAmaxarchivebody, SAtemprejectSavCond);
1475 CHECKERR(ret,where,line);
1476 }
1477
1478 spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAtemprejectthreshold);
1479 log_write(0, LOG_MAIN | LOG_REJECT, "SA: Action: temporarily rejected message: %s (scanned in %d/%d secs | Message-Id: %s). %s", spamstatus, scantime, fulltime, safemesgid, mailinfo);
1480 *return_text=string_sprintf(SAmsgtemprej, spamstatus);
1481 return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1482 }
1483 else
1484 {
1485 ret=savemail(fd, fdstart, SAnotspamsave, "SAnotspamsave", (char *)mesgfn, SAmaxarchivebody, SAnotspamSavCond);
1486 CHECKERR(ret,where,line);
1487 log_write(0, LOG_MAIN, "SA: Action: scanned but message isn't spam: %s (scanned in %d/%d secs | Message-Id: %s). %s", spamstatus, scantime, fulltime, safemesgid, mailinfo);
1488 return LOCAL_SCAN_ACCEPT;
1489 }
1490 }
1491
1492
1493
1494 errexit:
1495 if (SAtemprejectonerror)
1496 {
1497 log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: Unexpected error on %s, file "__FILE__", line %d: %s", where, line-1, strerror(errno));
1498 }
1499 else
1500 {
1501 log_write(0, LOG_MAIN, "SA: PANIC: Unexpected error on %s (but message was accepted), file "__FILE__", line %d: %s", where, line-1, strerror(errno));
1502 }
1503
1504 header_add(' ', "X-SA-Exim-Scanned: No (on %s); Exit with error (see exim mainlog)\n", primary_hostname);
1505
1506 ret=savemail(fd, fdstart, SAerrorsave, "SAerrorsave", (char *)mesgfn, SAerrmaxarchivebody, SAerrorSavCond);
1507 if (ret < 0)
1508 {
1509 log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: Error in error handler while trying to save mail to %s, file "__FILE__", line %d: %s", string_sprintf("%s/%s", SAerrorsave, mesgfn), __LINE__ - 3, strerror(errno));
1510 }
1511
1512 if (SAtemprejectonerror)
1513 {
1514 *return_text=SAmsgerror;
1515 return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1516 }
1517 else
1518 {
1519 return LOCAL_SCAN_ACCEPT;
1520 }
1521
1522
1523 panicexit:
1524 if (SAtemprejectonerror)
1525 {
1526 log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: %s", panicerror);
1527 }
1528 else
1529 {
1530 log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: %s (but message was accepted)", panicerror);
1531 }
1532
1533 header_add(' ', "X-SA-Exim-Scanned: No (on %s); Panic (see exim mainlog)\n", primary_hostname);
1534
1535 ret=savemail(fd, fdstart, SAerrorsave, "SAerrorsave", (char *)mesgfn, SAerrmaxarchivebody, SAerrorSavCond);
1536 if (ret < 0)
1537 {
1538 log_write(0, LOG_MAIN | LOG_PANIC , "SA: PANIC: Error in error handler while trying to save mail to %s, file "__FILE__", line %d: %s", string_sprintf("%s/%s", SAerrorsave, mesgfn), __LINE__ - 3, strerror(errno));
1539 }
1540
1541 if (SAtemprejectonerror)
1542 {
1543 *return_text=SAmsgerror;
1544 return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1545 }
1546 else
1547 {
1548 return LOCAL_SCAN_ACCEPT;
1549 }
1550 }
1551