0.8.9claws18
[claws.git] / src / plugins / spamassassin / libspamc.c
index 177925658079d0e121ad09dc1d5ad4c8a7f41b24..c870ad31f43fb0e47bbb91889e419632a240f5c4 100644 (file)
@@ -1,12 +1,15 @@
 /*
  * This code is copyright 2001 by Craig Hughes
  * Portions copyright 2002 by Brad Jorsch
- * It is licensed for use with SpamAssassin according to the terms of the Perl Artistic License
- * The text of this license is included in the SpamAssassin distribution in the file named "License"
+ * 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".
  */
 
+#include "../config.h"
 #include "libspamc.h"
 #include "utils.h"
+
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
-#include <netdb.h>
 #include <arpa/inet.h>
 
+#ifdef SPAMC_SSL
+#include <openssl/crypto.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
 #ifdef HAVE_SYSEXITS_H
 #include <sysexits.h>
 #endif
@@ -66,6 +75,8 @@ extern char *optarg;
 #define EX__MAX 200 
 #endif
 
+static const int DO_CONNECT_DEBUG_SYSLOGS = 0;
+
 static const int ESC_PASSTHROUGHRAW = EX__MAX+666;
 
 /* set EXPANSION_ALLOWANCE to something more than might be
@@ -81,9 +92,11 @@ static const int EXPANSION_ALLOWANCE = 16384;
 /* Set the protocol version that this spamc speaks */
 static const char *PROTOCOL_VERSION="SPAMC/1.2";
 
-/* Aug 14, 2002 bj: No more ctx! */
+int libspamc_timeout = 0;
+
 static int
-try_to_connect (const struct sockaddr *addr, int *sockptr)
+try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
+                int hent_port, int *sockptr)
 {
 #ifdef USE_TCP_NODELAY
   int value;
@@ -92,6 +105,78 @@ try_to_connect (const struct sockaddr *addr, int *sockptr)
   int status = -1;
   int origerr;
   int numloops;
+  int hostnum = 0;
+  struct sockaddr_in addrbuf, *addr;
+  struct in_addr inaddrlist[256];
+
+  int i; char dbgbuf[2048]; int dbgbuflen = 0;         // DBG
+
+  /* NOTE: do not call syslog() (unless you are about to return) before
+   * we take a copy of the h_addr_list.
+   */
+
+  /* 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;
+  }
+
+  /* take a copy of the h_addr_list part of the struct hostent */
+  if (hent != NULL) {
+    memset (inaddrlist, 0, sizeof(inaddrlist));
+
+    for (hostnum=0; hent->h_addr_list[hostnum] != 0; hostnum++) {
+      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]);
+
+      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;
+      }
+
+      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]);
+
+      memcpy ((void *) &(inaddrlist[hostnum]),
+               (void *) hent->h_addr_list[hostnum],
+               sizeof (struct in_addr));
+    }
+
+    if (DO_CONNECT_DEBUG_SYSLOGS) {
+      syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0;
+    }
+  }
+
+
+  if (DO_CONNECT_DEBUG_SYSLOGS) {
+    for (i = 0; i < hostnum; i++) {
+      syslog (LOG_DEBUG, "dbg: host addr %d/%d = %lx at %lx",
+                 i, hostnum, inaddrlist[i].s_addr, &(inaddrlist[i]));
+    }
+  }
+
+  hent = NULL; /* cannot use hent after this point, syslog() may overwrite it */
+
+  if (DO_CONNECT_DEBUG_SYSLOGS) { syslog (LOG_DEBUG, "dbg: socket"); }
 
   if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
   {
@@ -114,6 +199,8 @@ try_to_connect (const struct sockaddr *addr, int *sockptr)
     }
   }
   
+  if (DO_CONNECT_DEBUG_SYSLOGS) { syslog (LOG_DEBUG, "dbg: setsockopt"); }
+
 #ifdef USE_TCP_NODELAY
   /* TODO: should this be up above the connect()? */
   value = 1;           /* make this explicit! */
@@ -126,6 +213,7 @@ try_to_connect (const struct sockaddr *addr, int *sockptr)
     case ENOPROTOOPT:
     case EFAULT:
       syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
+      close (mysock);
       return EX_SOFTWARE;
 
     default:
@@ -135,7 +223,50 @@ try_to_connect (const struct sockaddr *addr, int *sockptr)
 #endif
 
   for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
+    if (DO_CONNECT_DEBUG_SYSLOGS) {
+      syslog (LOG_DEBUG, "dbg: connect() to spamd %d", numloops);
+    }
+    if (argaddr != NULL) {
+      addr = (struct sockaddr_in *) argaddr;     /* use the one provided */
+      if (DO_CONNECT_DEBUG_SYSLOGS) {
+       syslog (LOG_DEBUG, "dbg: using argaddr");
+      }
+
+    } 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;
+      }
+
+      if (DO_CONNECT_DEBUG_SYSLOGS) {
+       syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx",
+               numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum]));
+      }
+
+      memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]),
+                        sizeof(addrbuf.sin_addr));
+      addr = &addrbuf;
+
+      if (DO_CONNECT_DEBUG_SYSLOGS) {
+       syslog (LOG_DEBUG, "dbg: conn addr %d/%d = %lx",
+           numloops%hostnum, hostnum, addrbuf.sin_addr.s_addr);
+      }
+
+    }
+
+    syslog (LOG_DEBUG, "dbg: connect() to spamd at %s",
+               inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
     status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
+    if (DO_CONNECT_DEBUG_SYSLOGS) {
+      syslog (LOG_DEBUG, "dbg: connect() to spamd at %s done",
+         inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
+    }
 
     if (status < 0)
     {
@@ -143,7 +274,7 @@ try_to_connect (const struct sockaddr *addr, int *sockptr)
       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(1);
+      sleep(CONNECT_RETRY_SLEEP);
 
     } else {
       *sockptr = mysock;
@@ -152,6 +283,7 @@ try_to_connect (const struct sockaddr *addr, int *sockptr)
   }
  
   /* failed, even with a few retries */
+  close (mysock);
   syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
        MAX_CONNECT_RETRIES);
  
@@ -196,7 +328,7 @@ static void clear_message(struct message *m){
 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, m->raw, m->max_len+1, m->max_len+1);
+    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;
@@ -219,7 +351,7 @@ static int message_read_bsmtp(int fd, struct message *m){
     if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
 
     /* Find the DATA line */
-    m->raw_len=full_read(fd, m->raw, m->max_len+1, m->max_len+1);
+    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;
@@ -273,6 +405,8 @@ static int message_read_bsmtp(int fd, 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);
@@ -289,10 +423,11 @@ int message_read(int fd, int flags, struct message *m){
 long message_write(int fd, struct message *m){
     long total=0;
     off_t i, j;
+    off_t jlimit;
     char buffer[1024];
 
     if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
-        return full_write(fd, m->out, m->out_len);
+        return full_write(fd, (unsigned char *) m->out, m->out_len);
     }
 
     switch(m->type){
@@ -301,16 +436,22 @@ long message_write(int fd, struct message *m){
         return -1;
 
       case MESSAGE_ERROR:
-        return full_write(fd, m->raw, m->raw_len);
+        return full_write(fd, (unsigned char *) m->raw, m->raw_len);
 
       case MESSAGE_RAW:
-        return full_write(fd, m->out, m->out_len);
+        return full_write(fd, (unsigned char *) m->out, m->out_len);
 
       case MESSAGE_BSMTP:
-        total=full_write(fd, m->pre, m->pre_len);
+        total=full_write(fd, (unsigned char *) m->pre, m->pre_len);
         for(i=0; i<m->out_len; ){
-            for(j=0; i<m->out_len && j<sizeof(buffer)/sizeof(*buffer)-1; ){
+           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 */
+                   }
                     buffer[j++]=m->out[i++];
                     buffer[j++]=m->out[i++];
                     buffer[j++]='.';
@@ -318,9 +459,9 @@ long message_write(int fd, struct message *m){
                     buffer[j++]=m->out[i++];
                 }
             }
-            total+=full_write(fd, buffer, j);
+            total+=full_write(fd, (unsigned char *) buffer, j);
         }
-        return total+full_write(fd, m->post, m->post_len);
+        return total+full_write(fd, (unsigned char *) m->post, m->post_len);
 
       default:
         syslog(LOG_ERR, "Unknown message type %d\n", m->type);
@@ -335,66 +476,102 @@ void message_dump(int in_fd, int out_fd, struct message *m){
     if(m!=NULL && m->type!=MESSAGE_NONE) {
         message_write(out_fd, m);
     }
-    while((bytes=full_read(in_fd, buf, 8192, 8192))>0){
-        if(bytes!=full_write(out_fd, buf, bytes));
+    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);
+        }
     }
 }
 
-int message_filter(const struct sockaddr *addr, char *username, int flags, struct message *m){
-    char *buf=NULL, is_spam[5];
+static int _message_filter(const struct sockaddr *addr,
+                const struct hostent *hent, int hent_port, char *username,
+                int flags, struct message *m)
+{
+    char buf[8192], is_spam[6];
+    int bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
     int len, expected_len, i, header_read=0;
     int sock;
     float version;
     int response;
+    int failureval;
+#ifdef SPAMC_SSL
+    SSL_CTX* ctx;
+    SSL* ssl;
+    SSL_METHOD *meth;
+
+    if(flags&SPAMC_USE_SSL){   
+      SSLeay_add_ssl_algorithms();
+      meth = SSLv2_client_method();
+      SSL_load_error_strings();
+      ctx = SSL_CTX_new(meth);
+    }    
+#endif
 
     m->is_spam=EX_TOOBIG;
-    if((buf=malloc(8192))==NULL) return EX_OSERR;
     if((m->out=malloc(m->max_len+EXPANSION_ALLOWANCE+1))==NULL){
-        free(buf);
         return EX_OSERR;
     }
     m->out_len=0;
 
     /* Build spamd protocol header */
-    len=snprintf(buf, 1024, "%s %s\r\n", (flags&SPAMC_CHECK_ONLY)?"CHECK":"PROCESS", PROTOCOL_VERSION);
-    if(len<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
+    len=snprintf(buf, bufsiz, "%s %s\r\n", (flags&SPAMC_CHECK_ONLY)?"CHECK":"PROCESS", PROTOCOL_VERSION);
+    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, 1024-len, "User: %s\r\n", username);
-        if(i<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
+        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; }
     }
-    len+=i=snprintf(buf+len, 1024-len, "Content-length: %d\r\n", m->msg_len);
-    if(i<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
-    len+=i=snprintf(buf+len, 1024-len, "\r\n");
-    if(i<0 || len>1024){ free(buf); 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; }
+
+    libspamc_timeout = m->timeout;
 
-    if((i=try_to_connect(addr, &sock))!=EX_OK){
-        free(buf);
+    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(flags&SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+      ssl = SSL_new(ctx);
+      SSL_set_fd(ssl, sock);
+      SSL_connect(ssl);
+#endif    
+    }
+
     /* Send to spamd */
-    full_write(sock, buf, len);
-    full_write(sock, m->msg, m->msg_len);
-    shutdown(sock, SHUT_WR);
+    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, (unsigned char *) buf, len);
+      full_write(sock, (unsigned char *) m->msg, m->msg_len);
+      shutdown(sock, SHUT_WR);
+    }
 
     /* Now, read from spamd */
-    for(len=0; len<8192; len++){
-        i=read(sock, buf+len, 1);
+    for(len=0; len<bufsiz; len++) {
+       if(flags&SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+         i=timeout_read(SSL_read, ssl, buf+len, 1);
+#endif
+       } else {
+         i=timeout_read(read, sock, buf+len, 1);
+       }
+
         if(i<0){
-            free(buf);
-            free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-            close(sock);
-            return EX_IOERR;
+           failureval = EX_IOERR; goto failure;
         }
         if(i==0){
             /* Read to end of message! Must be a version <1.0 server */
             if(len<100){
                 /* Nope, communication error */
-                free(buf);
-                free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-                close(sock);
-                return EX_IOERR;
+               failureval = EX_IOERR; goto failure;
             }
             break;
         }
@@ -402,10 +579,7 @@ int message_filter(const struct sockaddr *addr, char *username, int flags, struc
             buf[len]='\0';
             if(sscanf(buf, "SPAMD/%f %d %*s", &version, &response)!=2){
                 syslog(LOG_ERR, "spamd responded with bad string '%s'", buf);
-                free(buf);
-                free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-                close(sock);
-                return EX_PROTOCOL;
+               failureval = EX_PROTOCOL; goto failure;
             }
             header_read=-1;
             break;
@@ -418,20 +592,25 @@ int message_filter(const struct sockaddr *addr, char *username, int flags, struc
     } else {
         /* Handle different versioned headers */
         if(version-1.0>0.01){
-            for(len=0; len<8192; len++){
-                i=read(sock, buf+len, 1);
+            for(len=0; len<bufsiz; len++){
+#ifdef SPAMC_SSL
+             if(flags&SPAMC_USE_SSL){
+               i=timeout_read(SSL_read, ssl, buf+len, 1);
+             } else{
+#endif
+               i=timeout_read(read, sock, buf+len, 1);
+#ifdef SPAMC_SSL
+             }
+#endif
                 if(i<=0){
-                    free(buf);
-                    free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-                    close(sock);
-                    return (i<0)?EX_IOERR:EX_PROTOCOL;
+                   failureval = (i<0)?EX_IOERR:EX_PROTOCOL; goto failure;
                 }
                 if(buf[len]=='\n'){
                     buf[len]='\0';
                     if(flags&SPAMC_CHECK_ONLY){
                         /* Check only mode, better be "Spam: x; y / x" */
                         i=sscanf(buf, "Spam: %5s ; %f / %f", is_spam, &m->score, &m->threshold);
-                        free(buf);
+                        
                         if(i!=3){
                             free(m->out); m->out=m->msg; m->out_len=m->msg_len;
                             return EX_PROTOCOL;
@@ -443,20 +622,23 @@ int message_filter(const struct sockaddr *addr, char *username, int flags, struc
                     } else {
                         /* Not check-only, better be Content-length */
                         if(sscanf(buf, "Content-length: %d", &expected_len)!=1){
-                            free(buf);
-                            free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-                            close(sock);
-                            return EX_PROTOCOL;
+                           failureval = EX_PROTOCOL;
+                           goto failure;
                         }
                     }
 
                     /* Should be end of headers now */
-                    if(full_read(sock, buf, 2, 2)!=2 || buf[0]!='\r' || buf[1]!='\n'){
+                   if(flags&SPAMC_USE_SSL){
+#ifdef SPAMC_SSL
+                     i=timeout_read(SSL_read,ssl, buf, 2);
+#endif
+                   } else{
+                     i=full_read (sock, (unsigned char *) buf, 2, 2);
+                   }
+
+                    if(i!=2 || buf[0]!='\r' || buf[1]!='\n'){
                         /* Nope, bail. */
-                        free(buf);
-                        free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-                        close(sock);
-                        return EX_PROTOCOL;
+                       failureval = EX_PROTOCOL; goto failure;
                     }
 
                     break;
@@ -464,90 +646,96 @@ int message_filter(const struct sockaddr *addr, char *username, int flags, struc
             }
         }
     }
-    free(buf);
 
     if(flags&SPAMC_CHECK_ONLY){
         /* We should have gotten headers back... Damnit. */
-        free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-        close(sock);
-        return EX_PROTOCOL;
+       failureval = EX_PROTOCOL; goto failure;
+    }
+
+    if(flags&SPAMC_USE_SSL){
+#ifdef SPAMC_SSL
+      len=timeout_read(SSL_read,ssl, m->out+m->out_len,
+                m->max_len+EXPANSION_ALLOWANCE+1-m->out_len);
+#endif
+    } 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);
     }
 
-    len=full_read(sock, 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){
-        free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-        close(sock);
-        return EX_TOOBIG;
+       failureval = EX_TOOBIG; goto failure;
     }
     m->out_len+=len;
 
     shutdown(sock, SHUT_RD);
     close(sock);
+    libspamc_timeout = 0;
 
     if(m->out_len!=expected_len){
         syslog(LOG_ERR, "failed sanity check, %d bytes claimed, %d bytes seen", expected_len, m->out_len);
-        free(m->out); m->out=m->msg; m->out_len=m->msg_len;
-        close(sock);
-        return EX_PROTOCOL;
+       failureval = EX_PROTOCOL; goto failure;
     }
 
     return EX_OK;
-}
 
-int lookup_host(const char *hostname, int port, struct sockaddr *a){
-    struct sockaddr_in *addr=(struct sockaddr_in *)a;
-  struct hostent *hent;
-  int origherr;
+failure:
+    free(m->out); m->out=m->msg; m->out_len=m->msg_len;
+    close(sock);
+    libspamc_timeout = 0;
 
-    memset(&a, 0, sizeof(a));
+#ifdef SPAMC_SSL
+    if(flags&SPAMC_USE_SSL){
+      SSL_free(ssl);
+      SSL_CTX_free(ctx);
+    }
+#endif
+    return failureval;
+}
 
-    addr->sin_family=AF_INET;
-    addr->sin_port=htons(port);
+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 */
 
-    /* first, try to mangle it directly into an addr->  This will work
-   * for numeric IP addresses, but not for hostnames...
-   */
-    addr->sin_addr.s_addr = inet_addr (hostname);
-    if (addr->sin_addr.s_addr == INADDR_NONE) {
-    /* If that failed, we can use gethostbyname() to resolve it.
-     */
     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;
-      }
+        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 (&addr->sin_addr, hent->h_addr, sizeof(addr->sin_addr));
-  }
+    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 sockaddr addr;
+    struct hostent hent;
     int ret;
     struct message m;
 
     m.type=MESSAGE_NONE;
 
-    ret=lookup_host(hostname, port, &addr);
+    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(&addr, username, flags, &m);
+    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) {
@@ -559,7 +747,7 @@ int message_process(const char *hostname, int port, char *username, int max_size
 
 FAIL:
    if(flags&SPAMC_CHECK_ONLY){
-       full_write(out_fd, "0/0\n", 4);
+       full_write(out_fd, (unsigned char *) "0/0\n", 4);
        message_cleanup(&m);
        return EX_NOTSPAM;
    } else {
@@ -585,3 +773,33 @@ int process_message(const char *hostname, int port, char *username, int max_size
 
     return message_process(hostname, port, username, max_size, in_fd, out_fd, flags);
 }
+
+/* public APIs, which call into the static code and enforce sockaddr-OR-hostent
+ * conventions */
+
+int lookup_host(const char *hostname, int port, struct sockaddr *out_addr)
+{
+  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 lookup_host_for_failover(const char *hostname, struct hostent *hent) {
+  return _lookup_host(hostname, hent);
+}
+
+int message_filter(const struct sockaddr *addr, char *username, int flags,
+                struct message *m)
+{ return _message_filter (addr, NULL, 0, username, flags, m); }
+
+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); }
+