Debianization.
authormdw <mdw>
Sat, 29 Nov 2003 23:47:33 +0000 (23:47 +0000)
committermdw <mdw>
Sat, 29 Nov 2003 23:47:33 +0000 (23:47 +0000)
12 files changed:
.cvsignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
config.h [new file with mode: 0644]
debian/.cvsignore [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/rules [new file with mode: 0755]
libspamc.c [new file with mode: 0644]
libspamc.h [new file with mode: 0644]
utils.c [new file with mode: 0644]
utils.h [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..d4672c5
--- /dev/null
@@ -0,0 +1 @@
+build libspamc.so.1.0.0
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..2d0f5f1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,43 @@
+## Throwaway makefile for libspamc
+
+CC = gcc
+CFLAGS = -O2 -fPIC -DSPAMC_SSL
+LIBS = -lssl
+
+MAJOR = 1
+MINOR = 0
+PATCH = 0
+VER = $(MAJOR).$(MINOR).$(PATCH)
+BASE = libspamc
+SO = $(BASE).so.$(MAJOR)
+LD = gcc
+FULL = $(BASE).so.$(VER)
+LDLINK = $(BASE).so
+
+.SUFFIXES = .c .o
+.c.o:; $(CC) -c $(CFLAGS) -o $@ $^
+
+INST = 
+prefix = /usr/local
+libdir = $(prefix)/lib
+includedir = $(prefix)/include
+
+all: $(FULL)
+clean:; rm -f *.o $(FULL)
+install: all
+       mkdir -p $(INST)$(includedir)
+       install -m644 libspamc.h $(INST)$(includedir)/libspamc.h
+       mkdir -p $(INST)$(libdir)
+       install -m644 $(FULL) $(INST)$(libdir)/$(FULL)
+       ln -s -f $(FULL) $(INST)$(libdir)/$(SO)
+       ln -s -f $(FULL) $(INST)$(libdir)/$(LDLINK)
+uninstall:
+       rm -f $(INST)$(includedir)/libspamc.h
+       rm -f $(INST)$(libdir)/$(FULL)
+       rm -f $(INST)$(libdir)/$(SO)
+       rm -f $(INST)$(libdir)/$(LDLINK)
+
+$(FULL): libspamc.o utils.o
+       $(LD) -Wl,-soname,$(SO) -shared -o $@ $^ $(LIBS)
+
+.PHONY: all clean install
diff --git a/config.h b/config.h
new file mode 100644 (file)
index 0000000..ca2ff58
--- /dev/null
+++ b/config.h
@@ -0,0 +1,159 @@
+/* config.h.  Generated by configure.  */
+/* config.h.in.  Generated from configure.in by autoheader.  */
+
+/* #undef CCDLFLAGS */
+
+/* #undef LDDLFLAGS */
+
+#define HAVE_SHUT_RD 1
+
+#define HAVE_H_ERRNO 1
+
+#define HAVE_OPTARG 1
+
+/* #undef in_addr_t */
+
+#define HAVE_INADDR_NONE 1
+
+#define HAVE_EX__MAX 1
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the <getopt.h> header file. */
+#define HAVE_GETOPT_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `crypto' library (-lcrypto). */
+#define HAVE_LIBCRYPTO 1
+
+/* Define to 1 if you have the `dl' library (-ldl). */
+#define HAVE_LIBDL 1
+
+/* Define to 1 if you have the `inet' library (-linet). */
+/* #undef HAVE_LIBINET */
+
+/* Define to 1 if you have the `nsl' library (-lnsl). */
+/* #undef HAVE_LIBNSL */
+
+/* Define to 1 if you have the `socket' library (-lsocket). */
+/* #undef HAVE_LIBSOCKET */
+
+/* Define to 1 if you have the `ssl' library (-lssl). */
+#define HAVE_LIBSSL 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <openssl/crypto.h> header file. */
+#define HAVE_OPENSSL_CRYPTO_H 1
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1
+
+/* Define to 1 if you have the `shutdown' function. */
+#define HAVE_SHUTDOWN 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#define HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the `snprintf' function. */
+#define HAVE_SNPRINTF 1
+
+/* Define to 1 if you have the `socket' function. */
+#define HAVE_SOCKET 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strtod' function. */
+#define HAVE_STRTOD 1
+
+/* Define to 1 if you have the `strtol' function. */
+#define HAVE_STRTOL 1
+
+/* Define to 1 if you have the <sysexits.h> header file. */
+#define HAVE_SYSEXITS_H 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#define HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/errno.h> header file. */
+#define HAVE_SYS_ERRNO_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `unsigned long' if <sys/types.h> does not define. */
+/* #undef in_addr_t */
+
+/* Define to `long' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
diff --git a/debian/.cvsignore b/debian/.cvsignore
new file mode 100644 (file)
index 0000000..064a21d
--- /dev/null
@@ -0,0 +1,3 @@
+libspamc libspamc-dev libspamc1
+*.debhelper *.substvars
+files
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..308931d
--- /dev/null
@@ -0,0 +1,5 @@
+libspamc (2.55-1) experimental; urgency=low
+  
+  * Debianized.
+  
+ -- Mark Wooding <mdw@nsict.org>  Wed, 12 Nov 2003 18:25:55 +0000
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..2a6c059
--- /dev/null
@@ -0,0 +1,18 @@
+Source: libspamc
+Standards-Version: 3.1.1
+Section: libs
+Priority: extra
+Build-Depends: debhelper (>= 4.0.2)
+Maintainer: Mark Wooding <mdw@nsict.org>
+
+Package: libspamc1
+Architecture: any
+Depends: ${shlibs:Depends}
+Description: Client library for spamassassin
+ Just the shared library here.
+
+Package: libspamc-dev
+Architecture: any
+Depends: libspamc1 (= ${Source-Version})
+Description: Client library for spamassassin
+ Header and static library.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..e475625
--- /dev/null
@@ -0,0 +1,13 @@
+The source code comes from Mail::SpamAssassin-2.55.tar.gz, available from
+http://www.spamassassin.org/.  This package is a stopgap until the Debian
+spamassassin package provides libspamc.  The original COPYRIGHT file from
+SpamAssassin says this:
+
+The code in the files of the SpamAssassin distribution are Copyright
+2000-2002 Justin Mason and others, unless specified otherwise in that
+particular file.
+
+All files in the SpamAssassin distribution fall under the same terms
+as Perl itself, as described in the file named "License".
+
+Debianiazation by Mark Wooding.
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..969571d
--- /dev/null
@@ -0,0 +1,51 @@
+#! /usr/bin/make -f
+
+export DH_COMPAT = 4
+
+VER = 2.55
+
+build:
+       make
+       touch build
+
+clean:
+       dh_clean
+       rm -f *.o *.so *.so.* build
+
+install: build
+       dh_clean
+       make install INST=debian/libspamc1 prefix=/usr
+       mkdir -p debian/libspamc-dev/usr/lib
+       mv debian/libspamc1/usr/include debian/libspamc-dev/usr
+       mv debian/libspamc1/usr/lib/*.so debian/libspamc-dev/usr/lib
+
+binary-indep:
+
+binary-arch: install
+       dh_testdir -a
+       dh_testroot -a
+       dh_makeshlibs -a -V
+       dh_installman -a
+       dh_compress -a
+       dh_installdocs -a
+       dh_strip -a
+       dh_shlibdeps -a
+       dh_gencontrol -a
+       dh_fixperms -a
+       dh_installdeb -a
+       dh_md5sums -a
+       dh_builddeb -a
+
+binary: binary-indep binary-arch
+
+source:
+       rm -rf =deb=
+       mkdir -p =deb=/libspamc-$(VER)
+       cp *.[ch] =deb=/libspamc-$(VER)
+       mkdir =deb=/libspamc-$(VER)/debian
+       for i in copyright rules changelog control; do \
+               cp debian/$$i =deb=/libspamc-$(VER)/debian; done
+       d=`pwd`; cd ..; dpkg-source -b -i $$d/=deb=/libspamc-$(VER)
+       rm -rf =deb=
+
+.PHONY: binary binary-arch binary-indep clean install source 
diff --git a/libspamc.c b/libspamc.c
new file mode 100644 (file)
index 0000000..5d5f988
--- /dev/null
@@ -0,0 +1,919 @@
+/*
+ * 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".
+ */
+
+#include "config.h"
+#include "libspamc.h"
+#include "utils.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_SYSEXITS_H
+#include <sysexits.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_ERRNO_H
+#include <sys/errno.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#define MAX_CONNECT_RETRIES 3
+#define CONNECT_RETRY_SLEEP 1
+
+/* 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.  */
+#endif
+
+#ifndef HAVE_H_ERRNO
+#define h_errno errno
+#endif
+
+#ifndef HAVE_OPTARG
+extern char *optarg;
+#endif
+
+#ifndef HAVE_INADDR_NONE
+#define INADDR_NONE             ((in_addr_t) 0xffffffff)
+#endif
+
+/* jm: turned off for now, it should not be necessary. */
+#undef USE_TCP_NODELAY
+
+#ifndef HAVE_EX__MAX
+/* jm: very conservative figure, should be well out of range on almost all NIXes */
+#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;
+
+/* set EXPANSION_ALLOWANCE to something more than might be
+   added to a message in X-headers and the report template */
+static const int EXPANSION_ALLOWANCE = 16384;
+
+/* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
+   of the data streams before and after processing by spamd 
+   Aug  7 2002 jm: no longer seems to be used
+   static const int NUM_CHECK_BYTES = 32;
+ */
+
+/* Set the protocol version that this spamc speaks */
+static const char *PROTOCOL_VERSION="SPAMC/1.3";
+
+int libspamc_timeout = 0;
+
+static int
+try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
+                int hent_port, int *sockptr)
+{
+#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];
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+  int dbgiter; char dbgbuf[2048]; int dbgbuflen = 0;
+#endif
+
+  /* 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++) {
+
+#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;
+      }
+
+#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]);
+#endif
+
+      memcpy ((void *) &(inaddrlist[hostnum]),
+               (void *) hent->h_addr_list[hostnum],
+               sizeof (struct in_addr));
+    }
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+      syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0;
+#endif
+  }
+
+
+#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]));
+    }
+#endif
+
+  hent = NULL; /* cannot use hent after this point, syslog() may overwrite it */
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+  syslog (LOG_DEBUG, "dbg: socket");
+#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;
+    }
+  }
+
+#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;
+
+    default:
+      break;           /* ignored */
+    }
+  }
+#endif
+
+  for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
+  
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+      syslog (LOG_DEBUG, "dbg: connect() to spamd %d", numloops);
+#endif
+
+    if (argaddr != NULL) {
+      addr = (struct sockaddr_in *) argaddr;     /* use the one provided */
+  
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+       syslog (LOG_DEBUG, "dbg: using argaddr");
+#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;
+      }
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+       syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx",
+               numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum]));
+#endif
+
+      memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]),
+                        sizeof(addrbuf.sin_addr));
+      addr = &addrbuf;
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+       syslog (LOG_DEBUG, "dbg: conn addr %d/%d = %lx",
+           numloops%hostnum, hostnum, addrbuf.sin_addr.s_addr);
+#endif
+
+    }
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+    syslog (LOG_DEBUG, "dbg: connect() to spamd at %s",
+               inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
+#endif
+    status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
+
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+      syslog (LOG_DEBUG, "dbg: connect() to spamd at %s done",
+         inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
+#endif
+
+    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);
+
+    } 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;
+  }
+}
+
+/* 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
+ * 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 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;
+    return EX_OK;
+}
+
+static int message_read_bsmtp(int fd, struct message *m){
+    off_t i, j;
+    char prev;
+
+    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;
+
+    /* 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;
+    return EX_OK;
+}
+
+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);
+
+      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;
+    }
+}
+
+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->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;
+    }
+
+    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]=='.'){
+                   if (j > jlimit - 4) {
+                       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;
+    }
+}
+
+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);
+    }
+    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);
+        }
+    }
+}
+
+static int
+_spamc_read_full_line (struct message *m, int flags, SSL *ssl, int sock,
+               char *buf, int *lenp, int bufsiz)
+{
+    int failureval;
+    int bytesread = 0;
+    int len;
+
+    /* 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);
+       }
+
+        if(buf[len]=='\n') {
+            buf[len]='\0';
+           if (len > 0 && buf[len-1] == '\r') {
+               len--;
+               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);
+    failureval = EX_TOOBIG;
+
+failure:
+    return failureval;
+}
+
+/*
+ * 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)
+{
+  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)
+{
+    char is_spam[6];
+    char s_str[20], t_str[20];
+
+    /* 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);
+
+       /* Format is "Spam: x; y / x" */
+       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);
+       }
+       return EX_OK;
+
+    } 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);
+           return EX_PROTOCOL;
+       }
+       return EX_OK;
+    }
+
+    syslog(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)
+{
+    char buf[8192];
+    int bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
+    int len, i;
+    int sock = -1;
+    char versbuf[20];
+    float version;
+    int response;
+    int failureval;
+    SSL_CTX* ctx;
+    SSL* ssl;
+    SSL_METHOD *meth;
+
+    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);
+#else
+      (void) ssl; (void) meth; (void) ctx;     /* avoid "unused" warnings */
+      syslog(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->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);
+    else
+      len=snprintf(buf, bufsiz, "PROCESS %s\r\n", 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, 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, 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, (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 */
+    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);
+    }
+
+    /* ok, now read and parse it.  SPAMD/1.2 line first... */
+    failureval = _spamc_read_full_line (m, flags, ssl, sock, buf, &len, bufsiz);
+    if (failureval != EX_OK) { goto failure; }
+
+    if(sscanf(buf, "SPAMD/%s %d %*s", versbuf, &response)!=2) {
+       syslog(LOG_ERR, "spamd responded with bad string '%s'", buf);
+       failureval = EX_PROTOCOL; goto failure;
+    }
+
+    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;
+    }
+
+    m->score = 0;
+    m->threshold = 0;
+    m->is_spam = EX_TOOBIG;
+    while (1) {
+       failureval = _spamc_read_full_line (m, flags, ssl, sock, buf, &len, bufsiz);
+       if (failureval != EX_OK) { goto failure; }
+
+       if (len == 0 && buf[0] == '\0') {
+           break;      /* end of headers */
+       }
+
+       if (_handle_spamd_header(m, flags, buf, len) < 0) {
+           failureval = EX_PROTOCOL; goto failure;
+       }
+    }
+
+    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;
+    }
+    else {
+       m->is_spam=EX_OUTPUTMESSAGE;
+       if (m->content_length < 0) {
+           /* should have got a length too. */
+           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);
+       }
+
+
+       if(len+m->out_len>m->max_len+EXPANSION_ALLOWANCE){
+           failureval = EX_TOOBIG; goto failure;
+       }
+       m->out_len+=len;
+
+       shutdown(sock, SHUT_RD);
+       close(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;
+    }
+
+    return EX_OK;
+
+failure:
+    free(m->out); m->out=m->msg; m->out_len=m->msg_len;
+    if (sock != -1) {
+      close(sock);
+    }
+    libspamc_timeout = 0;
+
+    if(flags&SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+      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 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;
+    }
+    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;
+    }
+}
+
+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);
+}
+
+/* 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 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);
+}
+
+/* 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); }
+
diff --git a/libspamc.h b/libspamc.h
new file mode 100644 (file)
index 0000000..4441676
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * This code is copyright 2001 by Craig Hughes
+ * Conversion to a thread-safe shared library copyright 2002 Liam Widdowson
+ * 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".
+ */
+#ifndef LIBSPAMC_H
+#define LIBSPAMC_H 1
+
+#include <sys/types.h>
+#include <sys/socket.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
+#define SPAMC_RAW_MODE       0
+#define SPAMC_BSMTP_MODE     1
+
+#define SPAMC_USE_SSL       (1<<27)
+#define SPAMC_SAFE_FALLBACK  (1<<28)
+#define SPAMC_CHECK_ONLY     (1<<29)
+
+/* Jan 30, 2003 ym: added reporting options */
+#define SPAMC_REPORT         (1<<26)
+#define SPAMC_REPORT_IFSPAM  (1<<25)
+
+/* Feb  1 2003 jm: might as well fix bug 191 as well */
+#define SPAMC_SYMBOLS        (1<<24)
+
+
+/* Aug 14, 2002 bj: A struct for storing a message-in-progress */
+typedef enum {
+    MESSAGE_NONE,
+    MESSAGE_ERROR,
+    MESSAGE_RAW,
+    MESSAGE_BSMTP,
+    MAX_MESSAGE_TYPE
+} message_type_t;
+
+struct message {
+    /* Set before passing the struct on! */
+    int max_len;  /* messages larger than this will return EX_TOOBIG */
+    int timeout;  /* timeout for read() system calls */
+
+    /* Filled in by message_read */
+    message_type_t type;
+    char *raw; int raw_len;   /* Raw message buffer */
+    char *pre; int pre_len;   /* Pre-message data (e.g. SMTP commands) */
+    char *msg; int msg_len;   /* The message */
+    char *post; int post_len; /* Post-message data (e.g. SMTP commands) */
+    int content_length;
+
+    /* 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. */
+    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. */
+};
+
+/* Aug 14, 2002 bj: New interface functions */
+
+/* Read in a message from the fd, with the mode specified in the flags.
+ * Returns EX_OK on success, EX_otherwise on failure. On failure, m may be
+ * either MESSAGE_NONE or MESSAGE_ERROR. */
+int message_read(int in_fd, int flags, struct message *m);
+
+/* Write out a message to the fd, as specified by m->type. Note that
+ * MESSAGE_NONE messages have nothing to write. Also note that if you ran the
+ * message through message_filter with SPAMC_CHECK_ONLY, it will only output
+ * 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().
+ */
+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);
+
+/* 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
+ * to fd_out intol EOF. This is particularly useful if you get back an
+ * EX_TOOBIG. */
+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);
+
+/* 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 max_size, int in_fd, int out_fd,
+                    const int check_only, const int safe_fallback);
+
+#endif
+
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
index 0000000..a4bb891
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,144 @@
+/*
+ * 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".
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <stdio.h>
+#include "utils.h"
+
+/* Dec 13 2001 jm: added safe full-read and full-write functions.  These
+ * can cope with networks etc., where a write or read may not read all
+ * the data that's there, in one call.
+ */
+/* 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 */
+
+/* -------------------------------------------------------------------------- */
+
+typedef void    sigfunc(int);   /* for signal handlers */
+
+sigfunc* sig_catch(int sig, void (*f)(int))
+{
+  struct sigaction act, oact;
+  act.sa_handler = f;
+  act.sa_flags = 0;
+  sigemptyset(&act.sa_mask);
+  sigaction(sig, &act, &oact);
+  return oact.sa_handler;
+}
+
+static void catch_alrm(int x) {
+  /* dummy */
+}
+
+ssize_t
+fd_timeout_read (int fd, void *buf, size_t nbytes)
+{
+  ssize_t nred;
+  sigfunc* sig;
+
+  sig = sig_catch(SIGALRM, catch_alrm);
+  if (libspamc_timeout > 0) {
+    alarm(libspamc_timeout);
+  }
+
+  do {
+    nred = read (fd, buf, nbytes);
+  } while(nred < 0 && errno == EAGAIN);
+
+  if(nred < 0 && errno == EINTR)
+    errno = ETIMEDOUT;
+
+  if (libspamc_timeout > 0) {
+    alarm(0);
+  }
+
+  /* restore old signal handler */
+  sig_catch(SIGALRM, sig);
+
+  return nred;
+}
+
+int
+ssl_timeout_read (SSL *ssl, void *buf, int nbytes)
+{
+  int nred;
+  sigfunc* sig;
+
+  sig = sig_catch(SIGALRM, catch_alrm);
+  if (libspamc_timeout > 0) {
+    alarm(libspamc_timeout);
+  }
+
+  do {
+#ifdef SPAMC_SSL
+    nred = SSL_read (ssl, buf, nbytes);
+#else
+    nred = 0;                  /* never used */
+#endif
+  } while(nred < 0 && errno == EAGAIN);
+
+  if(nred < 0 && errno == EINTR)
+    errno = ETIMEDOUT;
+
+  if (libspamc_timeout > 0) {
+    alarm(0);
+  }
+
+  /* restore old signal handler */
+  sig_catch(SIGALRM, sig);
+
+  return nred;
+}
+
+/* -------------------------------------------------------------------------- */
+
+int
+full_read (int fd, unsigned char *buf, int min, int len)
+{
+  int total;
+  int thistime;
+
+  for (total = 0; total < min; ) {
+    thistime = fd_timeout_read (fd, 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 unsigned char *buf, int len)
+{
+  int total;
+  int thistime;
+
+  for (total = 0; total < len; ) {
+    thistime = write (fd, buf+total, len-total);
+
+    if (thistime < 0) {
+      if(EINTR == errno || EAGAIN == errno) continue;
+      return thistime;        /* always an error for writes */
+    }
+    total += thistime;
+  }
+  return total;
+}
diff --git a/utils.h b/utils.h
new file mode 100644 (file)
index 0000000..9522226
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,24 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+extern int libspamc_timeout;  /* default timeout in seconds */
+
+#ifdef SPAMC_SSL
+#include <openssl/crypto.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#else
+typedef int    SSL;    /* fake type to avoid conditional compilation */
+typedef int    SSL_CTX;
+typedef int    SSL_METHOD;
+#endif
+
+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);
+
+#endif