From: epg@pretzelnet.org <> Date: Sun, 8 Mar 2009 09:20:24 +0000 (-0800) Subject: Update libspam.[ch] utils.[ch] from spamassassin 3.1.0 to 3.2.5 . X-Git-Url: https://diplodocus.org/git/mdeliver/commitdiff_plain/57caa47724e6089d724793ecb494fa05be7b1089?ds=sidebyside;hp=bfbc67c65ff0f5e4c5be9d1f9a460a805ef1eef0 Update libspam.[ch] utils.[ch] from spamassassin 3.1.0 to 3.2.5 . --- diff --git a/libspamc.c b/libspamc.c index b4d1936..1294e00 100644 --- a/libspamc.c +++ b/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,6 +16,13 @@ * */ +/* + 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 "libspamc.h" #include "utils.h" @@ -56,10 +64,9 @@ #ifdef HAVE_SYS_TIME_H #include #endif - -/* FIXME: Make this configurable */ -#define MAX_CONNECT_RETRIES 3 -#define CONNECT_RETRY_SLEEP 1 +#ifdef HAVE_ZLIB_H +#include +#endif /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */ /* KAM 12-4-01 */ @@ -78,6 +85,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 +108,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 +*/ -static const int ESC_PASSTHROUGHRAW = EX__MAX + 666; +/* 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; 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 +139,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.4"; /* "private" part of struct message. * we use this instead of the struct message directly, so that we @@ -119,6 +148,7 @@ 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 */ }; int libspamc_timeout = 0; @@ -168,11 +198,16 @@ static int _translate_connect_errno(int err) * * 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 @@ -184,6 +219,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"; } @@ -191,30 +242,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 @@ -243,21 +297,20 @@ static int _opensocket(int flags, int type, int *psock) if (type == PF_INET && setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, (char *)&socktout, sizeof(socktout)) != 0) { - int origerrno; - origerrno = WSAGetLastError(); - switch (origerrno) + origerr = spamc_get_errno(); + switch (origerr) { case EBADF: case ENOTSOCK: case ENOPROTOOPT: case EFAULT: - libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerrno); + libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerr); closesocket(*psock); return EX_SOFTWARE; default: - break; /* ignored */ + break; /* ignored */ } } #endif @@ -272,12 +325,7 @@ static int _opensocket(int flags, int type, int *psock) if (type == PF_INET && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) { - int origerrno; -#ifndef _WIN32 - origerr = errno; -#else - origerrno = WSAGetLastError(); -#endif + origerr = spamc_get_errno(); switch (origerr) { case EBADF: case ENOTSOCK: @@ -315,16 +363,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 */ @@ -334,17 +394,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; @@ -377,78 +437,163 @@ 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; +#else + int res = PF_INET; +#endif + + char host[SPAMC_MAXHOST-1]; /* hostname, for logging */ + char port[SPAMC_MAXSERV-1]; /* port, 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])); - } -#endif + /* 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; + } + + for (numloops = 0; numloops < connect_retries; numloops++) { + const int hostix = numloops % tp->nhosts; + int status, mysock; + + /*-------------------------------------------------------- + * 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; + } - for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) { - struct sockaddr_in addrbuf; - const int hostix = numloops % tp->nhosts; - int status, mysock; - const char *ipaddr; + if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK) { + res = res->ai_next; + continue; + } - /*-------------------------------------------------------- - * 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; + getnameinfo(res->ai_addr, res->ai_addrlen, + host, sizeof(host), + port, sizeof(port), + NI_NUMERICHOST|NI_NUMERICSERV); - 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); + } - 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 + + /* 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)); + } + #endif - status = - connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf)); + if (status != 0) { + origerr = spamc_get_errno(); + closesocket(mysock); - 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)); + libspamc_log(tp->flags, LOG_ERR, + "connect to spamd on %s failed, retrying (#%d of %d): %s", + host, numloops+1, connect_retries, strerror(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); + libspamc_log(tp->flags, LOG_ERR, + "connect to spamd on %s failed, retrying (#%d of %d): %d", + host, numloops+1, connect_retries, 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 + sleep(retry_sleep); + } /* for(numloops...) */ + +#ifdef SPAMC_HAS_ADDRINFO + for(numloops=0;numloopsnhosts;numloops++) { + freeaddrinfo(tp->hosts[numloops]); } +#endif - 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); } @@ -514,8 +659,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) @@ -530,44 +676,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; @@ -577,6 +731,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; @@ -596,6 +753,12 @@ int message_read(int fd, int flags, struct message *m) return EX_OSERR; } m->priv->flags = flags; + m->priv->alloced_size = 0; + + if (flags & SPAMC_PING) { + _clear_message(m); + return EX_OK; + } switch (flags & SPAMC_MODE_MASK) { case SPAMC_RAW_MODE: @@ -620,7 +783,7 @@ long message_write(int fd, struct message *m) assert(m != NULL); - if (m->priv->flags & SPAMC_CHECK_ONLY) { + 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); @@ -849,7 +1012,7 @@ _handle_spamd_header(struct message *m, int flags, char *buf, int len, } return EX_OK; } - else if (sscanf(buf, "DidSet: %s", didset_ret) == 1) { + else if (sscanf(buf, "DidSet: %14s", didset_ret) == 1) { if (strstr(didset_ret, "local")) { *didtellflags |= SPAMC_SET_LOCAL; } @@ -857,7 +1020,7 @@ _handle_spamd_header(struct message *m, int flags, char *buf, int len, *didtellflags |= SPAMC_SET_REMOTE; } } - else if (sscanf(buf, "DidRemove: %s", didremove_ret) == 1) { + else if (sscanf(buf, "DidRemove: %14s", didremove_ret) == 1) { if (strstr(didremove_ret, "local")) { *didtellflags |= SPAMC_REMOVE_LOCAL; } @@ -869,6 +1032,112 @@ _handle_spamd_header(struct message *m, int flags, char *buf, int 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(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; + } + + 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) { @@ -885,14 +1154,31 @@ int message_filter(struct transport *tp, const char *username, 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; 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_SSLV2) && (flags & SPAMC_SSLV3)) { + meth = TLSv1_client_method(); /* both flag bits on means use TLSv1 */ + } else if (flags & SPAMC_SSLV2) { + meth = SSLv2_client_method(); + } else if (flags & SPAMC_SSLV3) { + meth = SSLv3_client_method(); + } else { + meth = SSLv23_client_method(); /* no flag bits, default SSLv23 */ + } SSL_load_error_strings(); ctx = SSL_CTX_new(meth); #else @@ -905,7 +1191,8 @@ int message_filter(struct transport *tp, const char *username, } m->is_spam = EX_TOOBIG; - if ((m->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) { + m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1; + if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) { failureval = EX_OSERR; goto failure; } @@ -921,6 +1208,10 @@ int message_filter(struct transport *tp, const char *username, 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 "); @@ -934,21 +1225,37 @@ int message_filter(struct transport *tp, const char *username, strcat(buf, "\r\n"); len = strlen(buf); - if (username != NULL) { + 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) + { + 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); - return EX_OSERR; + _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 > 9999999) || ((len + 27) >= (bufsiz - 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); - return EX_OSERR; + return EX_DATAERR; + } + len += snprintf(buf + len, 8192-len, "Content-length: %d\r\n\r\n", (int) towrite_len); } - len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len); libspamc_timeout = m->timeout; @@ -974,12 +1281,12 @@ int message_filter(struct transport *tp, const char *username, if (flags & SPAMC_USE_SSL) { #ifdef SPAMC_SSL SSL_write(ssl, buf, len); - SSL_write(ssl, m->msg, m->msg_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); + full_write(sock, 0, towrite_buf, towrite_len); shutdown(sock, SHUT_WR); } @@ -1005,6 +1312,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; @@ -1054,20 +1369,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 (len + m->out_len > (m->priv->alloced_size-1)) { failureval = EX_TOOBIG; goto failure; } @@ -1087,6 +1399,12 @@ 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: @@ -1105,7 +1423,6 @@ 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) { @@ -1116,7 +1433,13 @@ int message_process(struct transport *trans, char *username, int max_size, 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; @@ -1181,7 +1504,8 @@ int message_tell(struct transport *tp, const char *username, int flags, } m->is_spam = EX_TOOBIG; - if ((m->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) { + m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1; + if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) { failureval = EX_OSERR; goto failure; } @@ -1256,11 +1580,11 @@ int message_tell(struct transport *tp, const char *username, int flags, strcat(buf + len, "\r\n"); len += strlen(buf + len); } - if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) { + if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) { _use_msg_for_out(m); - return EX_OSERR; + return EX_DATAERR; } - len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len); + len += sprintf(buf + len, "Content-length: %d\r\n\r\n", (int) m->msg_len); libspamc_timeout = m->timeout; @@ -1410,6 +1734,7 @@ void transport_init(struct transport *tp) tp->type = TRANSPORT_LOCALHOST; tp->port = 783; tp->flags = 0; + tp->retry_sleep = -1; } /* @@ -1424,6 +1749,12 @@ void transport_init(struct transport *tp) 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); @@ -1434,9 +1765,9 @@ static void _randomize_hosts(struct transport *tp) rnum = rand() % tp->nhosts; while (rnum-- > 0) { - struct in_addr tmp = tp->hosts[0]; - int i; + tmp = tp->hosts[0]; + /* TODO: free using freeaddrinfo() */ for (i = 1; i < tp->nhosts; i++) tp->hosts[i - 1] = tp->hosts[i]; @@ -1461,13 +1792,19 @@ static void _randomize_hosts(struct transport *tp) */ int transport_setup(struct transport *tp, int flags) { +#ifdef SPAMC_HAS_ADDRINFO + struct addrinfo hints, *res, *addrp; + char port[6]; +#else struct hostent *hp; + char **addrp; +#endif char *hostlist, *hostname; int errbits; - char **addrp; + int origerr; #ifdef _WIN32 - // Start Winsock up + /* Start Winsock up */ WSADATA wsaData; int nCode; if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) { @@ -1480,6 +1817,15 @@ int transport_setup(struct transport *tp, int flags) assert(tp != NULL); tp->flags = flags; +#ifdef SPAMC_HAS_ADDRINFO + snprintf(port, 6, "%d", tp->port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = 0; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; +#endif + switch (tp->type) { #ifndef _WIN32 case TRANSPORT_UNIX: @@ -1487,7 +1833,21 @@ int transport_setup(struct transport *tp, int flags) return EX_OK; #endif case TRANSPORT_LOCALHOST: +#ifdef SPAMC_HAS_ADDRINFO + /* getaddrinfo(NULL) will look up the loopback address. + * bug 5057: unfortunately, it's the IPv6 loopback address on + * linux! Be explicit, and force IPv4 using "127.0.0.1". + */ + if ((origerr = getaddrinfo("127.0.0.1", port, &hints, &res)) != 0) { + libspamc_log(flags, LOG_ERR, + "getaddrinfo(127.0.0.1) 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; @@ -1518,7 +1878,42 @@ int transport_setup(struct transport *tp, int flags) 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", @@ -1539,15 +1934,18 @@ int transport_setup(struct transport *tp, int flags) } goto nexthost; /* try next host in list */ } +#endif - /* 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. TODO: IPv6 - */ + /* 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) { + || hp->h_addrtype != AF_INET) /* no hosts/bad size/wrong family */ +#endif + { errbits |= 1; goto nexthost; /* try next host in list */ } @@ -1557,6 +1955,20 @@ int transport_setup(struct transport *tp, int flags) * 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; + } + 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++; + } +#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", @@ -1566,7 +1978,7 @@ int transport_setup(struct transport *tp, int flags) memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length); tp->nhosts++; } - +#endif nexthost: hostname = hostend; } while (hostname != NULL); @@ -1627,11 +2039,11 @@ libspamc_log (int flags, int level, char *msg, ...) va_start(ap, msg); if ((flags & SPAMC_LOG_TO_STDERR) != 0) { - // create a log-line buffer + /* create a log-line buffer */ len = snprintf(buf, LOG_BUFSIZ, "spamc: "); len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap); - // avoid buffer overflow + /* avoid buffer overflow */ if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); } len += snprintf(buf+len, LOG_BUFSIZ-len, "\n"); diff --git a/libspamc.h b/libspamc.h index be3d0fd..e26cf6f 100644 --- a/libspamc.h +++ b/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 #else #include #include #include +/* 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 @@ -103,6 +113,20 @@ /* 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_SSLV2 (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) #define SPAMC_MESSAGE_CLASS_SPAM 1 #define SPAMC_MESSAGE_CLASS_HAM 2 @@ -113,6 +137,8 @@ #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 { @@ -134,11 +160,12 @@ struct message /* 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; @@ -195,11 +222,23 @@ 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; }; +/* 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; SpamAssassin + * 3.3.0 will include a new API to free this leakage. */ extern void transport_init(struct transport *tp); extern int transport_setup(struct transport *tp, int flags); diff --git a/utils.c b/utils.c index f42c961..d69751f 100644 --- a/utils.c +++ b/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 * @@ -67,6 +68,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_timeout > 0) { + alarm(libspamc_timeout); + } +#endif + + ret = connect(sockfd, serv_addr, addrlen); + +#ifndef _WIN32 + if (libspamc_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/utils.h b/utils.h index 3a28754..7262d9a 100644 --- a/utils.h +++ b/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,6 +21,8 @@ #define UNUSED_VARIABLE(v) ((void)(v)) +#include + extern int libspamc_timeout; /* default timeout in seconds */ #ifdef SPAMC_SSL @@ -35,9 +38,9 @@ typedef int SSL_METHOD; #ifdef _WIN32 #include -// -// BSD-compatible socket error codes for Win32 -// +/* + * BSD-compatible socket error codes for Win32 + */ #define EWOULDBLOCK WSAEWOULDBLOCK #define EINPROGRESS WSAEINPROGRESS @@ -67,17 +70,17 @@ typedef int SSL_METHOD; #define ETIMEDOUT WSAETIMEDOUT #define ECONNREFUSED WSAECONNREFUSED #define ELOOP WSAELOOP -// #define ENAMETOOLONG WSAENAMETOOLONG +/* #define ENAMETOOLONG WSAENAMETOOLONG */ #define EHOSTDOWN WSAEHOSTDOWN #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 +91,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);