[Commits] [SCM] claws branch, master, updated. 3.16.0-118-gaee9dd9
wwp at claws-mail.org
wwp at claws-mail.org
Wed Apr 11 10:55:27 CEST 2018
The branch, master has been updated
via aee9dd9c3322a3649c94da73d8139a9fa0ea0e0d (commit)
from cccb29c3b0745ab9f5449b30e8285852d242e578 (commit)
Summary of changes:
src/plugins/spamassassin/libspamc.c | 1650 +++++++++++++++++++++++++++--------
src/plugins/spamassassin/libspamc.h | 126 ++-
src/plugins/spamassassin/utils.c | 41 +-
src/plugins/spamassassin/utils.h | 59 +-
4 files changed, 1482 insertions(+), 394 deletions(-)
- Log -----------------------------------------------------------------
commit aee9dd9c3322a3649c94da73d8139a9fa0ea0e0d
Author: wwp <wwp at free.fr>
Date: Wed Apr 11 10:54:47 2018 +0200
Updated Apache SpamAssassin code (libspam.[ch], utils.[ch]) from 3.0.x
to 3.4.1.
diff --git a/src/plugins/spamassassin/libspamc.c b/src/plugins/spamassassin/libspamc.c
index e78fd00..0be68e8 100644
--- a/src/plugins/spamassassin/libspamc.c
+++ b/src/plugins/spamassassin/libspamc.c
@@ -1,9 +1,10 @@
/* <@LICENSE>
- * Copyright 2004 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
@@ -15,11 +16,18 @@
* </@LICENSE>
*/
+/*
+ Compile with extra warnings -- gcc only, not suitable for use as default:
+
+ gcc -Wextra -Wdeclaration-after-statement -Wall -g -O2 spamc/spamc.c \
+ spamc/getopt.c spamc/libspamc.c spamc/utils.c -o spamc/spamc -ldl -lz
+ */
+
#include "config.h"
#include "claws-features.h"
#include "libspamc.h"
-#include "utils.h"
+#include <stdarg.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
@@ -57,9 +65,12 @@
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
-#define MAX_CONNECT_RETRIES 3
-#define CONNECT_RETRY_SLEEP 1
+/* must load *after* errno.h, Bug 6697 */
+#include "utils.h"
/* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
/* KAM 12-4-01 */
@@ -78,6 +89,12 @@
#define h_errno errno
#endif
+#ifdef _WIN32
+#define spamc_get_errno() WSAGetLastError()
+#else
+#define spamc_get_errno() errno
+#endif
+
#ifndef HAVE_OPTARG
extern char *optarg;
#endif
@@ -95,9 +112,25 @@ extern char *optarg;
#endif
#undef DO_CONNECT_DEBUG_SYSLOGS
-/* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
+/*
+#define DO_CONNECT_DEBUG_SYSLOGS 1
+#define CONNECT_DEBUG_LEVEL LOG_DEBUG
+*/
+
+/* bug 4477 comment 14 */
+#ifdef NI_MAXHOST
+#define SPAMC_MAXHOST NI_MAXHOST
+#else
+#define SPAMC_MAXHOST 256
+#endif
+
+#ifdef NI_MAXSERV
+#define SPAMC_MAXSERV NI_MAXSERV
+#else
+#define SPAMC_MAXSERV 256
+#endif
-static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
+/* static const int ESC_PASSTHROUGHRAW = EX__MAX + 666; No longer seems to be used */
/* set EXPANSION_ALLOWANCE to something more than might be
added to a message in X-headers and the report template */
@@ -110,7 +143,7 @@ static const int EXPANSION_ALLOWANCE = 16384;
*/
/* Set the protocol version that this spamc speaks */
-static const char *PROTOCOL_VERSION = "SPAMC/1.3";
+static const char *PROTOCOL_VERSION = "SPAMC/1.5";
/* "private" part of struct message.
* we use this instead of the struct message directly, so that we
@@ -119,9 +152,16 @@ static const char *PROTOCOL_VERSION = "SPAMC/1.3";
struct libspamc_private_message
{
int flags; /* copied from "flags" arg to message_read() */
+ int alloced_size; /* allocated space for the "out" buffer */
+
+ void (*spamc_header_callback)(struct message *m, int flags, char *buf, int len);
+ void (*spamd_header_callback)(struct message *m, int flags, const char *buf, int len);
};
+void (*libspamc_log_callback)(int flags, int level, char *msg, va_list args) = NULL;
+
int libspamc_timeout = 0;
+int libspamc_connect_timeout = 0; /* Sep 8, 2008 mrgus: separate connect timeout */
/*
* translate_connect_errno()
@@ -161,17 +201,27 @@ static int _translate_connect_errno(int err)
/*
* opensocket()
*
- * Given a socket type (PF_INET or PF_UNIX), try to create this socket
- * and store the FD in the pointed-to place. If it's successful, do any
- * other setup required to make the socket ready to use, such as setting
- * TCP_NODELAY mode, and in any case we return EX_OK if all is well.
+ * Given a socket family (PF_INET or PF_INET6 or PF_UNIX), try to
+ * create this socket and store the FD in the pointed-to place.
+ * If it's successful, do any other setup required to make the socket
+ * ready to use, such as setting TCP_NODELAY mode, and in any case
+ * we return EX_OK if all is well.
*
* Upon failure we return one of the other EX_??? error codes.
*/
+#ifdef SPAMC_HAS_ADDRINFO
+static int _opensocket(int flags, struct addrinfo *res, int *psock)
+{
+#else
static int _opensocket(int flags, int type, int *psock)
{
- const char *typename;
int proto = 0;
+#endif
+ const char *typename;
+ int origerr;
+#ifdef _WIN32
+ int socktout;
+#endif
assert(psock != 0);
@@ -180,6 +230,22 @@ static int _opensocket(int flags, int type, int *psock)
* type given by the user. The typename is strictly used for debug
* reporting.
*/
+#ifdef SPAMC_HAS_ADDRINFO
+ switch(res->ai_family) {
+ case PF_UNIX:
+ typename = "PF_UNIX";
+ break;
+ case PF_INET:
+ typename = "PF_INET";
+ break;
+ case PF_INET6:
+ typename = "PF_INET6";
+ break;
+ default:
+ typename = "Unknown";
+ break;
+ }
+#else
if (type == PF_UNIX) {
typename = "PF_UNIX";
}
@@ -187,30 +253,33 @@ static int _opensocket(int flags, int type, int *psock)
typename = "PF_INET";
proto = IPPROTO_TCP;
}
+#endif
#ifdef DO_CONNECT_DEBUG_SYSLOGS
- libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
+ libspamc_log(flags, CONNECT_DEBUG_LEVEL, "dbg: create socket(%s)", typename);
#endif
+#ifdef SPAMC_HAS_ADDRINFO
+ if ((*psock = socket(res->ai_family, res->ai_socktype, res->ai_protocol))
+#else
if ((*psock = socket(type, SOCK_STREAM, proto))
+#endif
#ifndef _WIN32
< 0
#else
== INVALID_SOCKET
#endif
) {
- int origerr;
/*--------------------------------------------------------
* At this point we had a failure creating the socket, and
* this is pretty much fatal. Translate the error reason
* into something the user can understand.
*/
+ origerr = spamc_get_errno();
#ifndef _WIN32
- origerr = errno; /* take a copy before syslog() */
libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
#else
- origerr = WSAGetLastError();
libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
#endif
@@ -233,6 +302,29 @@ static int _opensocket(int flags, int type, int *psock)
}
}
+#ifdef _WIN32
+ /* bug 4344: makes timeout functional on Win32 */
+ socktout = libspamc_timeout * 1000;
+ if (type == PF_INET
+ && setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, (char *)&socktout, sizeof(socktout)) != 0)
+ {
+
+ origerr = spamc_get_errno();
+ switch (origerr)
+ {
+ case EBADF:
+ case ENOTSOCK:
+ case ENOPROTOOPT:
+ case EFAULT:
+ libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerr);
+ closesocket(*psock);
+ return EX_SOFTWARE;
+
+ default:
+ break; /* ignored */
+ }
+ }
+#endif
/*----------------------------------------------------------------
* Do a bit of setup on the TCP socket if required. Notes above
@@ -242,14 +334,12 @@ static int _opensocket(int flags, int type, int *psock)
{
int one = 1;
- if (type == PF_INET
- && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
- int origerrno;
-#ifndef _WIN32
- origerr = errno;
-#else
- origerrno = WSAGetLastError();
+ if ( ( type == PF_INET
+#ifdef PF_INET6
+ || type == PF_INET6
#endif
+ ) && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
+ origerr = spamc_get_errno();
switch (origerr) {
case EBADF:
case ENOTSOCK:
@@ -287,16 +377,28 @@ static int _try_to_connect_unix(struct transport *tp, int *sockptr)
#ifndef _WIN32
int mysock, status, origerr;
struct sockaddr_un addrbuf;
+#ifdef SPAMC_HAS_ADDRINFO
+ struct addrinfo hints, *res;
+#else
+ int res = PF_UNIX;
+#endif
int ret;
assert(tp != 0);
assert(sockptr != 0);
assert(tp->socketpath != 0);
+#ifdef SPAMC_HAS_ADDRINFO
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNIX;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ res = &hints;
+#endif
/*----------------------------------------------------------------
* If the socket itself can't be created, this is a fatal error.
*/
- if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
+ if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK)
return ret;
/* set up the UNIX domain socket */
@@ -306,17 +408,17 @@ static int _try_to_connect_unix(struct transport *tp, int *sockptr)
addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
#ifdef DO_CONNECT_DEBUG_SYSLOGS
- libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
+ libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
addrbuf.sun_path);
#endif
- status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
+ status = timeout_connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
origerr = errno;
if (status >= 0) {
#ifdef DO_CONNECT_DEBUG_SYSLOGS
- libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
+ libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
#endif
*sockptr = mysock;
@@ -324,7 +426,7 @@ static int _try_to_connect_unix(struct transport *tp, int *sockptr)
return EX_OK;
}
- libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
+ libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd using --socket='%s' failed: %s",
addrbuf.sun_path, strerror(origerr));
closesocket(mysock);
@@ -349,78 +451,158 @@ static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
int numloops;
int origerr = 0;
int ret;
+#ifdef SPAMC_HAS_ADDRINFO
+ struct addrinfo *res = NULL;
+ char port[SPAMC_MAXSERV-1]; /* port, for logging */
+#else
+ int res = PF_INET;
+#endif
+ char host[SPAMC_MAXHOST-1]; /* hostname, for logging */
+ int connect_retries, retry_sleep;
assert(tp != 0);
assert(sockptr != 0);
assert(tp->nhosts > 0);
-#ifdef DO_CONNECT_DEBUG_SYSLOGS
- for (numloops = 0; numloops < tp->nhosts; numloops++) {
- libspamc_log(tp->flags, LOG_ERR, "dbg: %d/%d: %s",
- numloops + 1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
+ /* default values */
+ retry_sleep = tp->retry_sleep;
+ connect_retries = tp->connect_retries;
+ if (connect_retries == 0) {
+ connect_retries = 3;
+ }
+ if (retry_sleep < 0) {
+ retry_sleep = 1;
}
-#endif
- for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
- struct sockaddr_in addrbuf;
- const int hostix = numloops % tp->nhosts;
- int status, mysock;
- const char *ipaddr;
+ for (numloops = 0; numloops < connect_retries; numloops++) {
+ const int hostix = numloops % tp->nhosts;
+ int status, mysock;
+ int innocent = 0;
+
+ /*--------------------------------------------------------
+ * We always start by creating the socket, as we get only
+ * one attempt to connect() on each one. If this fails,
+ * we're done.
+ */
+
+#ifdef SPAMC_HAS_ADDRINFO
+ res = tp->hosts[hostix];
+ while(res) {
+ char *family = NULL;
+ switch(res->ai_family) {
+ case AF_INET:
+ family = "AF_INET";
+ break;
+ case AF_INET6:
+ family = "AF_INET6";
+ break;
+ default:
+ family = "Unknown";
+ break;
+ }
+
+ if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK) {
+ res = res->ai_next;
+ continue;
+ }
+
+ getnameinfo(res->ai_addr, res->ai_addrlen,
+ host, sizeof(host),
+ port, sizeof(port),
+ NI_NUMERICHOST|NI_NUMERICSERV);
- /*--------------------------------------------------------
- * We always start by creating the socket, as we get only
- * one attempt to connect() on each one. If this fails,
- * we're done.
- */
- if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
- return ret;
-
- memset(&addrbuf, 0, sizeof(addrbuf));
+#ifdef DO_CONNECT_DEBUG_SYSLOGS
+ libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
+ "dbg: connect(%s) to spamd (host %s, port %s) (try #%d of %d)",
+ family, host, port, numloops + 1, connect_retries);
+#endif
- addrbuf.sin_family = AF_INET;
- addrbuf.sin_port = htons(tp->port);
- addrbuf.sin_addr = tp->hosts[hostix];
+ /* this is special-cased so that we have an address we can
+ * safely use as an "always fail" test case */
+ if (!strcmp(host, "255.255.255.255")) {
+ libspamc_log(tp->flags, LOG_ERR,
+ "connect to spamd on %s failed, broadcast addr",
+ host);
+ status = -1;
+ }
+ else {
+ status = timeout_connect(mysock, res->ai_addr, res->ai_addrlen);
+ if (status != 0) origerr = spamc_get_errno();
+ }
- ipaddr = inet_ntoa(addrbuf.sin_addr);
+#else
+ struct sockaddr_in addrbuf;
+ const char *ipaddr;
+ const char* family="AF_INET";
+ if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
+ return ret;
+
+ memset(&addrbuf, 0, sizeof(addrbuf));
+
+ addrbuf.sin_family = AF_INET;
+ addrbuf.sin_port = htons(tp->port);
+ addrbuf.sin_addr = tp->hosts[hostix];
+
+ ipaddr = inet_ntoa(addrbuf.sin_addr);
+
+ /* make a copy in host, for logging (bug 5577) */
+ strncpy (host, ipaddr, sizeof(host) - 1);
#ifdef DO_CONNECT_DEBUG_SYSLOGS
- libspamc_log(tp->flags, DEBUG_LEVEL,
- "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
- ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
+ libspamc_log(tp->flags, LOG_DEBUG,
+ "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
+ ipaddr, numloops + 1, connect_retries);
#endif
- status =
- connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
+ /* this is special-cased so that we have an address we can
+ * safely use as an "always fail" test case */
+ if (!strcmp(ipaddr, "255.255.255.255")) {
+ libspamc_log(tp->flags, LOG_ERR,
+ "connect to spamd on %s failed, broadcast addr",
+ ipaddr);
+ status = -1;
+ }
+ else {
+ status = timeout_connect(mysock, (struct sockaddr *) &addrbuf,
+ sizeof(addrbuf));
+ if (status != 0) origerr = spamc_get_errno();
+ }
- if (status != 0) {
-#ifndef _WIN32
- origerr = errno;
- libspamc_log(tp->flags, LOG_ERR,
- "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
- ipaddr, numloops + 1, MAX_CONNECT_RETRIES, strerror(origerr));
+#endif
+
+ if (status != 0) {
+ closesocket(mysock);
+
+ innocent = origerr == ECONNREFUSED && numloops+1 < tp->nhosts;
+ libspamc_log(tp->flags, innocent ? LOG_DEBUG : LOG_ERR,
+ "connect to spamd on %s failed, retrying (#%d of %d): %s",
+ host, numloops+1, connect_retries,
+#ifdef _WIN32
+ origerr
#else
- origerr = WSAGetLastError();
- libspamc_log(tp->flags, LOG_ERR,
- "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
- ipaddr, numloops + 1, MAX_CONNECT_RETRIES, origerr);
+ strerror(origerr)
#endif
- closesocket(mysock);
+ );
- sleep(CONNECT_RETRY_SLEEP);
- }
- else {
+ } else {
#ifdef DO_CONNECT_DEBUG_SYSLOGS
- libspamc_log(tp->flags, DEBUG_LEVEL,
- "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
+ libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
+ "dbg: connect(%s) to spamd done",family);
#endif
- *sockptr = mysock;
+ *sockptr = mysock;
- return EX_OK;
- }
- }
+ return EX_OK;
+ }
+#ifdef SPAMC_HAS_ADDRINFO
+ res = res->ai_next;
+ }
+#endif
+ if (numloops+1 < connect_retries && !innocent) sleep(retry_sleep);
+ } /* for(numloops...) */
- libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
- MAX_CONNECT_RETRIES);
+ libspamc_log(tp->flags, LOG_ERR,
+ "connection attempt to spamd aborted after %d retries",
+ connect_retries);
return _translate_connect_errno(origerr);
}
@@ -444,11 +626,30 @@ static void _clear_message(struct message *m)
m->is_spam = EX_TOOBIG;
m->score = 0.0;
m->threshold = 0.0;
+ m->outbuf = NULL;
m->out = NULL;
m->out_len = 0;
m->content_length = -1;
}
+static void _free_zlib_buffer(unsigned char **zlib_buf, int *zlib_bufsiz)
+{
+ if(*zlib_buf) {
+ free(*zlib_buf);
+ *zlib_buf=NULL;
+ }
+ *zlib_bufsiz=0;
+}
+
+static void _use_msg_for_out(struct message *m)
+{
+ if (m->outbuf)
+ free(m->outbuf);
+ m->outbuf = NULL;
+ m->out = m->msg;
+ m->out_len = m->msg_len;
+}
+
static int _message_read_raw(int fd, struct message *m)
{
_clear_message(m);
@@ -462,8 +663,13 @@ static int _message_read_raw(int fd, struct message *m)
return EX_IOERR;
}
m->type = MESSAGE_ERROR;
- if (m->raw_len > m->max_len)
+ if (m->raw_len > (int) m->max_len)
+ {
+ libspamc_log(m->priv->flags, LOG_NOTICE,
+ "skipped message, greater than max message size (%d bytes)",
+ m->max_len);
return EX_TOOBIG;
+ }
m->type = MESSAGE_RAW;
m->msg = m->raw;
m->msg_len = m->raw_len;
@@ -474,8 +680,9 @@ static int _message_read_raw(int fd, struct message *m)
static int _message_read_bsmtp(int fd, struct message *m)
{
- unsigned int i, j;
+ unsigned int i, j, p_len;
char prev;
+ char* p;
_clear_message(m);
if ((m->raw = malloc(m->max_len + 1)) == NULL)
@@ -490,44 +697,52 @@ static int _message_read_bsmtp(int fd, struct message *m)
return EX_IOERR;
}
m->type = MESSAGE_ERROR;
- if (m->raw_len > m->max_len)
+ if (m->raw_len > (int) 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;
+ p = m->pre = m->raw;
+ /* Search for \nDATA\n which marks start of actual message */
+ while ((p_len = (m->raw_len - (p - m->raw))) > 8) { /* leave room for at least \nDATA\n.\n */
+ char* q = memchr(p, '\n', p_len - 8); /* find next \n then see if start of \nDATA\n */
+ if (q == NULL) break;
+ q++;
+ if (((q[0]|0x20) == 'd') && /* case-insensitive ASCII comparison */
+ ((q[1]|0x20) == 'a') &&
+ ((q[2]|0x20) == 't') &&
+ ((q[3]|0x20) == 'a')) {
+ q+=4;
+ if (q[0] == '\r') ++q;
+ if (*(q++) == '\n') { /* leave q at start of message if we found it */
+ m->msg = q;
+ m->pre_len = q - m->raw;
+ m->msg_len = m->raw_len - m->pre_len;
+ break;
}
+ }
+ p = q; /* the above code ensures no other '\n' comes before q */
}
if (m->msg == NULL)
return EX_DATAERR;
+ /* ensure this is >= 0 */
+ if (m->msg_len < 0) {
+ return EX_SOFTWARE;
+ }
+
/* Find the end-of-DATA line */
prev = '\n';
- for (i = j = 0; i < m->msg_len; i++) {
+ for (i = j = 0; i < (unsigned int) 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') {
+ if (((int) (i+1) == m->msg_len)
+ || ((int) (i+1) < m->msg_len && m->msg[i + 1] == '\n')
+ || ((int) (i+2) < m->msg_len && m->msg[i + 1] == '\r' && m->msg[i + 2] == '\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] == '.') {
+ else if ((int) (i+1) < m->msg_len && m->msg[i + 1] == '.') {
/* Escaping dot, eliminate. */
prev = '.';
continue;
@@ -537,6 +752,9 @@ static int _message_read_bsmtp(int fd, struct message *m)
m->msg[j++] = m->msg[i];
}
+ /* if bad format with no end "\n.\n", error out */
+ if (m->post == NULL)
+ return EX_DATAERR;
m->type = MESSAGE_BSMTP;
m->out = m->msg;
m->out_len = m->msg_len;
@@ -545,6 +763,8 @@ static int _message_read_bsmtp(int fd, struct message *m)
int message_read(int fd, int flags, struct message *m)
{
+ assert(m != NULL);
+
libspamc_timeout = 0;
/* create the "private" part of the struct message */
@@ -554,6 +774,14 @@ int message_read(int fd, int flags, struct message *m)
return EX_OSERR;
}
m->priv->flags = flags;
+ m->priv->alloced_size = 0;
+ m->priv->spamc_header_callback = 0;
+ m->priv->spamd_header_callback = 0;
+
+ if (flags & SPAMC_PING) {
+ _clear_message(m);
+ return EX_OK;
+ }
switch (flags & SPAMC_MODE_MASK) {
case SPAMC_RAW_MODE:
@@ -576,7 +804,9 @@ long message_write(int fd, struct message *m)
off_t jlimit;
char buffer[1024];
- if (m->priv->flags & SPAMC_CHECK_ONLY) {
+ assert(m != NULL);
+
+ if (m->priv->flags & (SPAMC_CHECK_ONLY|SPAMC_PING)) {
if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
return full_write(fd, 1, m->out, m->out_len);
@@ -628,17 +858,23 @@ long message_write(int fd, struct message *m)
}
}
-void message_dump(int in_fd, int out_fd, struct message *m)
+void message_dump(int in_fd, int out_fd, struct message *m, int flags)
{
char buf[8196];
int bytes;
- if (m != NULL && m->type != MESSAGE_NONE) {
+ if (m == NULL) {
+ libspamc_log(flags, LOG_ERR, "oops! message_dump called with NULL message");
+ return;
+ }
+
+ if (m->type != MESSAGE_NONE) {
message_write(out_fd, m);
}
+
while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
if (bytes != full_write(out_fd, 1, buf, bytes)) {
- libspamc_log(m->priv->flags, LOG_ERR, "oops! message_dump of %d returned different",
+ libspamc_log(flags, LOG_ERR, "oops! message_dump of %d returned different",
bytes);
}
}
@@ -719,7 +955,8 @@ static float _locale_safe_string_to_float(char *buf, int siz)
cp = (dot + 1);
postdot = (float) (strtol(cp, NULL, 10));
- if (postdot == 0.0) {
+ /* note: don't compare floats == 0.0, it's unsafe. use a range */
+ if (postdot >= -0.00001 && postdot <= 0.00001) {
return ret;
}
@@ -751,10 +988,13 @@ static float _locale_safe_string_to_float(char *buf, int siz)
}
static int
-_handle_spamd_header(struct message *m, int flags, char *buf, int len)
+_handle_spamd_header(struct message *m, int flags, char *buf, int len,
+ unsigned int *didtellflags)
{
char is_spam[6];
char s_str[21], t_str[21];
+ char didset_ret[15];
+ char didremove_ret[15];
UNUSED_VARIABLE(len);
@@ -801,13 +1041,140 @@ _handle_spamd_header(struct message *m, int flags, char *buf, int len)
}
return EX_OK;
}
+ else if (sscanf(buf, "DidSet: %14s", didset_ret) == 1) {
+ if (strstr(didset_ret, "local")) {
+ *didtellflags |= SPAMC_SET_LOCAL;
+ }
+ if (strstr(didset_ret, "remote")) {
+ *didtellflags |= SPAMC_SET_REMOTE;
+ }
+ }
+ else if (sscanf(buf, "DidRemove: %14s", didremove_ret) == 1) {
+ if (strstr(didremove_ret, "local")) {
+ *didtellflags |= SPAMC_REMOVE_LOCAL;
+ }
+ if (strstr(didremove_ret, "remote")) {
+ *didtellflags |= SPAMC_REMOVE_REMOTE;
+ }
+ }
+ else if (m->priv->spamd_header_callback != NULL)
+ m->priv->spamd_header_callback(m, flags, buf, len);
+
+ return EX_OK;
+}
+
+static int
+_zlib_compress (char *m_msg, int m_msg_len,
+ unsigned char **zlib_buf, int *zlib_bufsiz, int flags)
+{
+ int rc;
+ int len, totallen;
+
+#ifndef HAVE_LIBZ
+
+ UNUSED_VARIABLE(m_msg);
+ UNUSED_VARIABLE(m_msg_len);
+ UNUSED_VARIABLE(zlib_buf);
+ UNUSED_VARIABLE(zlib_bufsiz);
+ UNUSED_VARIABLE(rc);
+ UNUSED_VARIABLE(len);
+ UNUSED_VARIABLE(totallen);
+ libspamc_log(flags, LOG_ERR, "spamc not built with zlib support");
+ return EX_SOFTWARE;
+
+#else
+ z_stream strm;
+
+ UNUSED_VARIABLE(flags);
+
+ /* worst-case, according to http://www.zlib.org/zlib_tech.html ;
+ * same as input, plus 5 bytes per 16k, plus 6 bytes. this should
+ * be plenty */
+ *zlib_bufsiz = (int) (m_msg_len * 1.0005) + 1024;
+ *zlib_buf = (unsigned char *) malloc (*zlib_bufsiz);
+ if (*zlib_buf == NULL) {
+ return EX_OSERR;
+ }
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ rc = deflateInit(&strm, 3);
+ if (rc != Z_OK) {
+ return EX_OSERR;
+ }
- libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
- return EX_PROTOCOL;
+ strm.avail_in = m_msg_len;
+ strm.next_in = (unsigned char *) m_msg;
+ strm.avail_out = *zlib_bufsiz;
+ strm.next_out = (unsigned char *) *zlib_buf;
+
+ totallen = 0;
+ do {
+ rc = deflate(&strm, Z_FINISH);
+ assert(rc != Z_STREAM_ERROR);
+ len = (size_t) (*zlib_bufsiz - strm.avail_out);
+ strm.next_out += len;
+ totallen += len;
+ } while (strm.avail_out == 0);
+
+ *zlib_bufsiz = totallen;
+ return EX_OK;
+
+#endif
+}
+
+int
+_append_original_body (struct message *m, int flags)
+{
+ char *cp, *cpend, *bodystart;
+ int bodylen, outspaceleft, towrite;
+
+ /* at this stage, m->out now contains the rewritten headers.
+ * find and append the raw message's body, up to m->priv->alloced_size
+ * bytes.
+ */
+
+#define CRNLCRNL "\r\n\r\n"
+#define CRNLCRNL_LEN 4
+#define NLNL "\n\n"
+#define NLNL_LEN 2
+
+ cpend = m->raw + m->raw_len;
+ bodystart = NULL;
+
+ for (cp = m->raw; cp < cpend; cp++) {
+ if (*cp == '\r' && cpend - cp >= CRNLCRNL_LEN &&
+ !strncmp(cp, CRNLCRNL, CRNLCRNL_LEN))
+ {
+ bodystart = cp + CRNLCRNL_LEN;
+ break;
+ }
+ else if (*cp == '\n' && cpend - cp >= NLNL_LEN &&
+ !strncmp(cp, NLNL, NLNL_LEN))
+ {
+ bodystart = cp + NLNL_LEN;
+ break;
+ }
+ }
+
+ if (bodystart == NULL) {
+ libspamc_log(flags, LOG_ERR, "failed to find end-of-headers");
+ return EX_SOFTWARE;
+ }
+
+ bodylen = cpend - bodystart;
+ outspaceleft = (m->priv->alloced_size-1) - m->out_len;
+ towrite = (bodylen < outspaceleft ? bodylen : outspaceleft);
+
+ /* copy in the body; careful not to overflow */
+ strncpy (m->out + m->out_len, bodystart, towrite);
+ m->out_len += towrite;
+ return EX_OK;
}
int message_filter(struct transport *tp, const char *username,
- int flags, struct message *m)
+ int flags, struct message *m)
{
char buf[8192];
size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
@@ -817,15 +1184,41 @@ int message_filter(struct transport *tp, const char *username,
char versbuf[20];
float version;
int response;
- int failureval;
+ int failureval = EX_SOFTWARE;
+ unsigned int throwaway;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
SSL_METHOD *meth;
+ char zlib_on = 0;
+ unsigned char *zlib_buf = NULL;
+ int zlib_bufsiz = 0;
+ unsigned char *towrite_buf;
+ int towrite_len;
+ int filter_retry_count;
+ int filter_retry_sleep;
+ int filter_retries;
+ #ifdef SPAMC_HAS_ADDRINFO
+ struct addrinfo *tmphost;
+ #else
+ struct in_addr tmphost;
+ #endif
+ int nhost_counter;
+
+ assert(tp != NULL);
+ assert(m != NULL);
+
+ if ((flags & SPAMC_USE_ZLIB) != 0) {
+ zlib_on = 1;
+ }
if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
SSLeay_add_ssl_algorithms();
- meth = SSLv2_client_method();
+ if (flags & SPAMC_TLSV1) {
+ meth = TLSv1_client_method();
+ } else {
+ meth = SSLv3_client_method(); /* default */
+ }
SSL_load_error_strings();
ctx = SSL_CTX_new(meth);
#else
@@ -838,97 +1231,184 @@ int message_filter(struct transport *tp, const char *username,
}
m->is_spam = EX_TOOBIG;
- if ((m->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
+
+ if (m->outbuf != NULL)
+ free(m->outbuf);
+ m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
+ if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
failureval = EX_OSERR;
goto failure;
}
+ m->out = m->outbuf;
m->out_len = 0;
+ /* If the spamd filter takes too long and we timeout, then
+ * retry again. This gets us around a hung child thread
+ * in spamd or a problem on a spamd host in a multi-host
+ * setup. If there is more than one destination host
+ * we move to the next host on each attempt.
+ */
- /* Build spamd protocol header */
- if (flags & SPAMC_CHECK_ONLY)
- strcpy(buf, "CHECK ");
- else if (flags & SPAMC_REPORT_IFSPAM)
- strcpy(buf, "REPORT_IFSPAM ");
- else if (flags & SPAMC_REPORT)
- strcpy(buf, "REPORT ");
- else if (flags & SPAMC_SYMBOLS)
- strcpy(buf, "SYMBOLS ");
- else
- strcpy(buf, "PROCESS ");
-
- len = strlen(buf);
- if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
- free(m->out);
- m->out = m->msg;
- m->out_len = m->msg_len;
- return EX_OSERR;
- }
-
- strcat(buf, PROTOCOL_VERSION);
- strcat(buf, "\r\n");
- len = strlen(buf);
-
- if (username != NULL) {
- if (strlen(username) + 8 >= (bufsiz - len)) {
- free(m->out);
- m->out = m->msg;
- m->out_len = m->msg_len;
- return EX_OSERR;
- }
- strcpy(buf + len, "User: ");
- strcat(buf + len, username);
- strcat(buf + len, "\r\n");
- len += strlen(buf + len);
+ /* default values */
+ filter_retry_sleep = tp->filter_retry_sleep;
+ filter_retries = tp->filter_retries;
+ if (filter_retries == 0) {
+ filter_retries = 1;
}
- if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
- free(m->out);
- m->out = m->msg;
- m->out_len = m->msg_len;
- return EX_OSERR;
+ if (filter_retry_sleep < 0) {
+ filter_retry_sleep = 1;
}
- len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
- libspamc_timeout = m->timeout;
-
- if (tp->socketpath)
- rc = _try_to_connect_unix(tp, &sock);
- else
- rc = _try_to_connect_tcp(tp, &sock);
-
- if (rc != EX_OK) {
- free(m->out);
- m->out = m->msg;
- m->out_len = m->msg_len;
- return rc; /* use the error code try_to_connect_*() gave us. */
- }
-
- if (flags & SPAMC_USE_SSL) {
+ /* filterloop - Ensure that we run through this at least
+ * once, and again if there are errors
+ */
+ filter_retry_count = 0;
+ while ((filter_retry_count==0) ||
+ ((filter_retry_count<tp->filter_retries) && (failureval == EX_IOERR)))
+ {
+ if (filter_retry_count != 0){
+ /* Ensure that the old socket gets closed */
+ if (sock != -1) {
+ closesocket(sock);
+ sock=-1;
+ }
+
+ /* Move to the next host in the list, if nhosts>1 */
+ if (tp->nhosts > 1) {
+ tmphost = tp->hosts[0];
+
+ /* TODO: free using freeaddrinfo() */
+ for (nhost_counter = 1; nhost_counter < tp->nhosts; nhost_counter++) {
+ tp->hosts[nhost_counter - 1] = tp->hosts[nhost_counter];
+ }
+
+ tp->hosts[nhost_counter - 1] = tmphost;
+ }
+
+ /* Now sleep the requested amount */
+ sleep(filter_retry_sleep);
+ }
+
+ filter_retry_count++;
+
+ /* Build spamd protocol header */
+ if (flags & SPAMC_CHECK_ONLY)
+ strcpy(buf, "CHECK ");
+ else if (flags & SPAMC_REPORT_IFSPAM)
+ strcpy(buf, "REPORT_IFSPAM ");
+ else if (flags & SPAMC_REPORT)
+ strcpy(buf, "REPORT ");
+ else if (flags & SPAMC_SYMBOLS)
+ strcpy(buf, "SYMBOLS ");
+ else if (flags & SPAMC_PING)
+ strcpy(buf, "PING ");
+ else if (flags & SPAMC_HEADERS)
+ strcpy(buf, "HEADERS ");
+ else
+ strcpy(buf, "PROCESS ");
+
+ len = strlen(buf);
+ if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
+ _use_msg_for_out(m);
+ return EX_OSERR;
+ }
+
+ strcat(buf, PROTOCOL_VERSION);
+ strcat(buf, "\r\n");
+ len = strlen(buf);
+
+ towrite_buf = (unsigned char *) m->msg;
+ towrite_len = (int) m->msg_len;
+ if (zlib_on) {
+ if (_zlib_compress(m->msg, m->msg_len, &zlib_buf, &zlib_bufsiz, flags) != EX_OK)
+ {
+ _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+ return EX_OSERR;
+ }
+ towrite_buf = zlib_buf;
+ towrite_len = zlib_bufsiz;
+ }
+
+ if (!(flags & SPAMC_PING)) {
+ if (username != NULL) {
+ if (strlen(username) + 8 >= (bufsiz - len)) {
+ _use_msg_for_out(m);
+ if (zlib_on) {
+ _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+ }
+ return EX_OSERR;
+ }
+ strcpy(buf + len, "User: ");
+ strcat(buf + len, username);
+ strcat(buf + len, "\r\n");
+ len += strlen(buf + len);
+ }
+ if (zlib_on) {
+ len += snprintf(buf + len, 8192-len, "Compress: zlib\r\n");
+ }
+ if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
+ _use_msg_for_out(m);
+ if (zlib_on) {
+ _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+ }
+ return EX_DATAERR;
+ }
+ len += snprintf(buf + len, 8192-len, "Content-length: %d\r\n", (int) towrite_len);
+ }
+ /* bug 6187, PING needs empty line too, bumps protocol version to 1.5 */
+ len += snprintf(buf + len, 8192-len, "\r\n");
+
+ libspamc_timeout = m->timeout;
+ libspamc_connect_timeout = m->connect_timeout; /* Sep 8, 2008 mrgus: separate connect timeout */
+
+ if (tp->socketpath)
+ rc = _try_to_connect_unix(tp, &sock);
+ else
+ rc = _try_to_connect_tcp(tp, &sock);
+
+ if (rc != EX_OK) {
+ _use_msg_for_out(m);
+ if (zlib_on) {
+ _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+ }
+ return rc; /* use the error code try_to_connect_*() gave us. */
+ }
+
+ if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
- ssl = SSL_new(ctx);
- SSL_set_fd(ssl, sock);
- SSL_connect(ssl);
+ ssl = SSL_new(ctx);
+ SSL_set_fd(ssl, sock);
+ SSL_connect(ssl);
#endif
- }
-
- /* Send to spamd */
- if (flags & SPAMC_USE_SSL) {
+ }
+
+ /* Send to spamd */
+ if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
- SSL_write(ssl, buf, len);
- SSL_write(ssl, m->msg, m->msg_len);
+ SSL_write(ssl, buf, len);
+ SSL_write(ssl, towrite_buf, towrite_len);
#endif
- }
- else {
- full_write(sock, 0, buf, len);
- full_write(sock, 0, m->msg, m->msg_len);
- shutdown(sock, SHUT_WR);
- }
+ }
+ else {
+ full_write(sock, 0, buf, len);
+ full_write(sock, 0, towrite_buf, towrite_len);
+ shutdown(sock, SHUT_WR);
+ }
+
+ /* free zlib buffer
+ * bug 6025: zlib buffer not freed if compression is used
+ */
+ if (zlib_on) {
+ _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
+ }
+
+ /* ok, now read and parse it. SPAMD/1.2 line first... */
+ failureval =
+ _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
+ } /* end of filterloop */
- /* 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;
+ goto failure;
}
if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
@@ -946,6 +1426,14 @@ int message_filter(struct transport *tp, const char *username,
goto failure;
}
+ if (flags & SPAMC_PING) {
+ closesocket(sock);
+ sock = -1;
+ m->out_len = sprintf(m->out, "SPAMD/%s %d\n", versbuf, response);
+ m->is_spam = EX_NOTSPAM;
+ return EX_OK;
+ }
+
m->score = 0;
m->threshold = 0;
m->is_spam = EX_TOOBIG;
@@ -960,7 +1448,7 @@ int message_filter(struct transport *tp, const char *username,
break; /* end of headers */
}
- if (_handle_spamd_header(m, flags, buf, len) < 0) {
+ if (_handle_spamd_header(m, flags, buf, len, &throwaway) < 0) {
failureval = EX_PROTOCOL;
goto failure;
}
@@ -995,20 +1483,17 @@ int message_filter(struct transport *tp, const char *username,
if (flags & SPAMC_USE_SSL) {
len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
- m->max_len + EXPANSION_ALLOWANCE + 1 -
- m->out_len,
- m->max_len + EXPANSION_ALLOWANCE + 1 -
- m->out_len);
+ m->priv->alloced_size - m->out_len,
+ m->priv->alloced_size - m->out_len);
}
else {
len = full_read(sock, 0, m->out + m->out_len,
- m->max_len + EXPANSION_ALLOWANCE + 1 - m->out_len,
- m->max_len + EXPANSION_ALLOWANCE + 1 -
- m->out_len);
+ m->priv->alloced_size - m->out_len,
+ m->priv->alloced_size - m->out_len);
}
- if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
+ if ((int) len + (int) m->out_len > (m->priv->alloced_size - 1)) {
failureval = EX_TOOBIG;
goto failure;
}
@@ -1028,12 +1513,16 @@ int message_filter(struct transport *tp, const char *username,
goto failure;
}
+ if (flags & SPAMC_HEADERS) {
+ if (_append_original_body(m, flags) != EX_OK) {
+ goto failure;
+ }
+ }
+
return EX_OK;
failure:
- free(m->out);
- m->out = m->msg;
- m->out_len = m->msg_len;
+ _use_msg_for_out(m);
if (sock != -1) {
closesocket(sock);
}
@@ -1048,52 +1537,289 @@ int message_filter(struct transport *tp, const char *username,
return failureval;
}
-
int message_process(struct transport *trans, char *username, int max_size,
int in_fd, int out_fd, const int flags)
{
int ret;
struct message m;
+ assert(trans != NULL);
+
m.type = MESSAGE_NONE;
- m.max_len = max_size;
+ /* enforce max_size being unsigned, therefore >= 0 */
+ if (max_size < 0) {
+ ret = EX_SOFTWARE;
+ goto FAIL;
+ }
+ m.max_len = (unsigned int) max_size;
+
ret = message_read(in_fd, flags, &m);
if (ret != EX_OK)
- goto FAIL;
+ goto FAIL;
ret = message_filter(trans, username, flags, &m);
if (ret != EX_OK)
- goto FAIL;
+ goto FAIL;
if (message_write(out_fd, &m) < 0)
- goto FAIL;
+ goto FAIL;
if (m.is_spam != EX_TOOBIG) {
- message_cleanup(&m);
- return m.is_spam;
+ message_cleanup(&m);
+ return m.is_spam;
}
message_cleanup(&m);
return ret;
FAIL:
if (flags & SPAMC_CHECK_ONLY) {
- full_write(out_fd, 1, "0/0\n", 4);
- message_cleanup(&m);
- return EX_NOTSPAM;
+ full_write(out_fd, 1, "0/0\n", 4);
+ message_cleanup(&m);
+ return EX_NOTSPAM;
}
else {
- message_dump(in_fd, out_fd, &m);
- message_cleanup(&m);
- return ret;
+ message_dump(in_fd, out_fd, &m, flags);
+ message_cleanup(&m);
+ return ret;
+ }
+}
+
+int message_tell(struct transport *tp, const char *username, int flags,
+ struct message *m, int msg_class,
+ unsigned int tellflags, unsigned int *didtellflags)
+{
+ char buf[8192];
+ size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
+ size_t len;
+ int sock = -1;
+ int rc;
+ char versbuf[20];
+ float version;
+ int response;
+ int failureval;
+ SSL_CTX *ctx = NULL;
+ SSL *ssl = NULL;
+ SSL_METHOD *meth;
+
+ assert(tp != NULL);
+ assert(m != NULL);
+
+ if (flags & SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+ SSLeay_add_ssl_algorithms();
+ meth = SSLv3_client_method();
+ SSL_load_error_strings();
+ ctx = SSL_CTX_new(meth);
+#else
+ UNUSED_VARIABLE(ssl);
+ UNUSED_VARIABLE(meth);
+ UNUSED_VARIABLE(ctx);
+ libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
+ return EX_SOFTWARE;
+#endif
+ }
+
+ m->is_spam = EX_TOOBIG;
+
+ if (m->outbuf != NULL)
+ free(m->outbuf);
+ m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
+ if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
+ failureval = EX_OSERR;
+ goto failure;
+ }
+ m->out = m->outbuf;
+ m->out_len = 0;
+
+ /* Build spamd protocol header */
+ strcpy(buf, "TELL ");
+
+ len = strlen(buf);
+ if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
+ _use_msg_for_out(m);
+ return EX_OSERR;
+ }
+
+ strcat(buf, PROTOCOL_VERSION);
+ strcat(buf, "\r\n");
+ len = strlen(buf);
+
+ if (msg_class != 0) {
+ strcpy(buf + len, "Message-class: ");
+ if (msg_class == SPAMC_MESSAGE_CLASS_SPAM) {
+ strcat(buf + len, "spam\r\n");
+ }
+ else {
+ strcat(buf + len, "ham\r\n");
+ }
+ len += strlen(buf + len);
+ }
+
+ if ((tellflags & SPAMC_SET_LOCAL) || (tellflags & SPAMC_SET_REMOTE)) {
+ int needs_comma_p = 0;
+ strcat(buf + len, "Set: ");
+ if (tellflags & SPAMC_SET_LOCAL) {
+ strcat(buf + len, "local");
+ needs_comma_p = 1;
+ }
+ if (tellflags & SPAMC_SET_REMOTE) {
+ if (needs_comma_p == 1) {
+ strcat(buf + len, ",");
+ }
+ strcat(buf + len, "remote");
+ }
+ strcat(buf + len, "\r\n");
+ len += strlen(buf + len);
+ }
+
+ if ((tellflags & SPAMC_REMOVE_LOCAL) || (tellflags & SPAMC_REMOVE_REMOTE)) {
+ int needs_comma_p = 0;
+ strcat(buf + len, "Remove: ");
+ if (tellflags & SPAMC_REMOVE_LOCAL) {
+ strcat(buf + len, "local");
+ needs_comma_p = 1;
+ }
+ if (tellflags & SPAMC_REMOVE_REMOTE) {
+ if (needs_comma_p == 1) {
+ strcat(buf + len, ",");
+ }
+ strcat(buf + len, "remote");
+ }
+ strcat(buf + len, "\r\n");
+ len += strlen(buf + len);
+ }
+
+ if (username != NULL) {
+ if (strlen(username) + 8 >= (bufsiz - len)) {
+ _use_msg_for_out(m);
+ return EX_OSERR;
+ }
+ strcpy(buf + len, "User: ");
+ strcat(buf + len, username);
+ strcat(buf + len, "\r\n");
+ len += strlen(buf + len);
+ }
+ if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
+ _use_msg_for_out(m);
+ return EX_DATAERR;
+ }
+ len += sprintf(buf + len, "Content-length: %d\r\n\r\n", (int) m->msg_len);
+
+ if (m->priv->spamc_header_callback != NULL) {
+ char buf2[1024];
+ m->priv->spamc_header_callback(m, flags, buf2, 1024);
+ strncat(buf, buf2, bufsiz - len);
+ }
+
+ libspamc_timeout = m->timeout;
+ libspamc_connect_timeout = m->connect_timeout; /* Sep 8, 2008 mrgus: separate connect timeout */
+
+ if (tp->socketpath)
+ rc = _try_to_connect_unix(tp, &sock);
+ else
+ rc = _try_to_connect_tcp(tp, &sock);
+
+ if (rc != EX_OK) {
+ _use_msg_for_out(m);
+ return rc; /* use the error code try_to_connect_*() gave us. */
+ }
+
+ 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, 0, buf, len);
+ full_write(sock, 0, 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/%18s %d %*s", versbuf, &response) != 2) {
+ libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
+ failureval = EX_PROTOCOL;
+ goto failure;
+ }
+
+ versbuf[19] = '\0';
+ version = _locale_safe_string_to_float(versbuf, 20);
+ if (version < 1.0) {
+ libspamc_log(flags, 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, didtellflags) < 0) {
+ failureval = EX_PROTOCOL;
+ goto failure;
+ }
+ }
+
+ len = 0; /* overwrite those headers */
+
+ shutdown(sock, SHUT_RD);
+ closesocket(sock);
+ sock = -1;
+
+ libspamc_timeout = 0;
+
+ return EX_OK;
+
+ failure:
+ _use_msg_for_out(m);
+ if (sock != -1) {
+ closesocket(sock);
}
+ libspamc_timeout = 0;
+
+ if (flags & SPAMC_USE_SSL) {
+#ifdef SPAMC_SSL
+ SSL_free(ssl);
+ SSL_CTX_free(ctx);
+#endif
+ }
+ return failureval;
}
void message_cleanup(struct message *m)
{
- if (m->out != NULL)
- free(m->out);
- if (m->raw != NULL && m->raw != m->out)
- free(m->raw);
- if (m->priv != NULL && (char*)m->priv != m->out && (char*)m->priv != m->raw)
- free(m->priv);
+ assert(m != NULL);
+ if (m->outbuf != NULL)
+ free(m->outbuf);
+ if (m->raw != NULL)
+ free(m->raw);
+ if (m->priv != NULL)
+ free(m->priv);
_clear_message(m);
}
@@ -1106,19 +1832,19 @@ int process_message(struct transport *tp, char *username, int max_size,
flags = SPAMC_RAW_MODE;
if (my_check_only)
- flags |= SPAMC_CHECK_ONLY;
+ flags |= SPAMC_CHECK_ONLY;
if (my_safe_fallback)
- flags |= SPAMC_SAFE_FALLBACK;
+ flags |= SPAMC_SAFE_FALLBACK;
return message_process(tp, username, max_size, in_fd, out_fd, flags);
}
/*
- * init_transport()
- *
- * Given a pointer to a transport structure, set it to "all empty".
- * The default is a localhost connection.
- */
+* init_transport()
+*
+* Given a pointer to a transport structure, set it to "all empty".
+* The default is a localhost connection.
+*/
void transport_init(struct transport *tp)
{
assert(tp != 0);
@@ -1128,163 +1854,364 @@ void transport_init(struct transport *tp)
tp->type = TRANSPORT_LOCALHOST;
tp->port = 783;
tp->flags = 0;
+ tp->retry_sleep = -1;
}
/*
- * randomize_hosts()
- *
- * Given the transport object that contains one or more IP addresses
- * in this "hosts" list, rotate it by a random number of shifts to
- * randomize them - this is a kind of load balancing. It's possible
- * that the random number will be 0, which says not to touch. We don't
- * do anything unless
- */
+* randomize_hosts()
+*
+* Given the transport object that contains one or more IP addresses
+* in this "hosts" list, rotate it by a random number of shifts to
+* randomize them - this is a kind of load balancing. It's possible
+* that the random number will be 0, which says not to touch. We don't
+* do anything unless
+*/
static void _randomize_hosts(struct transport *tp)
{
+#ifdef SPAMC_HAS_ADDRINFO
+ struct addrinfo *tmp;
+#else
+ struct in_addr tmp;
+#endif
+ int i;
int rnum;
assert(tp != 0);
if (tp->nhosts <= 1)
- return;
+ return;
rnum = rand() % tp->nhosts;
while (rnum-- > 0) {
- struct in_addr tmp = tp->hosts[0];
- int i;
+ tmp = tp->hosts[0];
- for (i = 1; i < tp->nhosts; i++)
- tp->hosts[i - 1] = tp->hosts[i];
+ for (i = 1; i < tp->nhosts; i++)
+ tp->hosts[i - 1] = tp->hosts[i];
- tp->hosts[i - 1] = tmp;
+ tp->hosts[i - 1] = tmp;
}
}
/*
- * transport_setup()
- *
- * Given a "transport" object that says how we're to connect to the
- * spam daemon, perform all the initial setup required to make the
- * connection process a smooth one. The main work is to do the host
- * name lookup and copy over all the IP addresses to make a local copy
- * so they're not kept in the resolver's static state.
- *
- * Here we also manage quasi-load balancing and failover: if we're
- * doing load balancing, we randomly "rotate" the list to put it in
- * a different order, and then if we're not doing failover we limit
- * the hosts to just one. This way *all* connections are done with
- * the intention of failover - makes the code a bit more clear.
- */
+* transport_setup()
+*
+* Given a "transport" object that says how we're to connect to the
+* spam daemon, perform all the initial setup required to make the
+* connection process a smooth one. The main work is to do the host
+* name lookup and copy over all the IP addresses to make a local copy
+* so they're not kept in the resolver's static state.
+*
+* Here we also manage quasi-load balancing and failover: if we're
+* doing load balancing, we randomly "rotate" the list to put it in
+* a different order, and then if we're not doing failover we limit
+* the hosts to just one. This way *all* connections are done with
+* the intention of failover - makes the code a bit more clear.
+*/
int transport_setup(struct transport *tp, int flags)
{
- struct hostent *hp = 0;
+#ifdef SPAMC_HAS_ADDRINFO
+ struct addrinfo hints, *res, *addrp;
+ char port[6];
+ int origerr;
+#else
+ struct hostent *hp;
char **addrp;
+#endif
+ char *hostlist, *hostname;
+ int errbits;
#ifdef _WIN32
/* Start Winsock up */
WSADATA wsaData;
int nCode;
if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
- g_print("WSAStartup() returned error code %d\n", nCode);
- return EX_OSERR;
+ printf("WSAStartup() returned error code %d\n", nCode);
+ return EX_OSERR;
}
#endif
+ assert(tp != NULL);
tp->flags = flags;
- assert(tp != 0);
+#ifdef SPAMC_HAS_ADDRINFO
+ snprintf(port, 6, "%d", tp->port);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = 0;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ( (flags & SPAMC_USE_INET4) && !(flags & SPAMC_USE_INET6)) {
+ hints.ai_family = PF_INET;
+#ifdef PF_INET6
+ } else if ((flags & SPAMC_USE_INET6) && !(flags & SPAMC_USE_INET4)) {
+ hints.ai_family = PF_INET6;
+#endif
+ } else {
+ hints.ai_family = PF_UNSPEC;
+ }
+#endif
switch (tp->type) {
#ifndef _WIN32
case TRANSPORT_UNIX:
- assert(tp->socketpath != 0);
- return EX_OK;
+ assert(tp->socketpath != 0);
+ return EX_OK;
#endif
case TRANSPORT_LOCALHOST:
- tp->hosts[0].s_addr = inet_addr("127.0.0.1");
- tp->nhosts = 1;
- return EX_OK;
+#ifdef SPAMC_HAS_ADDRINFO
+ /* getaddrinfo(NULL) will look up the loopback address.
+ * See also bug 5057, ::1 will be tried before 127.0.0.1
+ * unless overridden (through hints) by a command line option -4
+ */
+ if ((origerr = getaddrinfo(NULL, port, &hints, &res)) != 0) {
+ libspamc_log(flags, LOG_ERR,
+ "getaddrinfo for a loopback address failed: %s",
+ gai_strerror(origerr));
+ return EX_OSERR;
+ }
+ tp->hosts[0] = res;
+#else
+ tp->hosts[0].s_addr = inet_addr("127.0.0.1");
+#endif
+ tp->nhosts = 1;
+ return EX_OK;
case TRANSPORT_TCP:
- if (NULL == (hp = gethostbyname(tp->hostname))) {
- int origherr = h_errno; /* take a copy before syslog() */
-
- libspamc_log(flags, LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
- tp->hostname, origherr);
- switch (origherr) {
- case HOST_NOT_FOUND:
- case NO_ADDRESS:
- case NO_RECOVERY:
- return EX_NOHOST;
- case TRY_AGAIN:
- return EX_TEMPFAIL;
- default:
- return EX_OSERR;
- }
- }
+ if ((hostlist = strdup(tp->hostname)) == NULL)
+ return EX_OSERR;
+
+ /* We want to return the least permanent error, in this bitmask we
+ * record the errors seen with:
+ * 0: no error
+ * 1: EX_TEMPFAIL
+ * 2: EX_NOHOST
+ * EX_OSERR will return immediately.
+ * Bits aren't reset so a check against nhosts is needed to determine
+ * if something went wrong.
+ */
+ errbits = 0;
+ tp->nhosts = 0;
+ /* Start with char offset in front of the string because we'll add
+ * one in the loop
+ */
+ hostname = hostlist - 1;
+ do {
+ char *hostend;
+
+ hostname += 1;
+ hostend = strchr(hostname, ',');
+ if (hostend != NULL) {
+ *hostend = '\0';
+ }
+#ifdef SPAMC_HAS_ADDRINFO
+ if ((origerr = getaddrinfo(hostname, port, &hints, &res))) {
+ libspamc_log(flags, LOG_DEBUG,
+ "getaddrinfo(%s) failed: %s",
+ hostname, gai_strerror(origerr));
+ switch (origerr) {
+ case EAI_AGAIN:
+ errbits |= 1;
+ break;
+ case EAI_FAMILY: /*address family not supported*/
+ case EAI_SOCKTYPE: /*socket type not supported*/
+ case EAI_BADFLAGS: /*ai_flags is invalid*/
+ case EAI_NONAME: /*node or service unknown*/
+ case EAI_SERVICE: /*service not available*/
+/* work around Cygwin IPv6 patch - err codes not defined in Windows aren't in patch */
+#ifdef HAVE_EAI_ADDRFAMILY
+ case EAI_ADDRFAMILY: /*no addresses in requested family*/
+#endif
+#ifdef HAVE_EAI_SYSTEM
+ case EAI_SYSTEM: /*system error, check errno*/
+#endif
+#ifdef HAVE_EAI_NODATA
+ case EAI_NODATA: /*address exists, but no data*/
+#endif
+ case EAI_MEMORY: /*out of memory*/
+ case EAI_FAIL: /*name server returned permanent error*/
+ errbits |= 2;
+ break;
+ default:
+ /* should not happen, all errors are checked above */
+ free(hostlist);
+ return EX_OSERR;
+ }
+ goto nexthost; /* try next host in list */
+ }
+#else
+ if ((hp = gethostbyname(hostname)) == NULL) {
+ int origerr = h_errno; /* take a copy before syslog() */
+ libspamc_log(flags, LOG_DEBUG, "gethostbyname(%s) failed: h_errno=%d",
+ hostname, origerr);
+ switch (origerr) {
+ case TRY_AGAIN:
+ errbits |= 1;
+ break;
+ case HOST_NOT_FOUND:
+ case NO_ADDRESS:
+ case NO_RECOVERY:
+ errbits |= 2;
+ break;
+ default:
+ /* should not happen, all errors are checked above */
+ free(hostlist);
+ return EX_OSERR;
+ }
+ goto nexthost; /* try next host in list */
+ }
+#endif
+
+ /* If we have no hosts at all */
+#ifdef SPAMC_HAS_ADDRINFO
+ if(res == NULL)
+#else
+ if (hp->h_addr_list[0] == NULL
+ || hp->h_length != sizeof tp->hosts[0]
+ || hp->h_addrtype != AF_INET)
+ /* no hosts/bad size/wrong family */
+#endif
+ {
+ errbits |= 1;
+ goto nexthost; /* try next host in list */
+ }
+
+ /* Copy all the IP addresses into our private structure.
+ * This gets them out of the resolver's static area and
+ * means we won't ever walk all over the list with other
+ * calls.
+ */
+#ifdef SPAMC_HAS_ADDRINFO
+ if(tp->nhosts == TRANSPORT_MAX_HOSTS) {
+ libspamc_log(flags, LOG_NOTICE,
+ "hit limit of %d hosts, ignoring remainder",
+ TRANSPORT_MAX_HOSTS);
+ break;
+ }
+
+ /* treat all A or AAAA records of each host as one entry */
+ tp->hosts[tp->nhosts++] = res;
+
+ /* alternatively, treat multiple A or AAAA records
+ of one host as individual entries */
+/* for (addrp = res; addrp != NULL; ) {
+ * tp->hosts[tp->nhosts] = addrp;
+ * addrp = addrp->ai_next; /-* before NULLing ai_next *-/
+ * tp->hosts[tp->nhosts]->ai_next = NULL;
+ * tp->nhosts++;
+ * }
+ */
- /*--------------------------------------------------------
- * If we have no hosts at all, or if they are some other
- * kind of address family besides IPv4, then we really
- * just have no hosts at all.
- */
- if (hp->h_addr_list[0] == 0) {
- /* no hosts in this list */
- return EX_NOHOST;
- }
+#else
+ for (addrp = hp->h_addr_list; *addrp; addrp++) {
+ if (tp->nhosts == TRANSPORT_MAX_HOSTS) {
+ libspamc_log(flags, LOG_NOTICE, "hit limit of %d hosts, ignoring remainder",
+ TRANSPORT_MAX_HOSTS);
+ break;
+ }
+ memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length);
+ tp->nhosts++;
+ }
+#endif
+nexthost:
+ hostname = hostend;
+ } while (hostname != NULL);
+ free(hostlist);
+
+ if (tp->nhosts == 0) {
+ if (errbits & 1) {
+ libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): a temporary error occurred",
+ tp->hostname);
+ return EX_TEMPFAIL;
+ }
+ else {
+ libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): no such host",
+ tp->hostname);
+ return EX_NOHOST;
+ }
+ }
+
+ /* QUASI-LOAD-BALANCING
+ *
+ * If the user wants to do quasi load balancing, "rotate"
+ * the list by a random amount based on the current time.
+ * This may later be truncated to a single item. This is
+ * meaningful only if we have more than one host.
+ */
- if (hp->h_length != sizeof tp->hosts[0]
- || hp->h_addrtype != AF_INET) {
- /* FAIL - bad size/protocol/family? */
- return EX_NOHOST;
- }
+ if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
+ _randomize_hosts(tp);
+ }
+
+ /* If the user wants no fallback, simply truncate the host
+ * list to just one - this pretends that this is the extent
+ * of our connection list - then it's not a special case.
+ */
+ if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
+ /* truncating list */
+ tp->nhosts = 1;
+ }
+
+ return EX_OK;
+ }
+
+ /* oops, unknown transport type */
+ return EX_OSERR;
+}
- /*--------------------------------------------------------
- * Copy all the IP addresses into our private structure.
- * This gets them out of the resolver's static area and
- * means we won't ever walk all over the list with other
- * calls.
- */
- tp->nhosts = 0;
+/*
+* transport_cleanup()
+*
+* Given a "transport" object that says how we're to connect to the
+* spam daemon, delete and free any buffers allocated so that it
+* can be discarded without causing a memory leak.
+*/
+void transport_cleanup(struct transport *tp)
+{
- for (addrp = hp->h_addr_list; *addrp; addrp++) {
- if (tp->nhosts >= TRANSPORT_MAX_HOSTS - 1) {
- libspamc_log(flags, LOG_ERR, "hit limit of %d hosts, ignoring remainder",
- TRANSPORT_MAX_HOSTS - 1);
- break;
- }
+#ifdef SPAMC_HAS_ADDRINFO
+ int i;
- memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
+ for(i=0;i<tp->nhosts;i++) {
+ if (tp->hosts[i] != NULL) {
+ freeaddrinfo(tp->hosts[i]);
+ tp->hosts[i] = NULL;
+ }
+ }
+#endif
- tp->nhosts++;
- }
+}
- /*--------------------------------------------------------
- * QUASI-LOAD-BALANCING
- *
- * If the user wants to do quasi load balancing, "rotate"
- * the list by a random amount based on the current time.
- * This may later be truncated to a single item. This is
- * meaningful only if we have more than one host.
- */
- if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
- _randomize_hosts(tp);
- }
+/*
+* register_libspamc_log_callback()
+*
+* Register a callback handler for libspamc_log to replace the default behaviour.
+*/
- /*--------------------------------------------------------
- * If the user wants no fallback, simply truncate the host
- * list to just one - this pretends that this is the extent
- * of our connection list - then it's not a special case.
- */
- if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
- /* truncating list */
- tp->nhosts = 1;
- }
- }
- return EX_OK;
+void register_libspamc_log_callback(void (*function)(int flags, int level, char *msg, va_list args)) {
+ libspamc_log_callback = function;
+}
+
+/*
+* register_spamc_header_callback()
+*
+* Register a callback handler to generate spamc headers for a given message
+*/
+
+void register_spamc_header_callback(const struct message *m, void (*func)(struct message *m, int flags, char *buf, int len)) {
+ m->priv->spamc_header_callback = func;
+}
+
+/*
+* register_spamd_header_callback()
+*
+* Register a callback handler to generate spamd headers for a given message
+*/
+
+void register_spamd_header_callback(const struct message *m, void (*func)(struct message *m, int flags, const char *buf, int len)) {
+ m->priv->spamd_header_callback = func;
}
/* --------------------------------------------------------------------------- */
@@ -1300,7 +2227,10 @@ libspamc_log (int flags, int level, char *msg, ...)
va_start(ap, msg);
- if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
+ if ((flags & SPAMC_LOG_TO_CALLBACK) != 0 && libspamc_log_callback != NULL) {
+ libspamc_log_callback(flags, level, msg, ap);
+ }
+ else if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
/* create a log-line buffer */
len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
@@ -1310,8 +2240,8 @@ libspamc_log (int flags, int level, char *msg, ...)
len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
- if (write (2, buf, len) != len)
- goto out;
+ (void) write (2, buf, len);
+
} else {
vsnprintf(buf, LOG_BUFSIZ, msg, ap);
buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
@@ -1319,22 +2249,22 @@ libspamc_log (int flags, int level, char *msg, ...)
syslog (level, "%s", buf);
#else
(void) level; /* not used. suppress compiler warning */
- g_printerr ("%s\n", buf);
+ f_printerr ("%s\n", buf);
#endif
}
-out:
+
va_end(ap);
}
/* --------------------------------------------------------------------------- */
/*
- * Unit tests. Must be built externally, e.g.:
- *
- * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
- * ./libspamctest
- *
- */
+* Unit tests. Must be built externally, e.g.:
+*
+* gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
+* ./libspamctest
+*
+*/
#ifdef LIBSPAMC_UNIT_TESTS
static void _test_locale_safe_string_to_float_val(float input)
@@ -1346,42 +2276,42 @@ static void _test_locale_safe_string_to_float_val(float input)
sprintf(inputstr, "%f", input);
output = _locale_safe_string_to_float(inputstr, 99);
if (input == output) {
- return;
+ return;
}
/* could be a rounding error. print as string and compare those */
sprintf(cmpbuf1, "%f", input);
sprintf(cmpbuf2, "%f", output);
if (!strcmp(cmpbuf1, cmpbuf2)) {
- return;
+ return;
}
- g_print("FAIL: input=%f != output=%f\n", input, output);
+ printf("FAIL: input=%f != output=%f\n", input, output);
}
static void unit_test_locale_safe_string_to_float(void)
{
float statictestset[] = { /* will try both +ve and -ve */
- 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
- 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
- 0.0 /* end of set constant */
+ 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
+ 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
+ 0.0 /* end of set constant */
};
float num;
int i;
- g_print("starting unit_test_locale_safe_string_to_float\n");
+ printf("starting unit_test_locale_safe_string_to_float\n");
/* tests of precision */
for (i = 0; statictestset[i] != 0.0; i++) {
- _test_locale_safe_string_to_float_val(statictestset[i]);
- _test_locale_safe_string_to_float_val(-statictestset[i]);
- _test_locale_safe_string_to_float_val(1 - statictestset[i]);
- _test_locale_safe_string_to_float_val(1 + statictestset[i]);
+ _test_locale_safe_string_to_float_val(statictestset[i]);
+ _test_locale_safe_string_to_float_val(-statictestset[i]);
+ _test_locale_safe_string_to_float_val(1 - statictestset[i]);
+ _test_locale_safe_string_to_float_val(1 + statictestset[i]);
}
/* now exhaustive, in steps of 0.01 */
for (num = -1000.0; num < 1000.0; num += 0.01) {
- _test_locale_safe_string_to_float_val(num);
+ _test_locale_safe_string_to_float_val(num);
}
- g_print("finished unit_test_locale_safe_string_to_float\n");
+ printf("finished unit_test_locale_safe_string_to_float\n");
}
void do_libspamc_unit_tests(void)
diff --git a/src/plugins/spamassassin/libspamc.h b/src/plugins/spamassassin/libspamc.h
index dd44ddc..7a6fc12 100644
--- a/src/plugins/spamassassin/libspamc.h
+++ b/src/plugins/spamassassin/libspamc.h
@@ -1,9 +1,10 @@
/* <@LICENSE>
- * Copyright 2004 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
@@ -26,17 +27,26 @@
4115 named type definition in parentheses
4127 conditional expression is constant
4514 unreferenced inline function removed
+ 4996 deprecated "unsafe" functions (bug 4855)
*/
#pragma warning( disable : 4115 4127 4514 )
+#if (_MSC_VER >= 1400) /* VC8+ */
+#pragma warning( disable : 4996 )
+#endif
+
#endif
#include <winsock.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
+/* some platforms (Cygwin) don't implement getaddrinfo */
+#ifdef EAI_AGAIN
+#define SPAMC_HAS_ADDRINFO 1
+#endif
#endif
-#ifdef _WIN32
+#if (defined(_WIN32) || !defined(_SYSEXITS_H))
/* FIXME: This stuff has to go somewhere else */
#define EX_OK 0
@@ -80,22 +90,65 @@
#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)
+#define SPAMC_USE_INET6 (1<<31)
+#define SPAMC_USE_INET4 (1<<30)
+
+#define SPAMC_CHECK_ONLY (1<<29)
+#define SPAMC_SAFE_FALLBACK (1<<28)
+#define SPAMC_USE_SSL (1<<27)
/* Jan 30, 2003 ym: added reporting options */
-#define SPAMC_REPORT (1<<26)
-#define SPAMC_REPORT_IFSPAM (1<<25)
+#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)
+#define SPAMC_SYMBOLS (1<<24)
/* 2003/04/16 SJF: randomize hostname order (quasi load balancing) */
#define SPAMC_RANDOMIZE_HOSTS (1<<23)
/* log to stderr */
-#define SPAMC_LOG_TO_STDERR (1<<22)
+#define SPAMC_LOG_TO_STDERR (1<<22)
+
+/* Nov 24, 2004 NP: added learning support */
+#define SPAMC_LEARN (1<<21)
+
+/* May 5, 2005 NP: added list reporting support */
+#define SPAMC_REPORT_MSG (1<<20)
+
+/* Oct 21, 2005 sidney: added ping test */
+#define SPAMC_PING (1<<19)
+
+/* Jan 1, 2007 sidney: added SSL protocol versions */
+/* no flags means use default of SSL_v23 */
+/* Set both flags to specify TSL_v1 */
+#define SPAMC_TLSV1 (1<<18)
+#define SPAMC_SSLV3 (1<<17)
+
+/* Nov 30, 2006 jm: add -z, zlib support */
+#define SPAMC_USE_ZLIB (1<<16)
+
+/* Jan 16, 2007 jm: get markup headers from spamd */
+#define SPAMC_HEADERS (1<<15)
+
+/* December 5, 2007 duncf: send log messages to callback */
+#define SPAMC_LOG_TO_CALLBACK (1<<14)
+
+/* December 6, 2011 Sebastian Wiesinger <sebastian at karotte.org>:
+ * Turn EX_UNAVAILABLE into EX_TEMPFAIL - bug 6717
+ * */
+#define SPAMC_UNAVAIL_TEMPFAIL (1<<13)
+
+#define SPAMC_MESSAGE_CLASS_SPAM 1
+#define SPAMC_MESSAGE_CLASS_HAM 2
+
+#define SPAMC_SET_LOCAL 1
+#define SPAMC_SET_REMOTE 2
+
+#define SPAMC_REMOVE_LOCAL 4
+#define SPAMC_REMOVE_REMOTE 8
+
+#define SPAMC_MAX_MESSAGE_LEN (256 * 1024 * 1024) /* see bug 4928 */
/* Aug 14, 2002 bj: A struct for storing a message-in-progress */
typedef enum
@@ -114,15 +167,17 @@ struct message
/* Set before passing the struct on! */
unsigned int max_len; /* messages larger than this will return EX_TOOBIG */
int timeout; /* timeout for read() system calls */
+ int connect_timeout; /* Sep 8, 2008 mrgus: timeout for opening sockets */
/* Filled in by message_read */
message_type_t type;
char *raw;
- unsigned int raw_len; /* Raw message buffer */
+ int raw_len; /* Raw message buffer */
+ /* note: do not make "raw_len" in particular unsigned! see bug 4593 */
char *pre;
int pre_len; /* Pre-message data (e.g. SMTP commands) */
char *msg;
- unsigned int msg_len; /* The message */
+ int msg_len; /* The message */
char *post;
int post_len; /* Post-message data (e.g. SMTP commands) */
int content_length;
@@ -131,6 +186,7 @@ struct message
int is_spam; /* EX_ISSPAM if the message is spam, EX_NOTSPAM
if not */
float score, threshold; /* score and threshold */
+ char *outbuf; /* Buffer for output from spamd */
char *out;
int out_len; /* Output from spamd. Either the filtered
message, or the check-only response. Or else,
@@ -178,11 +234,27 @@ struct transport
unsigned short port; /* for TCP sockets */
+#ifdef SPAMC_HAS_ADDRINFO
+ struct addrinfo *hosts[TRANSPORT_MAX_HOSTS];
+#else
struct in_addr hosts[TRANSPORT_MAX_HOSTS];
+#endif
int nhosts;
int flags;
+
+ /* added in SpamAssassin 3.2.0 */
+ int connect_retries;
+ int retry_sleep;
+
+ /* Added for filterloop */
+ int filter_retries;
+ int filter_retry_sleep;
};
+/* Initialise and setup transport-specific context for the connection
+ * to spamd. Note that this may leak a small amount of string data for
+ * the remote hostname (bug 5531) if called repeatedly; use
+ * transport_cleanup() to clean this up. */
extern void transport_init(struct transport *tp);
extern int transport_setup(struct transport *tp, int flags);
@@ -207,11 +279,20 @@ long message_write(int out_fd, struct message *m);
int message_filter(struct transport *tp, const char *username,
int flags, struct message *m);
+/* Process the message through the spamd tell command, making as many
+ * connection attempts as are implied by the transport structure. To make
+ * this do failover, more than one host is defined, but if there is only
+ * one there, no failover is done.
+ */
+int message_tell(struct transport *tp, const char *username, int flags,
+ struct message *m, int msg_class,
+ unsigned int tellflags, unsigned int *didtellflags);
+
/* 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);
+void message_dump(int in_fd, int out_fd, struct message *m, int flags);
/* Do a message_read->message_filter->message_write sequence, handling errors
* appropriately with dump_message or appropriate CHECK_ONLY output. Returns
@@ -228,6 +309,19 @@ int process_message(struct transport *tp, char *username,
int max_size, int in_fd, int out_fd,
const int check_only, const int safe_fallback);
+void register_spamc_header_callback(const struct message *m, void (*func)(struct message *m, int flags, char *buf, int len));
+void register_spamd_header_callback(const struct message *m, void (*func)(struct message *m, int flags, const char *buf, int len));
+
+void register_libspamc_log_callback(void (*function)(int flags, int level, char *msg, va_list args));
+
void libspamc_log(int flags, int level, char *msg, ...);
+/* Cleanup the resources allocated for storing details of the transport.
+ * Added in SpamAssassin 3.3.0. */
+void transport_cleanup(struct transport *tp);
+
+/* define a preprocessor symbol so that calling code can tell if the
+ * transport_cleanup() API function is available. */
+#define SPAMC_HAS_TRANSPORT_CLEANUP
+
#endif
diff --git a/src/plugins/spamassassin/utils.c b/src/plugins/spamassassin/utils.c
index f42c961..0e130fb 100644
--- a/src/plugins/spamassassin/utils.c
+++ b/src/plugins/spamassassin/utils.c
@@ -1,9 +1,10 @@
/* <@LICENSE>
- * Copyright 2004 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
@@ -27,8 +28,9 @@
4115 named type definition in parentheses
4127 conditional expression is constant
4514 unreferenced inline function removed
+ 4996 deprecated "unsafe" functions
*/
-#pragma warning( disable : 4115 4127 4514 )
+#pragma warning( disable : 4115 4127 4514 4996 )
#endif
#include <io.h>
@@ -67,6 +69,33 @@ static void catch_alrm(int x)
}
#endif
+int timeout_connect (int sockfd, const struct sockaddr *serv_addr, size_t addrlen)
+{
+ int ret;
+
+#ifndef _WIN32
+ sigfunc* sig;
+
+ sig = sig_catch(SIGALRM, catch_alrm);
+ if (libspamc_connect_timeout > 0) {
+ alarm(libspamc_connect_timeout);
+ }
+#endif
+
+ ret = connect(sockfd, serv_addr, addrlen);
+
+#ifndef _WIN32
+ if (libspamc_connect_timeout > 0) {
+ alarm(0);
+ }
+
+ /* restore old signal handler */
+ sig_catch(SIGALRM, sig);
+#endif
+
+ return ret;
+}
+
int fd_timeout_read(int fd, char fdflag, void *buf, size_t nbytes)
{
int nred;
diff --git a/src/plugins/spamassassin/utils.h b/src/plugins/spamassassin/utils.h
index 3a28754..fc34443 100644
--- a/src/plugins/spamassassin/utils.h
+++ b/src/plugins/spamassassin/utils.h
@@ -1,9 +1,10 @@
/* <@LICENSE>
- * Copyright 2004 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
@@ -20,7 +21,10 @@
#define UNUSED_VARIABLE(v) ((void)(v))
+#include <stddef.h>
+
extern int libspamc_timeout; /* default timeout in seconds */
+extern int libspamc_connect_timeout; /* Sep 8, 2008 mrgus: default connect timeout in seconds */
#ifdef SPAMC_SSL
#include <openssl/crypto.h>
@@ -35,49 +39,77 @@ typedef int SSL_METHOD;
#ifdef _WIN32
#include <winsock.h>
-//
-// BSD-compatible socket error codes for Win32
-//
-
+/*
+ * BSD-compatible socket error codes for Win32
+ */
+#undef EWOULDBLOCK /* override definition in errno.h */
#define EWOULDBLOCK WSAEWOULDBLOCK
+#undef EINPROGRESS /* override definition in errno.h */
#define EINPROGRESS WSAEINPROGRESS
+#undef EALREADY /* override definition in errno.h */
#define EALREADY WSAEALREADY
+#undef ENOTSOCK /* override definition in errno.h */
#define ENOTSOCK WSAENOTSOCK
+#undef EDESTADDRREQ /* override definition in errno.h */
#define EDESTADDRREQ WSAEDESTADDRREQ
+#undef EMSGSIZE /* override definition in errno.h */
#define EMSGSIZE WSAEMSGSIZE
+#undef EPROTOTYPE /* override definition in errno.h */
#define EPROTOTYPE WSAEPROTOTYPE
+#undef ENOPROTOOPT /* override definition in errno.h */
#define ENOPROTOOPT WSAENOPROTOOPT
+#undef EPROTONOSUPPORT /* override definition in errno.h */
#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#undef ESOCKTNOSUPPORT /* override definition in errno.h */
#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#undef EOPNOTSUPP /* override definition in errno.h */
#define EOPNOTSUPP WSAEOPNOTSUPP
+#undef EPFNOSUPPORT /* override definition in errno.h */
#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#undef EAFNOSUPPORT /* override definition in errno.h */
#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#undef EADDRINUSE /* override definition in errno.h */
#define EADDRINUSE WSAEADDRINUSE
+#undef EADDRNOTAVAIL /* override definition in errno.h */
#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#undef ENETDOWN /* override definition in errno.h */
#define ENETDOWN WSAENETDOWN
+#undef ENETUNREACH /* override definition in errno.h */
#define ENETUNREACH WSAENETUNREACH
+#undef ENETRESET /* override definition in errno.h */
#define ENETRESET WSAENETRESET
+#undef ECONNABORTED /* override definition in errno.h */
#define ECONNABORTED WSAECONNABORTED
+#undef ECONNRESET /* override definition in errno.h */
#define ECONNRESET WSAECONNRESET
+#undef ENOBUFS /* override definition in errno.h */
#define ENOBUFS WSAENOBUFS
+#undef EISCONN /* override definition in errno.h */
#define EISCONN WSAEISCONN
+#undef ENOTCONN /* override definition in errno.h */
#define ENOTCONN WSAENOTCONN
+#undef ESHUTDOWN /* override definition in errno.h */
#define ESHUTDOWN WSAESHUTDOWN
+#undef ETOOMANYREFS /* override definition in errno.h */
#define ETOOMANYREFS WSAETOOMANYREFS
+#undef ETIMEDOUT /* override definition in errno.h */
#define ETIMEDOUT WSAETIMEDOUT
+#undef ECONNREFUSED /* override definition in errno.h */
#define ECONNREFUSED WSAECONNREFUSED
+#undef ELOOP /* override definition in errno.h */
#define ELOOP WSAELOOP
-// #define ENAMETOOLONG WSAENAMETOOLONG
+/* #define ENAMETOOLONG WSAENAMETOOLONG */
#define EHOSTDOWN WSAEHOSTDOWN
+#undef EHOSTUNREACH /* override definition in errno.h */
#define EHOSTUNREACH WSAEHOSTUNREACH
-// #define ENOTEMPTY WSAENOTEMPTY
+/* #define ENOTEMPTY WSAENOTEMPTY */
#define EPROCLIM WSAEPROCLIM
#define EUSERS WSAEUSERS
#define EDQUOT WSAEDQUOT
#define ESTALE WSAESTALE
#define EREMOTE WSAEREMOTE
-// NOTE: these are not errno constants in UNIX!
+/* NOTE: these are not errno constants in UNIX! */
#define HOST_NOT_FOUND WSAHOST_NOT_FOUND
#define TRY_AGAIN WSATRY_AGAIN
#define NO_RECOVERY WSANO_RECOVERY
@@ -88,6 +120,9 @@ typedef int SSL_METHOD;
int fd_timeout_read(int fd, char fdflag, void *, size_t);
int ssl_timeout_read(SSL * ssl, void *, int);
+/* uses size_t instead of socket_t because socket_t not defined on some platforms */
+int timeout_connect (int sockfd, const struct sockaddr *serv_addr, size_t addrlen);
+
/* these are fd-only, no SSL support */
int full_read(int fd, char fdflag, void *buf, int min, int len);
int full_read_ssl(SSL * ssl, unsigned char *buf, int min, int len);
-----------------------------------------------------------------------
hooks/post-receive
--
Claws Mail
More information about the Commits
mailing list