RSSyl: Allow use of .netrc by libcurl. Bug/enhancement #3309, by Vincent Pelletier
[claws.git] / src / plugins / spamassassin / libspamc.c
index c6eb8882553098a3267ddc49575949f866a3d130..e78fd0081bc951f5b4aef0ea55ea186bb811f1dd 100644 (file)
@@ -1,25 +1,46 @@
-/*
- * This code is copyright 2001 by Craig Hughes
- * Portions copyright 2002 by Brad Jorsch
- * It is licensed under the same license as Perl itself.  The text of this
- * license is included in the SpamAssassin distribution in the file named
- * "License".
+/* <@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
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </@LICENSE>
  */
 
-#include "../config.h"
+#include "config.h"
+#include "claws-features.h"
 #include "libspamc.h"
 #include "utils.h"
 
-#include <unistd.h>
 #include <stdlib.h>
+#include <assert.h>
 #include <stdio.h>
 #include <string.h>
+#ifdef _WIN32
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#define strcasecmp stricmp
+#define sleep Sleep
+#include <io.h>
+#else
 #include <syslog.h>
+#include <unistd.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <sys/un.h>
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
+#define closesocket(x) close(x)
+#endif
 
 #ifdef HAVE_SYSEXITS_H
 #include <sysexits.h>
 
 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
 /* KAM 12-4-01 */
-#ifndef HAVE_SHUT_RD
-#define SHUT_RD (0)   /* No more receptions.  */
-#define SHUT_WR (1)   /* No more transmissions.  */
-#define SHUT_RDWR (2) /* No more receptions or transmissions.  */
+/* SJF 2003/04/25 - now test for macros directly */
+#ifndef SHUT_RD
+#  define SHUT_RD 0            /* no more receptions */
+#endif
+#ifndef SHUT_WR
+#  define SHUT_WR 1            /* no more transmissions */
+#endif
+#ifndef SHUT_RDWR
+#  define SHUT_RDWR 2          /* no more receptions or transmissions */
 #endif
 
 #ifndef HAVE_H_ERRNO
@@ -65,13 +91,13 @@ extern char *optarg;
 
 #ifndef HAVE_EX__MAX
 /* jm: very conservative figure, should be well out of range on almost all NIXes */
-#define EX__MAX 200 
+#define EX__MAX 200
 #endif
 
 #undef DO_CONNECT_DEBUG_SYSLOGS
 /* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
 
-static const int ESC_PASSTHROUGHRAW = EX__MAX+666;
+static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
 
 /* set EXPANSION_ALLOWANCE to something more than might be
    added to a message in X-headers and the report template */
@@ -84,236 +110,319 @@ 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.3";
+
+/* "private" part of struct message.
+ * we use this instead of the struct message directly, so that we
+ * can add new members without affecting the ABI.
+ */
+struct libspamc_private_message
+{
+    int flags;                 /* copied from "flags" arg to message_read() */
+};
 
 int libspamc_timeout = 0;
 
-static int
-try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
-                int hent_port, int *sockptr)
+/*
+ * translate_connect_errno()
+ *
+ *     Given a UNIX error number obtained (probably) from "connect(2)",
+ *     translate this to a failure code. This module is shared by both
+ *     transport modules - UNIX and TCP.
+ *
+ *     This should ONLY be called when there is an error.
+ */
+static int _translate_connect_errno(int err)
 {
-#ifdef USE_TCP_NODELAY
-  int value;
-#endif
-  int mysock = -1;
-  int status = -1;
-  int origerr;
-  int numloops;
-  int hostnum = 0;
-  struct sockaddr_in addrbuf, *addr;
-  struct in_addr inaddrlist[256];
+    switch (err) {
+    case EBADF:
+    case EFAULT:
+    case ENOTSOCK:
+    case EISCONN:
+    case EADDRINUSE:
+    case EINPROGRESS:
+    case EALREADY:
+    case EAFNOSUPPORT:
+       return EX_SOFTWARE;
+
+    case ECONNREFUSED:
+    case ETIMEDOUT:
+    case ENETUNREACH:
+       return EX_UNAVAILABLE;
+
+    case EACCES:
+       return EX_NOPERM;
+
+    default:
+       return EX_SOFTWARE;
+    }
+}
+
+/*
+ * 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.
+ *
+ *     Upon failure we return one of the other EX_??? error codes.
+ */
+static int _opensocket(int flags, int type, int *psock)
+{
+    const char *typename;
+    int proto = 0;
+
+    assert(psock != 0);
+
+       /*----------------------------------------------------------------
+        * Create a few induction variables that are implied by the socket
+        * type given by the user. The typename is strictly used for debug
+        * reporting.
+        */
+    if (type == PF_UNIX) {
+       typename = "PF_UNIX";
+    }
+    else {
+       typename = "PF_INET";
+       proto = IPPROTO_TCP;
+    }
 
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-  int dbgiter; char dbgbuf[2048]; int dbgbuflen = 0;
+    libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
 #endif
 
-  /* NOTE: do not call syslog() (unless you are about to return) before
-   * we take a copy of the h_addr_list.
-   */
+    if ((*psock = socket(type, SOCK_STREAM, proto))
+#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.
+                */
+#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
 
-  /* only one set of connection targets can be used.  assert this */
-  if (argaddr == NULL && hent == NULL) {
-      syslog (LOG_ERR, "oops! both NULL in try_to_connect");
-      return EX_SOFTWARE;
-  } else if (argaddr != NULL && hent != NULL) {
-      syslog (LOG_ERR, "oops! both non-NULL in try_to_connect");
-      return EX_SOFTWARE;
-  }
+       switch (origerr) {
+       case EPROTONOSUPPORT:
+       case EINVAL:
+           return EX_SOFTWARE;
 
-  /* take a copy of the h_addr_list part of the struct hostent */
-  if (hent != NULL) {
-    memset (inaddrlist, 0, sizeof(inaddrlist));
+       case EACCES:
+           return EX_NOPERM;
 
-    for (hostnum=0; hent->h_addr_list[hostnum] != 0; hostnum++) {
+       case ENFILE:
+       case EMFILE:
+       case ENOBUFS:
+       case ENOMEM:
+           return EX_OSERR;
 
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-      dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen,
-                 "[%d %lx: %d.%d.%d.%d]",
-                 hostnum, hent->h_addr_list[hostnum],
-                 hent->h_addr_list[hostnum][0],
-                 hent->h_addr_list[hostnum][1],
-                 hent->h_addr_list[hostnum][2],
-                 hent->h_addr_list[hostnum][3]);
-#endif
-
-      if (hostnum > 255) {
-       syslog (LOG_ERR, "too many address in hostent (%d), ignoring others",
-                           hostnum);
-       break;
-      }
-
-      if (hent->h_addr_list[hostnum] == NULL) {
-       /* shouldn't happen */
-       syslog (LOG_ERR, "hent->h_addr_list[hostnum] == NULL! foo!");
-       return EX_SOFTWARE;
-      }
+       default:
+           return EX_SOFTWARE;
+       }
+    }
 
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-      dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen,
-                 "[%d: %d.%d.%d.%d] ", sizeof (struct in_addr),
-                 hent->h_addr_list[hostnum][0],
-                 hent->h_addr_list[hostnum][1],
-                 hent->h_addr_list[hostnum][2],
-                 hent->h_addr_list[hostnum][3]);
+
+       /*----------------------------------------------------------------
+        * Do a bit of setup on the TCP socket if required. Notes above
+        * suggest this is probably not set
+        */
+#ifdef USE_TCP_NODELAY
+    {
+       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();
+#endif
+           switch (origerr) {
+           case EBADF:
+           case ENOTSOCK:
+           case ENOPROTOOPT:
+           case EFAULT:
+               libspamc_log(flags, LOG_ERR,
+#ifndef _WIN32
+                      "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
+#else
+                      "setsockopt(TCP_NODELAY) failed: %d", origerr);
 #endif
+               closesocket(*psock);
+               return EX_SOFTWARE;
 
-      memcpy ((void *) &(inaddrlist[hostnum]),
-               (void *) hent->h_addr_list[hostnum],
-               sizeof (struct in_addr));
+           default:
+               break;          /* ignored */
+           }
+       }
     }
+#endif /* USE_TCP_NODELAY */
 
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-      syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0;
-#endif
-  }
+    return EX_OK;              /* all is well */
+}
+
+/*
+ * try_to_connect_unix()
+ *
+ *     Given a transport handle that implies using a UNIX domain
+ *     socket, try to make a connection to it and store the resulting
+ *     file descriptor in *sockptr. Return is EX_OK if we did it,
+ *     and some other error code otherwise.
+ */
+static int _try_to_connect_unix(struct transport *tp, int *sockptr)
+{
+#ifndef _WIN32
+    int mysock, status, origerr;
+    struct sockaddr_un addrbuf;
+    int ret;
 
+    assert(tp != 0);
+    assert(sockptr != 0);
+    assert(tp->socketpath != 0);
+
+       /*----------------------------------------------------------------
+        * If the socket itself can't be created, this is a fatal error.
+        */
+    if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
+       return ret;
+
+    /* set up the UNIX domain socket */
+    memset(&addrbuf, 0, sizeof addrbuf);
+    addrbuf.sun_family = AF_UNIX;
+    strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
+    addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
 
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-    for (dbgiter = 0; dbgiter < hostnum; dbgiter++) {
-      syslog (LOG_DEBUG, "dbg: host addr %d/%d = %lx at %lx", dbgiter, hostnum,
-                 inaddrlist[dbgiter].s_addr, &(inaddrlist[dbgiter]));
-    }
+    libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
+          addrbuf.sun_path);
 #endif
 
-  hent = NULL; /* cannot use hent after this point, syslog() may overwrite it */
+    status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
+
+    origerr = errno;
 
+    if (status >= 0) {
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-  syslog (LOG_DEBUG, "dbg: socket");
+       libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
 #endif
 
-  if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
-  {
-    origerr = errno;    /* take a copy before syslog() */
-    syslog (LOG_ERR, "socket() to spamd failed: %m");
-    switch(origerr)
-    {
-    case EPROTONOSUPPORT:
-    case EINVAL:
-      return EX_SOFTWARE;
-    case EACCES:
-      return EX_NOPERM;
-    case ENFILE:
-    case EMFILE:
-    case ENOBUFS:
-    case ENOMEM:
-      return EX_OSERR;
-    default:
-      return EX_SOFTWARE;
+       *sockptr = mysock;
+
+       return EX_OK;
     }
-  }
 
-#ifdef USE_TCP_NODELAY
-  /* TODO: should this be up above the connect()? */
-  value = 1;           /* make this explicit! */
-  if(-1 == setsockopt(mysock,0,TCP_NODELAY,&value,sizeof(value)))
-  {
-    switch(errno)
-    {
-    case EBADF:
-    case ENOTSOCK:
-    case ENOPROTOOPT:
-    case EFAULT:
-      syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
-      close (mysock);
-      return EX_SOFTWARE;
+    libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
+          addrbuf.sun_path, strerror(origerr));
+    closesocket(mysock);
 
-    default:
-      break;           /* ignored */
-    }
-  }
+    return _translate_connect_errno(origerr);
+#else
+    (void) tp; /* not used. suppress compiler warning */
+    (void) sockptr; /* not used. suppress compiler warning */
+    return EX_OSERR;
 #endif
+}
 
-  for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
-  
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-      syslog (LOG_DEBUG, "dbg: connect() to spamd %d", numloops);
-#endif
+/*
+ * try_to_connect_tcp()
+ *
+ *     Given a transport that implies a TCP connection, either to
+ *     localhost or a list of IP addresses, attempt to connect. The
+ *     list of IP addresses has already been randomized (if requested)
+ *     and limited to just one if fallback has been enabled.
+ */
+static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
+{
+    int numloops;
+    int origerr = 0;
+    int ret;
+
+    assert(tp != 0);
+    assert(sockptr != 0);
+    assert(tp->nhosts > 0);
 
-    if (argaddr != NULL) {
-      addr = (struct sockaddr_in *) argaddr;     /* use the one provided */
-  
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-       syslog (LOG_DEBUG, "dbg: using argaddr");
+    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]));
+    }
 #endif
 
-    } else {
-      /* cycle through the addrs in hent */
-      memset(&addrbuf, 0, sizeof(addrbuf));
-      addrbuf.sin_family=AF_INET;
-      addrbuf.sin_port=htons(hent_port);
-
-      if (sizeof(addrbuf.sin_addr) != sizeof(struct in_addr)) {        /* shouldn't happen */
-       syslog (LOG_ERR,        
-               "foo! sizeof(sockaddr.sin_addr) != sizeof(struct in_addr)");
-       return EX_SOFTWARE;
-      }
+    for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
+       struct sockaddr_in addrbuf;
+       const int hostix = numloops % tp->nhosts;
+       int status, mysock;
+       const char *ipaddr;
 
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-       syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx",
-               numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum]));
-#endif
+               /*--------------------------------------------------------
+                * 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;
 
-      memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]),
-                        sizeof(addrbuf.sin_addr));
-      addr = &addrbuf;
+       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);
 
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-       syslog (LOG_DEBUG, "dbg: conn addr %d/%d = %lx",
-           numloops%hostnum, hostnum, addrbuf.sin_addr.s_addr);
+       libspamc_log(tp->flags, DEBUG_LEVEL,
+              "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
+               ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
 #endif
 
-    }
+       status =
+           connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
 
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
-    syslog (LOG_DEBUG, "dbg: connect() to spamd at %s",
-               inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
+       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));
+#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);
 #endif
-    status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
+           closesocket(mysock);
 
+           sleep(CONNECT_RETRY_SLEEP);
+       }
+       else {
 #ifdef DO_CONNECT_DEBUG_SYSLOGS
-      syslog (LOG_DEBUG, "dbg: connect() to spamd at %s done",
-         inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
+           libspamc_log(tp->flags, DEBUG_LEVEL,
+                  "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
 #endif
+           *sockptr = mysock;
 
-    if (status < 0)
-    {
-      origerr = errno;        /* take a copy before syslog() */
-      syslog (LOG_ERR, "connect() to spamd at %s failed, retrying (%d/%d): %m",
-                        inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
-                        numloops+1, MAX_CONNECT_RETRIES);
-      sleep(CONNECT_RETRY_SLEEP);
+           return EX_OK;
+       }
+    }
 
-    } else {
-      *sockptr = mysock;
-      return EX_OK;
-    }
-  }
-  /* failed, even with a few retries */
-  close (mysock);
-  syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
-       MAX_CONNECT_RETRIES);
-  switch(origerr)
-  {
-   case EBADF:
-   case EFAULT:
-   case ENOTSOCK:
-   case EISCONN:
-   case EADDRINUSE:
-   case EINPROGRESS:
-   case EALREADY:
-   case EAFNOSUPPORT:
-     return EX_SOFTWARE;
-   case ECONNREFUSED:
-   case ETIMEDOUT:
-   case ENETUNREACH:
-     return EX_UNAVAILABLE;
-   case EACCES:
-     return EX_NOPERM;
-   default:
-     return EX_SOFTWARE;
-  }
+    libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
+           MAX_CONNECT_RETRIES);
+
+    return _translate_connect_errno(origerr);
 }
 
 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
@@ -321,219 +430,260 @@ try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
  * of helper functions.
  */
 
-static void clear_message(struct message *m){
-    m->type=MESSAGE_NONE;
-    m->raw=NULL; m->raw_len=0;
-    m->pre=NULL; m->pre_len=0;
-    m->msg=NULL; m->msg_len=0;
-    m->post=NULL; m->post_len=0;
-    m->is_spam=EX_TOOBIG;
-    m->score=0.0; m->threshold=0.0;
-    m->out=NULL; m->out_len=0;
-    m->content_length=-1;
+static void _clear_message(struct message *m)
+{
+    m->type = MESSAGE_NONE;
+    m->raw = NULL;
+    m->raw_len = 0;
+    m->pre = NULL;
+    m->pre_len = 0;
+    m->msg = NULL;
+    m->msg_len = 0;
+    m->post = NULL;
+    m->post_len = 0;
+    m->is_spam = EX_TOOBIG;
+    m->score = 0.0;
+    m->threshold = 0.0;
+    m->out = NULL;
+    m->out_len = 0;
+    m->content_length = -1;
 }
 
-static int
-message_read_raw(int fd, struct message *m){
-    clear_message(m);
-    if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
-    m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1);
-    if(m->raw_len<=0){
-        free(m->raw); m->raw=NULL; m->raw_len=0;
-        return EX_IOERR;
-    }
-    m->type=MESSAGE_ERROR;
-    if(m->raw_len>m->max_len) return EX_TOOBIG;
-    m->type=MESSAGE_RAW;
-    m->msg=m->raw;
-    m->msg_len=m->raw_len;
-    m->out=m->msg;
-    m->out_len=m->msg_len;
+static int _message_read_raw(int fd, struct message *m)
+{
+    _clear_message(m);
+    if ((m->raw = malloc(m->max_len + 1)) == NULL)
+       return EX_OSERR;
+    m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
+    if (m->raw_len <= 0) {
+       free(m->raw);
+       m->raw = NULL;
+       m->raw_len = 0;
+       return EX_IOERR;
+    }
+    m->type = MESSAGE_ERROR;
+    if (m->raw_len > m->max_len)
+       return EX_TOOBIG;
+    m->type = MESSAGE_RAW;
+    m->msg = m->raw;
+    m->msg_len = m->raw_len;
+    m->out = m->msg;
+    m->out_len = m->msg_len;
     return EX_OK;
 }
 
-static int message_read_bsmtp(int fd, struct message *m){
-    off_t i, j;
+static int _message_read_bsmtp(int fd, struct message *m)
+{
+    unsigned int i, j;
     char prev;
 
-    clear_message(m);
-    if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
+    _clear_message(m);
+    if ((m->raw = malloc(m->max_len + 1)) == NULL)
+       return EX_OSERR;
 
     /* Find the DATA line */
-    m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1);
-    if(m->raw_len<=0){
-        free(m->raw); m->raw=NULL; m->raw_len=0;
-        return EX_IOERR;
-    }
-    m->type=MESSAGE_ERROR;
-    if(m->raw_len>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;
-        }
-    }
-    if(m->msg==NULL) return EX_DATAERR;
+    m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
+    if (m->raw_len <= 0) {
+       free(m->raw);
+       m->raw = NULL;
+       m->raw_len = 0;
+       return EX_IOERR;
+    }
+    m->type = MESSAGE_ERROR;
+    if (m->raw_len > 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;
+       }
+    }
+    if (m->msg == NULL)
+       return EX_DATAERR;
 
     /* Find the end-of-DATA line */
-    prev='\n';
-    for(i=j=0; i<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'){
-                /* 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]=='.'){
-                /* Escaping dot, eliminate. */
-                prev='.';
-                continue;
-            } /* Else an ordinary dot, drop down to ordinary char handler */
-        }
-        prev=m->msg[i];
-        m->msg[j++]=m->msg[i];
-    }
-
-    m->type=MESSAGE_BSMTP;
-    m->out=m->msg;
-    m->out_len=m->msg_len;
+    prev = '\n';
+    for (i = j = 0; i < 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') {
+               /* 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] == '.') {
+               /* Escaping dot, eliminate. */
+               prev = '.';
+               continue;
+           }                   /* Else an ordinary dot, drop down to ordinary char handler */
+       }
+       prev = m->msg[i];
+       m->msg[j++] = m->msg[i];
+    }
+
+    m->type = MESSAGE_BSMTP;
+    m->out = m->msg;
+    m->out_len = m->msg_len;
     return EX_OK;
 }
 
-int message_read(int fd, int flags, struct message *m){
+int message_read(int fd, int flags, struct message *m)
+{
     libspamc_timeout = 0;
 
-    switch(flags&SPAMC_MODE_MASK){
-      case SPAMC_RAW_MODE:
-        return message_read_raw(fd, m);
+    /* create the "private" part of the struct message */
+    m->priv = malloc(sizeof(struct libspamc_private_message));
+    if (m->priv == NULL) {
+       libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
+       return EX_OSERR;
+    }
+    m->priv->flags = flags;
+
+    switch (flags & SPAMC_MODE_MASK) {
+    case SPAMC_RAW_MODE:
+       return _message_read_raw(fd, m);
 
-      case SPAMC_BSMTP_MODE:
-        return message_read_bsmtp(fd, m);
+    case SPAMC_BSMTP_MODE:
+       return _message_read_bsmtp(fd, m);
 
-      default:
-        syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK);
-        return EX_USAGE;
+    default:
+       libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
+               flags & SPAMC_MODE_MASK);
+       return EX_USAGE;
     }
 }
 
-long message_write(int fd, struct message *m){
-    long total=0;
+long message_write(int fd, struct message *m)
+{
+    long total = 0;
     off_t i, j;
     off_t jlimit;
     char buffer[1024];
 
-    /* if we're to output a message, m->is_spam will be EX_OUTPUTMESSAGE */
-    if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
-       return full_write(fd, (unsigned char *) m->out, m->out_len);
-    }
+    if (m->priv->flags & SPAMC_CHECK_ONLY) {
+       if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
+           return full_write(fd, 1, m->out, m->out_len);
 
-    if (m->is_spam != EX_OUTPUTMESSAGE && m->is_spam != EX_TOOBIG) {
-      syslog(LOG_ERR,
-           "Cannot write this message, is_spam = %d!\n", m->is_spam);
-      return -1;
+       }
+       else {
+           libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
+                        m->is_spam);
+           return -1;
+       }
     }
 
-    switch(m->type){
-      case MESSAGE_NONE:
-        syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n");
-        return -1;
-
-      case MESSAGE_ERROR:
-        return full_write(fd, (unsigned char *) m->raw, m->raw_len);
-
-      case MESSAGE_RAW:
-        return full_write(fd, (unsigned char *) m->out, m->out_len);
-
-      case MESSAGE_BSMTP:
-        total=full_write(fd, (unsigned char *) m->pre, m->pre_len);
-        for(i=0; i<m->out_len; ){
-           jlimit = (off_t) (sizeof(buffer)/sizeof(*buffer)-4);
-            for(j=0; i < (off_t) m->out_len &&
-                                j < jlimit;)
-            {
-                if(i+1<m->out_len && m->out[i]=='\n' && m->out[i+1]=='.'){
+    /* else we're not in CHECK_ONLY mode */
+    switch (m->type) {
+    case MESSAGE_NONE:
+       libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
+       return -1;
+
+    case MESSAGE_ERROR:
+       return full_write(fd, 1, m->raw, m->raw_len);
+
+    case MESSAGE_RAW:
+       return full_write(fd, 1, m->out, m->out_len);
+
+    case MESSAGE_BSMTP:
+       total = full_write(fd, 1, m->pre, m->pre_len);
+       for (i = 0; i < m->out_len;) {
+           jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
+           for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
+               if (i + 1 < m->out_len && m->out[i] == '\n'
+                   && m->out[i + 1] == '.') {
                    if (j > jlimit - 4) {
-                       break;          /* avoid overflow */
+                       break;  /* avoid overflow */
                    }
-                    buffer[j++]=m->out[i++];
-                    buffer[j++]=m->out[i++];
-                    buffer[j++]='.';
-                } else {
-                    buffer[j++]=m->out[i++];
-                }
-            }
-            total+=full_write(fd, (unsigned char *) buffer, j);
-        }
-        return total+full_write(fd, (unsigned char *) m->post, m->post_len);
-
-      default:
-        syslog(LOG_ERR, "Unknown message type %d\n", m->type);
-        return -1;
+                   buffer[j++] = m->out[i++];
+                   buffer[j++] = m->out[i++];
+                   buffer[j++] = '.';
+               }
+               else {
+                   buffer[j++] = m->out[i++];
+               }
+           }
+           total += full_write(fd, 1, buffer, j);
+       }
+       return total + full_write(fd, 1, m->post, m->post_len);
+
+    default:
+       libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
+       return -1;
     }
 }
 
-void message_dump(int in_fd, int out_fd, struct message *m){
+void message_dump(int in_fd, int out_fd, struct message *m)
+{
     char buf[8196];
     int bytes;
-    
-    if(m!=NULL && m->type!=MESSAGE_NONE) {
-        message_write(out_fd, m);
+
+    if (m != NULL && m->type != MESSAGE_NONE) {
+       message_write(out_fd, m);
     }
-    while((bytes=full_read(in_fd, (unsigned char *) buf, 8192, 8192))>0){
-        if (bytes!=full_write(out_fd, (unsigned char *) buf, bytes)) {
-            syslog(LOG_ERR, "oops! message_dump of %d returned different", bytes);
-        }
+    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",
+                  bytes);
+       }
     }
 }
 
 static int
-_spamc_read_full_line (struct message *m, int flags, SSL *ssl, int sock,
-               char *buf, int *lenp, int bufsiz)
+_spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
+                     char *buf, size_t *lenp, size_t bufsiz)
 {
     int failureval;
     int bytesread = 0;
-    int len;
+    size_t len;
+
+    UNUSED_VARIABLE(m);
 
+    *lenp = 0;
     /* Now, read from spamd */
-    for(len=0; len<bufsiz-1; len++) {
-       if(flags&SPAMC_USE_SSL) {
-         bytesread = ssl_timeout_read (ssl, buf+len, 1);
-       } else {
-         bytesread = fd_timeout_read (sock, buf+len, 1);
+    for (len = 0; len < bufsiz - 1; len++) {
+       if (flags & SPAMC_USE_SSL) {
+           bytesread = ssl_timeout_read(ssl, buf + len, 1);
+       }
+       else {
+           bytesread = fd_timeout_read(sock, 0, buf + len, 1);
+       }
+
+       if (bytesread <= 0) {
+           failureval = EX_IOERR;
+           goto failure;
        }
 
-        if(buf[len]=='\n') {
-            buf[len]='\0';
-           if (len > 0 && buf[len-1] == '\r') {
+       if (buf[len] == '\n') {
+           buf[len] = '\0';
+           if (len > 0 && buf[len - 1] == '\r') {
                len--;
-               buf[len]='\0';
+               buf[len] = '\0';
            }
            *lenp = len;
            return EX_OK;
        }
-
-        if(bytesread<=0){
-           failureval = EX_IOERR; goto failure;
-        }
     }
 
-    syslog(LOG_ERR, "spamd responded with line of %d bytes, dying", len);
+    libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
     failureval = EX_TOOBIG;
 
-failure:
+  failure:
     return failureval;
 }
 
@@ -541,379 +691,703 @@ failure:
  * May  7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
  * work around using our own locale-independent float-parser code.
  */
-static float
-_locale_safe_string_to_float (char *buf, int siz)
+static float _locale_safe_string_to_float(char *buf, int siz)
 {
-  int is_neg;
-  char *cp, *dot;
-  int divider;
-  float ret, postdot;
-
-  buf[siz-1] = '\0';   /* ensure termination */
-
-  /* ok, let's illustrate using "100.033" as an example... */
-  
-  is_neg = 0;
-  if (*buf == '-') { is_neg = 1; }
-
-  ret = (float) (strtol (buf, &dot, 10));
-  if (dot == NULL) { return 0.0; }
-  if (dot != NULL && *dot != '.') { return ret; }
-
-  /* ex: ret == 100.0 */
-
-  cp = (dot + 1);
-  postdot = (float) (strtol (cp, NULL, 10));
-  if (postdot == 0.0) { return ret; }
-
-  /* ex: postdot == 33.0, cp="033" */
-
-  /* now count the number of decimal places and figure out what power of 10 to use */
-  divider = 1;
-  while (*cp != '\0') {
-    divider *= 10; cp++;
-  }
-
-  /* ex:
-   * cp="033", divider=1
-   * cp="33", divider=10
-   * cp="3", divider=100
-   * cp="", divider=1000
-   */
-
-  if (is_neg) {
-    ret -= (postdot / ((float) divider));
-  } else {
-    ret += (postdot / ((float) divider));
-  }
-  /* ex: ret == 100.033, tada! ... hopefully */
-
-  return ret;
+    int is_neg;
+    char *cp, *dot;
+    int divider;
+    float ret, postdot;
+
+    buf[siz - 1] = '\0';       /* ensure termination */
+
+    /* ok, let's illustrate using "100.033" as an example... */
+
+    is_neg = 0;
+    if (*buf == '-') {
+       is_neg = 1;
+    }
+
+    ret = (float) (strtol(buf, &dot, 10));
+    if (dot == NULL) {
+       return 0.0;
+    }
+    if (dot != NULL && *dot != '.') {
+       return ret;
+    }
+
+    /* ex: ret == 100.0 */
+
+    cp = (dot + 1);
+    postdot = (float) (strtol(cp, NULL, 10));
+    if (postdot == 0.0) {
+       return ret;
+    }
+
+    /* ex: postdot == 33.0, cp="033" */
+
+    /* now count the number of decimal places and figure out what power of 10 to use */
+    divider = 1;
+    while (*cp != '\0') {
+       divider *= 10;
+       cp++;
+    }
+
+    /* ex:
+     * cp="033", divider=1
+     * cp="33", divider=10
+     * cp="3", divider=100
+     * cp="", divider=1000
+     */
+
+    if (is_neg) {
+       ret -= (postdot / ((float) divider));
+    }
+    else {
+       ret += (postdot / ((float) divider));
+    }
+    /* ex: ret == 100.033, tada! ... hopefully */
+
+    return ret;
 }
 
 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)
 {
     char is_spam[6];
-    char s_str[20], t_str[20];
+    char s_str[21], t_str[21];
+
+    UNUSED_VARIABLE(len);
 
     /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
      * let's stick with it for this parser.
      * May  7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
      * work around using our own locale-independent float-parser code.
      */
-    if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3)
-    {
-       m->score = _locale_safe_string_to_float (s_str, 20);
-       m->threshold = _locale_safe_string_to_float (t_str, 20);
+    if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
+       m->score = _locale_safe_string_to_float(s_str, 20);
+       m->threshold = _locale_safe_string_to_float(t_str, 20);
+
+       /* set bounds on these to ensure no buffer overflow in the sprintf */
+       if (m->score > 1e10)
+           m->score = 1e10;
+       else if (m->score < -1e10)
+           m->score = -1e10;
+       if (m->threshold > 1e10)
+           m->threshold = 1e10;
+       else if (m->threshold < -1e10)
+           m->threshold = -1e10;
 
        /* Format is "Spam: x; y / x" */
-       m->is_spam=strcasecmp("true", is_spam) == 0 ? EX_ISSPAM: EX_NOTSPAM;
+       m->is_spam =
+           strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
 
-       if(flags&SPAMC_CHECK_ONLY) {
-           m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE,
-                       "%.1f/%.1f\n", m->score, m->threshold);
+       if (flags & SPAMC_CHECK_ONLY) {
+           m->out_len = sprintf(m->out,
+                                "%.1f/%.1f\n", m->score, m->threshold);
+       }
+       else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
+                || (flags & SPAMC_REPORT)) {
+           m->out_len = sprintf(m->out,
+                                "%.1f/%.1f\n", m->score, m->threshold);
        }
        return EX_OK;
 
-    } else if(sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
+    }
+    else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
        if (m->content_length < 0) {
-           syslog(LOG_ERR, "spamd responded with bad Content-length '%s'", buf);
+           libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
+                  buf);
            return EX_PROTOCOL;
        }
        return EX_OK;
     }
 
-    syslog(LOG_ERR, "spamd responded with bad header '%s'", buf);
+    libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
     return EX_PROTOCOL;
 }
 
-static int _message_filter(const struct sockaddr *addr,
-                const struct hostent *hent, int hent_port, char *username,
-                int flags, struct message *m)
+int message_filter(struct transport *tp, const char *username,
+                  int flags, struct message *m)
 {
     char buf[8192];
-    int bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
-    int len, i;
+    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;
-    SSL* ssl;
+    SSL_CTX *ctx = NULL;
+    SSL *ssl = NULL;
     SSL_METHOD *meth;
 
-    if (flags&SPAMC_USE_SSL) {
+    if (flags & SPAMC_USE_SSL) {
 #ifdef SPAMC_SSL
-      SSLeay_add_ssl_algorithms();
-      meth = SSLv2_client_method();
-      SSL_load_error_strings();
-      ctx = SSL_CTX_new(meth);
+       SSLeay_add_ssl_algorithms();
+       meth = SSLv2_client_method();
+       SSL_load_error_strings();
+       ctx = SSL_CTX_new(meth);
 #else
-      (void) ssl; (void) meth; (void) ctx;     /* avoid "unused" warnings */
-      syslog(LOG_ERR, "spamc not built with SSL support");
-      return EX_SOFTWARE;
+       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->out=malloc(m->max_len+EXPANSION_ALLOWANCE+1))==NULL){
-        failureval = EX_OSERR; goto failure;
+    m->is_spam = EX_TOOBIG;
+    if ((m->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
+       failureval = EX_OSERR;
+       goto failure;
     }
-    m->out_len=0;
+    m->out_len = 0;
 
 
     /* Build spamd protocol header */
-    if(flags & SPAMC_CHECK_ONLY) 
-      len=snprintf(buf, bufsiz, "CHECK %s\r\n", PROTOCOL_VERSION);
-    else if(flags & SPAMC_REPORT_IFSPAM)
-      len=snprintf(buf, bufsiz, "REPORT_IFSPAM %s\r\n", PROTOCOL_VERSION);
-    else if(flags & SPAMC_REPORT) 
-      len=snprintf(buf, bufsiz, "REPORT %s\r\n", PROTOCOL_VERSION);
-    else if(flags & SPAMC_SYMBOLS) 
-      len=snprintf(buf, bufsiz, "SYMBOLS %s\r\n", PROTOCOL_VERSION);
+    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
-      len=snprintf(buf, bufsiz, "PROCESS %s\r\n", PROTOCOL_VERSION);
+       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(len<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
-    if(username!=NULL){
-        len+=i=snprintf(buf+len, bufsiz-len, "User: %s\r\n", username);
-        if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
+    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);
+    }
+    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;
     }
-    len+=i=snprintf(buf+len, bufsiz-len, "Content-length: %d\r\n", m->msg_len);
-    if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
-    len+=i=snprintf(buf+len, bufsiz-len, "\r\n");
-    if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
+    len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
 
     libspamc_timeout = m->timeout;
 
-    if((i=try_to_connect(addr, (struct hostent *) hent,
-                       hent_port, &sock)) != EX_OK)
-    {
-        free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-        return i;
+    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) {
+    if (flags & SPAMC_USE_SSL) {
 #ifdef SPAMC_SSL
-      ssl = SSL_new(ctx);
-      SSL_set_fd(ssl, sock);
-      SSL_connect(ssl);
-#endif    
+       ssl = SSL_new(ctx);
+       SSL_set_fd(ssl, sock);
+       SSL_connect(ssl);
+#endif
     }
 
     /* Send to spamd */
-    if(flags&SPAMC_USE_SSL) {
+    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, m->msg, m->msg_len);
 #endif
-    } else {
-      full_write(sock, (unsigned char *) buf, len);
-      full_write(sock, (unsigned char *) m->msg, m->msg_len);
-      shutdown(sock, SHUT_WR);
+    }
+    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; }
+    failureval =
+       _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
+    if (failureval != EX_OK) {
+       goto failure;
+    }
 
-    if(sscanf(buf, "SPAMD/%s %d %*s", versbuf, &response)!=2) {
-       syslog(LOG_ERR, "spamd responded with bad string '%s'", buf);
-       failureval = EX_PROTOCOL; 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;
     }
 
-    version = _locale_safe_string_to_float (versbuf, 20);
+    versbuf[19] = '\0';
+    version = _locale_safe_string_to_float(versbuf, 20);
     if (version < 1.0) {
-       syslog(LOG_ERR, "spamd responded with bad version string '%s'", versbuf);
-       failureval = EX_PROTOCOL; goto failure;
+       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; }
+       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 */
+           break;              /* end of headers */
        }
 
        if (_handle_spamd_header(m, flags, buf, len) < 0) {
-           failureval = EX_PROTOCOL; goto failure;
+           failureval = EX_PROTOCOL;
+           goto failure;
        }
     }
 
-    len = 0;           /* overwrite those headers */
+    len = 0;                   /* overwrite those headers */
 
-    if (flags&SPAMC_CHECK_ONLY) {
-      close(sock); sock = -1;
-      if (m->is_spam == EX_TOOBIG) {
+    if (flags & SPAMC_CHECK_ONLY) {
+       closesocket(sock);
+       sock = -1;
+       if (m->is_spam == EX_TOOBIG) {
            /* We should have gotten headers back... Damnit. */
-           failureval = EX_PROTOCOL; goto failure;
-      }
-      return EX_OK;
+           failureval = EX_PROTOCOL;
+           goto failure;
+       }
+       return EX_OK;
     }
     else {
-       m->is_spam=EX_OUTPUTMESSAGE;
        if (m->content_length < 0) {
            /* should have got a length too. */
-           failureval = EX_PROTOCOL; goto failure;
+           failureval = EX_PROTOCOL;
+           goto failure;
        }
 
-       if (flags&SPAMC_USE_SSL) {
-         len = ssl_timeout_read (ssl, m->out+m->out_len,
-                    m->max_len+EXPANSION_ALLOWANCE+1-m->out_len);
-       } else{
-         len = full_read (sock, (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);
+       /* have we already got something in the buffer (e.g. REPORT and
+        * REPORT_IFSPAM both create a line from the "Spam:" hdr)?  If
+        * so, add the size of that so our sanity check passes.
+        */
+       if (m->out_len > 0) {
+           m->content_length += m->out_len;
        }
 
+       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);
+       }
+       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);
+       }
 
-       if(len+m->out_len>m->max_len+EXPANSION_ALLOWANCE){
-           failureval = EX_TOOBIG; goto failure;
+
+       if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
+           failureval = EX_TOOBIG;
+           goto failure;
        }
-       m->out_len+=len;
+       m->out_len += len;
 
        shutdown(sock, SHUT_RD);
-       close(sock); sock = -1;
+       closesocket(sock);
+       sock = -1;
     }
     libspamc_timeout = 0;
 
-    if(m->out_len!=m->content_length) {
-        syslog(LOG_ERR, "failed sanity check, %d bytes claimed, %d bytes seen",
-                               m->content_length, m->out_len);
-       failureval = EX_PROTOCOL; goto failure;
+    if (m->out_len != m->content_length) {
+       libspamc_log(flags, LOG_ERR,
+              "failed sanity check, %d bytes claimed, %d bytes seen",
+              m->content_length, m->out_len);
+       failureval = EX_PROTOCOL;
+       goto failure;
     }
 
     return EX_OK;
 
-failure:
-    free(m->out); m->out=m->msg; m->out_len=m->msg_len;
+  failure:
+    free(m->out);
+    m->out = m->msg;
+    m->out_len = m->msg_len;
     if (sock != -1) {
-      close(sock);
+       closesocket(sock);
     }
     libspamc_timeout = 0;
 
-    if(flags&SPAMC_USE_SSL) {
+    if (flags & SPAMC_USE_SSL) {
 #ifdef SPAMC_SSL
-      SSL_free(ssl);
-      SSL_CTX_free(ctx);
+       SSL_free(ssl);
+       SSL_CTX_free(ctx);
 #endif
     }
     return failureval;
 }
 
-static int _lookup_host(const char *hostname, struct hostent *out_hent)
-{
-    struct hostent *hent = NULL;
-    int origherr;
-
-    /* no need to try using inet_addr(), gethostbyname() will do that */
-
-    if (NULL == (hent = gethostbyname(hostname))) {
-        origherr = h_errno;    /* take a copy before syslog() */
-        syslog (LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
-                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;
-        }
-    }
-
-    memcpy (out_hent, hent, sizeof(struct hostent));
-
-    return EX_OK;
-}
 
-int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags){
-    struct hostent hent;
+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;
 
-    m.type=MESSAGE_NONE;
-
-    ret=lookup_host_for_failover(hostname, &hent);
-    if(ret!=EX_OK) goto FAIL;
-    
-    m.max_len=max_size;
-    ret=message_read(in_fd, flags, &m);
-    if(ret!=EX_OK) goto FAIL;
-    ret=message_filter_with_failover(&hent, port, username, flags, &m);
-    if(ret!=EX_OK) goto FAIL;
-    if(message_write(out_fd, &m)<0) goto FAIL;
-    if(m.is_spam!=EX_TOOBIG) {
-       message_cleanup(&m);
-       return m.is_spam;
+    m.type = MESSAGE_NONE;
+
+    m.max_len = max_size;
+    ret = message_read(in_fd, flags, &m);
+    if (ret != EX_OK)
+       goto FAIL;
+    ret = message_filter(trans, username, flags, &m);
+    if (ret != EX_OK)
+       goto FAIL;
+    if (message_write(out_fd, &m) < 0)
+       goto FAIL;
+    if (m.is_spam != EX_TOOBIG) {
+       message_cleanup(&m);
+       return m.is_spam;
     }
     message_cleanup(&m);
     return ret;
 
-FAIL:
-   if(flags&SPAMC_CHECK_ONLY){
-       full_write(out_fd, (unsigned char *) "0/0\n", 4);
-       message_cleanup(&m);
-       return EX_NOTSPAM;
-   } else {
-       message_dump(in_fd, out_fd, &m);
-       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;
+    }
+    else {
+       message_dump(in_fd, out_fd, &m);
+       message_cleanup(&m);
+       return ret;
     }
 }
 
-void message_cleanup(struct message *m) {
-   if (m->out != NULL && m->out != m->raw) free(m->out);
-   if (m->raw != NULL) free(m->raw);
-   clear_message(m);
+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);
+    _clear_message(m);
 }
 
 /* Aug 14, 2002 bj: Obsolete! */
-int process_message(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int my_check_only, const int my_safe_fallback){
+int process_message(struct transport *tp, char *username, int max_size,
+                   int in_fd, int out_fd, const int my_check_only,
+                   const int my_safe_fallback)
+{
     int flags;
 
-    flags=SPAMC_RAW_MODE;
-    if(my_check_only) flags|=SPAMC_CHECK_ONLY;
-    if(my_safe_fallback) flags|=SPAMC_SAFE_FALLBACK;
+    flags = SPAMC_RAW_MODE;
+    if (my_check_only)
+       flags |= SPAMC_CHECK_ONLY;
+    if (my_safe_fallback)
+       flags |= SPAMC_SAFE_FALLBACK;
 
-    return message_process(hostname, port, username, max_size, in_fd, out_fd, flags);
+    return message_process(tp, username, max_size, in_fd, out_fd, flags);
 }
 
-/* public APIs, which call into the static code and enforce sockaddr-OR-hostent
- * conventions */
+/*
+ * 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);
+
+    memset(tp, 0, sizeof *tp);
+
+    tp->type = TRANSPORT_LOCALHOST;
+    tp->port = 783;
+    tp->flags = 0;
+}
+
+/*
+ * 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 
+ */
 
-int lookup_host(const char *hostname, int port, struct sockaddr *out_addr)
+static void _randomize_hosts(struct transport *tp)
 {
-  struct sockaddr_in *addr = (struct sockaddr_in *)out_addr;
-  struct hostent hent;
-  int ret;
-
-  memset(&out_addr, 0, sizeof(out_addr));
-  addr->sin_family=AF_INET;
-  addr->sin_port=htons(port);
-  ret = _lookup_host(hostname, &hent);
-  memcpy (&(addr->sin_addr), hent.h_addr, sizeof(addr->sin_addr));
-  return ret;
+    int rnum;
+
+    assert(tp != 0);
+
+    if (tp->nhosts <= 1)
+       return;
+
+    rnum = rand() % tp->nhosts;
+
+    while (rnum-- > 0) {
+       struct in_addr tmp = tp->hosts[0];
+       int i;
+
+       for (i = 1; i < tp->nhosts; i++)
+           tp->hosts[i - 1] = tp->hosts[i];
+
+       tp->hosts[i - 1] = tmp;
+    }
 }
 
-int lookup_host_for_failover(const char *hostname, struct hostent *hent) {
-  return _lookup_host(hostname, hent);
+/*
+ * 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;
+    char **addrp;
+
+#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;
+    }
+
+#endif
+
+    tp->flags = flags;
+
+    assert(tp != 0);
+
+    switch (tp->type) {
+#ifndef _WIN32
+    case TRANSPORT_UNIX:
+       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;
+
+    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 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;
+       }
+
+       if (hp->h_length != sizeof tp->hosts[0]
+           || hp->h_addrtype != AF_INET) {
+           /* FAIL - bad size/protocol/family? */
+           return EX_NOHOST;
+       }
+
+               /*--------------------------------------------------------
+                * 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;
+
+       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;
+           }
+
+           memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
+
+           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);
+       }
+
+               /*--------------------------------------------------------
+                * 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;
 }
 
-int message_filter(const struct sockaddr *addr, char *username, int flags,
-                struct message *m)
-{ return _message_filter (addr, NULL, 0, username, flags, m); }
+/* --------------------------------------------------------------------------- */
+
+#define LOG_BUFSIZ      1023
+
+void
+libspamc_log (int flags, int level, char *msg, ...)
+{
+    va_list ap;
+    char buf[LOG_BUFSIZ+1];
+    int len = 0;
+
+    va_start(ap, msg);
+
+    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);
+
+        /* avoid buffer overflow */
+        if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
 
-int message_filter_with_failover (const struct hostent *hent, int port,
-                char *username, int flags, struct message *m)
-{ return _message_filter (NULL, hent, port, username, flags, m); }
+        len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
+        buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
+        if (write (2, buf, len) != len)
+               goto out;
+    } else {
+        vsnprintf(buf, LOG_BUFSIZ, msg, ap);
+        buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
+#ifndef _WIN32
+        syslog (level, "%s", buf);
+#else
+        (void) level;  /* not used. suppress compiler warning */
+        g_printerr ("%s\n", buf);
+#endif
+    }
+out:
+    va_end(ap);
+}
+
+/* --------------------------------------------------------------------------- */
+
+/*
+ * 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)
+{
+    char inputstr[99], cmpbuf1[99], cmpbuf2[99];
+    float output;
+
+    /* sprintf instead of snprintf is safe here because it is only a controlled test */
+    sprintf(inputstr, "%f", input);
+    output = _locale_safe_string_to_float(inputstr, 99);
+    if (input == output) {
+       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;
+    }
+
+    g_print("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 */
+    };
+    float num;
+    int i;
+
+    g_print("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]);
+    }
+    /* 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);
+    }
+    g_print("finished unit_test_locale_safe_string_to_float\n");
+}
+
+void do_libspamc_unit_tests(void)
+{
+    unit_test_locale_safe_string_to_float();
+    exit(0);
+}
 
+#endif /* LIBSPAMC_UNIT_TESTS */