+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
--- /dev/null
+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
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
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)
src/gtk/Makefile
src/plugins/Makefile
src/plugins/demo/Makefile
+src/plugins/spamassassin/Makefile
faq/Makefile
faq/de/Makefile
faq/en/Makefile
}
}
-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;
}
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 */
struct _Plugin
{
gchar *filename;
- gchar *name;
GModule *module;
- gchar *desc;
+ gchar *(*name) ();
+ gchar *(*desc) ();
};
/**
{
Plugin *plugin;
gint (*plugin_init) (gchar **error);
+ gchar *plugin_name, *plugin_desc;
gint ok;
g_return_val_if_fail(filename != NULL, -1);
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);
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;
}
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();
}
/* 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,
#include "filtering.h"
#include "selective_download.h"
#include "log.h"
+#include "hooks.h"
static GList *inc_dialog_list = NULL;
/* 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);
}
#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
{
gpointer data;
};
+struct _MailFilteringData
+{
+ MsgInfo *msginfo;
+};
+
#define TIMEOUT_ITV 200
void inc_mail (MainWindow *mainwin,
#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;
printf("Demo plugin unloaded\n");
}
+
+const gchar *plugin_name()
+{
+ return "Dummy plugin";
+}
+
+const gchar *plugin_desc()
+{
+ return "Plugin that does nothing and never loads";
+}
--- /dev/null
+.deps
+.libs
+Makefile
+Makefile.in
+*.o
+*.lo
+*.la
--- /dev/null
+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
+
--- /dev/null
+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.
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#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
+
--- /dev/null
+/*
+ * 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";
+}
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+#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