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