Subversion Repositories

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

Rev 31 | 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;
66 magnus 552
    static char *SAspamcPort=NULL;
553
    static char *SAspamcHost=NULL;
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
        {
66 magnus 964
            if (SAspamcHost) {
965
                spamc_argv[i++] = "-d";
966
                spamc_argv[i++] = SAspamcHost;
967
            }
968
            if (SAspamcPort) {
969
                spamc_argv[i++] = "-p";
970
                spamc_argv[i++] = SAspamcPort;
971
            }
1 magnus 972
        }
13 magnus 973
        spamc_argv[i++] = NULL;
974
 
975
        ret=execv(SAspamcpath, spamc_argv);
976
        CHECKERR(ret,string_sprintf("exec %s", SAspamcpath),__LINE__);
1 magnus 977
    }
978
 
979
    if (SAEximDebug > 8)
980
    {
981
        log_write(0, LOG_MAIN, "SA: Debug9: forked spamc");
982
    }
983
 
984
    ret=close(readfd[1]);
985
    CHECKERR(ret,"close r",__LINE__);
986
    ret=close(writefd[0]);
987
    CHECKERR(ret,"close w",__LINE__);
988
    readfh=fdopen(readfd[0], "r");
989
 
990
    if (SAEximDebug > 8)
991
    {
992
        log_write(0, LOG_MAIN, "SA: Debug9: closed filehandles");
993
    }
994
 
995
    /* Ok, we're ready for spewing the mail at spamc */
996
    /* First we need to get the header lines from exim, and then we can read
997
       the body from fd */
998
    hl=header_list;
999
    while (hl != NULL)
1000
    {
1001
        /* type '*' means the header is internal, don't print it */
1002
        if (hl->type == '*')
1003
        {
1004
            hl=hl->next;
1005
            continue;
1006
        }
1007
 
1008
        stret=write(writefd[1],hl->text,strlen(hl->text));
1009
        CHECKERR(stret,"header line write",__LINE__);
1010
 
1011
        hl=hl->next;
1012
    }
1013
    stret=write(writefd[1],"\n",1);
1014
    CHECKERR(stret,"header separation write",__LINE__);
1015
 
1016
    if (SAEximDebug > 6)
1017
    {
1018
        log_write(0, LOG_MAIN, "SA: Debug7: sent headers to spamc pipe. Sending body...");
1019
    }
1020
 
1021
    towrite=scansize;
1022
    chunk=0;
1023
    while (towrite>0 && (stret=read(fd, buffer, MIN(sizeof(buffera), towrite))) > 0)
1024
    {
1025
        chunk++;
1026
        if (SAEximDebug > 8)
1027
        {
1028
            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);
1029
        }
1030
        towrite-=stret;
1031
        stret=write(writefd[1], buffer, stret);
1032
        CHECKERR(stret,"body write in",__LINE__);
1033
        if (SAEximDebug > 8)
1034
        {
1035
            log_write(0, LOG_MAIN, "SA: Debug9: Spamc body wrote chunk %d (wrote %.0f, %.0f left to write)", chunk, (double)stret, (double)towrite);
1036
        }
1037
    }
1038
    CHECKERR(stret, "read body", __LINE__ - 14);
1039
    close(writefd[1]);
1040
 
1041
    if (SAEximDebug > 5)
1042
    {
1043
        log_write(0, LOG_MAIN, "SA: Debug6: fed spam to spamc, reading result");
1044
    }
1045
 
1046
    if (SAtimeout)
1047
    {
1048
        if (SAEximDebug > 2)
1049
        {
1050
            log_write(0, LOG_MAIN, "SA: Debug3: Setting timeout of %d secs before reading from spamc", SAtimeout);
1051
        }
1052
        /* SA can take very long to run for various reasons, let's not wait
1053
         * forever, that's just bad at SMTP time */
1054
        if (setjmp(jmp_env) == 0)
1055
        {
1056
            signal(SIGALRM, alarm_handler);
1057
            alarm (SAtimeout);
1058
        }
1059
        else
1060
        {
1061
            /* Make sure that all your variables here are volatile or static */
1062
            signal(SIGCHLD, old_sigchld);
1063
            fclose((FILE *)readfh);
1064
 
1065
            header_add(' ', "X-SA-Exim-Scanned: No (on %s); SA Timed out after %d secs\n", primary_hostname, SAtimeout);
1066
 
1067
            /* We sent it to LOG_REJECT too so that we get a header dump */
1068
            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);
1069
 
1070
            ret=savemail(fd, fdstart, SAtimeoutsave, "SAtimeoutsave", (char *)mesgfn, SAerrmaxarchivebody, SAtimeoutSavCond);
1071
            CHECKERR(ret,where,line);
1072
 
1073
            /* Make sure we kill spamc in case SIGPIPE from fclose didn't */
1074
            kill(pid, SIGTERM);
1075
            return LOCAL_SCAN_ACCEPT;
1076
 
1077
        }
1078
    }
1079
 
1080
    /* Let's see what SA has to tell us about this mail and store the headers */
1081
    while ((fgets((char *)buffer,sizeof(buffera),(FILE *) readfh)) != NULL)
1082
    {
1083
        /* Remove trailing newline */
1084
        if (buffer[strlen(buffer)-1] == '\n')
1085
        {
1086
            buffer[strlen(buffer)-1]=0;
1087
        }
1088
restart:
1089
        if (SAEximDebug > 5)
1090
        {
1091
            log_write(0, LOG_MAIN, "SA: Debug6: spamc read: %s", buffer);
1092
        }
1093
 
1094
        /* Let's handle special multi-line headers first, what a pain... */
1095
        /* We feed the one line we read and the filehandle because we'll need
1096
           to check whether more lines need to be concatenated */
1097
        /* This is ugly, there is an order dependency so we return to the
1098
           beginning of the loop without reading a new line since we already
1099
           did that */
1100
        if (parsemlheader(buffer, (FILE *)readfh, "Subject", NULL)) goto restart;
1101
        if ((SARewriteBody == 1) && parsemlheader(buffer, (FILE *)readfh, "Content-Type", NULL)) goto restart;
1102
        if ((SARewriteBody == 1) && parsemlheader(buffer, (FILE *)readfh, "Content-Transfer-Encoding", NULL)) goto restart;
1103
 
1104
        if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-Flag", &xspamflag))
1105
        {
1106
            if (xspamflag[13] == 'Y')
1107
            {
1108
                isspam=1;
1109
            }
1110
            if (SAEximDebug > 2)
1111
            {
1112
                log_write(0, LOG_MAIN, "SA: Debug3: isspam read from X-Spam-Flag: %d", isspam);
1113
            }
1114
            goto restart;
1115
        }
1116
 
1117
        if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-Status", &xspamstatus))
1118
        {
1119
            char *start;
1120
            char *end;
1121
 
1122
            gotsa=1;
1123
 
1124
            /* if we find the preconfigured greylist string (and it is defined
1125
             * in sa-exim.conf), we can raise the threshold for tempreject just
1126
             * for this mail, since it's been whitelisted */
1127
            if (SAgreylistiswhitestr && strstr(xspamstatus, SAgreylistiswhitestr))
1128
            {
1129
                SAtemprejectthreshold+=SAgreylistraisetempreject;
1130
                if (SAEximDebug > 2)
1131
                {
1132
                    log_write(0, LOG_MAIN, "SA: Debug3: read %s string, SAtempreject is now changed to %f", SAgreylistiswhitestr, SAtemprejectthreshold);
1133
                }
1134
            }
1135
            else
1136
            {
1137
                if (SAEximDebug > 2)
1138
                {
1139
                    log_write(0, LOG_MAIN, "SA: Debug3: did not find read GREYLIST_ISWHITE string in X-Spam-Status");
1140
                }
1141
            }
1142
 
1143
            start=strstr(xspamstatus, "hits=");
1144
            /* Support SA 3.0 format */
1145
            if (start == NULL)
1146
            {
1147
                start=strstr(xspamstatus, "score=");
1148
            }
1149
 
1150
            end=strstr(xspamstatus, " tests=");
1151
            if (end == NULL)
1152
            {
1153
                if (SAEximDebug > 5)
1154
                {
1155
                    log_write(0, LOG_MAIN, "SA: Debug6: Could not find old spamstatus format, trying new one...");
1156
                }
1157
                end=strstr(xspamstatus, "\n     tests=");
1158
            }
1159
            if (start!=NULL && end!=NULL)
1160
            {
1161
                spamstatus=string_copyn(start, end-start);
1162
                if (SAEximDebug > 2)
1163
                {
1164
                    log_write(0, LOG_MAIN, "SA: Debug3: Read from X-Spam-Status: %s", spamstatus);
1165
                }
1166
            }
1167
            else
1168
            {
1169
                PANIC(string_sprintf("SA: could not parse X-Spam-Status: to extract hits and required. Bad!. Got: '%s'", xspamstatus));
1170
            }
1171
 
1172
            start=strstr(spamstatus, "=");
1173
            end=strstr(spamstatus, " ");
1174
            if (start!=NULL && end!=NULL)
1175
            {
1176
                start++;
1177
                sscanf(start, "%f", &spamvalue);
1178
            }
1179
            else
1180
            {
1181
                PANIC(string_sprintf("SA: spam value extract failed in '%s'. Bad!", xspamstatus));
1182
            }
1183
 
1184
            goto restart;
1185
        }
1186
 
1187
        if (parsemlheader(buffer, (FILE *)readfh, "X-Spam-", NULL)) goto restart;
1188
 
1189
        /* Ok, now we can do normal processing */
1190
 
1191
        /* If no more headers here, we're done */
1192
        if (buffer[0] == 0)
1193
        {
1194
            if (SAEximDebug > 5)
1195
            {
1196
                log_write(0, LOG_MAIN, "SA: Debug6: spamc read got newline, end of headers", buffer);
1197
            }
1198
            goto exit;
1199
        }
1200
 
1201
        if (compare_header(buffer, "Message-Id: "))
1202
        {
1203
            char *start;
1204
            char *end;
1205
            char *mesgid=NULL;
1206
 
1207
            start=strchr(buffer, '<');
1208
            end=strchr(buffer, '>');
1209
 
1210
            if (start == NULL || end == NULL)
1211
            {
1212
                /* we keep the default mesgfn (unix date in seconds) */
1213
                if (SAEximDebug)
1214
                {
1215
                    log_write(0, LOG_MAIN, "SA: Debug: Could not get Message-Id from %s", buffer);
1216
                }
1217
            }
1218
            else if ((mesgid=string_copyn(start+1,end-start-1)) && mesgid[0])
1219
            {
1220
                /* We replace the exim Message-ID with the one read from
1221
                the message * as we use this to detect dupes when we
1222
                send 45x and get the same * message multiple times */
1223
                safemesgid=cleanmsgid(mesgid, SAsafemesgidchars);
1224
                mesgfn=string_sprintf("%d_%s", time(NULL), safemesgid);
1225
 
1226
                if (SAEximDebug > 5)
1227
                {
1228
                    log_write(0, LOG_MAIN, "SA: Debug6: Message-Id received and cleaned as: %s", safemesgid);
1229
                }
1230
            }
1231
            continue;
1232
        }
1233
    }
1234
 
1235
    exit:
1236
 
1237
 
1238
    if (isspam && SARewriteBody == 1)
1239
    {
1240
        int line;
1241
 
1242
        if (SAEximDebug)
1243
        {
1244
            log_write(0, LOG_MAIN, "SA: Debug: SARewriteBody == 1, rewriting message body");
1245
        }
1246
 
1247
        /* already read from fd? Better reset it... */
1248
        ret=lseek(fd, fdstart, SEEK_SET);
1249
        CHECKERR(ret, "lseek reset on spooled message", __LINE__);
1250
 
1251
        line=1;
1252
        while ((fgets((char *)buffer,sizeof(buffera),(FILE *) readfh)) != NULL)
1253
        {
1254
            if (SAEximDebug > 8)
1255
            {
1256
                log_write(0, LOG_MAIN, "SA: Debug9: Read body from SA; line %d (read %d)", line, strlen(buffer));
1257
            }
1258
 
1259
            stret=write(fd, buffer, strlen(buffer));
1260
            CHECKERR(stret,string_sprintf("SA body write to msg"),__LINE__);
1261
            if (SAEximDebug > 8)
1262
            {
1263
                log_write(0, LOG_MAIN, "SA: Debug9: Wrote to msg; line %d (wrote %d)", line, ret);
1264
            }
1265
            if (buffer[strlen(buffer)-1] == '\n')
1266
            {
1267
                line++;
1268
            }
1269
        }
1270
 
24 magnus 1271
/*      if (SAEximDebug > 1)
1 magnus 1272
        {
1273
            log_write(0, LOG_MAIN, "SA: Debug2: body_linecount before SA: %d", body_linecount);
1274
        }
24 magnus 1275
*/
1 magnus 1276
        /* update global variable $body_linecount to reflect the new body size*/
24 magnus 1277
/*      body_linecount = (line - 1);
1 magnus 1278
 
1279
        if (SAEximDebug > 1)
1280
        {
1281
            log_write(0, LOG_MAIN, "SA: Debug2: body_linecount after SA: %d", body_linecount);
1282
        }
24 magnus 1283
*/
1 magnus 1284
    }
1285
 
1286
    fclose((FILE *)readfh);
1287
 
1288
    afterscan=time(NULL);
1289
    scantime=afterscan-beforescan;
1290
 
1291
    wait(&ret);
1292
    signal(SIGCHLD, old_sigchld);
1293
 
1294
    if (ret)
1295
    {
1296
        sprintf(buffer, "%d", ret);
1297
        PANIC(string_sprintf("wait on spamc child yielded, %s", buffer));
1298
    }
1299
 
1300
    afterwait=time(NULL);
1301
    fulltime=afterwait-beforescan;
1302
 
1303
    if(!SAaddSAEheaderBeforeSA)
1304
    {
1305
        AddSAEheaders((char *)rcptlist, SAmaxrcptlistlength);
1306
    }
1307
 
1308
    header_add(' ', "X-SA-Exim-Version: %s\n",version);
1309
 
1310
    if (gotsa == 0)
1311
    {
1312
        header_add(' ', "X-SA-Exim-Scanned: No (on %s); Unknown failure\n", primary_hostname);
1313
        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);
1314
        return LOCAL_SCAN_ACCEPT;
1315
    }
1316
 
1317
    header_add(' ', "X-SA-Exim-Scanned: Yes (on %s)\n", primary_hostname);
1318
 
1319
    if (spamstatus == NULL)
1320
    {
1321
        spamstatus = (char *) nospamstatus;
1322
    }
1323
    if (isspam)
1324
    {
1325
        int dorej=1;
1326
        int doteergrube=0;
1327
 
1328
        if (SAEximRejCond[0] != '1' || SAEximRejCond[1] != 0)
1329
        {
1330
            expand=expand_string(SAEximRejCond);
1331
            if (expand == NULL)
1332
            {
1333
                PANIC(string_sprintf("SAEximRejCond expansion failure on %s", SAEximRejCond));
1334
            }
1335
 
1336
            if (SAEximDebug)
1337
            {
1338
                log_write(0, LOG_MAIN, "SA: Debug: SAEximRejCond expand returned: '%s'", expand);
1339
            }
1340
 
1341
            if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
1342
            {
1343
                log_write(0, LOG_MAIN, "SA: Notice: SAEximRejCond expanded to false, not applying reject rules");
1344
                dorej=0;
1345
            }
1346
        }
1347
 
1348
        if (dorej && spamvalue >= SAteergrubethreshold)
1349
        {
1350
            doteergrube=1;
1351
            if (SAteergrubecond[0] != '1' || SAteergrubecond[1] != 0)
1352
            {
1353
                expand=expand_string(SAteergrubecond);
1354
                if (expand == NULL)
1355
                {
1356
                    PANIC(string_sprintf("SAteergrubecond expansion failure on %s", SAteergrubecond));
1357
                }
1358
 
1359
                if (SAEximDebug)
1360
                {
1361
                    log_write(0, LOG_MAIN, "SA: Debug: SAteergrubecond expand returned: '%s'", expand);
1362
                }
1363
 
1364
                if (expand[0] == 0 || (expand[0] == '0' && expand[1] == 0))
1365
                {
1366
                    log_write(0, LOG_MAIN, "SA: Notice: SAteergrubecond expanded to false, not teergrubing known peer");
1367
                    doteergrube=0;
1368
                }
1369
            }
1370
        }
1371
 
1372
        if (dorej && doteergrube)
1373
        {
24 magnus 1374
            char *teergrubewaitstr;
1375
            teergrubewaitstr=string_sprintf(SAmsgteergrubewait, spamstatus);
1376
 
1 magnus 1377
            /* By default, we'll only save temp bounces by message ID so
1378
             * that when the same message is submitted several times, we
1379
             * overwrite the same file on disk and not create a brand new
1380
             * one every single time */
1381
            if (SAteergrubeoverwrite)
1382
            {
1383
                ret=savemail(fd, fdstart, SAteergrubesave, "SAteergrubesave", safemesgid, SAmaxarchivebody, SAteergrubeSavCond);
1384
                CHECKERR(ret,where,line);
1385
            }
1386
            else
1387
            {
1388
                ret=savemail(fd, fdstart, SAteergrubesave, "SAteergrubesave", (char *)mesgfn, SAmaxarchivebody, SAteergrubeSavCond);
1389
                CHECKERR(ret,where,line);
1390
            }
1391
 
1392
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAteergrubethreshold);
1393
            /* Exim might want to stop us if we run for too long, but that's
1394
             * exactly what we're trying to do, so let's override that */
1395
            alarm(0);
1396
 
1397
            for (i=0;i<SAteergrubetime/10;i++)
1398
            {
24 magnus 1399
                smtp_printf("451-%s\r\n", teergrubewaitstr);
1400
                ret=smtp_fflush();
1 magnus 1401
                if (ret != 0)
1402
                {
1403
                    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);
1404
                    /* The other side closed the connection, nothing to print */
1405
                    *return_text="";
1406
                    return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1407
                }
1408
                sleep(10);
1409
            }
1410
 
1411
            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);
1412
            *return_text=string_sprintf(SAmsgteergruberej, spamstatus);
1413
            return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1414
        }
1415
        else if (dorej && spamvalue >= SAdevnullthreshold)
1416
        {
1417
            ret=savemail(fd, fdstart, SAdevnullsave, "SAdevnullsave", (char *)mesgfn, SAmaxarchivebody, SAdevnullSavCond);
1418
            CHECKERR(ret,where,line);
1419
 
1420
            recipients_count=0;
1421
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAdevnullthreshold);
1422
            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);
1423
            return LOCAL_SCAN_ACCEPT;
1424
        }
1425
        else if (dorej && spamvalue >= SApermrejectthreshold)
1426
        {
1427
            ret=savemail(fd, fdstart, SApermrejectsave, "SApermrejectsave", (char *)mesgfn, SAmaxarchivebody, SApermrejectSavCond);
1428
            CHECKERR(ret,where,line);
1429
 
1430
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SApermrejectthreshold);
1431
            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);
1432
            *return_text=string_sprintf(SAmsgpermrej, spamstatus);
1433
            return LOCAL_SCAN_REJECT_NOLOGHDR;
1434
        }
1435
        else if (dorej && spamvalue >= SAtemprejectthreshold)
1436
        {
1437
            /* Yeah, gotos are harmful, but that'd be a function with a lot
1438
             * of options to send, so, here's a small shortcut */
1439
            goto dotempreject;
1440
        }
1441
        else
1442
        {
1443
            ret=savemail(fd, fdstart, SAspamacceptsave, "SAspamacceptsave", (char *)mesgfn, SAmaxarchivebody, SAspamacceptSavCond);
1444
            CHECKERR(ret,where,line);
1445
            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);
1446
            return LOCAL_SCAN_ACCEPT;
1447
        }
1448
    }
1449
    else
1450
    {
1451
        /* This is an exception to the rule, for grey listing, we allow for
1452
         * sending back a tempreject on SA scores that aren't considered as
1453
         * spam (greylisting is now done directly in spamassassin though */
1454
        if (spamvalue >= SAtemprejectthreshold)
1455
        {
1456
            dotempreject:
1457
 
1458
            /* By default, we'll only save temp bounces by message ID so
1459
             * that when the same message is submitted several times, we
1460
             * overwrite the same file on disk and not create a brand new
1461
             * one every single time */
1462
            if (SAtemprejectoverwrite)
1463
            {
1464
                ret=savemail(fd, fdstart, SAtemprejectsave, "SAtemprejectsave", safemesgid, SAmaxarchivebody, SAtemprejectSavCond);
1465
                CHECKERR(ret,where,line);
1466
            }
1467
            else
1468
            {
1469
                ret=savemail(fd, fdstart, SAtemprejectsave, "SAtemprejectsave", (char *)mesgfn, SAmaxarchivebody, SAtemprejectSavCond);
1470
                CHECKERR(ret,where,line);
1471
            }
1472
 
1473
            spamstatus=string_sprintf("%s trigger=%.1f", spamstatus, SAtemprejectthreshold);
1474
            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);
1475
            *return_text=string_sprintf(SAmsgtemprej, spamstatus);
1476
            return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1477
        }
1478
        else
1479
        {
1480
            ret=savemail(fd, fdstart, SAnotspamsave, "SAnotspamsave", (char *)mesgfn, SAmaxarchivebody, SAnotspamSavCond);
1481
            CHECKERR(ret,where,line);
1482
            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);
1483
            return LOCAL_SCAN_ACCEPT;
1484
        }
1485
    }
1486
 
1487
 
1488
 
1489
    errexit:
1490
    if (SAtemprejectonerror)
1491
    {
1492
        log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: Unexpected error on %s, file "__FILE__", line %d: %s", where, line-1, strerror(errno));
1493
    }
1494
    else
1495
    {
1496
        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));
1497
    }
1498
 
1499
    header_add(' ', "X-SA-Exim-Scanned: No (on %s); Exit with error (see exim mainlog)\n", primary_hostname);
1500
 
1501
    ret=savemail(fd, fdstart, SAerrorsave, "SAerrorsave", (char *)mesgfn, SAerrmaxarchivebody, SAerrorSavCond);
1502
    if (ret < 0)
1503
    {
1504
        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));
1505
    }
1506
 
1507
    if (SAtemprejectonerror)
1508
    {
1509
        *return_text=SAmsgerror;
1510
        return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1511
    }
1512
    else
1513
    {
1514
        return LOCAL_SCAN_ACCEPT;
1515
    }
1516
 
1517
 
1518
    panicexit:
1519
    if (SAtemprejectonerror)
1520
    {
1521
        log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: %s", panicerror);
1522
    }
1523
    else
1524
    {
1525
        log_write(0, LOG_MAIN | LOG_PANIC, "SA: PANIC: %s (but message was accepted)", panicerror);
1526
    }
1527
 
1528
    header_add(' ', "X-SA-Exim-Scanned: No (on %s); Panic (see exim mainlog)\n", primary_hostname);
1529
 
1530
    ret=savemail(fd, fdstart, SAerrorsave, "SAerrorsave", (char *)mesgfn, SAerrmaxarchivebody, SAerrorSavCond);
1531
    if (ret < 0)
1532
    {
1533
        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));
1534
    }
1535
 
1536
    if (SAtemprejectonerror)
1537
    {
1538
        *return_text=SAmsgerror;
1539
        return LOCAL_SCAN_TEMPREJECT_NOLOGHDR;
1540
    }
1541
    else
1542
    {
1543
        return LOCAL_SCAN_ACCEPT;
1544
    }
1545
}