Subversion Repositories

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

Rev 3 | Rev 17 | Go to most recent revision | Details | Compare with Previous | 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];
13 magnus 518
    char *spamc_argv[10];
1 magnus 519
    int i;
520
    /* These are the only values that we want working after the longjmp
521
     * The automatic ones can be clobbered, but we don't really care */
522
    volatile FILE *readfh;
523
    volatile char *mesgfn=NULL;
524
    volatile off_t fdsize;
525
    volatile off_t scansize;
526
    volatile off_t fdstart;
527
    volatile char *rcptlist;
528
    volatile void *old_sigchld;
529
    char *safemesgid=NULL;
530
    int isspam=0;
531
    int gotsa=0;
532
    int chunk;
533
    off_t towrite;
534
    char *mailinfo;
535
    float spamvalue=0.0;
536
    char *spamstatus=NULL;
537
    time_t beforescan;
538
    time_t afterscan;
539
    time_t afterwait;
540
    time_t scantime=0;
541
    time_t fulltime=0;
542
    struct stat stbuf;
543
 
544
    uschar *expand;
545
    header_line *hl;
546
 
547
    static int readconffile=0;
548
    static int wrotedebugenabled=0;
549
 
550
    /* Options we read from /etc/exim4/sa-exim.conf */
551
    static char *SAspamcpath=SPAMC_LOCATION;
552
    static char *SAsafemesgidchars=SAFEMESGIDCHARS
553
    static char *SAspamcSockPath=NULL;
554
    static char *SAspamcPort="783";
555
    static char *SAspamcHost="127.0.0.1";
13 magnus 556
    static char *SAspamcUser=NULL;
1 magnus 557
    static char *SAEximRunCond="0";
558
    static char *SAEximRejCond="1";
559
    static int SAmaxbody=250*1024;
560
    static char *SATruncBodyCond="0";
561
    static int SARewriteBody=0;
562
    static int SAmaxarchivebody=20*1048576;
563
    static int SAerrmaxarchivebody=1024*1048576;
564
    static int SAmaxrcptlistlength=0;
565
    static int SAaddSAEheaderBeforeSA=1;
566
    static int SAtimeout=240;
567
    static char *SAtimeoutsave=NULL;
568
    static char *SAtimeoutSavCond="1";
569
    static char *SAerrorsave=NULL;
570
    static char *SAerrorSavCond="1";
571
    static int SAtemprejectonerror=0;
572
    static char *SAteergrube="1048576";
573
    static float SAteergrubethreshold;
574
    /* This is obsolete, since SAteergrube (now a condition) can do the same */
575
    static char *SAteergrubecond="1";
576
    static int SAteergrubetime=900;
577
    static char *SAteergrubeSavCond="1";
578
    static char *SAteergrubesave=NULL;
579
    static int SAteergrubeoverwrite=1;
580
    static char *SAdevnull="1048576";
581
    static float SAdevnullthreshold;
582
    static char *SAdevnullSavCond="1";
583
    static char *SAdevnullsave=NULL;
584
    static char *SApermreject="1048576";
585
    static float SApermrejectthreshold;
586
    static char *SApermrejectSavCond="1";
587
    static char *SApermrejectsave=NULL;
588
    static char *SAtempreject="1048576";
589
    static float SAtemprejectthreshold;
590
    static char *SAtemprejectSavCond="1";
591
    static char *SAtemprejectsave=NULL;
592
    static int SAtemprejectoverwrite=1;
593
    static char *SAgreylistiswhitestr="GREYLIST_ISWHITE";
594
    static float SAgreylistraisetempreject=3.0;
595
    static char *SAspamacceptsave=NULL;
596
    static char *SAspamacceptSavCond="0";
597
    static char *SAnotspamsave=NULL;
598
    static char *SAnotspamSavCond="0";
599
    /* Those variables can take a %s to show the spam info */
600
    static char *SAmsgteergrubewait="wait for more output";
601
    static char *SAmsgteergruberej="Please try again later";
602
    static char *SAmsgpermrej="Rejected";
603
    static char *SAmsgtemprej="Please try again later";
604
    /* Do not put a %s in there, or you'll segfault */
605
    static char *SAmsgerror="Temporary local error while processing message, please contact postmaster";
606
 
607
    /* New values we read from spamassassin */
608
    char *xspamstatus=NULL;
609
    char *xspamflag=NULL;
610
 
611
 
612
    /* Any error can write the faulty message to mesgfn, so we need to
613
       give it a value right now. We'll set the real value later */
614
    /* message_id here comes from Exim, it's an internal disk Mesg-Id format
615
       which doesn't correlate to the actual message's Mesg-Id. We shouldn't
616
       need to clean it, and besides, SAsafemesgidchars hasn't been read from
617
       the config file yet, but eh, safety is always a good thing, right? */
618
    safemesgid=cleanmsgid(message_id, SAsafemesgidchars);
619
    mesgfn=string_sprintf("%d_%s", time(NULL), safemesgid);
620
 
621
    /* We won't scan local messages. I think exim bypasses local_scan for a
622
     * bounce generated after a locally submitted message, but better be safe */
623
    /* This is commented out now, because you can control it with SAEximRunCond
624
    if (!sender_host_address)
625
    {
626
        return LOCAL_SCAN_ACCEPT;
627
    }
628
    */
629
 
630
    /* If you discard a mail with exim ACLs, we get 0 recipients, so let's just
631
     * accept the mail, which won't matter either way since it'll get dropped
632
     * (thanks to John Horne for reporting this corner case) */
633
    if (recipients_count == 0)
634
    {
635
        return LOCAL_SCAN_ACCEPT;
636
    }
637
 
638
    /*
639
     * We keep track of whether we've alrady read the config file, but since
640
     * exim spawns itself, it will get read by exim children even though you
641
     * didn't restart exim. That said, after you change the config file, you
642
     * should restart exim to make sure all the instances pick up the new
643
     * config file
644
     */
645
    if (!readconffile)
646
    {
647
        ret=open(conffile, 0);
648
        CHECKERR(ret,string_sprintf("conf file open for %s", conffile),__LINE__);
649
        readfh=fdopen(ret, "r");
650
        CHECKERR(readfh,"fdopen",__LINE__);
651
 
652
        while ((fgets((char *)buffer, sizeof(buffera), (FILE *)readfh)) != NULL)
653
        {
654
            if (*buffer == '#' || *buffer == '\n' )
655
            {
656
                continue;
657
            }
658
 
659
            if (*buffer != 'S' || *(buffer+1) != 'A')
660
            {
661
                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);
662
                continue;
663
            }
664
 
665
#define     M_CHECKFORVAR(VAR, TYPE) \
666
            if (strstr(buffer, #VAR ": ") == buffer) \
667
            { \
668
                if (sscanf(buffer, #VAR ": " TYPE, &VAR)) \
669
                { \
670
                    if (SAEximDebug > 3) \
671
                    { \
672
                        if (SAEximDebug && ! wrotedebugenabled) \
673
                        { \
674
                            log_write(0, LOG_MAIN, "SA: Debug4: Debug enabled, reading config from file %s", conffile); \
675
                            wrotedebugenabled=1; \
676
                        } \
677
                        else \
678
                        { \
679
                            log_write(0, LOG_MAIN, "SA: Debug4: config read " #VAR " = " TYPE, VAR); \
680
                        }\
681
                    }\
682
                } \
683
                else \
684
                { \
685
                    log_write(0, LOG_MAIN, "SA: Warning: error while reading configuration file %s. Can't parse value in: '%s', ignoring", conffile, buffer); \
686
                } \
687
                continue; \
688
            } 
689
 
690
#define     M_CHECKFORSTR(VAR) \
691
            if (strstr(buffer, #VAR  ": ") == buffer) \
692
            { \
693
                VAR = strdup(buffer+strlen( #VAR )+2); \
694
                if (VAR == NULL) \
695
                { \
696
                    log_write(0, LOG_MAIN, "SA: PANIC: malloc failed, quitting..."); \
697
                    exit(-1); \
698
                } \
699
                \
700
                if (VAR[strlen(VAR)-1] == '\n') \
701
                { \
702
                    VAR[strlen(VAR)-1]=0; \
703
                } \
704
                if (SAEximDebug > 3) \
705
                { \
706
                    log_write(0, LOG_MAIN, "SA: Debug4: config read " #VAR " = %s", VAR); \
707
                } \
708
                continue; \
709
            } 
710
 
711
            M_CHECKFORVAR(SAEximDebug, "%d");
712
            M_CHECKFORSTR(SAspamcpath);
713
            M_CHECKFORSTR(SAsafemesgidchars);
714
            M_CHECKFORSTR(SAspamcSockPath);
715
            M_CHECKFORSTR(SAspamcPort);
716
            M_CHECKFORSTR(SAspamcHost);
13 magnus 717
            M_CHECKFORSTR(SAspamcUser);
1 magnus 718
            M_CHECKFORSTR(SAEximRunCond);
719
            M_CHECKFORSTR(SAEximRejCond);
720
            M_CHECKFORVAR(SAmaxbody, "%d");
721
            M_CHECKFORSTR(SATruncBodyCond);
722
            M_CHECKFORVAR(SARewriteBody, "%d");
723
            M_CHECKFORVAR(SAPrependArchiveWithFrom, "%d");
724
            M_CHECKFORVAR(SAmaxarchivebody, "%d");
725
            M_CHECKFORVAR(SAerrmaxarchivebody, "%d");
726
            M_CHECKFORVAR(SAmaxrcptlistlength, "%d");
727
            M_CHECKFORVAR(SAaddSAEheaderBeforeSA, "%d");
728
            M_CHECKFORVAR(SAtimeout, "%d");
729
            M_CHECKFORSTR(SAtimeoutsave);
730
            M_CHECKFORSTR(SAtimeoutSavCond);
731
            M_CHECKFORSTR(SAerrorsave);
732
            M_CHECKFORSTR(SAerrorSavCond);
733
            M_CHECKFORVAR(SAtemprejectonerror, "%d");
734
            M_CHECKFORSTR(SAteergrube);
735
            M_CHECKFORSTR(SAteergrubecond);
736
            M_CHECKFORVAR(SAteergrubetime, "%d");
737
            M_CHECKFORSTR(SAteergrubeSavCond);
738
            M_CHECKFORSTR(SAteergrubesave);
739
            M_CHECKFORVAR(SAteergrubeoverwrite, "%d");
740
            M_CHECKFORSTR(SAdevnull);
741
            M_CHECKFORSTR(SAdevnullSavCond);
742
            M_CHECKFORSTR(SAdevnullsave);
743
            M_CHECKFORSTR(SApermreject);
744
            M_CHECKFORSTR(SApermrejectsave);
745
            M_CHECKFORSTR(SApermrejectSavCond);
746
            M_CHECKFORSTR(SAtempreject);
747
            M_CHECKFORSTR(SAtemprejectSavCond);
748
            M_CHECKFORSTR(SAtemprejectsave);
749
            M_CHECKFORVAR(SAtemprejectoverwrite, "%d");
750
            M_CHECKFORSTR(SAgreylistiswhitestr);
751
            M_CHECKFORVAR(SAgreylistraisetempreject, "%f");
752
            M_CHECKFORSTR(SAspamacceptsave);
753
            M_CHECKFORSTR(SAspamacceptSavCond);
754
            M_CHECKFORSTR(SAnotspamsave);
755
            M_CHECKFORSTR(SAnotspamSavCond);
756
            M_CHECKFORSTR(SAmsgteergrubewait);
757
            M_CHECKFORSTR(SAmsgteergruberej);
758
            M_CHECKFORSTR(SAmsgpermrej);
759
            M_CHECKFORSTR(SAmsgtemprej);
760
            M_CHECKFORSTR(SAmsgerror);
761
 
762
 
763
        }
764
 
765
        readconffile=1;
766
    }
767
 
768
#define M_CONDTOFLOAT(VAR) \
769
    if ((expand=expand_string( VAR )) == NULL) \
770
    { \
771
        PANIC(string_sprintf(#VAR " config expansion failure on %s", #VAR ));\
772
    } \
773
    sscanf(expand, "%f", &VAR ## threshold); \
774
    if (SAEximDebug > 2) \
775
    { \
776
        log_write(0, LOG_MAIN, "SA: Debug3: expanded " #VAR " = %.2f", VAR ## threshold); \
777
    }\
778
 
779
    M_CONDTOFLOAT(SAteergrube);
780
    M_CONDTOFLOAT(SAdevnull);
781
    M_CONDTOFLOAT(SApermreject);
782
    M_CONDTOFLOAT(SAtempreject);
783
 
784
    /* Initialize the list of recipients here */
785
    rcptlist=string_copy(recipients_list[0].address);
786
    for (i=1; i < recipients_count && strlen((char *)rcptlist) < 7998 - strlen(recipients_list[i].address); i++)
787
    {
788
        rcptlist=string_sprintf("%s, %s", rcptlist, recipients_list[i].address);
789
    }
790
 
791
    if (sender_host_address != NULL)
792
    {
793
        mailinfo=string_sprintf("From <%s> (host=%s [%s]) for",
794
                sender_address, sender_host_name, sender_host_address);
795
    }
796
    else
797
    {
798
        mailinfo=string_sprintf("From <%s> (local) for", sender_address);
799
    }
800
    mailinfo=string_sprintf("%s %s", mailinfo, rcptlist);
801
 
802
 
803
    /* Remove SA-Exim headers that could have been set before we add ours*/
804
    RemoveHeaders("X-SA-Exim-");
805
 
806
    if(SAaddSAEheaderBeforeSA)
807
    {
808
        AddSAEheaders((char *)rcptlist, SAmaxrcptlistlength);
809
    }
810
 
811
    /* This is used later if we need to rewind and save the body elsewhere */
812
    fdstart=lseek(fd, 0, SEEK_CUR);
813
    CHECKERR(fdstart,"lseek SEEK_CUR",__LINE__);
814
 
815
    ret=fstat(fd, &stbuf);
816
    CHECKERR(ret,"fstat fd",__LINE__);
817
    /* this is the body size plus a few bytes (exim msg ID) */
818
    /* it should be 18 bytes, but I'll assume it could be more or less */
819
    fdsize=stbuf.st_size;
820
 
821
    if (SAEximDebug > 3)
822
    {
823
        log_write(0, LOG_MAIN, "SA: Debug4: Message body is about %.0f bytes and the initial offset is %.0f", (double)(fdsize-18), (double)fdstart);
824
    }
825
 
826
    if (fdsize > SAmaxbody)
827
    {
828
        if (SATruncBodyCond[0] != '1' || SATruncBodyCond[1] != 0)
829
        {
830
            expand=expand_string(SATruncBodyCond);
831
            if (expand == NULL)
832
            {
833
                PANIC(string_sprintf("SATruncBodyCond expansion failure on %s", SATruncBodyCond));
834
            }
835
 
836
            if (SAEximDebug)
837
            {
838
                log_write(0, LOG_MAIN, "SA: Debug: SATruncBodyCond expand returned: '%s'", expand);
839
            }
840
 
841
            if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
842
            {
843
                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);
844
                header_add(' ', "X-SA-Exim-Scanned: No (on %s); Message bigger than SAmaxbody (%d)\n", primary_hostname, SAmaxbody);
845
                return LOCAL_SCAN_ACCEPT;
846
            }
847
        }
848
 
849
        if (SAEximDebug > 1)
850
        {
851
            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));
852
        }
853
 
854
        /* Let's feed exactly spamc will accept */
855
        scansize=SAmaxbody;
856
        header_add(' ', "X-SA-Exim-Scan-Truncated: Fed %.0f bytes of the body to SA instead of %.0f\n", (double)scansize, (double)fdsize);
857
    }
858
    else
859
    {
860
        scansize=fdsize;
861
    }
862
 
863
    expand=expand_string(SAEximRunCond);
864
    if (expand == NULL)
865
    {
866
        PANIC(string_sprintf("SAEximRunCond expansion failure on %s", SAEximRunCond));
867
    }
868
 
869
    if (SAEximDebug)
870
    {
871
        log_write(0, LOG_MAIN, "SA: Debug: SAEximRunCond expand returned: '%s'", expand);
872
    }
873
 
874
 
875
    /* Bail from SA if the expansion string says so */
876
    if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
877
    {
878
        log_write(0, LOG_MAIN, "SA: Action: Not running SA because SAEximRunCond expanded to false (Message-Id: %s). %s", safemesgid, mailinfo);
879
        header_add(' ', "X-SA-Exim-Scanned: No (on %s); SAEximRunCond expanded to false\n", primary_hostname);
880
        return LOCAL_SCAN_ACCEPT;
881
    }
882
 
883
    if (SAEximDebug)
884
    {
885
        log_write(0, LOG_MAIN, "SA: Debug: check succeeded, running spamc");
886
    }
887
 
888
    /* Ok, so now that we know we're running SA, we remove the X-Spam headers */
889
    /* that might have been there */
890
    RemoveHeaders("X-Spam-");
891
 
892
 
893
    beforescan=time(NULL);
894
    /* Fork off spamc, and get ready to talk to it */
895
    ret=pipe(writefd);
896
    CHECKERR(ret,"write pipe",__LINE__);
897
    ret=pipe(readfd);
898
    CHECKERR(ret,"read pipe",__LINE__);
899
 
900
    /* Ensure that SIGCHLD isn't being ignored. */
901
    old_sigchld = signal(SIGCHLD, SIG_DFL);
902
 
903
    if ((pid=fork()) < 0)
904
    {
905
        CHECKERR(pid, "fork", __LINE__ - 1);
906
    }  
907
 
908
    if (pid == 0)
909
    {
910
        close(readfd[0]);
911
        close(writefd[1]);
912
 
913
        ret=dup2(writefd[0],0);
914
        CHECKERR(ret,"dup2 stdin",__LINE__);
915
        ret=dup2(readfd[1],1);
916
        CHECKERR(ret,"dup2 stdout",__LINE__);
917
        ret=dup2(readfd[1],2);
918
        CHECKERR(ret,"dup2 stderr",__LINE__);
919
 
13 magnus 920
        i = 0;
921
        spamc_argv[i++] = "spamc";
922
        if (SAspamcUser && SAspamcUser[0])
923
        {
924
            expand=expand_string(SAspamcUser);
925
            if (expand == NULL)
926
            {
927
                log_write(0, LOG_MAIN | LOG_PANIC, "SA: SAspamcUser expansion failure on %s, will run as Exim user instead.", SAspamcUser);
928
            }
929
            else
930
            {
931
                spamc_argv[i++] = "-u";
932
                spamc_argv[i++] = expand;
933
            }
934
        }
935
 
1 magnus 936
        /*
937
         * I could implement the spamc protocol and talk to spamd directly
938
         * instead of forking spamc, but considering the overhead spent
939
         * in spamd, forking off spamc seemed acceptable rather than
940
         * re-implementing and tracking the spamc/spamd protocol or linking
941
         * with a possibly changing library
942
         */
943
        /* Ok, we cheat, spamc cares about how big the whole message is and
944
         * we only know about the body size, so I'll  give an extra 16K
945
         * to account for any headers that can accompany the message */
13 magnus 946
 
947
        spamc_argv[i++] = "-s";
948
        spamc_argv[i++] = string_sprintf("%d", SAmaxbody+16384);
949
 
1 magnus 950
        if(SAspamcSockPath)
951
        {
13 magnus 952
            spamc_argv[i++] = "-U";
953
            spamc_argv[i++] = SAspamcSockPath;
1 magnus 954
        }
955
        else
956
        {
13 magnus 957
            spamc_argv[i++] = "-d";
958
            spamc_argv[i++] = SAspamcHost;
959
            spamc_argv[i++] = "-p";
960
            spamc_argv[i++] = SAspamcPort;
1 magnus 961
        }
13 magnus 962
        spamc_argv[i++] = NULL;
963
 
964
        ret=execv(SAspamcpath, spamc_argv);
965
        CHECKERR(ret,string_sprintf("exec %s", SAspamcpath),__LINE__);
1 magnus 966
    }
967
 
968
    if (SAEximDebug > 8)
969
    {
970
        log_write(0, LOG_MAIN, "SA: Debug9: forked spamc");
971
    }
972
 
973
    ret=close(readfd[1]);
974
    CHECKERR(ret,"close r",__LINE__);
975
    ret=close(writefd[0]);
976
    CHECKERR(ret,"close w",__LINE__);
977
    readfh=fdopen(readfd[0], "r");
978
 
979
    if (SAEximDebug > 8)
980
    {
981
        log_write(0, LOG_MAIN, "SA: Debug9: closed filehandles");
982
    }
983
 
984
    /* Ok, we're ready for spewing the mail at spamc */
985
    /* First we need to get the header lines from exim, and then we can read
986
       the body from fd */
987
    hl=header_list;
988
    while (hl != NULL)
989
    {
990
        /* type '*' means the header is internal, don't print it */
991
        if (hl->type == '*')
992
        {
993
            hl=hl->next;
994
            continue;
995
        }
996
 
997
        stret=write(writefd[1],hl->text,strlen(hl->text));
998
        CHECKERR(stret,"header line write",__LINE__);
999
 
1000
        hl=hl->next;
1001
    }
1002
    stret=write(writefd[1],"\n",1);
1003
    CHECKERR(stret,"header separation write",__LINE__);
1004
 
1005
    if (SAEximDebug > 6)
1006
    {
1007
        log_write(0, LOG_MAIN, "SA: Debug7: sent headers to spamc pipe. Sending body...");
1008
    }
1009
 
1010
    towrite=scansize;
1011
    chunk=0;
1012
    while (towrite>0 && (stret=read(fd, buffer, MIN(sizeof(buffera), towrite))) > 0)
1013
    {
1014
        chunk++;
1015
        if (SAEximDebug > 8)
1016
        {
1017
            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);
1018
        }
1019
        towrite-=stret;
1020
        stret=write(writefd[1], buffer, stret);
1021
        CHECKERR(stret,"body write in",__LINE__);
1022
        if (SAEximDebug > 8)
1023
        {
1024
            log_write(0, LOG_MAIN, "SA: Debug9: Spamc body wrote chunk %d (wrote %.0f, %.0f left to write)", chunk, (double)stret, (double)towrite);
1025
        }
1026
    }
1027
    CHECKERR(stret, "read body", __LINE__ - 14);
1028
    close(writefd[1]);
1029
 
1030
    if (SAEximDebug > 5)
1031
    {
1032
        log_write(0, LOG_MAIN, "SA: Debug6: fed spam to spamc, reading result");
1033
    }
1034
 
1035
    if (SAtimeout)
1036
    {
1037
        if (SAEximDebug > 2)
1038
        {
1039
            log_write(0, LOG_MAIN, "SA: Debug3: Setting timeout of %d secs before reading from spamc", SAtimeout);
1040
        }
1041
        /* SA can take very long to run for various reasons, let's not wait
1042
         * forever, that's just bad at SMTP time */
1043
        if (setjmp(jmp_env) == 0)
1044
        {
1045
            signal(SIGALRM, alarm_handler);
1046
            alarm (SAtimeout);
1047
        }
1048
        else
1049
        {
1050
            /* Make sure that all your variables here are volatile or static */
1051
            signal(SIGCHLD, old_sigchld);
1052
            fclose((FILE *)readfh);
1053
 
1054
            header_add(' ', "X-SA-Exim-Scanned: No (on %s); SA Timed out after %d secs\n", primary_hostname, SAtimeout);
1055
 
1056
            /* We sent it to LOG_REJECT too so that we get a header dump */
1057
            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);
1058
 
1059
            ret=savemail(fd, fdstart, SAtimeoutsave, "SAtimeoutsave", (char *)mesgfn, SAerrmaxarchivebody, SAtimeoutSavCond);
1060
            CHECKERR(ret,where,line);
1061
 
1062
            /* Make sure we kill spamc in case SIGPIPE from fclose didn't */
1063
            kill(pid, SIGTERM);
1064
            return LOCAL_SCAN_ACCEPT;
1065
 
1066
        }
1067
    }
1068
 
1069
    /* Let's see what SA has to tell us about this mail and store the headers */
1070
    while ((fgets((char *)buffer,sizeof(buffera),(FILE *) readfh)) != NULL)
1071
    {
1072
        /* Remove trailing newline */
1073
        if (buffer[strlen(buffer)-1] == '\n')
1074
        {
1075
            buffer[strlen(buffer)-1]=0;
1076
        }
1077
restart:
1078
        if (SAEximDebug > 5)
1079
        {
1080
            log_write(0, LOG_MAIN, "SA: Debug6: spamc read: %s", buffer);
1081
        }
1082
 
1083
        /* Let's handle special multi-line headers first, what a pain... */
1084
        /* We feed the one line we read and the filehandle because we'll need
1085
           to check whether more lines need to be concatenated */
1086
        /* This is ugly, there is an order dependency so we return to the
1087
           beginning of the loop without reading a new line since we already
1088
           did that */
1089
        if (parsemlheader(buffer, (FILE *)readfh, "Subject", NULL)) goto restart;
1090
        if ((SARewriteBody == 1) && parsemlheader(buffer, (FILE *)readfh, "Content-Type", NULL)) goto restart;
1091
        if ((SARewriteBody == 1) && parsemlheader(buffer, (FILE *)readfh, "Content-Transfer-Encoding", NULL)) goto restart;
1092
 
1093
        if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-Flag", &xspamflag))
1094
        {
1095
            if (xspamflag[13] == 'Y')
1096
            {
1097
                isspam=1;
1098
            }
1099
            if (SAEximDebug > 2)
1100
            {
1101
                log_write(0, LOG_MAIN, "SA: Debug3: isspam read from X-Spam-Flag: %d", isspam);
1102
            }
1103
            goto restart;
1104
        }
1105
 
1106
        if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-Status", &xspamstatus))
1107
        {
1108
            char *start;
1109
            char *end;
1110
 
1111
            gotsa=1;
1112
 
1113
            /* if we find the preconfigured greylist string (and it is defined
1114
             * in sa-exim.conf), we can raise the threshold for tempreject just
1115
             * for this mail, since it's been whitelisted */
1116
            if (SAgreylistiswhitestr && strstr(xspamstatus, SAgreylistiswhitestr))
1117
            {
1118
                SAtemprejectthreshold+=SAgreylistraisetempreject;
1119
                if (SAEximDebug > 2)
1120
                {
1121
                    log_write(0, LOG_MAIN, "SA: Debug3: read %s string, SAtempreject is now changed to %f", SAgreylistiswhitestr, SAtemprejectthreshold);
1122
                }
1123
            }
1124
            else
1125
            {
1126
                if (SAEximDebug > 2)
1127
                {
1128
                    log_write(0, LOG_MAIN, "SA: Debug3: did not find read GREYLIST_ISWHITE string in X-Spam-Status");
1129
                }
1130
            }
1131
 
1132
            start=strstr(xspamstatus, "hits=");
1133
            /* Support SA 3.0 format */
1134
            if (start == NULL)
1135
            {
1136
                start=strstr(xspamstatus, "score=");
1137
            }
1138
 
1139
            end=strstr(xspamstatus, " tests=");
1140
            if (end == NULL)
1141
            {
1142
                if (SAEximDebug > 5)
1143
                {
1144
                    log_write(0, LOG_MAIN, "SA: Debug6: Could not find old spamstatus format, trying new one...");
1145
                }
1146
                end=strstr(xspamstatus, "\n     tests=");
1147
            }
1148
            if (start!=NULL && end!=NULL)
1149
            {
1150
                spamstatus=string_copyn(start, end-start);
1151
                if (SAEximDebug > 2)
1152
                {
1153
                    log_write(0, LOG_MAIN, "SA: Debug3: Read from X-Spam-Status: %s", spamstatus);
1154
                }
1155
            }
1156
            else
1157
            {
1158
                PANIC(string_sprintf("SA: could not parse X-Spam-Status: to extract hits and required. Bad!. Got: '%s'", xspamstatus));
1159
            }
1160
 
1161
            start=strstr(spamstatus, "=");
1162
            end=strstr(spamstatus, " ");
1163
            if (start!=NULL && end!=NULL)
1164
            {
1165
                start++;
1166
                sscanf(start, "%f", &spamvalue);
1167
            }
1168
            else
1169
            {
1170
                PANIC(string_sprintf("SA: spam value extract failed in '%s'. Bad!", xspamstatus));
1171
            }
1172
 
1173
            goto restart;
1174
        }
1175
 
1176
        if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-", NULL)) goto restart;
1177
 
1178
        /* Ok, now we can do normal processing */
1179
 
1180
        /* If no more headers here, we're done */
1181
        if (buffer[0] == 0)
1182
        {
1183
            if (SAEximDebug > 5)
1184
            {
1185
                log_write(0, LOG_MAIN, "SA: Debug6: spamc read got newline, end of headers", buffer);
1186
            }
1187
            goto exit;
1188
        }
1189
 
1190
        if (compare_header(buffer, "Message-Id: "))
1191
        {
1192
            char *start;
1193
            char *end;
1194
            char *mesgid=NULL;
1195
 
1196
            start=strchr(buffer, '<');
1197
            end=strchr(buffer, '>');
1198
 
1199
            if (start == NULL || end == NULL)
1200
            {
1201
                /* we keep the default mesgfn (unix date in seconds) */
1202
                if (SAEximDebug)
1203
                {
1204
                    log_write(0, LOG_MAIN, "SA: Debug: Could not get Message-Id from %s", buffer);
1205
                }
1206
            }
1207
            else if ((mesgid=string_copyn(start+1,end-start-1)) && mesgid[0])
1208
            {
1209
                /* We replace the exim Message-ID with the one read from
1210
                the message * as we use this to detect dupes when we
1211
                send 45x and get the same * message multiple times */
1212
                safemesgid=cleanmsgid(mesgid, SAsafemesgidchars);
1213
                mesgfn=string_sprintf("%d_%s", time(NULL), safemesgid);
1214
 
1215
                if (SAEximDebug > 5)
1216
                {
1217
                    log_write(0, LOG_MAIN, "SA: Debug6: Message-Id received and cleaned as: %s", safemesgid);
1218
                }
1219
            }
1220
            continue;
1221
        }
1222
    }
1223
 
1224
    exit:
1225
 
1226
 
1227
    if (isspam && SARewriteBody == 1)
1228
    {
1229
        int line;
1230
 
1231
        if (SAEximDebug)
1232
        {
1233
            log_write(0, LOG_MAIN, "SA: Debug: SARewriteBody == 1, rewriting message body");
1234
        }
1235
 
1236
        /* already read from fd? Better reset it... */
1237
        ret=lseek(fd, fdstart, SEEK_SET);
1238
        CHECKERR(ret, "lseek reset on spooled message", __LINE__);
1239
 
1240
        line=1;
1241
        while ((fgets((char *)buffer,sizeof(buffera),(FILE *) readfh)) != NULL)
1242
        {
1243
            if (SAEximDebug > 8)
1244
            {
1245
                log_write(0, LOG_MAIN, "SA: Debug9: Read body from SA; line %d (read %d)", line, strlen(buffer));
1246
            }
1247
 
1248
            stret=write(fd, buffer, strlen(buffer));
1249
            CHECKERR(stret,string_sprintf("SA body write to msg"),__LINE__);
1250
            if (SAEximDebug > 8)
1251
            {
1252
                log_write(0, LOG_MAIN, "SA: Debug9: Wrote to msg; line %d (wrote %d)", line, ret);
1253
            }
1254
            if (buffer[strlen(buffer)-1] == '\n')
1255
            {
1256
                line++;
1257
            }
1258
        }
1259
 
1260
        if (SAEximDebug > 1)
1261
        {
1262
            log_write(0, LOG_MAIN, "SA: Debug2: body_linecount before SA: %d", body_linecount);
1263
        }
1264
 
1265
        /* update global variable $body_linecount to reflect the new body size*/
1266
        body_linecount = (line - 1);
1267
 
1268
        if (SAEximDebug > 1)
1269
        {
1270
            log_write(0, LOG_MAIN, "SA: Debug2: body_linecount after SA: %d", body_linecount);
1271
        }
1272
    }
1273
 
1274
    fclose((FILE *)readfh);
1275
 
1276
    afterscan=time(NULL);
1277
    scantime=afterscan-beforescan;
1278
 
1279
    wait(&ret);
1280
    signal(SIGCHLD, old_sigchld);
1281
 
1282
    if (ret)
1283
    {
1284
        sprintf(buffer, "%d", ret);
1285
        PANIC(string_sprintf("wait on spamc child yielded, %s", buffer));
1286
    }
1287
 
1288
    afterwait=time(NULL);
1289
    fulltime=afterwait-beforescan;
1290
 
1291
    if(!SAaddSAEheaderBeforeSA)
1292
    {
1293
        AddSAEheaders((char *)rcptlist, SAmaxrcptlistlength);
1294
    }
1295
 
1296
    header_add(' ', "X-SA-Exim-Version: %s\n",version);
1297
 
1298
    if (gotsa == 0)
1299
    {
1300
        header_add(' ', "X-SA-Exim-Scanned: No (on %s); Unknown failure\n", primary_hostname);
1301
        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);
1302
        return LOCAL_SCAN_ACCEPT;
1303
    }
1304
 
1305
    header_add(' ', "X-SA-Exim-Scanned: Yes (on %s)\n", primary_hostname);
1306
 
1307
    if (spamstatus == NULL)
1308
    {
1309
        spamstatus = (char *) nospamstatus;
1310
    }
1311
    if (isspam)
1312
    {
1313
        int dorej=1;
1314
        int doteergrube=0;
1315
 
1316
        if (SAEximRejCond[0] != '1' || SAEximRejCond[1] != 0)
1317
        {
1318
            expand=expand_string(SAEximRejCond);
1319
            if (expand == NULL)
1320
            {
1321
                PANIC(string_sprintf("SAEximRejCond expansion failure on %s", SAEximRejCond));
1322
            }
1323
 
1324
            if (SAEximDebug)
1325
            {
1326
                log_write(0, LOG_MAIN, "SA: Debug: SAEximRejCond expand returned: '%s'", expand);
1327
            }
1328
 
1329
            if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
1330
            {
1331
                log_write(0, LOG_MAIN, "SA: Notice: SAEximRejCond expanded to false, not applying reject rules");
1332
                dorej=0;
1333
            }
1334
        }
1335
 
1336
        if (dorej && spamvalue >= SAteergrubethreshold)
1337
        {
1338
            doteergrube=1;
1339
            if (SAteergrubecond[0] != '1' || SAteergrubecond[1] != 0)
1340
            {
1341
                expand=expand_string(SAteergrubecond);
1342
                if (expand == NULL)
1343
                {
1344
                    PANIC(string_sprintf("SAteergrubecond expansion failure on %s", SAteergrubecond));
1345
                }
1346
 
1347
                if (SAEximDebug)
1348
                {
1349
                    log_write(0, LOG_MAIN, "SA: Debug: SAteergrubecond expand returned: '%s'", expand);
1350
                }
1351
 
1352
                if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
1353
                {
1354
                    log_write(0, LOG_MAIN, "SA: Notice: SAteergrubecond expanded to false, not teergrubing known peer");
1355
                    doteergrube=0;
1356
                }
1357
            }
1358
        }
1359
 
1360
        if (dorej && doteergrube)
1361
        {
1362
            /* By default, we'll only save temp bounces by message ID so
1363
             * that when the same message is submitted several times, we
1364
             * overwrite the same file on disk and not create a brand new
1365
             * one every single time */
1366
            if (SAteergrubeoverwrite)
1367
            {
1368
                ret=savemail(fd, fdstart, SAteergrubesave, "SAteergrubesave", safemesgid, SAmaxarchivebody, SAteergrubeSavCond);
1369
                CHECKERR(ret,where,line);
1370
            }
1371
            else
1372
            {
1373
                ret=savemail(fd, fdstart, SAteergrubesave, "SAteergrubesave", (char *)mesgfn, SAmaxarchivebody, SAteergrubeSavCond);
1374
                CHECKERR(ret,where,line);
1375
            }
1376
 
1377
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAteergrubethreshold);
1378
            /* Exim might want to stop us if we run for too long, but that's
1379
             * exactly what we're trying to do, so let's override that */
1380
            alarm(0);
1381
 
1382
            for (i=0;i<SAteergrubetime/10;i++)
1383
            {
1384
                char *str;
1385
 
1386
                /* Unfortunately, we can't use exim's smtp_printf because it
1387
                 * doesn't return an error code if the write gets an EPIPE.
1388
                 * So, we write ourselves, but this won't work if you have a
1389
                 * TLS connection opened (that said, if you are teergrubing
1390
                 * a TLS connection, it's probably a relay host, not a
1391
                 * spammer, and in this case you should not teergrube a
1392
                 * friendly relay, so basically we should be ok).
1393
                 * If you do teergrube an SSL connection with the current
1394
                 * code, you will break it, but that's acceptable */
1395
                str=string_sprintf(string_sprintf("451- %s\r\n",SAmsgteergrubewait), spamstatus);
1396
                fprintf(smtp_out, str);
1397
                ret=fflush(smtp_out);
1398
                if (ret != 0)
1399
                {
1400
                    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);
1401
                    /* The other side closed the connection, nothing to print */
1402
                    *return_text="";
1403
                    return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1404
                }
1405
                sleep(10);
1406
            }
1407
 
1408
            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);
1409
            *return_text=string_sprintf(SAmsgteergruberej, spamstatus);
1410
            return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1411
        }
1412
        else if (dorej && spamvalue >= SAdevnullthreshold)
1413
        {
1414
            ret=savemail(fd, fdstart, SAdevnullsave, "SAdevnullsave", (char *)mesgfn, SAmaxarchivebody, SAdevnullSavCond);
1415
            CHECKERR(ret,where,line);
1416
 
1417
            recipients_count=0;
1418
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAdevnullthreshold);
1419
            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);
1420
            return LOCAL_SCAN_ACCEPT;
1421
        }
1422
        else if (dorej && spamvalue >= SApermrejectthreshold)
1423
        {
1424
            ret=savemail(fd, fdstart, SApermrejectsave, "SApermrejectsave", (char *)mesgfn, SAmaxarchivebody, SApermrejectSavCond);
1425
            CHECKERR(ret,where,line);
1426
 
1427
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SApermrejectthreshold);
1428
            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);
1429
            *return_text=string_sprintf(SAmsgpermrej, spamstatus);
1430
            return LOCAL_SCAN_REJECT_NOLOGHDR;
1431
        }
1432
        else if (dorej && spamvalue >= SAtemprejectthreshold)
1433
        {
1434
            /* Yeah, gotos are harmful, but that'd be a function with a lot
1435
             * of options to send, so, here's a small shortcut */
1436
            goto dotempreject;
1437
        }
1438
        else
1439
        {
1440
            ret=savemail(fd, fdstart, SAspamacceptsave, "SAspamacceptsave", (char *)mesgfn, SAmaxarchivebody, SAspamacceptSavCond);
1441
            CHECKERR(ret,where,line);
1442
            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);
1443
            return LOCAL_SCAN_ACCEPT;
1444
        }
1445
    }
1446
    else
1447
    {
1448
        /* This is an exception to the rule, for grey listing, we allow for
1449
         * sending back a tempreject on SA scores that aren't considered as
1450
         * spam (greylisting is now done directly in spamassassin though */
1451
        if (spamvalue >= SAtemprejectthreshold)
1452
        {
1453
            dotempreject:
1454
 
1455
            /* By default, we'll only save temp bounces by message ID so
1456
             * that when the same message is submitted several times, we
1457
             * overwrite the same file on disk and not create a brand new
1458
             * one every single time */
1459
            if (SAtemprejectoverwrite)
1460
            {
1461
                ret=savemail(fd, fdstart, SAtemprejectsave, "SAtemprejectsave", safemesgid, SAmaxarchivebody, SAtemprejectSavCond);
1462
                CHECKERR(ret,where,line);
1463
            }
1464
            else
1465
            {
1466
                ret=savemail(fd, fdstart, SAtemprejectsave, "SAtemprejectsave", (char *)mesgfn, SAmaxarchivebody, SAtemprejectSavCond);
1467
                CHECKERR(ret,where,line);
1468
            }
1469
 
1470
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAtemprejectthreshold);
1471
            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);
1472
            *return_text=string_sprintf(SAmsgtemprej, spamstatus);
1473
            return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1474
        }
1475
        else
1476
        {
1477
            ret=savemail(fd, fdstart, SAnotspamsave, "SAnotspamsave", (char *)mesgfn, SAmaxarchivebody, SAnotspamSavCond);
1478
            CHECKERR(ret,where,line);
1479
            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);
1480
            return LOCAL_SCAN_ACCEPT;
1481
        }
1482
    }
1483
 
1484
 
1485
 
1486
    errexit:
1487
    if (SAtemprejectonerror)
1488
    {
1489
        log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: Unexpected error on %s, file "__FILE__", line %d: %s", where, line-1, strerror(errno));
1490
    }
1491
    else
1492
    {
1493
        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));
1494
    }
1495
 
1496
    header_add(' ', "X-SA-Exim-Scanned: No (on %s); Exit with error (see exim mainlog)\n", primary_hostname);
1497
 
1498
    ret=savemail(fd, fdstart, SAerrorsave, "SAerrorsave", (char *)mesgfn, SAerrmaxarchivebody, SAerrorSavCond);
1499
    if (ret < 0)
1500
    {
1501
        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));
1502
    }
1503
 
1504
    if (SAtemprejectonerror)
1505
    {
1506
        *return_text=SAmsgerror;
1507
        return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1508
    }
1509
    else
1510
    {
1511
        return LOCAL_SCAN_ACCEPT;
1512
    }
1513
 
1514
 
1515
    panicexit:
1516
    if (SAtemprejectonerror)
1517
    {
1518
        log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: %s", panicerror);
1519
    }
1520
    else
1521
    {
1522
        log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: %s (but message was accepted)", panicerror);
1523
    }
1524
 
1525
    header_add(' ', "X-SA-Exim-Scanned: No (on %s); Panic (see exim mainlog)\n", primary_hostname);
1526
 
1527
    ret=savemail(fd, fdstart, SAerrorsave, "SAerrorsave", (char *)mesgfn, SAerrmaxarchivebody, SAerrorSavCond);
1528
    if (ret < 0)
1529
    {
1530
        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));
1531
    }
1532
 
1533
    if (SAtemprejectonerror)
1534
    {
1535
        *return_text=SAmsgerror;
1536
        return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1537
    }
1538
    else
1539
    {
1540
        return LOCAL_SCAN_ACCEPT;
1541
    }
1542
}