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