Subversion Repositories

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

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