> The exiscan patch to go with it is now at
>
> http://duncanthrax.net/exiscan-acl/
It looks like it does not contain any of my patches, so here is a current
one. It improves parsing the spamd output by allowing multiple header
fields, uses dynamic instead of static memory (thus reducing memory usage
by around ~32k), caches not only the most recent query, but all queries,
and allows to query multiple spamd servers. Note: This patch requires
to change your configuration, like in the following example:
warn message = ${if !eq{$spam_score}{0.0} {X-Warning: $spam_report}}
spam = 127.0.0.1:783:user:true
It does not use the global variable "spamd_address" any more. Most
likely the "true" option could be removed, using the side effects of
one ACL rule with the spam condition and a second ACL that makes use of
its variables, but I left it for now.
Michael
----------------------------------------------------------------------
--- src/spam.c.orig 2003-08-14 11:29:31.000000000 +0200
+++ src/spam.c 2003-08-14 11:33:47.000000000 +0200
@@ -13,39 +13,66 @@
#include "exim.h"
#include "spam.h"
-uschar spam_score_buffer[16];
-uschar spam_score_int_buffer[16];
-uschar spam_bar_buffer[128];
-uschar spam_report_buffer[32600];
-uschar prev_user_name[128];
-int spam_ok = 0;
-int spam_rc = 0;
+static struct SpamCache {
+ /* key */
+ uschar *tcp_addr;
+ unsigned short tcp_port;
+ uschar *user_name;
+ /* result */
+ uschar *spam_score,*spam_score_int,*spam_bar,*spam_report;
+ int spam_rc;
+ /* next entry */
+ struct SpamCache *next;
+} *spamCache;
int spam(uschar **listptr) {
int sep = 0;
uschar *list = *listptr;
uschar *user_name;
- uschar user_name_buffer[128];
+ struct SpamCache **entry;
unsigned long long mbox_size;
FILE *mbox_file;
int spamd_sock;
- uschar tcp_addr[24];
+ uschar *tcp_addr,*tcp_port_str;
unsigned int tcp_port;
uschar spamd_buffer[32600];
- int i, j, offset;
- uschar spamd_version[8];
+ int i, j, offset, spam_rc;
+ unsigned int spamd_major, spamd_minor, spamd_status;
uschar spamd_score_char;
double spamd_threshold, spamd_score;
- int spamd_report_offset;
- uschar *p,*q;
+ int advance, capacity, length;
+ uschar *p;
int override = 0;
+ /* find the spamd server address from the option list */
+ if ((tcp_addr = string_nextinlist(&list, &sep,
+ NULL,
+ 0)) == NULL) {
+ /* no address given, this means no scanning should be done */
+ return FAIL;
+ }
+
+ /* find the spamd port from the option list */
+ if ((tcp_port_str = string_nextinlist(&list, &sep,
+ NULL,
+ 0)) == NULL) {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "spam acl condition: no spamd port specified");
+ return DEFER;
+ }
+ if( sscanf(CS tcp_port_str, "%u", &tcp_port) != 1 ) {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "spam acl condition: invalid spamd port: '%s'", tcp_port_str);
+ return DEFER;
+ }
+
/* find the username from the option list */
if ((user_name = string_nextinlist(&list, &sep,
- user_name_buffer,
- sizeof(user_name_buffer))) == NULL) {
- /* no username given, this means no scanning should be done */
- return FAIL;
+ NULL,
+ 0)) == NULL) {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "spam acl condition: no user specified");
+ return DEFER;
};
/* if username is "0" or "false", do not scan */
@@ -60,12 +87,18 @@
override = 1;
};
- /* if we scanned for this username last time, just return */
- if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) )
- if (override)
- return OK;
- else
- return spam_rc;
+ /* if we scanned with these paramters before, just return */
+ for (entry=&spamCache; *entry; entry=&(*entry)->next) {
+ if (strcmp((*entry)->tcp_addr,tcp_addr)==0
+ && (*entry)->tcp_port==tcp_port
+ && strcmp((*entry)->user_name,user_name)==0) {
+ spam_score = (*entry)->spam_score;
+ spam_score_int = (*entry)->spam_score_int;
+ spam_bar = (*entry)->spam_bar;
+ spam_report = (*entry)->spam_report;
+ return (override ? OK : (*entry)->spam_rc);
+ }
+ }
/* make sure the eml mbox file is spooled up */
mbox_file = spool_mbox(&mbox_size);
@@ -94,15 +127,6 @@
return DEFER;
};
- /* grok spamd address and port */
- if( sscanf(CS spamd_address, "%s %u", tcp_addr, &tcp_port) != 2 ) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "spam acl condition: invalid spamd address: '%s'", spamd_address);
- fclose(mbox_file);
- close(spamd_sock);
- return DEFER;
- };
-
if (ip_connect(spamd_sock, AF_INET, tcp_addr, tcp_port, 5) < 0) {
log_write(0, LOG_MAIN|LOG_PANIC,
"spam acl condition: spamd connection to %s, port %u failed: %s", tcp_addr, tcp_port, strerror(errno));
@@ -114,7 +138,7 @@
/* now we are connected to spamd on spamd_sock */
snprintf(CS spamd_buffer,
sizeof(spamd_buffer),
- "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %lld\r\n\r\n",
+ "REPORT SPAMC/1.3\r\nUser: %s\r\nContent-length: %lld\r\n\r\n",
user_name,
mbox_size);
@@ -170,34 +194,53 @@
/* reading done */
close(spamd_sock);
+ p = spamd_buffer;
+
/* dig in the spamd output and put the report in a multiline header, if requested */
- if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
- spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
-
- /* try to fall back to pre-2.50 spamd output */
- if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
- spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
+ if( sscanf(CS p,"SPAMD/%d.%d %d %*s\r\n%n",&spamd_major,&spamd_minor,&spamd_status,&advance) != 3) {
log_write(0, LOG_MAIN|LOG_PANIC,
- "spam acl condition: cannot parse spamd output");
+ "spam acl condition: cannot parse spamd response");
return DEFER;
- };
- };
+ }
+ p += advance;
+
+ if( spamd_status != 0 ) {
+ log_write(0, LOG_MAIN,
+ "spam acl condition: spamd returns status %d");
+ return DEFER;
+ }
+
+ /* process header */
+ while (*p && (*p!='\r' || *(p+1)!='\n')) {
+ /* process header field here */
+ /* skip header field */
+ while (*p && (*p!='\r' || *(p+1)!='\n')) ++p;
+ if (*p) p+=2;
+ }
+ /* skip empty line after header */
+ if (*p) p+=2;
+
+ /* process body */
+ if( sscanf(CS p,"%lf/%lf\r\n%n",&spamd_score,&spamd_threshold,&advance) != 2 ) {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "spam acl condition: cannot parse spamd response body");
+ return DEFER;
+ }
+ p+=advance;
/* Create report. Since this is a multiline string,
we must hack it into shape first */
- p = &spamd_buffer[spamd_report_offset];
- q = spam_report_buffer;
+ spam_report = NULL;
+ capacity = length = 0;
while (*p != '\0') {
/* skip \r */
if (*p == '\r') {
p++;
continue;
};
- *q = *p;
- q++;
+ spam_report = string_cat(spam_report,&capacity,&length,p,1);
if (*p == '\n') {
- *q = '\t';
- q++;
+ spam_report = string_cat(spam_report,&capacity,&length,CUS "\t",1);
/* eat whitespace */
while( (*p <= ' ') && (*p != '\0') ) {
p++;
@@ -206,39 +249,28 @@
};
p++;
};
- /* NULL-terminate */
- *q = '\0';
- q--;
/* cut off trailing leftovers */
- while (*q <= ' ') {
- *q = '\0';
- q--;
- };
- spam_report = spam_report_buffer;
+ while (length>0 && spam_report[length-1]<=' ') --length;
+ /* NUL-terminate */
+ spam_report = string_cat(spam_report,&capacity,&length,CUS "",1);
/* create spam bar */
spamd_score_char = spamd_score > 0 ? '+' : '-';
- j = abs((int)(spamd_score));
- i = 0;
- if( j != 0 ) {
- while((i < j) && (i <= MAX_SPAM_BAR_CHARS))
- spam_bar_buffer[i++] = spamd_score_char;
- }
- else{
- spam_bar_buffer[0] = '/';
- i = 1;
+ spam_bar = NULL;
+ length = capacity = 0;
+ if ((j = abs((int)(spamd_score)))==0) {
+ spam_bar = string_cat(spam_bar,&capacity,&length,CUS "/",1);
+ }
+ else {
+ for (i=0; i<j && i<=MAX_SPAM_BAR_CHARS; ++i) spam_bar = string_cat(spam_bar,&capacity,&length,&spamd_score_char,1);
}
- spam_bar_buffer[i] = '\0';
- spam_bar = spam_bar_buffer;
+ spam_bar[length]='\0';
/* create "float" spam score */
- snprintf(CS spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score);
- spam_score = spam_score_buffer;
+ spam_score = string_sprintf("%.1f", spamd_score);
/* create "int" spam score */
- j = (int)(spamd_score*10);
- snprintf(CS spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j);
- spam_score_int = spam_score_int_buffer;
+ spam_score_int = string_sprintf("%d",(int)(spamd_score*10));
/* compare threshold against score */
if (spamd_score >= spamd_threshold) {
@@ -250,10 +282,18 @@
spam_rc = FAIL;
};
- /* remember user name and "been here" for it */
- Ustrcpy(prev_user_name, user_name);
- spam_ok = 1;
-
+ /* cache result */
+ *entry = store_get(sizeof(struct SpamCache));
+ (*entry)->tcp_addr = tcp_addr;
+ (*entry)->tcp_port = tcp_port;
+ (*entry)->user_name = user_name;
+ (*entry)->spam_score = spam_score;
+ (*entry)->spam_score_int = spam_score_int;
+ (*entry)->spam_bar = spam_bar;
+ (*entry)->spam_report = spam_report;
+ (*entry)->spam_rc = spam_rc;
+ (*entry)->next = (struct SpamCache*)0;
+
if (override) {
/* always return OK, no matter what the score */
return OK;
@@ -262,3 +302,8 @@
return spam_rc;
};
}
+
+void spam_reset(void)
+{
+ spamCache = (struct SpamCache*)0;
+}
--- src/spool_mbox.c.orig 2003-08-14 11:29:31.000000000 +0200
+++ src/spool_mbox.c 2003-08-14 11:35:11.000000000 +0200
@@ -16,7 +16,7 @@
/* externals, we must reset them on unspooling */
extern int demime_ok;
extern int malware_ok;
-extern int spam_ok;
+extern void spam_reset(void);
extern struct file_extension *file_extensions;
int spool_mbox_ok = 0;
@@ -124,7 +124,7 @@
/* reset all exiscan state variables */
demime_ok = 0;
file_extensions = NULL;
- spam_ok = 0;
+ spam_reset();
malware_ok = 0;
if (spool_mbox_ok) {