Re: [Exim] Exim 4.21 released

Top Page
Delete this message
Reply to this message
Author: Michael Haardt
Date:  
To: exim-users
Subject: Re: [Exim] Exim 4.21 released
> 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) {