0.9.6claws4
authorChristoph Hohmann <reboot@gmx.ch>
Sat, 4 Oct 2003 18:16:23 +0000 (18:16 +0000)
committerChristoph Hohmann <reboot@gmx.ch>
Sat, 4 Oct 2003 18:16:23 +0000 (18:16 +0000)
* src/plugins/spamassassin/libspamc.[ch]
* src/plugins/spamassassin/spamassassin.c
* src/plugins/spamassassin/utils.[ch]
        update libspamc to version from SpamAssassin 2.60

ChangeLog.claws
configure.ac
src/plugins/spamassassin/libspamc.c
src/plugins/spamassassin/libspamc.h
src/plugins/spamassassin/spamassassin.c
src/plugins/spamassassin/utils.c
src/plugins/spamassassin/utils.h

index 440a80c..f3fb625 100644 (file)
@@ -1,3 +1,10 @@
+2003-10-04 [christoph] 0.9.6claws4
+
+       * src/plugins/spamassassin/libspamc.[ch]
+       * src/plugins/spamassassin/spamassassin.c
+       * src/plugins/spamassassin/utils.[ch]
+               update libspamc to version from SpamAssassin 2.60
+
 2003-10-03 [christoph] 0.9.6claws3
 
        * src/send_message.c
index 83f1f53..298190b 100644 (file)
@@ -11,7 +11,7 @@ MINOR_VERSION=9
 MICRO_VERSION=6
 INTERFACE_AGE=0
 BINARY_AGE=0
-EXTRA_VERSION=3
+EXTRA_VERSION=4
 if test $EXTRA_VERSION -eq 0; then
     VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}claws
 else
index 5d5f988..84d21da 100644 (file)
 
 #include <unistd.h>
 #include <stdlib.h>
+#include <assert.h>
 #include <stdio.h>
 #include <string.h>
 #include <syslog.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>
 
 
 /* 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
@@ -86,8 +93,304 @@ static const int EXPANSION_ALLOWANCE = 16384;
 /* Set the protocol version that this spamc speaks */
 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;
 
+/*
+ * 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)
+{
+       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 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
+       syslog (DEBUG_LEVEL, "dbg: create socket(%s)", typename);
+#endif
+
+       if ( (*psock = socket(type, SOCK_STREAM, proto)) < 0 )
+       {
+       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 = errno;    /* take a copy before syslog() */
+
+               syslog (LOG_ERR, "socket(%s) to spamd failed: %m", typename);
+
+               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;
+               }
+       }
+
+
+       /*----------------------------------------------------------------
+        * 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 )
+               {
+                       switch(errno)
+                       {
+                         case EBADF:
+                         case ENOTSOCK:
+                         case ENOPROTOOPT:
+                         case EFAULT:
+                               syslog(LOG_ERR,
+                                  "setsockopt(TCP_NODELAY) failed: %m");
+                               close (*psock);
+                               return EX_SOFTWARE;
+
+                         default:
+                               break;            /* ignored */
+                       }
+               }
+       }
+#endif /* USE_TCP_NODELAY */
+
+       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)
+{
+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(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
+       syslog (DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
+               addrbuf.sun_path);
+#endif
+
+       status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
+
+       origerr = errno;
+
+       if ( status >= 0 )
+       {
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+               syslog(DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
+#endif
+
+               *sockptr = mysock;
+
+               return EX_OK;
+       }
+
+       syslog(LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %m",
+               addrbuf.sun_path);
+
+       close(mysock);
+
+       return translate_connect_errno(origerr);
+}
+
+/*
+ * 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);
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+       for (numloops = 0; numloops < tp->nhosts; numloops++)
+       {
+               syslog(LOG_ERR, "dbg: %d/%d: %s",
+                       numloops+1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
+       }
+#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;
+
+               /*--------------------------------------------------------
+                * 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(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);
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+               syslog (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));
+
+               if (status != 0)
+               {
+                       syslog (LOG_ERR,
+                       "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %m",
+                               ipaddr, numloops+1, MAX_CONNECT_RETRIES);
+
+                       close(mysock);
+
+                       sleep(CONNECT_RETRY_SLEEP);
+               }
+               else
+               {
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+                       syslog(DEBUG_LEVEL,
+                               "dbg: connect(AF_INET) to spamd at %s done",
+                               ipaddr);
+#endif
+                       *sockptr = mysock;
+
+                       return EX_OK;
+               }
+       }
+
+       syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
+               MAX_CONNECT_RETRIES);
+
+       return translate_connect_errno(origerr);
+}
+
+#if 0
 static int
 try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
                 int hent_port, int *sockptr)
@@ -315,6 +618,7 @@ try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
      return EX_SOFTWARE;
   }
 }
+#endif
 
 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
  * message_dump, lookup_host, message_filter, and message_process, and a bunch
@@ -337,7 +641,7 @@ 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);
+    m->raw_len=full_read(fd, 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;
@@ -360,7 +664,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, (unsigned char *) m->raw, m->max_len+1, m->max_len+1);
+    m->raw_len=full_read(fd, 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;
@@ -416,6 +720,14 @@ static int message_read_bsmtp(int fd, struct message *m){
 int message_read(int fd, int flags, struct message *m){
     libspamc_timeout = 0;
 
+    /* create the "private" part of the struct message */
+    m->priv = malloc (sizeof (struct libspamc_private_message));
+    if (m->priv == NULL) {
+        syslog(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);
@@ -435,30 +747,30 @@ long message_write(int fd, struct message *m){
     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, 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 {
+           syslog(LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d\n", m->is_spam);
+           return -1;
+       }
     }
 
+    /* else we're not in CHECK_ONLY mode */
     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);
+        return full_write(fd, m->raw, m->raw_len);
 
       case MESSAGE_RAW:
-        return full_write(fd, (unsigned char *) m->out, m->out_len);
+        return full_write(fd, m->out, m->out_len);
 
       case MESSAGE_BSMTP:
-        total=full_write(fd, (unsigned char *) m->pre, m->pre_len);
+        total=full_write(fd, 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 &&
@@ -475,9 +787,9 @@ long message_write(int fd, struct message *m){
                     buffer[j++]=m->out[i++];
                 }
             }
-            total+=full_write(fd, (unsigned char *) buffer, j);
+            total+=full_write(fd, buffer, j);
         }
-        return total+full_write(fd, (unsigned char *) m->post, m->post_len);
+        return total+full_write(fd, m->post, m->post_len);
 
       default:
         syslog(LOG_ERR, "Unknown message type %d\n", m->type);
@@ -492,8 +804,8 @@ 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, (unsigned char *) buf, 8192, 8192))>0){
-        if (bytes!=full_write(out_fd, (unsigned char *) buf, bytes)) {
+    while((bytes=full_read(in_fd, buf, 8192, 8192))>0){
+        if (bytes!=full_write(out_fd, buf, bytes)) {
             syslog(LOG_ERR, "oops! message_dump of %d returned different", bytes);
         }
     }
@@ -507,6 +819,8 @@ _spamc_read_full_line (struct message *m, int flags, SSL *ssl, int sock,
     int bytesread = 0;
     int len;
 
+    UNUSED_VARIABLE(m);
+
     /* Now, read from spamd */
     for(len=0; len<bufsiz-1; len++) {
        if(flags&SPAMC_USE_SSL) {
@@ -595,7 +909,9 @@ static int
 _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.
@@ -614,6 +930,12 @@ _handle_spamd_header (struct message *m, int flags, char *buf, int len)
            m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE,
                        "%.1f/%.1f\n", m->score, m->threshold);
        }
+       else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
+               || (flags & SPAMC_REPORT))
+       {
+           m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE,
+                       "%.1f/%.1f\n", m->score, m->threshold);
+       }
        return EX_OK;
 
     } else if(sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
@@ -628,14 +950,14 @@ _handle_spamd_header (struct message *m, int flags, char *buf, int len)
     return EX_PROTOCOL;
 }
 
-static int _message_filter(const struct sockaddr *addr,
-                const struct hostent *hent, int hent_port, char *username,
+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;
     int sock = -1;
+    int rc;
     char versbuf[20];
     float version;
     int response;
@@ -651,7 +973,9 @@ static int _message_filter(const struct sockaddr *addr,
       SSL_load_error_strings();
       ctx = SSL_CTX_new(meth);
 #else
-      (void) ssl; (void) meth; (void) ctx;     /* avoid "unused" warnings */
+      UNUSED_VARIABLE(ssl);
+      UNUSED_VARIABLE(meth);
+      UNUSED_VARIABLE(ctx);
       syslog(LOG_ERR, "spamc not built with SSL support");
       return EX_SOFTWARE;
 #endif
@@ -688,8 +1012,12 @@ static int _message_filter(const struct sockaddr *addr,
 
     libspamc_timeout = m->timeout;
 
-    if((i=try_to_connect(addr, (struct hostent *) hent,
-                       hent_port, &sock)) != EX_OK)
+    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 i;
@@ -710,8 +1038,8 @@ static int _message_filter(const struct sockaddr *addr,
       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);
+      full_write(sock, buf, len);
+      full_write(sock, m->msg, m->msg_len);
       shutdown(sock, SHUT_WR);
     }
 
@@ -719,11 +1047,12 @@ static int _message_filter(const struct sockaddr *addr,
     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) {
+    if(sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response)!=2) {
        syslog(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) {
        syslog(LOG_ERR, "spamd responded with bad version string '%s'", versbuf);
@@ -749,25 +1078,33 @@ static int _message_filter(const struct sockaddr *addr,
     len = 0;           /* overwrite those headers */
 
     if (flags&SPAMC_CHECK_ONLY) {
-      close(sock); sock = -1;
-      if (m->is_spam == EX_TOOBIG) {
-           /* We should have gotten headers back... Damnit. */
-           failureval = EX_PROTOCOL; goto failure;
-      }
-      return EX_OK;
+       close(sock); sock = -1;
+       if (m->is_spam == EX_TOOBIG) {
+             /* We should have gotten headers back... Damnit. */
+             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;
        }
 
+       /* 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 = ssl_timeout_read (ssl, m->out+m->out_len,
+         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, (unsigned char *) m->out+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);
        }
@@ -807,49 +1144,17 @@ failure:
     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);
+    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) {
@@ -861,7 +1166,7 @@ int message_process(const char *hostname, int port, char *username, int max_size
 
 FAIL:
    if(flags&SPAMC_CHECK_ONLY){
-       full_write(out_fd, (unsigned char *) "0/0\n", 4);
+       full_write(out_fd, "0/0\n", 4);
        message_cleanup(&m);
        return EX_NOTSPAM;
    } else {
@@ -874,46 +1179,246 @@ FAIL:
 void message_cleanup(struct message *m) {
    if (m->out != NULL && m->out != m->raw) free(m->out);
    if (m->raw != NULL) free(m->raw);
+   if (m->priv != NULL) 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;
 
-    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;
+}
+
+/*
+ * 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;
+       }
+}
+
+/*
+ * 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;
+
+       assert(tp != 0);
+
+       switch ( tp->type )
+       {
+         case TRANSPORT_UNIX:
+               assert(tp->socketpath != 0);
+               return EX_OK;
+
+         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() */
+
+                       syslog (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) {
+                               syslog (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 lookup_host_for_failover(const char *hostname, struct hostent *hent) {
-  return _lookup_host(hostname, hent);
+
+/* --------------------------------------------------------------------------- */
+
+/*
+ * 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;
+
+  snprintf (inputstr, 99, "%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 */
+  snprintf (cmpbuf1, 98, "%f", input);
+  snprintf (cmpbuf2, 98, "%f", output);
+  if (!strcmp (cmpbuf1, cmpbuf2)) { return; }
+
+  printf ("FAIL: input=%f != output=%f\n", input, output);
 }
 
-int message_filter(const struct sockaddr *addr, char *username, int flags,
-                struct message *m)
-{ return _message_filter (addr, NULL, 0, username, flags, m); }
+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;
+
+  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]);
+  }
+  /* 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);
+  }
+  printf ("finished unit_test_locale_safe_string_to_float\n");
+}
 
-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); }
+void
+do_libspamc_unit_tests (void) {
+  unit_test_locale_safe_string_to_float();
+  exit (0);
+}
 
+#endif /* LIBSPAMC_UNIT_TESTS */
index 4441676..cf00e8a 100644 (file)
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <netinet/in.h>
 #include <netdb.h>
 #include <stdio.h>
 
 #define EX_NOTSPAM               0
 #define EX_ISSPAM                1
 #define EX_TOOBIG              866
-#define EX_OUTPUTMESSAGE       867
 
 /* Aug 14, 2002 bj: Bitflags instead of lots of bool parameters */
 #define SPAMC_MODE_MASK      1
@@ -35,6 +35,9 @@
 /* Feb  1 2003 jm: might as well fix bug 191 as well */
 #define SPAMC_SYMBOLS        (1<<24)
 
+/* 2003/04/16 SJF: randomize hostname order (quasi load balancing) */
+#define SPAMC_RANDOMIZE_HOSTS (1<<23)
+
 
 /* Aug 14, 2002 bj: A struct for storing a message-in-progress */
 typedef enum {
@@ -45,6 +48,8 @@ typedef enum {
     MAX_MESSAGE_TYPE
 } message_type_t;
 
+struct libspamc_private_message;
+
 struct message {
     /* Set before passing the struct on! */
     int max_len;  /* messages larger than this will return EX_TOOBIG */
@@ -60,14 +65,60 @@ struct message {
 
     /* Filled in by filter_message */
     int is_spam;              /* EX_ISSPAM if the message is spam, EX_NOTSPAM
-                                 if not, EX_OUTPUTMESSAGE if a filtered message
-                                is returned in "out" below. */
+                                 if not */
     float score, threshold;   /* score and threshold */
     char *out; int out_len;   /* Output from spamd. Either the filtered
                                  message, or the check-only response. Or else,
                                  a pointer to msg above. */
+
+    /* these members added in SpamAssassin version 2.60: */
+    struct libspamc_private_message *priv;
+};
+
+/*------------------------------------------------------------------------
+ * TRANSPORT (2004/04/16 - SJF)
+ *
+ * The code to connect with the daemon has gotten more complicated: support
+ * for SSL, fallback to multiple hosts, and using UNIX domain sockets. The
+ * code has gotten ugly with way too many parameters being passed all around.
+ *
+ * So we've created this object to hold all the info required to connect with
+ * the remote site, including a self-contained list of all the IP addresses
+ * in the event this is using TCP sockets. These multiple IPs can be obtained
+ * only from DNS returning more than one A record for a single name, and
+ * this allows for fallback.
+ *
+ * We also allow a kind of quasi-load balancing, where we take the list of
+ * A records from DNS and randomize them before starting out - this lets
+ * us spread the load out among multiple servers if desired. The idea for
+ * load balancing goes to Jeremy Zawodny.
+ *
+ * By putting all our data here, we remove "fallback" from being a special
+ * case. We may find ourselves with several IP addresses, but if the user
+ * disables fallback, we set the IP address count to one. Now the connect
+ * code just loops over that same address.
+ */
+#define TRANSPORT_LOCALHOST 0x01       /* TCP to localhost only */
+#define        TRANSPORT_TCP       0x02        /* standard TCP socket   */
+#define TRANSPORT_UNIX     0x03        /* UNIX domain socket    */
+
+#define TRANSPORT_MAX_HOSTS 256                /* max hosts we can failover between */
+
+struct transport {
+       int             type;
+
+       const char      *socketpath;    /* for UNIX dommain socket      */
+       const char      *hostname;      /* for TCP sockets              */
+
+       unsigned short  port;           /* for TCP sockets              */
+
+       struct in_addr  hosts[TRANSPORT_MAX_HOSTS];
+       int             nhosts;
 };
 
+extern void transport_init(struct transport *tp);
+extern int  transport_setup(struct transport *tp, int flags);
+
 /* Aug 14, 2002 bj: New interface functions */
 
 /* Read in a message from the fd, with the mode specified in the flags.
@@ -81,31 +132,13 @@ int message_read(int in_fd, int flags, struct message *m);
  * the "score/threshold" line. */
 long message_write(int out_fd, struct message *m);
 
-/* Pass the message through spamd (at addr) as the specified user, with the
- * given flags. Returns EX_OK on success, or various errors on error. If it was
- * successful, message_write will print either the CHECK_ONLY output, or the
- * filtered message in the appropriate output format. */
-int message_filter(const struct sockaddr *addr, char *username, int flags, struct message *m);
-
-/* Convert the host/port into a struct sockaddr. Returns EX_OK on success, or
- * else an error EX. */
-int lookup_host(const char *hostname, int port, struct sockaddr *a);
-
-/* Pass the message through one of a set of spamd's. This variant will handle
- * multiple spamd machines; if a connect failure occurs, it will fail-over to
- * the next one in the struct hostent. Otherwise identical to message_filter().
+/* Process the message through the spamd filter, making as many connection
+ * attempts as are implied by the transport structure. To make this do
+ * failover, more than one host is defined, but if there is only one there,
+ * no failover is done.
  */
-int message_filter_with_failover (const struct hostent *hent, int port, char
-    *username, int flags, struct message *m);
-
-/* Convert the host into a struct hostent, for use with
- * message_filter_with_failover() above. Returns EX_OK on success, or else an
- * error EX.  Note that the data filled into hent is from gethostbyname()'s
- * static storage, so any call to gethostbyname() between
- * lookup_host_for_failover() and message_filter_with_failover() will overwrite
- * this.  Take a copy, and use that instead, if you think a call may occur in
- * your code, or library code that you use (such as syslog()). */
-int lookup_host_for_failover(const char *hostname, struct hostent *hent);
+int message_filter(struct transport *tp, const char *username,
+               int flags, struct message *m);
 
 /* Dump the message. If there is any data in the message (typically, m->type
  * will be MESSAGE_ERROR) it will be message_writed. Then, fd_in will be piped
@@ -116,14 +149,14 @@ void message_dump(int in_fd, int out_fd, struct message *m);
 /* Do a message_read->message_filter->message_write sequence, handling errors
  * appropriately with dump_message or appropriate CHECK_ONLY output. Returns
  * EX_OK or EX_ISSPAM/EX_NOTSPAM on success, some error EX on error. */
-int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags);
+int message_process(struct transport *trans, char *username, int max_size, int in_fd, int out_fd, const int flags);
 
 /* Cleanup the resources we allocated for storing the message. Call after
  * you're done processing. */
 void message_cleanup(struct message *m);
 
 /* Aug 14, 2002 bj: This is now legacy, don't use it. */
-int process_message(const char *hostname, int port, char *username, 
+int process_message(struct transport *tp, char *username, 
                     int max_size, int in_fd, int out_fd,
                     const int check_only, const int safe_fallback);
 
index 195dfe5..dde2a56 100644 (file)
@@ -111,12 +111,17 @@ gboolean timeout_func(gpointer data)
 
 static gboolean msg_is_spam(FILE *fp)
 {
-       struct sockaddr addr;
+       struct transport trans;
        struct message m;
        gboolean is_spam = FALSE;
 
-       if (lookup_host(config.hostname, config.port, &addr) != EX_OK) {
-               debug_print("failed to look up spamd host\n");
+       transport_init(&trans);
+       trans.type = TRANSPORT_TCP;
+       trans.hostname = config.hostname;
+       trans.port = config.port;
+
+       if (transport_setup(&trans, flags) != EX_OK) {
+               debug_print("failed to setup transport\n");
                return FALSE;
        }
 
@@ -130,7 +135,7 @@ static gboolean msg_is_spam(FILE *fp)
                return FALSE;
        }
 
-       if (message_filter(&addr, username, flags, &m) != EX_OK) {
+       if (message_filter(&trans, username, flags, &m) != EX_OK) {
                debug_print("filtering the message failed\n");
                message_cleanup(&m);
                return FALSE;
index a4bb891..b67ca67 100644 (file)
@@ -22,6 +22,7 @@
 /* Aug 14, 2002 bj: EINTR and EAGAIN aren't fatal, are they? */
 /* Aug 14, 2002 bj: moved these to utils.c */
 /* Jan 13, 2003 ym: added timeout functionality */
+/* Apr 24, 2003 sjf: made full_read and full_write void* params */
 
 /* -------------------------------------------------------------------------- */
 
@@ -38,7 +39,7 @@ sigfunc* sig_catch(int sig, void (*f)(int))
 }
 
 static void catch_alrm(int x) {
-  /* dummy */
+  UNUSED_VARIABLE(x);
 }
 
 ssize_t
@@ -75,6 +76,12 @@ ssl_timeout_read (SSL *ssl, void *buf, int nbytes)
   int nred;
   sigfunc* sig;
 
+#ifndef SPAMC_SSL
+  UNUSED_VARIABLE(ssl);
+  UNUSED_VARIABLE(buf);
+  UNUSED_VARIABLE(nbytes);
+#endif
+
   sig = sig_catch(SIGALRM, catch_alrm);
   if (libspamc_timeout > 0) {
     alarm(libspamc_timeout);
@@ -104,8 +111,9 @@ ssl_timeout_read (SSL *ssl, void *buf, int nbytes)
 /* -------------------------------------------------------------------------- */
 
 int
-full_read (int fd, unsigned char *buf, int min, int len)
+full_read (int fd, void *vbuf, int min, int len)
 {
+  unsigned char *buf = (unsigned char *)vbuf;
   int total;
   int thistime;
 
@@ -126,8 +134,31 @@ full_read (int fd, unsigned char *buf, int min, int len)
 }
 
 int
-full_write (int fd, const unsigned char *buf, int len)
+full_read_ssl (SSL *ssl, unsigned char *buf, int min, int len)
+{
+  int total;
+  int thistime;
+
+  for (total = 0; total < min; ) {
+    thistime = ssl_timeout_read (ssl, buf+total, len-total);
+
+    if (thistime < 0) {
+      return -1;
+    } else if (thistime == 0) {
+      /* EOF, but we didn't read the minimum.  return what we've read
+       * so far and next read (if there is one) will return 0. */
+      return total;
+    }
+
+    total += thistime;
+  }
+  return total;
+}
+
+int
+full_write (int fd, const void *vbuf, int len)
 {
+  const unsigned char *buf = (const unsigned char *)vbuf;
   int total;
   int thistime;
 
index 9522226..4df7c53 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef UTILS_H
 #define UTILS_H
 
+#define UNUSED_VARIABLE(v)     ((void)(v))
+
 extern int libspamc_timeout;  /* default timeout in seconds */
 
 #ifdef SPAMC_SSL
@@ -18,7 +20,8 @@ ssize_t fd_timeout_read (int fd, void *, size_t );
 int ssl_timeout_read (SSL *ssl, void *, int );  
 
 /* these are fd-only, no SSL support */
-int full_read(int fd, unsigned char *buf, int min, int len);
-int full_write(int fd, const unsigned char *buf, int len);
+int full_read(int fd, void *buf, int min, int len);
+int full_read_ssl(SSL *ssl, unsigned char *buf, int min, int len);
+int full_write(int fd, const void *buf, int len);
 
 #endif