Disable SSLv3 in libspamc.c (patch from Debian spamassassin package).
[claws.git] / src / plugins / spamassassin / libspamc.c
index dca8cda74054c345b79c658e3b7df7896662d9d3..623c1ab33dbda4ee4cbca6f83d8f1d37307720c8 100644 (file)
@@ -1,9 +1,10 @@
 /* <@LICENSE>
- * Copyright 2004 Apache Software Foundation
- * 
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at:
  * 
  *     http://www.apache.org/licenses/LICENSE-2.0
  * 
  * </@LICENSE>
  */
 
+/* 
+  Compile with extra warnings -- gcc only, not suitable for use as default:
+
+  gcc -Wextra -Wdeclaration-after-statement -Wall -g -O2 spamc/spamc.c \
+  spamc/getopt.c spamc/libspamc.c spamc/utils.c -o spamc/spamc -ldl -lz
+ */
+
 #include "config.h"
+#include "claws-features.h"
 #include "libspamc.h"
-#include "utils.h"
 
+#include <stdarg.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <stdio.h>
 #ifdef HAVE_SYS_TIME_H
 #include <sys/time.h>
 #endif
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
 
-#define MAX_CONNECT_RETRIES 3
-#define CONNECT_RETRY_SLEEP 1
+/* must load *after* errno.h, Bug 6697 */
+#include "utils.h"
 
 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
 /* KAM 12-4-01 */
 #define h_errno errno
 #endif
 
+#ifdef _WIN32
+#define spamc_get_errno()   WSAGetLastError()
+#else
+#define spamc_get_errno()   errno
+#endif
+
 #ifndef HAVE_OPTARG
 extern char *optarg;
 #endif
@@ -94,9 +112,25 @@ extern char *optarg;
 #endif
 
 #undef DO_CONNECT_DEBUG_SYSLOGS
-/* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
+/*
+#define DO_CONNECT_DEBUG_SYSLOGS 1
+#define CONNECT_DEBUG_LEVEL LOG_DEBUG
+*/
+
+/* bug 4477 comment 14 */
+#ifdef NI_MAXHOST
+#define SPAMC_MAXHOST NI_MAXHOST
+#else
+#define SPAMC_MAXHOST 256
+#endif
+
+#ifdef NI_MAXSERV
+#define SPAMC_MAXSERV NI_MAXSERV
+#else
+#define SPAMC_MAXSERV 256
+#endif
 
-static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
+/* static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;  No longer seems to be used */
 
 /* set EXPANSION_ALLOWANCE to something more than might be
    added to a message in X-headers and the report template */
@@ -109,7 +143,7 @@ static const int EXPANSION_ALLOWANCE = 16384;
  */
 
 /* Set the protocol version that this spamc speaks */
-static const char *PROTOCOL_VERSION = "SPAMC/1.3";
+static const char *PROTOCOL_VERSION = "SPAMC/1.5";
 
 /* "private" part of struct message.
  * we use this instead of the struct message directly, so that we
@@ -118,9 +152,16 @@ static const char *PROTOCOL_VERSION = "SPAMC/1.3";
 struct libspamc_private_message
 {
     int flags;                 /* copied from "flags" arg to message_read() */
+    int alloced_size;           /* allocated space for the "out" buffer */
+
+    void (*spamc_header_callback)(struct message *m, int flags, char *buf, int len);
+    void (*spamd_header_callback)(struct message *m, int flags, const char *buf, int len);
 };
 
+void (*libspamc_log_callback)(int flags, int level, char *msg, va_list args) = NULL;
+
 int libspamc_timeout = 0;
+int libspamc_connect_timeout = 0;      /* Sep 8, 2008 mrgus: separate connect timeout */
 
 /*
  * translate_connect_errno()
@@ -160,17 +201,27 @@ static int _translate_connect_errno(int err)
 /*
  * opensocket()
  *
- *     Given a socket type (PF_INET or PF_UNIX), try to create this socket
- *     and store the FD in the pointed-to place. If it's successful, do any
- *     other setup required to make the socket ready to use, such as setting
- *     TCP_NODELAY mode, and in any case we return EX_OK if all is well.
+ *     Given a socket family (PF_INET or PF_INET6 or PF_UNIX), try to
+ *     create this socket and store the FD in the pointed-to place.
+ *     If it's successful, do any other setup required to make the socket
+ *     ready to use, such as setting TCP_NODELAY mode, and in any case
+ *      we return EX_OK if all is well.
  *
  *     Upon failure we return one of the other EX_??? error codes.
  */
+#ifdef SPAMC_HAS_ADDRINFO
+static int _opensocket(int flags, struct addrinfo *res, int *psock)
+{
+#else
 static int _opensocket(int flags, int type, int *psock)
 {
-    const char *typename;
     int proto = 0;
+#endif
+    const char *typename;
+    int origerr;
+#ifdef _WIN32
+    int socktout;
+#endif
 
     assert(psock != 0);
 
@@ -179,6 +230,22 @@ static int _opensocket(int flags, int type, int *psock)
         * type given by the user. The typename is strictly used for debug
         * reporting.
         */
+#ifdef SPAMC_HAS_ADDRINFO
+    switch(res->ai_family) {
+       case PF_UNIX:
+          typename = "PF_UNIX";
+          break;
+       case PF_INET:
+          typename = "PF_INET";
+          break;
+       case PF_INET6:
+          typename = "PF_INET6";
+          break;
+       default:
+          typename = "Unknown";
+          break;
+    }
+#else
     if (type == PF_UNIX) {
        typename = "PF_UNIX";
     }
@@ -186,30 +253,33 @@ static int _opensocket(int flags, int type, int *psock)
        typename = "PF_INET";
        proto = IPPROTO_TCP;
     }
+#endif
 
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-    libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
+    libspamc_log(flags, CONNECT_DEBUG_LEVEL, "dbg: create socket(%s)", typename);
 #endif
 
+#ifdef SPAMC_HAS_ADDRINFO
+    if ((*psock = socket(res->ai_family, res->ai_socktype, res->ai_protocol))
+#else
     if ((*psock = socket(type, SOCK_STREAM, proto))
+#endif
 #ifndef _WIN32
        < 0
 #else
        == INVALID_SOCKET
 #endif
        ) {
-       int origerr;
 
                /*--------------------------------------------------------
                 * At this point we had a failure creating the socket, and
                 * this is pretty much fatal. Translate the error reason
                 * into something the user can understand.
                 */
+       origerr = spamc_get_errno();
 #ifndef _WIN32
-       origerr = errno;        /* take a copy before syslog() */
        libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
 #else
-       origerr = WSAGetLastError();
        libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
 #endif
 
@@ -232,6 +302,29 @@ static int _opensocket(int flags, int type, int *psock)
        }
     }
 
+#ifdef _WIN32
+    /* bug 4344: makes timeout functional on Win32 */
+    socktout = libspamc_timeout * 1000;
+    if (type == PF_INET
+        && setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, (char *)&socktout, sizeof(socktout)) != 0)
+    {
+
+        origerr = spamc_get_errno();
+        switch (origerr)
+        {
+        case EBADF:
+        case ENOTSOCK:
+        case ENOPROTOOPT:
+        case EFAULT:
+            libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerr);
+            closesocket(*psock);
+            return EX_SOFTWARE;
+
+        default:
+            break;             /* ignored */
+        }
+    }
+#endif
 
        /*----------------------------------------------------------------
         * Do a bit of setup on the TCP socket if required. Notes above
@@ -241,14 +334,12 @@ static int _opensocket(int flags, int type, int *psock)
     {
        int one = 1;
 
-       if (type == PF_INET
-           && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
-           int origerrno;
-#ifndef _WIN32
-           origerr = errno;
-#else
-           origerrno = WSAGetLastError();
+       if ( (   type == PF_INET
+#ifdef PF_INET6
+              || type == PF_INET6
 #endif
+             ) && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
+           origerr = spamc_get_errno();
            switch (origerr) {
            case EBADF:
            case ENOTSOCK:
@@ -286,16 +377,28 @@ static int _try_to_connect_unix(struct transport *tp, int *sockptr)
 #ifndef _WIN32
     int mysock, status, origerr;
     struct sockaddr_un addrbuf;
+#ifdef SPAMC_HAS_ADDRINFO
+    struct addrinfo hints, *res;
+#else
+    int res = PF_UNIX;
+#endif
     int ret;
 
     assert(tp != 0);
     assert(sockptr != 0);
     assert(tp->socketpath != 0);
 
+#ifdef SPAMC_HAS_ADDRINFO
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = PF_UNIX;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = 0;
+    res = &hints;
+#endif
        /*----------------------------------------------------------------
         * If the socket itself can't be created, this is a fatal error.
         */
-    if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
+    if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK)
        return ret;
 
     /* set up the UNIX domain socket */
@@ -305,17 +408,17 @@ static int _try_to_connect_unix(struct transport *tp, int *sockptr)
     addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
 
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-    libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
+    libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
           addrbuf.sun_path);
 #endif
 
-    status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
+    status = timeout_connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
 
     origerr = errno;
 
     if (status >= 0) {
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-       libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
+       libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
 #endif
 
        *sockptr = mysock;
@@ -323,7 +426,7 @@ static int _try_to_connect_unix(struct transport *tp, int *sockptr)
        return EX_OK;
     }
 
-    libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
+    libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd using --socket='%s' failed: %s",
           addrbuf.sun_path, strerror(origerr));
     closesocket(mysock);
 
@@ -348,78 +451,158 @@ static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
     int numloops;
     int origerr = 0;
     int ret;
+#ifdef SPAMC_HAS_ADDRINFO
+    struct addrinfo *res = NULL;
+    char port[SPAMC_MAXSERV-1]; /* port, for logging */
+#else
+    int res = PF_INET;
+#endif
+    char host[SPAMC_MAXHOST-1]; /* hostname, for logging */
+    int connect_retries, retry_sleep;
 
     assert(tp != 0);
     assert(sockptr != 0);
     assert(tp->nhosts > 0);
 
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-    for (numloops = 0; numloops < tp->nhosts; numloops++) {
-       libspamc_log(tp->flags, LOG_ERR, "dbg: %d/%d: %s",
-               numloops + 1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
+    /* default values */
+    retry_sleep = tp->retry_sleep;
+    connect_retries = tp->connect_retries;
+    if (connect_retries == 0) {
+      connect_retries = 3;
+    }
+    if (retry_sleep < 0) {
+      retry_sleep = 1;
     }
-#endif
 
-    for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
-       struct sockaddr_in addrbuf;
-       const int hostix = numloops % tp->nhosts;
-       int status, mysock;
-       const char *ipaddr;
+    for (numloops = 0; numloops < connect_retries; numloops++) {
+        const int hostix = numloops % tp->nhosts;
+        int status, mysock;
+        int innocent = 0;
+
+                /*--------------------------------------------------------
+                * We always start by creating the socket, as we get only
+                * one attempt to connect() on each one. If this fails,
+                * we're done.
+                */
+
+#ifdef SPAMC_HAS_ADDRINFO
+        res = tp->hosts[hostix];
+        while(res) {
+            char *family = NULL;
+            switch(res->ai_family) {
+            case AF_INET:
+                family = "AF_INET";
+                break;
+            case AF_INET6:
+                family = "AF_INET6";
+                break;
+            default:
+                family = "Unknown";
+                break;
+            }
+
+            if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK) {
+                res = res->ai_next;
+                continue;
+            }
+
+            getnameinfo(res->ai_addr, res->ai_addrlen,
+                  host, sizeof(host),
+                  port, sizeof(port),
+                  NI_NUMERICHOST|NI_NUMERICSERV);
 
-               /*--------------------------------------------------------
-                * We always start by creating the socket, as we get only
-                * one attempt to connect() on each one. If this fails,
-                * we're done.
-                */
-       if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
-           return ret;
-
-       memset(&addrbuf, 0, sizeof(addrbuf));
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+            libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
+              "dbg: connect(%s) to spamd (host %s, port %s) (try #%d of %d)",
+                      family, host, port, numloops + 1, connect_retries);
+#endif
 
-       addrbuf.sin_family = AF_INET;
-       addrbuf.sin_port = htons(tp->port);
-       addrbuf.sin_addr = tp->hosts[hostix];
+            /* this is special-cased so that we have an address we can
+             * safely use as an "always fail" test case */
+            if (!strcmp(host, "255.255.255.255")) {
+              libspamc_log(tp->flags, LOG_ERR,
+                          "connect to spamd on %s failed, broadcast addr",
+                          host);
+              status = -1;
+            }
+            else {
+              status = timeout_connect(mysock, res->ai_addr, res->ai_addrlen);
+              if (status != 0) origerr = spamc_get_errno();
+            }
 
-       ipaddr = inet_ntoa(addrbuf.sin_addr);
+#else
+           struct sockaddr_in addrbuf;
+           const char *ipaddr;
+           const char* family="AF_INET";
+           if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
+             return ret;
+           
+           memset(&addrbuf, 0, sizeof(addrbuf));
+           
+           addrbuf.sin_family = AF_INET;
+           addrbuf.sin_port = htons(tp->port);
+           addrbuf.sin_addr = tp->hosts[hostix];
+           
+           ipaddr = inet_ntoa(addrbuf.sin_addr);
+
+            /* make a copy in host, for logging (bug 5577) */
+            strncpy (host, ipaddr, sizeof(host) - 1);
 
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-       libspamc_log(tp->flags, DEBUG_LEVEL,
-              "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
-               ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
+           libspamc_log(tp->flags, LOG_DEBUG,
+                        "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
+                        ipaddr, numloops + 1, connect_retries);
 #endif
 
-       status =
-           connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
+            /* this is special-cased so that we have an address we can
+             * safely use as an "always fail" test case */
+            if (!strcmp(ipaddr, "255.255.255.255")) {
+              libspamc_log(tp->flags, LOG_ERR,
+                          "connect to spamd on %s failed, broadcast addr",
+                          ipaddr);
+              status = -1;
+            }
+            else {
+              status = timeout_connect(mysock, (struct sockaddr *) &addrbuf,
+                        sizeof(addrbuf));
+              if (status != 0) origerr = spamc_get_errno();
+            }
 
-       if (status != 0) {
-#ifndef _WIN32
-           origerr = errno;
-           libspamc_log(tp->flags, LOG_ERR,
-                  "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
-                  ipaddr, numloops + 1, MAX_CONNECT_RETRIES, strerror(origerr));
+#endif
+
+            if (status != 0) {
+                  closesocket(mysock);
+
+                  innocent = origerr == ECONNREFUSED && numloops+1 < tp->nhosts;
+                  libspamc_log(tp->flags, innocent ? LOG_DEBUG : LOG_ERR,
+                      "connect to spamd on %s failed, retrying (#%d of %d): %s",
+                      host, numloops+1, connect_retries,
+#ifdef _WIN32
+                      origerr
 #else
-           origerr = WSAGetLastError();
-           libspamc_log(tp->flags, LOG_ERR,
-                  "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
-                  ipaddr, numloops + 1, MAX_CONNECT_RETRIES, origerr);
+                      strerror(origerr)
 #endif
-           closesocket(mysock);
+                  );
 
-           sleep(CONNECT_RETRY_SLEEP);
-       }
-       else {
+            } else {
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-           libspamc_log(tp->flags, DEBUG_LEVEL,
-                  "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
+                  libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
+                          "dbg: connect(%s) to spamd done",family);
 #endif
-           *sockptr = mysock;
+                  *sockptr = mysock;
 
-           return EX_OK;
-       }
-    }
+                  return EX_OK;
+            }
+#ifdef SPAMC_HAS_ADDRINFO
+            res = res->ai_next;
+        }
+#endif
+        if (numloops+1 < connect_retries && !innocent) sleep(retry_sleep);
+    } /* for(numloops...) */
 
-    libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
-           MAX_CONNECT_RETRIES);
+    libspamc_log(tp->flags, LOG_ERR,
+              "connection attempt to spamd aborted after %d retries",
+              connect_retries);
 
     return _translate_connect_errno(origerr);
 }
@@ -443,11 +626,30 @@ static void _clear_message(struct message *m)
     m->is_spam = EX_TOOBIG;
     m->score = 0.0;
     m->threshold = 0.0;
+    m->outbuf = NULL;
     m->out = NULL;
     m->out_len = 0;
     m->content_length = -1;
 }
 
+static void _free_zlib_buffer(unsigned char **zlib_buf, int *zlib_bufsiz)
+{
+       if(*zlib_buf) {
+       free(*zlib_buf);
+       *zlib_buf=NULL;
+       }
+       *zlib_bufsiz=0;
+}
+
+static void _use_msg_for_out(struct message *m)
+{
+    if (m->outbuf)
+       free(m->outbuf);
+    m->outbuf = NULL;
+    m->out = m->msg;
+    m->out_len = m->msg_len;
+}
+
 static int _message_read_raw(int fd, struct message *m)
 {
     _clear_message(m);
@@ -461,8 +663,13 @@ static int _message_read_raw(int fd, struct message *m)
        return EX_IOERR;
     }
     m->type = MESSAGE_ERROR;
-    if (m->raw_len > m->max_len)
+    if (m->raw_len > (int) m->max_len)
+    {
+        libspamc_log(m->priv->flags, LOG_NOTICE,
+                "skipped message, greater than max message size (%d bytes)",
+                m->max_len);
        return EX_TOOBIG;
+    }
     m->type = MESSAGE_RAW;
     m->msg = m->raw;
     m->msg_len = m->raw_len;
@@ -473,8 +680,9 @@ static int _message_read_raw(int fd, struct message *m)
 
 static int _message_read_bsmtp(int fd, struct message *m)
 {
-    unsigned int i, j;
+    unsigned int i, j, p_len;
     char prev;
+    char* p;
 
     _clear_message(m);
     if ((m->raw = malloc(m->max_len + 1)) == NULL)
@@ -489,44 +697,52 @@ static int _message_read_bsmtp(int fd, struct message *m)
        return EX_IOERR;
     }
     m->type = MESSAGE_ERROR;
-    if (m->raw_len > m->max_len)
+    if (m->raw_len > (int) m->max_len)
        return EX_TOOBIG;
-    m->pre = m->raw;
-    for (i = 0; i < m->raw_len - 6; i++) {
-       if ((m->raw[i] == '\n') &&
-           (m->raw[i + 1] == 'D' || m->raw[i + 1] == 'd') &&
-           (m->raw[i + 2] == 'A' || m->raw[i + 2] == 'a') &&
-           (m->raw[i + 3] == 'T' || m->raw[i + 3] == 't') &&
-           (m->raw[i + 4] == 'A' || m->raw[i + 4] == 'a') &&
-           ((m->raw[i + 5] == '\r' && m->raw[i + 6] == '\n')
-            || m->raw[i + 5] == '\n')) {
-           /* Found it! */
-           i += 6;
-           if (m->raw[i - 1] == '\r')
-               i++;
-           m->pre_len = i;
-           m->msg = m->raw + i;
-           m->msg_len = m->raw_len - i;
-           break;
+    p = m->pre = m->raw;
+    /* Search for \nDATA\n which marks start of actual message */
+    while ((p_len = (m->raw_len - (p - m->raw))) > 8) { /* leave room for at least \nDATA\n.\n */
+      char* q = memchr(p, '\n', p_len - 8);  /* find next \n then see if start of \nDATA\n */
+      if (q == NULL) break;
+      q++;
+      if (((q[0]|0x20) == 'd') && /* case-insensitive ASCII comparison */
+         ((q[1]|0x20) == 'a') &&
+         ((q[2]|0x20) == 't') &&
+         ((q[3]|0x20) == 'a')) {
+       q+=4;
+       if (q[0] == '\r') ++q;
+       if (*(q++) == '\n') {  /* leave q at start of message if we found it */
+         m->msg = q;
+         m->pre_len = q - m->raw;
+         m->msg_len = m->raw_len - m->pre_len;
+         break;
        }
+      }
+      p = q; /* the above code ensures no other '\n' comes before q */
     }
     if (m->msg == NULL)
        return EX_DATAERR;
 
+    /* ensure this is >= 0 */
+    if (m->msg_len < 0) {
+       return EX_SOFTWARE;
+    }
+
     /* Find the end-of-DATA line */
     prev = '\n';
-    for (i = j = 0; i < m->msg_len; i++) {
+    for (i = j = 0; i < (unsigned int) m->msg_len; i++) {
        if (prev == '\n' && m->msg[i] == '.') {
            /* Dot at the beginning of a line */
-           if ((m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')
-               || m->msg[i + 1] == '\n') {
+            if (((int) (i+1) == m->msg_len)
+                || ((int) (i+1) < m->msg_len && m->msg[i + 1] == '\n')
+                || ((int) (i+2) < m->msg_len && m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')) {
                /* Lone dot! That's all, folks */
                m->post = m->msg + i;
                m->post_len = m->msg_len - i;
                m->msg_len = j;
                break;
            }
-           else if (m->msg[i + 1] == '.') {
+           else if ((int) (i+1) < m->msg_len && m->msg[i + 1] == '.') {
                /* Escaping dot, eliminate. */
                prev = '.';
                continue;
@@ -536,6 +752,9 @@ static int _message_read_bsmtp(int fd, struct message *m)
        m->msg[j++] = m->msg[i];
     }
 
+    /* if bad format with no end "\n.\n", error out */
+    if (m->post == NULL)
+       return EX_DATAERR;
     m->type = MESSAGE_BSMTP;
     m->out = m->msg;
     m->out_len = m->msg_len;
@@ -544,6 +763,8 @@ static int _message_read_bsmtp(int fd, struct message *m)
 
 int message_read(int fd, int flags, struct message *m)
 {
+    assert(m != NULL);
+
     libspamc_timeout = 0;
 
     /* create the "private" part of the struct message */
@@ -553,6 +774,14 @@ int message_read(int fd, int flags, struct message *m)
        return EX_OSERR;
     }
     m->priv->flags = flags;
+    m->priv->alloced_size = 0;
+    m->priv->spamc_header_callback = 0;
+    m->priv->spamd_header_callback = 0;
+
+    if (flags & SPAMC_PING) {
+      _clear_message(m);
+      return EX_OK;
+    }
 
     switch (flags & SPAMC_MODE_MASK) {
     case SPAMC_RAW_MODE:
@@ -575,7 +804,9 @@ long message_write(int fd, struct message *m)
     off_t jlimit;
     char buffer[1024];
 
-    if (m->priv->flags & SPAMC_CHECK_ONLY) {
+    assert(m != NULL);
+
+    if (m->priv->flags & (SPAMC_CHECK_ONLY|SPAMC_PING)) {
        if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
            return full_write(fd, 1, m->out, m->out_len);
 
@@ -627,17 +858,23 @@ long message_write(int fd, struct message *m)
     }
 }
 
-void message_dump(int in_fd, int out_fd, struct message *m)
+void message_dump(int in_fd, int out_fd, struct message *m, int flags)
 {
     char buf[8196];
     int bytes;
 
-    if (m != NULL && m->type != MESSAGE_NONE) {
+    if (m == NULL) {
+       libspamc_log(flags, LOG_ERR, "oops! message_dump called with NULL message");
+       return;
+    }
+
+    if (m->type != MESSAGE_NONE) {
        message_write(out_fd, m);
     }
+
     while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
        if (bytes != full_write(out_fd, 1, buf, bytes)) {
-           libspamc_log(m->priv->flags, LOG_ERR, "oops! message_dump of %d returned different",
+           libspamc_log(flags, LOG_ERR, "oops! message_dump of %d returned different",
                   bytes);
        }
     }
@@ -718,7 +955,8 @@ static float _locale_safe_string_to_float(char *buf, int siz)
 
     cp = (dot + 1);
     postdot = (float) (strtol(cp, NULL, 10));
-    if (postdot == 0.0) {
+    /* note: don't compare floats == 0.0, it's unsafe.  use a range */
+    if (postdot >= -0.00001 && postdot <= 0.00001) {
        return ret;
     }
 
@@ -750,10 +988,13 @@ static float _locale_safe_string_to_float(char *buf, int siz)
 }
 
 static int
-_handle_spamd_header(struct message *m, int flags, char *buf, int len)
+_handle_spamd_header(struct message *m, int flags, char *buf, int len,
+                    unsigned int *didtellflags)
 {
     char is_spam[6];
     char s_str[21], t_str[21];
+    char didset_ret[15];
+    char didremove_ret[15];
 
     UNUSED_VARIABLE(len);
 
@@ -800,13 +1041,140 @@ _handle_spamd_header(struct message *m, int flags, char *buf, int len)
        }
        return EX_OK;
     }
+    else if (sscanf(buf, "DidSet: %14s", didset_ret) == 1) {
+      if (strstr(didset_ret, "local")) {
+         *didtellflags |= SPAMC_SET_LOCAL;
+       }
+       if (strstr(didset_ret, "remote")) {
+         *didtellflags |= SPAMC_SET_REMOTE;
+       }
+    }
+    else if (sscanf(buf, "DidRemove: %14s", didremove_ret) == 1) {
+        if (strstr(didremove_ret, "local")) {
+         *didtellflags |= SPAMC_REMOVE_LOCAL;
+       }
+       if (strstr(didremove_ret, "remote")) {
+         *didtellflags |= SPAMC_REMOVE_REMOTE;
+       }
+    }
+    else if (m->priv->spamd_header_callback != NULL)
+      m->priv->spamd_header_callback(m, flags, buf, len);
+
+    return EX_OK;
+}
+
+static int
+_zlib_compress (char *m_msg, int m_msg_len,
+        unsigned char **zlib_buf, int *zlib_bufsiz, int flags)
+{
+    int rc;
+    int len, totallen;
+
+#ifndef HAVE_LIBZ
+
+    UNUSED_VARIABLE(m_msg);
+    UNUSED_VARIABLE(m_msg_len);
+    UNUSED_VARIABLE(zlib_buf);
+    UNUSED_VARIABLE(zlib_bufsiz);
+    UNUSED_VARIABLE(rc);
+    UNUSED_VARIABLE(len);
+    UNUSED_VARIABLE(totallen);
+    libspamc_log(flags, LOG_ERR, "spamc not built with zlib support");
+    return EX_SOFTWARE;
+
+#else
+    z_stream strm;
+
+    UNUSED_VARIABLE(flags);
+
+    /* worst-case, according to http://www.zlib.org/zlib_tech.html ;
+      * same as input, plus 5 bytes per 16k, plus 6 bytes.  this should
+      * be plenty */
+    *zlib_bufsiz = (int) (m_msg_len * 1.0005) + 1024;
+    *zlib_buf = (unsigned char *) malloc (*zlib_bufsiz);
+    if (*zlib_buf == NULL) {
+        return EX_OSERR;
+    }
+
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    rc = deflateInit(&strm, 3);
+    if (rc != Z_OK) {
+        return EX_OSERR;
+    }
+
+    strm.avail_in = m_msg_len;
+    strm.next_in = (unsigned char *) m_msg;
+    strm.avail_out = *zlib_bufsiz;
+    strm.next_out = (unsigned char *) *zlib_buf;
+
+    totallen = 0;
+    do {
+        rc = deflate(&strm, Z_FINISH);
+        assert(rc != Z_STREAM_ERROR);
+        len = (size_t) (*zlib_bufsiz - strm.avail_out);
+        strm.next_out += len;
+        totallen += len;
+    } while (strm.avail_out == 0);
+
+    *zlib_bufsiz = totallen;
+    return EX_OK;
+
+#endif
+}
+
+int
+_append_original_body (struct message *m, int flags)
+{
+    char *cp, *cpend, *bodystart;
+    int bodylen, outspaceleft, towrite;
+
+    /* at this stage, m->out now contains the rewritten headers.
+     * find and append the raw message's body, up to m->priv->alloced_size
+     * bytes.
+     */
+
+#define CRNLCRNL        "\r\n\r\n"
+#define CRNLCRNL_LEN    4
+#define NLNL            "\n\n"
+#define NLNL_LEN        2
+
+    cpend = m->raw + m->raw_len;
+    bodystart = NULL;
+
+    for (cp = m->raw; cp < cpend; cp++) {
+        if (*cp == '\r' && cpend - cp >= CRNLCRNL_LEN && 
+                            !strncmp(cp, CRNLCRNL, CRNLCRNL_LEN))
+        {
+            bodystart = cp + CRNLCRNL_LEN;
+            break;
+        }
+        else if (*cp == '\n' && cpend - cp >= NLNL_LEN && 
+                           !strncmp(cp, NLNL, NLNL_LEN))
+        {
+            bodystart = cp + NLNL_LEN;
+            break;
+        }
+    }
+
+    if (bodystart == NULL) {
+        libspamc_log(flags, LOG_ERR, "failed to find end-of-headers");
+        return EX_SOFTWARE;
+    }
 
-    libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
-    return EX_PROTOCOL;
+    bodylen = cpend - bodystart;
+    outspaceleft = (m->priv->alloced_size-1) - m->out_len;
+    towrite = (bodylen < outspaceleft ? bodylen : outspaceleft);
+
+    /* copy in the body; careful not to overflow */
+    strncpy (m->out + m->out_len, bodystart, towrite);
+    m->out_len += towrite;
+    return EX_OK;
 }
 
 int message_filter(struct transport *tp, const char *username,
-                  int flags, struct message *m)
+                   int flags, struct message *m)
 {
     char buf[8192];
     size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
@@ -816,15 +1184,37 @@ int message_filter(struct transport *tp, const char *username,
     char versbuf[20];
     float version;
     int response;
-    int failureval;
+    int failureval = EX_SOFTWARE;
+    unsigned int throwaway;
     SSL_CTX *ctx = NULL;
     SSL *ssl = NULL;
-    SSL_METHOD *meth;
+    const SSL_METHOD *meth;
+    char zlib_on = 0;
+    unsigned char *zlib_buf = NULL;
+    int zlib_bufsiz = 0;
+    unsigned char *towrite_buf;
+    int towrite_len;
+    int filter_retry_count;
+    int filter_retry_sleep;
+    int filter_retries;
+    #ifdef SPAMC_HAS_ADDRINFO
+        struct addrinfo *tmphost;
+    #else
+        struct in_addr tmphost;
+    #endif
+    int nhost_counter;
+
+    assert(tp != NULL);
+    assert(m != NULL);
+
+    if ((flags & SPAMC_USE_ZLIB) != 0) {
+      zlib_on = 1;
+    }
 
     if (flags & SPAMC_USE_SSL) {
 #ifdef SPAMC_SSL
        SSLeay_add_ssl_algorithms();
-       meth = SSLv2_client_method();
+       meth = SSLv23_client_method();
        SSL_load_error_strings();
        ctx = SSL_CTX_new(meth);
 #else
@@ -837,97 +1227,184 @@ int message_filter(struct transport *tp, const char *username,
     }
 
     m->is_spam = EX_TOOBIG;
-    if ((m->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
+
+    if (m->outbuf != NULL)
+        free(m->outbuf);
+    m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
+    if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
        failureval = EX_OSERR;
        goto failure;
     }
+    m->out = m->outbuf;
     m->out_len = 0;
 
+    /* If the spamd filter takes too long and we timeout, then
+     * retry again.  This gets us around a hung child thread 
+     * in spamd or a problem on a spamd host in a multi-host
+     * setup.  If there is more than one destination host
+     * we move to the next host on each attempt.
+     */
 
-    /* Build spamd protocol header */
-    if (flags & SPAMC_CHECK_ONLY)
-       strcpy(buf, "CHECK ");
-    else if (flags & SPAMC_REPORT_IFSPAM)
-       strcpy(buf, "REPORT_IFSPAM ");
-    else if (flags & SPAMC_REPORT)
-       strcpy(buf, "REPORT ");
-    else if (flags & SPAMC_SYMBOLS)
-       strcpy(buf, "SYMBOLS ");
-    else
-       strcpy(buf, "PROCESS ");
-
-    len = strlen(buf);
-    if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
-       free(m->out);
-       m->out = m->msg;
-       m->out_len = m->msg_len;
-       return EX_OSERR;
-    }
-
-    strcat(buf, PROTOCOL_VERSION);
-    strcat(buf, "\r\n");
-    len = strlen(buf);
-
-    if (username != NULL) {
-       if (strlen(username) + 8 >= (bufsiz - len)) {
-           free(m->out);
-           m->out = m->msg;
-           m->out_len = m->msg_len;
-           return EX_OSERR;
-       }
-       strcpy(buf + len, "User: ");
-       strcat(buf + len, username);
-       strcat(buf + len, "\r\n");
-       len += strlen(buf + len);
+    /* default values */
+    filter_retry_sleep = tp->filter_retry_sleep;
+    filter_retries = tp->filter_retries;
+    if (filter_retries == 0) {
+        filter_retries = 1;
     }
-    if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
-       free(m->out);
-       m->out = m->msg;
-       m->out_len = m->msg_len;
-       return EX_OSERR;
+    if (filter_retry_sleep < 0) {
+        filter_retry_sleep = 1;
     }
-    len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
 
-    libspamc_timeout = m->timeout;
-
-    if (tp->socketpath)
-       rc = _try_to_connect_unix(tp, &sock);
-    else
-       rc = _try_to_connect_tcp(tp, &sock);
-
-    if (rc != EX_OK) {
-       free(m->out);
-       m->out = m->msg;
-       m->out_len = m->msg_len;
-       return rc;      /* use the error code try_to_connect_*() gave us. */
-    }
-
-    if (flags & SPAMC_USE_SSL) {
+    /* filterloop - Ensure that we run through this at least
+     * once, and again if there are errors 
+     */
+    filter_retry_count = 0;
+    while ((filter_retry_count==0) || 
+                ((filter_retry_count<tp->filter_retries) && (failureval == EX_IOERR)))
+    {
+        if (filter_retry_count != 0){
+            /* Ensure that the old socket gets closed */
+            if (sock != -1) {
+                closesocket(sock);
+                sock=-1;
+            }
+
+            /* Move to the next host in the list, if nhosts>1 */
+            if (tp->nhosts > 1) {
+                tmphost = tp->hosts[0];
+
+                /* TODO: free using freeaddrinfo() */
+                for (nhost_counter = 1; nhost_counter < tp->nhosts; nhost_counter++) {
+                    tp->hosts[nhost_counter - 1] = tp->hosts[nhost_counter];
+                }
+        
+                tp->hosts[nhost_counter - 1] = tmphost;
+            }
+
+            /* Now sleep the requested amount */
+            sleep(filter_retry_sleep);
+        }
+
+        filter_retry_count++;
+    
+        /* Build spamd protocol header */
+        if (flags & SPAMC_CHECK_ONLY)
+          strcpy(buf, "CHECK ");
+        else if (flags & SPAMC_REPORT_IFSPAM)
+          strcpy(buf, "REPORT_IFSPAM ");
+        else if (flags & SPAMC_REPORT)
+          strcpy(buf, "REPORT ");
+        else if (flags & SPAMC_SYMBOLS)
+          strcpy(buf, "SYMBOLS ");
+        else if (flags & SPAMC_PING)
+          strcpy(buf, "PING ");
+        else if (flags & SPAMC_HEADERS)
+          strcpy(buf, "HEADERS ");
+        else
+          strcpy(buf, "PROCESS ");
+    
+        len = strlen(buf);
+        if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
+            _use_msg_for_out(m);
+            return EX_OSERR;
+        }
+    
+        strcat(buf, PROTOCOL_VERSION);
+        strcat(buf, "\r\n");
+        len = strlen(buf);
+    
+        towrite_buf = (unsigned char *) m->msg;
+        towrite_len = (int) m->msg_len;
+        if (zlib_on) {
+            if (_zlib_compress(m->msg, m->msg_len, &zlib_buf, &zlib_bufsiz, flags) != EX_OK)
+            {
+                _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+                return EX_OSERR;
+            }
+            towrite_buf = zlib_buf;
+            towrite_len = zlib_bufsiz;
+        }
+    
+        if (!(flags & SPAMC_PING)) {
+          if (username != NULL) {
+              if (strlen(username) + 8 >= (bufsiz - len)) {
+                  _use_msg_for_out(m);
+                  if (zlib_on) {
+                      _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+                  }
+                  return EX_OSERR;
+              }
+              strcpy(buf + len, "User: ");
+              strcat(buf + len, username);
+              strcat(buf + len, "\r\n");
+              len += strlen(buf + len);
+          }
+          if (zlib_on) {
+              len += snprintf(buf + len, 8192-len, "Compress: zlib\r\n");
+          }
+          if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
+              _use_msg_for_out(m);
+              if (zlib_on) {
+                  _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+              }
+              return EX_DATAERR;
+          }
+          len += snprintf(buf + len, 8192-len, "Content-length: %d\r\n", (int) towrite_len);
+        }
+        /* bug 6187, PING needs empty line too, bumps protocol version to 1.5 */
+        len += snprintf(buf + len, 8192-len, "\r\n");
+    
+        libspamc_timeout = m->timeout;
+        libspamc_connect_timeout = m->connect_timeout; /* Sep 8, 2008 mrgus: separate connect timeout */
+
+        if (tp->socketpath)
+          rc = _try_to_connect_unix(tp, &sock);
+        else
+          rc = _try_to_connect_tcp(tp, &sock);
+    
+        if (rc != EX_OK) {
+          _use_msg_for_out(m);
+          if (zlib_on) {
+              _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+          }
+          return rc;      /* use the error code try_to_connect_*() gave us. */
+        }
+    
+        if (flags & SPAMC_USE_SSL) {
 #ifdef SPAMC_SSL
-       ssl = SSL_new(ctx);
-       SSL_set_fd(ssl, sock);
-       SSL_connect(ssl);
+            ssl = SSL_new(ctx);
+            SSL_set_fd(ssl, sock);
+            SSL_connect(ssl);
 #endif
-    }
-
-    /* Send to spamd */
-    if (flags & SPAMC_USE_SSL) {
+        }
+    
+        /* Send to spamd */
+        if (flags & SPAMC_USE_SSL) {
 #ifdef SPAMC_SSL
-       SSL_write(ssl, buf, len);
-       SSL_write(ssl, m->msg, m->msg_len);
+            SSL_write(ssl, buf, len);
+            SSL_write(ssl, towrite_buf, towrite_len);
 #endif
-    }
-    else {
-       full_write(sock, 0, buf, len);
-       full_write(sock, 0, m->msg, m->msg_len);
-       shutdown(sock, SHUT_WR);
-    }
+        }
+        else {
+            full_write(sock, 0, buf, len);
+            full_write(sock, 0, towrite_buf, towrite_len);
+            shutdown(sock, SHUT_WR);
+        }
+
+        /* free zlib buffer
+        * bug 6025: zlib buffer not freed if compression is used
+        */
+        if (zlib_on) {
+            _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+        }
+    
+        /* ok, now read and parse it.  SPAMD/1.2 line first... */
+        failureval =
+                _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
+    } /* end of filterloop */
 
-    /* ok, now read and parse it.  SPAMD/1.2 line first... */
-    failureval =
-       _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
     if (failureval != EX_OK) {
-       goto failure;
+        goto failure;
     }
 
     if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
@@ -945,6 +1422,14 @@ int message_filter(struct transport *tp, const char *username,
        goto failure;
     }
 
+    if (flags & SPAMC_PING) {
+       closesocket(sock);
+       sock = -1;
+        m->out_len = sprintf(m->out, "SPAMD/%s %d\n", versbuf, response);
+        m->is_spam = EX_NOTSPAM;
+        return EX_OK;
+    }
+
     m->score = 0;
     m->threshold = 0;
     m->is_spam = EX_TOOBIG;
@@ -959,7 +1444,7 @@ int message_filter(struct transport *tp, const char *username,
            break;              /* end of headers */
        }
 
-       if (_handle_spamd_header(m, flags, buf, len) < 0) {
+       if (_handle_spamd_header(m, flags, buf, len, &throwaway) < 0) {
            failureval = EX_PROTOCOL;
            goto failure;
        }
@@ -994,20 +1479,17 @@ int message_filter(struct transport *tp, const char *username,
 
        if (flags & SPAMC_USE_SSL) {
            len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
-                               m->max_len + EXPANSION_ALLOWANCE + 1 -
-                               m->out_len,
-                               m->max_len + EXPANSION_ALLOWANCE + 1 -
-                               m->out_len);
+                               m->priv->alloced_size - m->out_len,
+                               m->priv->alloced_size - m->out_len);
        }
        else {
            len = full_read(sock, 0, m->out + m->out_len,
-                           m->max_len + EXPANSION_ALLOWANCE + 1 - m->out_len,
-                           m->max_len + EXPANSION_ALLOWANCE + 1 -
-                           m->out_len);
+                           m->priv->alloced_size - m->out_len,
+                           m->priv->alloced_size - m->out_len);
        }
 
 
-       if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
+       if ((int) len + (int) m->out_len > (m->priv->alloced_size - 1)) {
            failureval = EX_TOOBIG;
            goto failure;
        }
@@ -1027,12 +1509,16 @@ int message_filter(struct transport *tp, const char *username,
        goto failure;
     }
 
+    if (flags & SPAMC_HEADERS) {
+        if (_append_original_body(m, flags) != EX_OK) {
+            goto failure;
+        }
+    }
+
     return EX_OK;
 
   failure:
-    free(m->out);
-    m->out = m->msg;
-    m->out_len = m->msg_len;
+       _use_msg_for_out(m);
     if (sock != -1) {
        closesocket(sock);
     }
@@ -1047,52 +1533,289 @@ int message_filter(struct transport *tp, const char *username,
     return failureval;
 }
 
-
 int message_process(struct transport *trans, char *username, int max_size,
                    int in_fd, int out_fd, const int flags)
 {
     int ret;
     struct message m;
 
+    assert(trans != NULL);
+
     m.type = MESSAGE_NONE;
 
-    m.max_len = max_size;
+    /* enforce max_size being unsigned, therefore >= 0 */
+    if (max_size < 0) {
+       ret = EX_SOFTWARE;
+        goto FAIL;
+    }
+    m.max_len = (unsigned int) max_size;
+
     ret = message_read(in_fd, flags, &m);
     if (ret != EX_OK)
-       goto FAIL;
+        goto FAIL;
     ret = message_filter(trans, username, flags, &m);
     if (ret != EX_OK)
-       goto FAIL;
+        goto FAIL;
     if (message_write(out_fd, &m) < 0)
-       goto FAIL;
+        goto FAIL;
     if (m.is_spam != EX_TOOBIG) {
-       message_cleanup(&m);
-       return m.is_spam;
+        message_cleanup(&m);
+        return m.is_spam;
     }
     message_cleanup(&m);
     return ret;
 
   FAIL:
     if (flags & SPAMC_CHECK_ONLY) {
-       full_write(out_fd, 1, "0/0\n", 4);
-       message_cleanup(&m);
-       return EX_NOTSPAM;
+        full_write(out_fd, 1, "0/0\n", 4);
+        message_cleanup(&m);
+        return EX_NOTSPAM;
     }
     else {
-       message_dump(in_fd, out_fd, &m);
-       message_cleanup(&m);
-       return ret;
+        message_dump(in_fd, out_fd, &m, flags);
+        message_cleanup(&m);
+        return ret;
+    }
+}
+
+int message_tell(struct transport *tp, const char *username, int flags,
+                struct message *m, int msg_class,
+                unsigned int tellflags, unsigned int *didtellflags)
+{
+    char buf[8192];
+    size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
+    size_t len;
+    int sock = -1;
+    int rc;
+    char versbuf[20];
+    float version;
+    int response;
+    int failureval;
+    SSL_CTX *ctx = NULL;
+    SSL *ssl = NULL;
+    const SSL_METHOD *meth;
+
+    assert(tp != NULL);
+    assert(m != NULL);
+
+    if (flags & SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+       SSLeay_add_ssl_algorithms();
+       meth = SSLv23_client_method();
+       SSL_load_error_strings();
+       ctx = SSL_CTX_new(meth);
+#else
+       UNUSED_VARIABLE(ssl);
+       UNUSED_VARIABLE(meth);
+       UNUSED_VARIABLE(ctx);
+       libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
+       return EX_SOFTWARE;
+#endif
+    }
+
+    m->is_spam = EX_TOOBIG;
+
+    if (m->outbuf != NULL)
+        free(m->outbuf);
+    m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
+    if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
+       failureval = EX_OSERR;
+       goto failure;
+    }
+    m->out = m->outbuf;
+    m->out_len = 0;
+
+    /* Build spamd protocol header */
+    strcpy(buf, "TELL ");
+
+    len = strlen(buf);
+    if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
+       _use_msg_for_out(m);
+       return EX_OSERR;
+    }
+
+    strcat(buf, PROTOCOL_VERSION);
+    strcat(buf, "\r\n");
+    len = strlen(buf);
+
+    if (msg_class != 0) {
+      strcpy(buf + len, "Message-class: ");
+      if (msg_class == SPAMC_MESSAGE_CLASS_SPAM) {
+       strcat(buf + len, "spam\r\n");
+      }
+      else {
+       strcat(buf + len, "ham\r\n");
+      }
+      len += strlen(buf + len);
+    }
+
+    if ((tellflags & SPAMC_SET_LOCAL) || (tellflags & SPAMC_SET_REMOTE)) {
+      int needs_comma_p = 0;
+      strcat(buf + len, "Set: ");
+      if (tellflags & SPAMC_SET_LOCAL) {
+       strcat(buf + len, "local");
+       needs_comma_p = 1;
+      }
+      if (tellflags & SPAMC_SET_REMOTE) {
+       if (needs_comma_p == 1) {
+         strcat(buf + len, ",");
+       }
+       strcat(buf + len, "remote");
+      }
+      strcat(buf + len, "\r\n");
+      len += strlen(buf + len);
+    }
+
+    if ((tellflags & SPAMC_REMOVE_LOCAL) || (tellflags & SPAMC_REMOVE_REMOTE)) {
+      int needs_comma_p = 0;
+      strcat(buf + len, "Remove: ");
+      if (tellflags & SPAMC_REMOVE_LOCAL) {
+       strcat(buf + len, "local");
+       needs_comma_p = 1;
+      }
+      if (tellflags & SPAMC_REMOVE_REMOTE) {
+       if (needs_comma_p == 1) {
+         strcat(buf + len, ",");
+       }
+       strcat(buf + len, "remote");
+      }
+      strcat(buf + len, "\r\n");
+      len += strlen(buf + len);
+    }
+
+    if (username != NULL) {
+       if (strlen(username) + 8 >= (bufsiz - len)) {
+           _use_msg_for_out(m);
+           return EX_OSERR;
+       }
+       strcpy(buf + len, "User: ");
+       strcat(buf + len, username);
+       strcat(buf + len, "\r\n");
+       len += strlen(buf + len);
+    }
+    if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
+       _use_msg_for_out(m);
+       return EX_DATAERR;
+    }
+    len += sprintf(buf + len, "Content-length: %d\r\n\r\n", (int) m->msg_len);
+
+    if (m->priv->spamc_header_callback != NULL) {
+      char buf2[1024];
+      m->priv->spamc_header_callback(m, flags, buf2, 1024);
+      strncat(buf, buf2, bufsiz - len);
+    }
+
+    libspamc_timeout = m->timeout;
+    libspamc_connect_timeout = m->connect_timeout;     /* Sep 8, 2008 mrgus: separate connect timeout */
+
+    if (tp->socketpath)
+       rc = _try_to_connect_unix(tp, &sock);
+    else
+       rc = _try_to_connect_tcp(tp, &sock);
+
+    if (rc != EX_OK) {
+       _use_msg_for_out(m);
+       return rc;      /* use the error code try_to_connect_*() gave us. */
+    }
+
+    if (flags & SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+       ssl = SSL_new(ctx);
+       SSL_set_fd(ssl, sock);
+       SSL_connect(ssl);
+#endif
+    }
+
+    /* Send to spamd */
+    if (flags & SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+       SSL_write(ssl, buf, len);
+       SSL_write(ssl, m->msg, m->msg_len);
+#endif
+    }
+    else {
+       full_write(sock, 0, buf, len);
+       full_write(sock, 0, m->msg, m->msg_len);
+       shutdown(sock, SHUT_WR);
+    }
+
+    /* ok, now read and parse it.  SPAMD/1.2 line first... */
+    failureval =
+       _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
+    if (failureval != EX_OK) {
+       goto failure;
+    }
+
+    if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
+       libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
+       failureval = EX_PROTOCOL;
+       goto failure;
+    }
+
+    versbuf[19] = '\0';
+    version = _locale_safe_string_to_float(versbuf, 20);
+    if (version < 1.0) {
+       libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
+              versbuf);
+       failureval = EX_PROTOCOL;
+       goto failure;
+    }
+
+    m->score = 0;
+    m->threshold = 0;
+    m->is_spam = EX_TOOBIG;
+    while (1) {
+       failureval =
+           _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
+       if (failureval != EX_OK) {
+           goto failure;
+       }
+
+       if (len == 0 && buf[0] == '\0') {
+           break;              /* end of headers */
+       }
+
+       if (_handle_spamd_header(m, flags, buf, len, didtellflags) < 0) {
+           failureval = EX_PROTOCOL;
+           goto failure;
+       }
+    }
+
+    len = 0;                   /* overwrite those headers */
+
+    shutdown(sock, SHUT_RD);
+    closesocket(sock);
+    sock = -1;
+
+    libspamc_timeout = 0;
+
+    return EX_OK;
+
+  failure:
+    _use_msg_for_out(m);
+    if (sock != -1) {
+        closesocket(sock);
     }
+    libspamc_timeout = 0;
+
+    if (flags & SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+       SSL_free(ssl);
+       SSL_CTX_free(ctx);
+#endif
+    }
+    return failureval;
 }
 
 void message_cleanup(struct message *m)
 {
-    if (m->out != NULL)
-       free(m->out);
-    if (m->raw != NULL && m->raw != m->out)
-       free(m->raw);
-    if (m->priv != NULL && (char*)m->priv != m->out && (char*)m->priv != m->raw)
-       free(m->priv);
+    assert(m != NULL);
+    if (m->outbuf != NULL)
+        free(m->outbuf);
+    if (m->raw != NULL)
+        free(m->raw);
+    if (m->priv != NULL)
+        free(m->priv);
     _clear_message(m);
 }
 
@@ -1105,19 +1828,19 @@ int process_message(struct transport *tp, char *username, int max_size,
 
     flags = SPAMC_RAW_MODE;
     if (my_check_only)
-       flags |= SPAMC_CHECK_ONLY;
+        flags |= SPAMC_CHECK_ONLY;
     if (my_safe_fallback)
-       flags |= SPAMC_SAFE_FALLBACK;
+        flags |= SPAMC_SAFE_FALLBACK;
 
     return message_process(tp, username, max_size, in_fd, out_fd, flags);
 }
 
 /*
- * init_transport()
- *
- *     Given a pointer to a transport structure, set it to "all empty".
- *     The default is a localhost connection.
- */
+* init_transport()
+*
+     Given a pointer to a transport structure, set it to "all empty".
+     The default is a localhost connection.
+*/
 void transport_init(struct transport *tp)
 {
     assert(tp != 0);
@@ -1127,163 +1850,364 @@ void transport_init(struct transport *tp)
     tp->type = TRANSPORT_LOCALHOST;
     tp->port = 783;
     tp->flags = 0;
+    tp->retry_sleep = -1;
 }
 
 /*
- * randomize_hosts()
- *
- *     Given the transport object that contains one or more IP addresses
- *     in this "hosts" list, rotate it by a random number of shifts to
- *     randomize them - this is a kind of load balancing. It's possible
- *     that the random number will be 0, which says not to touch. We don't
- *     do anything unless 
- */
+* randomize_hosts()
+*
+     Given the transport object that contains one or more IP addresses
+     in this "hosts" list, rotate it by a random number of shifts to
+     randomize them - this is a kind of load balancing. It's possible
+     that the random number will be 0, which says not to touch. We don't
+     do anything unless 
+*/
 
 static void _randomize_hosts(struct transport *tp)
 {
+#ifdef SPAMC_HAS_ADDRINFO
+    struct addrinfo *tmp;
+#else
+    struct in_addr tmp;
+#endif
+    int i;
     int rnum;
 
     assert(tp != 0);
 
     if (tp->nhosts <= 1)
-       return;
+        return;
 
     rnum = rand() % tp->nhosts;
 
     while (rnum-- > 0) {
-       struct in_addr tmp = tp->hosts[0];
-       int i;
+        tmp = tp->hosts[0];
 
-       for (i = 1; i < tp->nhosts; i++)
-           tp->hosts[i - 1] = tp->hosts[i];
+        for (i = 1; i < tp->nhosts; i++)
+            tp->hosts[i - 1] = tp->hosts[i];
 
-       tp->hosts[i - 1] = tmp;
+        tp->hosts[i - 1] = tmp;
     }
 }
 
 /*
- * transport_setup()
- *
- *     Given a "transport" object that says how we're to connect to the
- *     spam daemon, perform all the initial setup required to make the
- *     connection process a smooth one. The main work is to do the host
- *     name lookup and copy over all the IP addresses to make a local copy
- *     so they're not kept in the resolver's static state.
- *
- *     Here we also manage quasi-load balancing and failover: if we're
- *     doing load balancing, we randomly "rotate" the list to put it in
- *     a different order, and then if we're not doing failover we limit
- *     the hosts to just one. This way *all* connections are done with
- *     the intention of failover - makes the code a bit more clear.
- */
+* transport_setup()
+*
+     Given a "transport" object that says how we're to connect to the
+     spam daemon, perform all the initial setup required to make the
+     connection process a smooth one. The main work is to do the host
+     name lookup and copy over all the IP addresses to make a local copy
+     so they're not kept in the resolver's static state.
+*
+     Here we also manage quasi-load balancing and failover: if we're
+     doing load balancing, we randomly "rotate" the list to put it in
+     a different order, and then if we're not doing failover we limit
+     the hosts to just one. This way *all* connections are done with
+     the intention of failover - makes the code a bit more clear.
+*/
 int transport_setup(struct transport *tp, int flags)
 {
-    struct hostent *hp = 0;
+#ifdef SPAMC_HAS_ADDRINFO
+    struct addrinfo hints, *res, *addrp;
+    char port[6];
+    int origerr;
+#else
+    struct hostent *hp;
     char **addrp;
+#endif
+    char *hostlist, *hostname;
+    int errbits;
 
 #ifdef _WIN32
     /* Start Winsock up */
     WSADATA wsaData;
     int nCode;
     if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
-       g_print("WSAStartup() returned error code %d\n", nCode);
-       return EX_OSERR;
+        printf("WSAStartup() returned error code %d\n", nCode);
+        return EX_OSERR;
     }
 
 #endif
 
+    assert(tp != NULL);
     tp->flags = flags;
 
-    assert(tp != 0);
+#ifdef SPAMC_HAS_ADDRINFO
+    snprintf(port, 6, "%d", tp->port);
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_flags = 0;
+    hints.ai_socktype = SOCK_STREAM;
+
+    if (       (flags & SPAMC_USE_INET4) && !(flags & SPAMC_USE_INET6)) {
+      hints.ai_family = PF_INET;
+#ifdef PF_INET6
+    } else if ((flags & SPAMC_USE_INET6) && !(flags & SPAMC_USE_INET4)) {
+      hints.ai_family = PF_INET6;
+#endif
+    } else {
+      hints.ai_family = PF_UNSPEC;
+    }
+#endif
 
     switch (tp->type) {
 #ifndef _WIN32
     case TRANSPORT_UNIX:
-       assert(tp->socketpath != 0);
-       return EX_OK;
+        assert(tp->socketpath != 0);
+        return EX_OK;
 #endif
     case TRANSPORT_LOCALHOST:
-       tp->hosts[0].s_addr = inet_addr("127.0.0.1");
-       tp->nhosts = 1;
-       return EX_OK;
+#ifdef SPAMC_HAS_ADDRINFO
+        /* getaddrinfo(NULL) will look up the loopback address.
+         * See also bug 5057,  ::1 will be tried before 127.0.0.1
+         * unless overridden (through hints) by a command line option -4
+         */
+        if ((origerr = getaddrinfo(NULL, port, &hints, &res)) != 0) {
+            libspamc_log(flags, LOG_ERR, 
+                  "getaddrinfo for a loopback address failed: %s",
+                  gai_strerror(origerr));
+            return EX_OSERR;
+        }
+        tp->hosts[0] = res;
+#else
+        tp->hosts[0].s_addr = inet_addr("127.0.0.1");
+#endif
+        tp->nhosts = 1;
+        return EX_OK;
 
     case TRANSPORT_TCP:
-       if (NULL == (hp = gethostbyname(tp->hostname))) {
-           int origherr = h_errno;     /* take a copy before syslog() */
-
-           libspamc_log(flags, LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
-                   tp->hostname, origherr);
-           switch (origherr) {
-           case HOST_NOT_FOUND:
-           case NO_ADDRESS:
-           case NO_RECOVERY:
-               return EX_NOHOST;
-           case TRY_AGAIN:
-               return EX_TEMPFAIL;
-           default:
-               return EX_OSERR;
-           }
-       }
+        if ((hostlist = strdup(tp->hostname)) == NULL)
+            return EX_OSERR;
+
+        /* We want to return the least permanent error, in this bitmask we
+         * record the errors seen with:
+         *  0: no error
+         *  1: EX_TEMPFAIL
+         *  2: EX_NOHOST
+         * EX_OSERR will return immediately.
+         * Bits aren't reset so a check against nhosts is needed to determine
+         * if something went wrong.
+         */
+        errbits = 0;
+        tp->nhosts = 0;
+        /* Start with char offset in front of the string because we'll add 
+         * one in the loop
+         */
+        hostname = hostlist - 1;
+        do {
+            char *hostend;
+            
+            hostname += 1;
+            hostend = strchr(hostname, ',');
+            if (hostend != NULL) {
+                *hostend = '\0';
+            }
+#ifdef SPAMC_HAS_ADDRINFO            
+            if ((origerr = getaddrinfo(hostname, port, &hints, &res))) {
+                libspamc_log(flags, LOG_DEBUG, 
+                      "getaddrinfo(%s) failed: %s",
+                      hostname, gai_strerror(origerr));
+                switch (origerr) { 
+                case EAI_AGAIN:
+                    errbits |= 1;
+                    break;
+                case EAI_FAMILY: /*address family not supported*/
+                case EAI_SOCKTYPE: /*socket type not supported*/
+                case EAI_BADFLAGS: /*ai_flags is invalid*/
+                case EAI_NONAME: /*node or service unknown*/
+                case EAI_SERVICE: /*service not available*/
+/* work around Cygwin IPv6 patch - err codes not defined in Windows aren't in patch */
+#ifdef HAVE_EAI_ADDRFAMILY
+                case EAI_ADDRFAMILY: /*no addresses in requested family*/
+#endif
+#ifdef HAVE_EAI_SYSTEM
+                case EAI_SYSTEM: /*system error, check errno*/
+#endif
+#ifdef HAVE_EAI_NODATA
+                case EAI_NODATA: /*address exists, but no data*/
+#endif
+                case EAI_MEMORY: /*out of memory*/
+                case EAI_FAIL: /*name server returned permanent error*/
+                    errbits |= 2;
+                    break;
+                default:
+                    /* should not happen, all errors are checked above */
+                    free(hostlist);
+                    return EX_OSERR;
+                }
+                goto nexthost; /* try next host in list */
+            }
+#else
+            if ((hp = gethostbyname(hostname)) == NULL) {
+                int origerr = h_errno; /* take a copy before syslog() */
+                libspamc_log(flags, LOG_DEBUG, "gethostbyname(%s) failed: h_errno=%d",
+                    hostname, origerr);
+                switch (origerr) {
+                case TRY_AGAIN:
+                    errbits |= 1;
+                    break;
+                case HOST_NOT_FOUND:
+                case NO_ADDRESS:
+                case NO_RECOVERY:
+                    errbits |= 2;
+                    break;
+                default:
+                    /* should not happen, all errors are checked above */
+                    free(hostlist);
+                    return EX_OSERR;
+                }
+                goto nexthost; /* try next host in list */
+            }
+#endif
+            
+            /* If we have no hosts at all */
+#ifdef SPAMC_HAS_ADDRINFO
+            if(res == NULL)
+#else
+            if (hp->h_addr_list[0] == NULL
+             || hp->h_length != sizeof tp->hosts[0]
+             || hp->h_addrtype != AF_INET)
+                /* no hosts/bad size/wrong family */
+#endif
+            {
+                errbits |= 1;
+                goto nexthost; /* try next host in list */
+            }
+
+            /* Copy all the IP addresses into our private structure.
+             * This gets them out of the resolver's static area and
+             * means we won't ever walk all over the list with other
+             * calls.
+             */
+#ifdef SPAMC_HAS_ADDRINFO
+            if(tp->nhosts == TRANSPORT_MAX_HOSTS) {
+               libspamc_log(flags, LOG_NOTICE, 
+                     "hit limit of %d hosts, ignoring remainder",
+                     TRANSPORT_MAX_HOSTS);
+               break;
+            }
+
+            /* treat all A or AAAA records of each host as one entry */
+            tp->hosts[tp->nhosts++] = res;
+
+            /* alternatively, treat multiple A or AAAA records
+               of one host as individual entries */
+/*          for (addrp = res; addrp != NULL; ) {
+ *              tp->hosts[tp->nhosts] = addrp;
+ *              addrp = addrp->ai_next;     /-* before NULLing ai_next *-/
+ *              tp->hosts[tp->nhosts]->ai_next = NULL;
+ *              tp->nhosts++;
+ *          }
+ */
 
-               /*--------------------------------------------------------
-                * If we have no hosts at all, or if they are some other
-                * kind of address family besides IPv4, then we really
-                * just have no hosts at all.
-                */
-       if (hp->h_addr_list[0] == 0) {
-           /* no hosts in this list */
-           return EX_NOHOST;
-       }
+#else
+            for (addrp = hp->h_addr_list; *addrp; addrp++) {
+                if (tp->nhosts == TRANSPORT_MAX_HOSTS) {
+                    libspamc_log(flags, LOG_NOTICE, "hit limit of %d hosts, ignoring remainder",
+                        TRANSPORT_MAX_HOSTS);
+                    break;
+                }
+                memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length);
+                tp->nhosts++;
+            }
+#endif            
+nexthost:
+            hostname = hostend;
+        } while (hostname != NULL);
+        free(hostlist);
+        
+        if (tp->nhosts == 0) {
+            if (errbits & 1) {
+                libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): a temporary error occurred",
+                    tp->hostname); 
+                return EX_TEMPFAIL;
+            }
+            else {
+                libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): no such host",
+                    tp->hostname); 
+                return EX_NOHOST;
+            }
+        }
+        
+        /* QUASI-LOAD-BALANCING
+         *
+         * If the user wants to do quasi load balancing, "rotate"
+         * the list by a random amount based on the current time.
+         * This may later be truncated to a single item. This is
+         * meaningful only if we have more than one host.
+        */
 
-       if (hp->h_length != sizeof tp->hosts[0]
-           || hp->h_addrtype != AF_INET) {
-           /* FAIL - bad size/protocol/family? */
-           return EX_NOHOST;
-       }
+        if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
+            _randomize_hosts(tp);
+        }
+
+        /* If the user wants no fallback, simply truncate the host
+         * list to just one - this pretends that this is the extent
+         * of our connection list - then it's not a special case.
+         */
+        if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
+            /* truncating list */
+            tp->nhosts = 1;
+        }
+        
+        return EX_OK;
+    }
+    
+    /* oops, unknown transport type */
+    return EX_OSERR;
+}
 
-               /*--------------------------------------------------------
-                * Copy all the IP addresses into our private structure.
-                * This gets them out of the resolver's static area and
-                * means we won't ever walk all over the list with other
-                * calls.
-                */
-       tp->nhosts = 0;
+/*
+* transport_cleanup()
+*
+*      Given a "transport" object that says how we're to connect to the
+*      spam daemon, delete and free any buffers allocated so that it
+*       can be discarded without causing a memory leak.
+*/
+void transport_cleanup(struct transport *tp)
+{
 
-       for (addrp = hp->h_addr_list; *addrp; addrp++) {
-           if (tp->nhosts >= TRANSPORT_MAX_HOSTS - 1) {
-               libspamc_log(flags, LOG_ERR, "hit limit of %d hosts, ignoring remainder",
-                      TRANSPORT_MAX_HOSTS - 1);
-               break;
-           }
+#ifdef SPAMC_HAS_ADDRINFO
+  int i;
 
-           memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
+  for(i=0;i<tp->nhosts;i++) {
+      if (tp->hosts[i] != NULL) {
+          freeaddrinfo(tp->hosts[i]);
+          tp->hosts[i] = NULL;
+      }
+  }
+#endif
 
-           tp->nhosts++;
-       }
+}
 
-               /*--------------------------------------------------------
-                * QUASI-LOAD-BALANCING
-                *
-                * If the user wants to do quasi load balancing, "rotate"
-                * the list by a random amount based on the current time.
-                * This may later be truncated to a single item. This is
-                * meaningful only if we have more than one host.
-                */
-       if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
-           _randomize_hosts(tp);
-       }
+/*
+* register_libspamc_log_callback()
+*
+* Register a callback handler for libspamc_log to replace the default behaviour.
+*/
 
-               /*--------------------------------------------------------
-                * If the user wants no fallback, simply truncate the host
-                * list to just one - this pretends that this is the extent
-                * of our connection list - then it's not a special case.
-                */
-       if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
-           /* truncating list */
-           tp->nhosts = 1;
-       }
-    }
-    return EX_OK;
+void register_libspamc_log_callback(void (*function)(int flags, int level, char *msg, va_list args)) {
+  libspamc_log_callback = function;
+}
+
+/*
+* register_spamc_header_callback()
+*
+* Register a callback handler to generate spamc headers for a given message
+*/
+
+void register_spamc_header_callback(const struct message *m, void (*func)(struct message *m, int flags, char *buf, int len)) {
+  m->priv->spamc_header_callback = func;
+}
+
+/*
+* register_spamd_header_callback()
+*
+* Register a callback handler to generate spamd headers for a given message
+*/
+
+void register_spamd_header_callback(const struct message *m, void (*func)(struct message *m, int flags, const char *buf, int len)) {
+  m->priv->spamd_header_callback = func;
 }
 
 /* --------------------------------------------------------------------------- */
@@ -1299,7 +2223,10 @@ libspamc_log (int flags, int level, char *msg, ...)
 
     va_start(ap, msg);
 
-    if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
+    if ((flags & SPAMC_LOG_TO_CALLBACK) != 0 && libspamc_log_callback != NULL) {
+      libspamc_log_callback(flags, level, msg, ap);
+    }
+    else if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
         /* create a log-line buffer */
         len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
         len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
@@ -1318,7 +2245,7 @@ libspamc_log (int flags, int level, char *msg, ...)
         syslog (level, "%s", buf);
 #else
         (void) level;  /* not used. suppress compiler warning */
-        g_printerr ("%s\n", buf);
+        f_printerr ("%s\n", buf);
 #endif
     }
 
@@ -1328,12 +2255,12 @@ libspamc_log (int flags, int level, char *msg, ...)
 /* --------------------------------------------------------------------------- */
 
 /*
- * Unit tests.  Must be built externally, e.g.:
- *
- * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
- * ./libspamctest
- *
- */
+* Unit tests.  Must be built externally, e.g.:
+*
+* gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
+* ./libspamctest
+*
+*/
 #ifdef LIBSPAMC_UNIT_TESTS
 
 static void _test_locale_safe_string_to_float_val(float input)
@@ -1345,42 +2272,42 @@ static void _test_locale_safe_string_to_float_val(float input)
     sprintf(inputstr, "%f", input);
     output = _locale_safe_string_to_float(inputstr, 99);
     if (input == output) {
-       return;
+        return;
     }
 
     /* could be a rounding error.  print as string and compare those */
     sprintf(cmpbuf1, "%f", input);
     sprintf(cmpbuf2, "%f", output);
     if (!strcmp(cmpbuf1, cmpbuf2)) {
-       return;
+        return;
     }
 
-    g_print("FAIL: input=%f != output=%f\n", input, output);
+    printf("FAIL: input=%f != output=%f\n", input, output);
 }
 
 static void unit_test_locale_safe_string_to_float(void)
 {
     float statictestset[] = {  /* will try both +ve and -ve */
-       0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
-       9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
-       0.0                     /* end of set constant */
+        0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
+        9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
+        0.0                    /* end of set constant */
     };
     float num;
     int i;
 
-    g_print("starting unit_test_locale_safe_string_to_float\n");
+    printf("starting unit_test_locale_safe_string_to_float\n");
     /* tests of precision */
     for (i = 0; statictestset[i] != 0.0; i++) {
-       _test_locale_safe_string_to_float_val(statictestset[i]);
-       _test_locale_safe_string_to_float_val(-statictestset[i]);
-       _test_locale_safe_string_to_float_val(1 - statictestset[i]);
-       _test_locale_safe_string_to_float_val(1 + statictestset[i]);
+        _test_locale_safe_string_to_float_val(statictestset[i]);
+        _test_locale_safe_string_to_float_val(-statictestset[i]);
+        _test_locale_safe_string_to_float_val(1 - statictestset[i]);
+        _test_locale_safe_string_to_float_val(1 + statictestset[i]);
     }
     /* now exhaustive, in steps of 0.01 */
     for (num = -1000.0; num < 1000.0; num += 0.01) {
-       _test_locale_safe_string_to_float_val(num);
+        _test_locale_safe_string_to_float_val(num);
     }
-    g_print("finished unit_test_locale_safe_string_to_float\n");
+    printf("finished unit_test_locale_safe_string_to_float\n");
 }
 
 void do_libspamc_unit_tests(void)