0.8.8claws26
authorChristoph Hohmann <reboot@gmx.ch>
Mon, 30 Dec 2002 20:53:59 +0000 (20:53 +0000)
committerChristoph Hohmann <reboot@gmx.ch>
Mon, 30 Dec 2002 20:53:59 +0000 (20:53 +0000)
* src/common/hooks.[ch]
        return abort status to caller of hooks_invoke

* src/inc.[ch]
        invoke message filtering hook and stop
        default handling when filter returns abort

* src/common/plugin.[ch]
* src/plugins/demo/demo.c
        modify plugin loader, because resolving symbols for strings
        does not work correctly

* configure.in
* ac/spamassassin.m4                            ** NEW **
* src/plugins/spamassassin/.cvsignore           ** NEW **
* src/plugins/spamassassin/Makefile.am          ** NEW **
* src/plugins/spamassassin/README               ** NEW **
* src/plugins/spamassassin/libspamc.[ch]        ** NEW **
* src/plugins/spamassassin/spamassassin.c       ** NEW **
* src/plugins/spamassassin/utils.[ch]           ** NEW **
        add spamassassin plugin, see src/plugins/spamassassin/README
        for details

18 files changed:
ChangeLog.claws
ac/spamassassin.m4 [new file with mode: 0644]
configure.in
src/common/hooks.c
src/common/hooks.h
src/common/plugin.c
src/common/plugin.h
src/inc.c
src/inc.h
src/plugins/demo/demo.c
src/plugins/spamassassin/.cvsignore [new file with mode: 0644]
src/plugins/spamassassin/Makefile.am [new file with mode: 0644]
src/plugins/spamassassin/README [new file with mode: 0644]
src/plugins/spamassassin/libspamc.c [new file with mode: 0644]
src/plugins/spamassassin/libspamc.h [new file with mode: 0644]
src/plugins/spamassassin/spamassassin.c [new file with mode: 0644]
src/plugins/spamassassin/utils.c [new file with mode: 0644]
src/plugins/spamassassin/utils.h [new file with mode: 0644]

index c12bd58..0d2107c 100644 (file)
@@ -1,3 +1,28 @@
+2002-12-30 [christoph] 0.8.8claws26
+
+       * src/common/hooks.[ch]
+               return abort status to caller of hooks_invoke
+               
+       * src/inc.[ch]
+               invoke message filtering hook and stop
+               default handling when filter returns abort
+
+       * src/common/plugin.[ch]
+       * src/plugins/demo/demo.c
+               modify plugin loader, because resolving symbols for strings
+               does not work correctly
+       
+       * configure.in
+       * ac/spamassassin.m4                            ** NEW **
+       * src/plugins/spamassassin/.cvsignore           ** NEW **
+       * src/plugins/spamassassin/Makefile.am          ** NEW **
+       * src/plugins/spamassassin/README               ** NEW **
+       * src/plugins/spamassassin/libspamc.[ch]        ** NEW **
+       * src/plugins/spamassassin/spamassassin.c       ** NEW **
+       * src/plugins/spamassassin/utils.[ch]           ** NEW **
+               add spamassassin plugin, see src/plugins/spamassassin/README
+               for details
+
 2002-12-29 [alfons]    0.8.8claws25
 
        * src/mainwindow.c
diff --git a/ac/spamassassin.m4 b/ac/spamassassin.m4
new file mode 100644 (file)
index 0000000..2c196fc
--- /dev/null
@@ -0,0 +1,89 @@
+dnl check for libspamc required includes
+
+AC_DEFUN(AC_SPAMASSASSIN,
+[dnl
+
+AC_CHECK_HEADERS(sys/time.h syslog.h unistd.h errno.h sys/errno.h)
+AC_CHECK_HEADERS(time.h sysexits.h sys/socket.h netdb.h netinet/in.h)
+
+AC_CACHE_CHECK([for SHUT_RD],
+       shutrd, [
+                AC_TRY_COMPILE([#include <sys/types.h>
+#include <sys/socket.h>],
+                        [printf ("%d", SHUT_RD); return 0;],
+                                        [shutrd=yes],
+                                        [shutrd=no]),
+       ])
+if test $shutrd = yes ; then
+  AC_DEFINE(HAVE_SHUT_RD, 1, HAVE_SHUT_RD)
+fi
+
+dnl ----------------------------------------------------------------------
+
+AC_CHECK_LIB(socket, socket)
+AC_CHECK_LIB(inet, connect)
+AC_CHECK_LIB(nsl, t_accept)
+AC_CHECK_LIB(dl, dlopen)
+
+AC_CHECK_FUNCS(socket strdup strtod strtol snprintf shutdown)
+
+dnl ----------------------------------------------------------------------
+
+AC_CACHE_CHECK([for h_errno],
+        herrno, [
+                AC_TRY_COMPILE([#include <netdb.h>],
+                        [printf ("%d", h_errno); return 0;],
+                                        [herrno=yes],
+                                        [herrno=no]),
+        ])
+if test $herrno = yes ; then
+  AC_DEFINE(HAVE_H_ERRNO, 1, HAVE_H_ERRNO)
+fi
+
+dnl ----------------------------------------------------------------------
+
+dnl ----------------------------------------------------------------------
+
+AC_CACHE_CHECK([for in_addr_t],
+        inaddrt, [
+                AC_TRY_COMPILE([#include <sys/types.h>
+#include <netinet/in.h>],
+                        [in_addr_t foo; return 0;],
+                                        [inaddrt=yes],
+                                        [inaddrt=no]),
+        ])
+if test $inaddrt = no ; then
+  AC_CHECK_TYPE(in_addr_t, unsigned long)
+fi
+
+dnl ----------------------------------------------------------------------
+
+AC_CACHE_CHECK([for INADDR_NONE],
+        haveinaddrnone, [
+                AC_TRY_COMPILE([#include <sys/types.h>
+#include <netinet/in.h>],
+                        [in_addr_t foo = INADDR_NONE; return 0;],
+                                        [haveinaddrnone=yes],
+                                        [haveinaddrnone=no]),
+        ])
+if test $haveinaddrnone = yes ; then
+  AC_DEFINE(HAVE_INADDR_NONE, 1, HAVE_INADDR_NONE)
+fi
+
+dnl ----------------------------------------------------------------------
+
+AC_CACHE_CHECK([for EX__MAX],
+        haveexmax, [
+                AC_TRY_COMPILE([#ifdef HAVE_SYSEXITS_H
+#include <sysexits.h>
+#endif
+#include <errno.h>],
+                        [int foo = EX__MAX; return 0;],
+                                        [haveexmax=yes],
+                                        [haveexmax=no]),
+        ])
+if test $haveexmax = yes ; then
+  AC_DEFINE(HAVE_EX__MAX, 1, HAVE_EX__MAX)
+fi
+
+])
\ No newline at end of file
index 78e8268..cd65aed 100644 (file)
@@ -11,7 +11,7 @@ MINOR_VERSION=8
 MICRO_VERSION=8
 INTERFACE_AGE=0
 BINARY_AGE=0
-EXTRA_VERSION=claws25
+EXTRA_VERSION=claws26
 VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
 
 dnl set $target
@@ -381,6 +381,15 @@ if test x"$ac_cv_enable_demo_plugin" = xyes; then
        PLUGINS="demo $PLUGINS"
 fi
 
+AC_ARG_ENABLE(spamassassin-plugin,
+       [  --enable-spamassassin-plugin    Build spamassassin plugin [default=no]],
+       [ac_cv_enable_spamassassin_plugin=$enableval], [ac_cv_enable_spamassassin_plugin=no])
+if test x"$ac_cv_enable_spamassassin_plugin" = xyes; then
+       AC_SPAMASSASSIN
+
+       PLUGINS="spamassassin $PLUGINS"
+fi
+
 AC_SUBST(PLUGINS)
 AC_SUBST(PLUGINDIR)
 
@@ -400,6 +409,7 @@ src/common/Makefile
 src/gtk/Makefile
 src/plugins/Makefile
 src/plugins/demo/Makefile
+src/plugins/spamassassin/Makefile
 faq/Makefile
 faq/de/Makefile
 faq/en/Makefile
index 95dc0a2..965137c 100644 (file)
@@ -103,19 +103,21 @@ static void hooks_marshal(GHook *hook, gpointer data)
        }
 }
 
-void hooks_invoke(gchar *hooklist_name,
+gboolean hooks_invoke(gchar *hooklist_name,
                  gpointer source)
 {
        GHookList *hooklist;
        struct MarshalData marshal_data;
        
-       g_return_if_fail(hooklist_name != NULL);
+       g_return_val_if_fail(hooklist_name != NULL, FALSE);
 
        hooklist = hooks_get_hooklist(hooklist_name);
-       g_return_if_fail(hooklist != NULL);
+       g_return_val_if_fail(hooklist != NULL, FALSE);
 
        marshal_data.source = source;
        marshal_data.abort = FALSE;
 
        g_hook_list_marshal(hooklist, TRUE, hooks_marshal, &marshal_data);
+
+       return marshal_data.abort;
 }
index e8cfb13..447d688 100644 (file)
@@ -28,7 +28,7 @@ gint hooks_register_hook      (gchar                  *hooklist_name,
                                 gpointer                userdata);
 void hooks_unregister_hook     (gchar                  *hooklist_name,
                                 guint                   hook_id);
-void hooks_invoke              (gchar                  *hooklist_name,
+gboolean hooks_invoke          (gchar                  *hooklist_name,
                                 gpointer                source);
 
 #endif /* HOOKS_H */
index 0071715..07c00f3 100644 (file)
@@ -31,9 +31,9 @@
 struct _Plugin
 {
        gchar   *filename;
-       gchar   *name;
        GModule *module;
-       gchar   *desc;
+       gchar   *(*name) ();
+       gchar   *(*desc) ();
 };
 
 /**
@@ -78,6 +78,7 @@ gint plugin_load(const gchar *filename, gchar **error)
 {
        Plugin *plugin;
        gint (*plugin_init) (gchar **error);
+       gchar *plugin_name, *plugin_desc;
        gint ok;
 
        g_return_val_if_fail(filename != NULL, -1);
@@ -96,8 +97,8 @@ gint plugin_load(const gchar *filename, gchar **error)
                return -1;
        }
 
-       if (!g_module_symbol(plugin->module, "plugin_name", (gpointer *)&plugin->name) ||
-           !g_module_symbol(plugin->module, "plugin_desc", (gpointer *)&plugin->desc) ||
+       if (!g_module_symbol(plugin->module, "plugin_name", (gpointer *)&plugin_name) ||
+           !g_module_symbol(plugin->module, "plugin_desc", (gpointer *)&plugin_desc) ||
            !g_module_symbol(plugin->module, "plugin_init", (gpointer *)&plugin_init)) {
                *error = g_strdup(g_module_error());
                g_module_close(plugin->module);
@@ -111,9 +112,13 @@ gint plugin_load(const gchar *filename, gchar **error)
                return ok;
        }
 
+       plugin->name = plugin_name;
+       plugin->desc = plugin_desc;
+       plugin->filename = g_strdup(filename);
+
        plugins = g_slist_append(plugins, plugin);
 
-       debug_print("Plugin %s (from file %s) loaded\n", plugin->name, plugin->filename);
+       debug_print("Plugin %s (from file %s) loaded\n", plugin->name(), filename);
 
        return 0;
 }
@@ -173,10 +178,10 @@ GSList *plugin_get_list()
 
 const gchar *plugin_get_name(Plugin *plugin)
 {
-       return plugin->name;
+       return plugin->name();
 }
 
 const gchar *plugin_get_desc(Plugin *plugin)
 {
-       return plugin->desc;
+       return plugin->desc();
 }
index 28b291a..6d18e27 100644 (file)
@@ -27,6 +27,8 @@ typedef struct _Plugin Plugin;
 /* Functions to implement by the plugin */
 gint plugin_init               (gchar          **error);
 void plugin_done               ();
+const gchar *plugin_name       ();
+const gchar *plugin_desc       ();
 
 /* Functions by the sylpheed plugin system */
 gint plugin_load               (const gchar     *filename,
index b56185a..52a19d3 100644 (file)
--- a/src/inc.c
+++ b/src/inc.c
@@ -66,6 +66,7 @@
 #include "filtering.h"
 #include "selective_download.h"
 #include "log.h"
+#include "hooks.h"
 
 static GList *inc_dialog_list = NULL;
 
@@ -597,13 +598,19 @@ static gint inc_start(IncProgressDialog *inc_dialog)
 
                /* process messages */
                for(msglist_element = msglist; msglist_element != NULL; msglist_element = msglist_element->next) {
+                       MailFilteringData mail_filtering_data;
                        msginfo = (MsgInfo *) msglist_element->data;
-                       /* filter if enabled in prefs or move to inbox if not */
-                       if(global_processing && pop3_state->ac_prefs->filter_on_recv) {
-                               filter_message_by_msginfo_with_inbox(global_processing, msginfo,
-                                                                    inbox);
-                       } else {
-                               folder_item_move_msg(inbox, msginfo);
+                       
+                       mail_filtering_data.msginfo = msginfo;
+                       
+                       if (!hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data)) {
+                               /* filter if enabled in prefs or move to inbox if not */
+                               if(global_processing && pop3_state->ac_prefs->filter_on_recv) {
+                                       filter_message_by_msginfo_with_inbox(global_processing, msginfo,
+                                                                            inbox);
+                               } else {
+                                       folder_item_move_msg(inbox, msginfo);
+                               }
                        }
                        procmsg_msginfo_free(msginfo);
                }
index 2e9e27b..ddb441b 100644 (file)
--- a/src/inc.h
+++ b/src/inc.h
 #include "automaton.h"
 #include "socket.h"
 
+#define MAIL_FILTERING_HOOKLIST "mail_filtering_hooklist"
+
 typedef struct _IncProgressDialog      IncProgressDialog;
 typedef struct _IncSession             IncSession;
+typedef struct _MailFilteringData      MailFilteringData;
 
 typedef enum
 {
@@ -70,6 +73,11 @@ struct _IncSession
        gpointer data;
 };
 
+struct _MailFilteringData
+{
+       MsgInfo *msginfo;
+};
+
 #define TIMEOUT_ITV    200
 
 void inc_mail                  (MainWindow     *mainwin,
index 2636a84..fd46655 100644 (file)
@@ -22,9 +22,6 @@
 #include "hooks.h"
 #include "log.h"
 
-gchar *plugin_name = "Dummy plugin";
-gchar *plugin_desc = "Plugin that does nothing and never loads";
-
 gboolean my_log_hook(gpointer source, gpointer data)
 {
        LogText *logtext = (LogText *)source;
@@ -55,3 +52,13 @@ void plugin_done()
 
        printf("Demo plugin unloaded\n");
 }
+
+const gchar *plugin_name()
+{
+       return "Dummy plugin";
+}
+
+const gchar *plugin_desc()
+{
+       return "Plugin that does nothing and never loads";
+}
diff --git a/src/plugins/spamassassin/.cvsignore b/src/plugins/spamassassin/.cvsignore
new file mode 100644 (file)
index 0000000..2a6ab49
--- /dev/null
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.o
+*.lo
+*.la
diff --git a/src/plugins/spamassassin/Makefile.am b/src/plugins/spamassassin/Makefile.am
new file mode 100644 (file)
index 0000000..8f05e2e
--- /dev/null
@@ -0,0 +1,25 @@
+plugindir = $(pkglibdir)/plugins
+
+plugin_LTLIBRARIES = spamassassin.la
+
+spamassassin_la_SOURCES = \
+       spamassassin.c \
+       libspamc.c libspamc.h \
+       utils.c utils.h
+
+spamassassin_la_LDFLAGS = \
+       -avoid-version -module \
+       $(GTK_LIBS)
+
+INCLUDES = \
+       -I../.. \
+       -I../../common \
+       -I../../gtk
+
+CPPFLAGS = \
+       $(GLIB_CFLAGS) \
+       $(GTK_CFLAGS)
+
+EXTRA_DIST = \
+       README
+
diff --git a/src/plugins/spamassassin/README b/src/plugins/spamassassin/README
new file mode 100644 (file)
index 0000000..2931b8c
--- /dev/null
@@ -0,0 +1,23 @@
+SpamAssassin Plugin
+-------------------
+
+This plugin will filter incoming messages using SpamAssassin. Like the
+spamc command from the SpamAssassin package the message is send to a
+spamd server that decides if the message is spam or not.
+
+To build the plugin run configure with --enable-spamassassin-plugin
+
+The spamd server localtion is currently fixed to localhost:783. If you
+want to change that spamd's location you have to edit the lines in the
+spamassassin_read_config function in spamassassin.c until a configuration
+for the plugin is available. The plugin is also configured to filter
+only messages that are smaller then 250kB.
+
+Message that are classified as spam will be moved to the default trash
+folder.
+
+libspamc.[ch] and utils.[ch] are copied from the SpamAssassin package. I
+hope SpamAssassin will provide their functions as a library with the
+required includes files in the future for easier building. Building the
+library as a shared object is already possible, but it is not installable
+with the package and the includes are also not available.
diff --git a/src/plugins/spamassassin/libspamc.c b/src/plugins/spamassassin/libspamc.c
new file mode 100644 (file)
index 0000000..1779256
--- /dev/null
@@ -0,0 +1,587 @@
+/*
+ * 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"
+ */
+
+#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 <netdb.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
+
+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.2";
+
+/* Aug 14, 2002 bj: No more ctx! */
+static int
+try_to_connect (const struct sockaddr *addr, int *sockptr)
+{
+#ifdef USE_TCP_NODELAY
+  int value;
+#endif
+  int mysock = -1;
+  int status = -1;
+  int origerr;
+  int numloops;
+
+  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");
+      return EX_SOFTWARE;
+
+    default:
+      break;           /* ignored */
+    }
+  }
+#endif
+
+  for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
+    status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
+
+    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(1);
+
+    } else {
+      *sockptr = mysock;
+      return EX_OK;
+    }
+  }
+  /* failed, even with a few retries */
+  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;
+}
+
+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);
+    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, 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){
+    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;
+    char buffer[1024];
+
+    if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
+        return full_write(fd, m->out, m->out_len);
+    }
+
+    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, m->raw, m->raw_len);
+
+      case MESSAGE_RAW:
+        return full_write(fd, m->out, m->out_len);
+
+      case MESSAGE_BSMTP:
+        total=full_write(fd, 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; ){
+                if(i+1<m->out_len && m->out[i]=='\n' && m->out[i+1]=='.'){
+                    buffer[j++]=m->out[i++];
+                    buffer[j++]=m->out[i++];
+                    buffer[j++]='.';
+                } else {
+                    buffer[j++]=m->out[i++];
+                }
+            }
+            total+=full_write(fd, buffer, j);
+        }
+        return total+full_write(fd, 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, buf, 8192, 8192))>0){
+        if(bytes!=full_write(out_fd, buf, bytes));
+    }
+}
+
+int message_filter(const struct sockaddr *addr, char *username, int flags, struct message *m){
+    char *buf=NULL, is_spam[5];
+    int len, expected_len, i, header_read=0;
+    int sock;
+    float version;
+    int response;
+
+    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; }
+    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, 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; }
+
+    if((i=try_to_connect(addr, &sock))!=EX_OK){
+        free(buf);
+        free(m->out); m->out=m->msg; m->out_len=m->msg_len;
+        return i;
+    }
+
+    /* Send to spamd */
+    full_write(sock, buf, len);
+    full_write(sock, 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);
+        if(i<0){
+            free(buf);
+            free(m->out); m->out=m->msg; m->out_len=m->msg_len;
+            close(sock);
+            return EX_IOERR;
+        }
+        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;
+            }
+            break;
+        }
+        if(buf[len]=='\n'){
+            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;
+            }
+            header_read=-1;
+            break;
+        }
+    }
+    if(!header_read){
+        /* No header, so it must be a version <1.0 server */
+        memcpy(m->out, buf, len);
+        m->out_len=len;
+    } else {
+        /* Handle different versioned headers */
+        if(version-1.0>0.01){
+            for(len=0; len<8192; len++){
+                i=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 (i<0)?EX_IOERR:EX_PROTOCOL;
+                }
+                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;
+                        }
+                        m->out_len=snprintf(m->out, m->max_len+EXPANSION_ALLOWANCE, "%.1f/%.1f\n", m->score, m->threshold);
+                        m->is_spam=strcasecmp("true", is_spam)?EX_NOTSPAM:EX_ISSPAM;
+                        close(sock);
+                        return EX_OK;
+                    } 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;
+                        }
+                    }
+
+                    /* Should be end of headers now */
+                    if(full_read(sock, buf, 2, 2)!=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;
+                    }
+
+                    break;
+                }
+            }
+        }
+    }
+    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;
+    }
+
+    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;
+    }
+    m->out_len+=len;
+
+    shutdown(sock, SHUT_RD);
+    close(sock);
+
+    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;
+    }
+
+    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;
+
+    memset(&a, 0, sizeof(a));
+
+    addr->sin_family=AF_INET;
+    addr->sin_port=htons(port);
+
+    /* 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;
+      }
+    }
+
+        memcpy (&addr->sin_addr, hent->h_addr, sizeof(addr->sin_addr));
+  }
+
+    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;
+    int ret;
+    struct message m;
+
+    m.type=MESSAGE_NONE;
+
+    ret=lookup_host(hostname, port, &addr);
+    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);
+    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, "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);
+}
diff --git a/src/plugins/spamassassin/libspamc.h b/src/plugins/spamassassin/libspamc.h
new file mode 100644 (file)
index 0000000..3ea33f2
--- /dev/null
@@ -0,0 +1,106 @@
+#ifndef LIBSPAMC_H
+#define LIBSPAMC_H 1
+
+#include "../config.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+
+/*
+ * 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 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"
+ */
+
+#include <stdio.h>
+
+#define EX_ISSPAM       1
+#define EX_NOTSPAM      0
+#define EX_TOOBIG     866
+
+/* 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_SAFE_FALLBACK  1<<30
+#define SPAMC_CHECK_ONLY     1<<31
+
+/* 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 */
+
+    /* 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) */
+
+    /* Filled in by filter_message */
+    int is_spam;              /* EX_ISSPAM if the message is spam, EX_NOTSPAM
+                                 if not, EX_TOOBIG 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);
+
+/* 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/src/plugins/spamassassin/spamassassin.c b/src/plugins/spamassassin/spamassassin.c
new file mode 100644 (file)
index 0000000..e642f45
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2002 Hiroyuki Yamamoto and the Sylpheed-Claws Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <glib.h>
+
+#if HAVE_LOCALE_H
+#  include <locale.h>
+#endif
+
+#include "plugin.h"
+#include "common/utils.h"
+#include "hooks.h"
+#include "inc.h"
+#include "procmsg.h"
+#include "folder.h"
+
+#include "libspamc.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
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+static gint hook_id;
+static int max_size;
+static int flags = SPAMC_RAW_MODE | SPAMC_SAFE_FALLBACK | SPAMC_CHECK_ONLY;
+static gchar *hostname = NULL;
+static int port;
+
+static gboolean mail_filtering_hook(gpointer source, gpointer data)
+{
+       MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
+       MsgInfo *msginfo = mail_filtering_data->msginfo;
+       gboolean is_spam = FALSE;
+       FILE *fp = NULL;
+       struct message m;
+       int ret;
+       gchar *username = NULL, *oldlocale = NULL;
+       struct sockaddr addr;
+       
+       debug_print("Filtering message %d\n", msginfo->msgnum);
+
+       oldlocale = g_strdup(setlocale(LC_ALL, NULL));
+       if (oldlocale == NULL)
+               goto CATCH;
+
+       setlocale(LC_ALL, "C");
+
+       ret = lookup_host(hostname, port, &addr);
+       if (ret != EX_OK)
+               goto CATCH;
+
+       m.type = MESSAGE_NONE;
+       m.max_len = max_size;
+
+       username = g_get_user_name();
+       if (username == NULL)
+               goto CATCH;
+
+       fp = procmsg_open_message(msginfo);
+       if (fp == NULL)
+               goto CATCH;
+
+       ret = message_read(fileno(fp), flags, &m);
+       if (ret != EX_OK)
+               goto CATCH;
+
+       ret = message_filter(&addr, username, flags, &m);
+       if ((ret == EX_OK) && (m.is_spam == EX_ISSPAM))
+               is_spam = TRUE;
+
+CATCH:
+       if (fp != NULL)
+               fclose(fp);
+       message_cleanup(&m);
+       if (oldlocale != NULL) {
+               setlocale(LC_ALL, oldlocale);
+               g_free(oldlocale);
+       }
+
+       if (is_spam) {
+               debug_print("Message is spam\n");
+
+               folder_item_move_msg(folder_get_default_trash(), msginfo);
+               return TRUE;
+       }
+       
+       return FALSE;
+}
+
+static void spamassassin_read_config()
+{
+       max_size = 250*1024;
+       hostname = "127.0.0.1";
+       port = 783;
+}
+
+gint plugin_init(gchar **error)
+{
+       hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
+       if (hook_id == -1) {
+               *error = g_strdup("Failed to register mail filtering hook");
+               return -1;
+       }
+
+       spamassassin_read_config();
+
+       debug_print("Spamassassin plugin loaded\n");
+
+       return 0;
+       
+}
+
+void plugin_done()
+{
+       hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
+
+       debug_print("Spamassassin plugin unloaded\n");
+}
+
+const gchar *plugin_name()
+{
+       return "Spamassassin Plugin";
+}
+
+const gchar *plugin_desc()
+{
+       return "Check incoming mails for spam with spamassassin";
+}
+
diff --git a/src/plugins/spamassassin/utils.c b/src/plugins/spamassassin/utils.c
new file mode 100644 (file)
index 0000000..e212808
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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"
+ */
+
+#include <unistd.h>
+#include <errno.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 */
+int
+full_read (int fd, unsigned char *buf, int min, int len)
+{
+  int total;
+  int thistime;
+
+  for (total = 0; total < min; ) {
+    thistime = read (fd, buf+total, len-total);
+
+    if (thistime < 0) {
+      if(EINTR == errno || EAGAIN == errno) continue;
+      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/src/plugins/spamassassin/utils.h b/src/plugins/spamassassin/utils.h
new file mode 100644 (file)
index 0000000..c305c39
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+int full_read(int fd, unsigned char *buf, int min, int len);
+int full_write(int fd, const unsigned char *buf, int len);
+
+#endif