talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit 43ca86e8186a530dacd3109db30e891a9f2bbccb
parent aeee0bb656c377ef4f1dce8397e87d5fa148b601
Author: wwp <wwp@free.fr>
Date:   Wed, 11 Apr 2018 10:54:47 +0200

Updated Apache SpamAssassin code (libspam.[ch], utils.[ch]) from 3.0.x
to 3.4.1.

Diffstat:
Msrc/plugins/spamassassin/libspamc.c | 1650+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/plugins/spamassassin/libspamc.h | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/plugins/spamassassin/utils.c | 41+++++++++++++++++++++++++++++++++++------
Msrc/plugins/spamassassin/utils.h | 59+++++++++++++++++++++++++++++++++++++++++++++++------------
4 files changed, 1482 insertions(+), 394 deletions(-)

diff --git 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 @@ -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@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 @@ -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 @@ -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);