Subversion Repositories

?revision_form?Rev ?revision_input??revision_submit??revision_endform?

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 magnus 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 */
45
int local_scan_version_major(void)
46
{
47
    return LOCAL_SCAN_ABI_VERSION_MAJOR;
48
}
49
 
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
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
 
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 */
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 */
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 */
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
 */
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
 
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
 */
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
 
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 */
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
}