Subversion Repositories

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

Rev 17 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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