#include "config.h"
#include "libspamc.h"
#include "utils.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define strcasecmp stricmp
#define sleep Sleep
#include <io.h>
#else
#include <syslog.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#define closesocket(x) close(x)
#endif
#ifdef HAVE_SYSEXITS_H
#include <sysexits.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#define MAX_CONNECT_RETRIES 3
#define CONNECT_RETRY_SLEEP 1
#ifndef SHUT_RD
# define SHUT_RD 0
#endif
#ifndef SHUT_WR
# define SHUT_WR 1
#endif
#ifndef SHUT_RDWR
# define SHUT_RDWR 2
#endif
#ifndef HAVE_H_ERRNO
#define h_errno errno
#endif
#ifndef HAVE_OPTARG
extern char *optarg;
#endif
#ifndef HAVE_INADDR_NONE
#define INADDR_NONE ((in_addr_t) 0xffffffff)
#endif
#undef USE_TCP_NODELAY
#ifndef HAVE_EX__MAX
#define EX__MAX 200
#endif
#undef DO_CONNECT_DEBUG_SYSLOGS
static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
static const int EXPANSION_ALLOWANCE = 16384;
static const char *PROTOCOL_VERSION = "SPAMC/1.3";
struct libspamc_private_message
{
int flags;
};
int libspamc_timeout = 0;
static int _translate_connect_errno(int err)
{
switch (err) {
case EBADF:
case EFAULT:
case ENOTSOCK:
case EISCONN:
case EADDRINUSE:
case EINPROGRESS:
case EALREADY:
case EAFNOSUPPORT:
return EX_SOFTWARE;
case ECONNREFUSED:
case ETIMEDOUT:
case ENETUNREACH:
return EX_UNAVAILABLE;
case EACCES:
return EX_NOPERM;
default:
return EX_SOFTWARE;
}
}
static int _opensocket(int flags, int type, int *psock)
{
const char *typename;
int proto = 0;
assert(psock != 0);
if (type == PF_UNIX) {
typename = "PF_UNIX";
}
else {
typename = "PF_INET";
proto = IPPROTO_TCP;
}
#ifdef DO_CONNECT_DEBUG_SYSLOGS
libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
#endif
if ((*psock = socket(type, SOCK_STREAM, proto))
#ifndef _WIN32
< 0
#else
== INVALID_SOCKET
#endif
) {
int origerr;
#ifndef _WIN32
origerr = errno;
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
switch (origerr) {
case EPROTONOSUPPORT:
case EINVAL:
return EX_SOFTWARE;
case EACCES:
return EX_NOPERM;
case ENFILE:
case EMFILE:
case ENOBUFS:
case ENOMEM:
return EX_OSERR;
default:
return EX_SOFTWARE;
}
}
#ifdef USE_TCP_NODELAY
{
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();
#endif
switch (origerr) {
case EBADF:
case ENOTSOCK:
case ENOPROTOOPT:
case EFAULT:
libspamc_log(flags, LOG_ERR,
#ifndef _WIN32
"setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
#else
"setsockopt(TCP_NODELAY) failed: %d", origerr);
#endif
closesocket(*psock);
return EX_SOFTWARE;
default:
break;
}
}
}
#endif
return EX_OK;
}
static int _try_to_connect_unix(struct transport *tp, int *sockptr)
{
#ifndef _WIN32
int mysock, status, origerr;
struct sockaddr_un addrbuf;
int ret;
assert(tp != 0);
assert(sockptr != 0);
assert(tp->socketpath != 0);
if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
return ret;
memset(&addrbuf, 0, sizeof addrbuf);
addrbuf.sun_family = AF_UNIX;
strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
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",
addrbuf.sun_path);
#endif
status = 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");
#endif
*sockptr = mysock;
return EX_OK;
}
libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
addrbuf.sun_path, strerror(origerr));
closesocket(mysock);
return _translate_connect_errno(origerr);
#else
(void) tp;
(void) sockptr;
return EX_OSERR;
#endif
}
static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
{
int numloops;
int origerr = 0;
int ret;
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
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, 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);
#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);
#endif
status =
connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
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));
#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);
#endif
closesocket(mysock);
sleep(CONNECT_RETRY_SLEEP);
}
else {
#ifdef DO_CONNECT_DEBUG_SYSLOGS
libspamc_log(tp->flags, DEBUG_LEVEL,
"dbg: connect(AF_INET) to spamd at %s done", ipaddr);
#endif
*sockptr = mysock;
return EX_OK;
}
}
libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
MAX_CONNECT_RETRIES);
return _translate_connect_errno(origerr);
}
static void _clear_message(struct message *m)
{
m->type = MESSAGE_NONE;
m->raw = NULL;
m->raw_len = 0;
m->pre = NULL;
m->pre_len = 0;
m->msg = NULL;
m->msg_len = 0;
m->post = NULL;
m->post_len = 0;
m->is_spam = EX_TOOBIG;
m->score = 0.0;
m->threshold = 0.0;
m->out = NULL;
m->out_len = 0;
m->content_length = -1;
}
static int _message_read_raw(int fd, struct message *m)
{
_clear_message(m);
if ((m->raw = malloc(m->max_len + 1)) == NULL)
return EX_OSERR;
m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
if (m->raw_len <= 0) {
free(m->raw);
m->raw = NULL;
m->raw_len = 0;
return EX_IOERR;
}
m->type = MESSAGE_ERROR;
if (m->raw_len > m->max_len)
return EX_TOOBIG;
m->type = MESSAGE_RAW;
m->msg = m->raw;
m->msg_len = m->raw_len;
m->out = m->msg;
m->out_len = m->msg_len;
return EX_OK;
}
static int _message_read_bsmtp(int fd, struct message *m)
{
unsigned int i, j;
char prev;
_clear_message(m);
if ((m->raw = malloc(m->max_len + 1)) == NULL)
return EX_OSERR;
m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
if (m->raw_len <= 0) {
free(m->raw);
m->raw = NULL;
m->raw_len = 0;
return EX_IOERR;
}
m->type = MESSAGE_ERROR;
if (m->raw_len > m->max_len)
return EX_TOOBIG;
m->pre = m->raw;
for (i = 0; i < m->raw_len - 6; i++) {
if ((m->raw[i] == '\n') &&
(m->raw[i + 1] == 'D' || m->raw[i + 1] == 'd') &&
(m->raw[i + 2] == 'A' || m->raw[i + 2] == 'a') &&
(m->raw[i + 3] == 'T' || m->raw[i + 3] == 't') &&
(m->raw[i + 4] == 'A' || m->raw[i + 4] == 'a') &&
((m->raw[i + 5] == '\r' && m->raw[i + 6] == '\n')
|| m->raw[i + 5] == '\n')) {
i += 6;
if (m->raw[i - 1] == '\r')
i++;
m->pre_len = i;
m->msg = m->raw + i;
m->msg_len = m->raw_len - i;
break;
}
}
if (m->msg == NULL)
return EX_DATAERR;
prev = '\n';
for (i = j = 0; i < m->msg_len; i++) {
if (prev == '\n' && m->msg[i] == '.') {
if ((m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')
|| m->msg[i + 1] == '\n') {
m->post = m->msg + i;
m->post_len = m->msg_len - i;
m->msg_len = j;
break;
}
else if (m->msg[i + 1] == '.') {
prev = '.';
continue;
}
}
prev = m->msg[i];
m->msg[j++] = m->msg[i];
}
m->type = MESSAGE_BSMTP;
m->out = m->msg;
m->out_len = m->msg_len;
return EX_OK;
}
int message_read(int fd, int flags, struct message *m)
{
libspamc_timeout = 0;
m->priv = malloc(sizeof(struct libspamc_private_message));
if (m->priv == NULL) {
libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
return EX_OSERR;
}
m->priv->flags = flags;
switch (flags & SPAMC_MODE_MASK) {
case SPAMC_RAW_MODE:
return _message_read_raw(fd, m);
case SPAMC_BSMTP_MODE:
return _message_read_bsmtp(fd, m);
default:
libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
flags & SPAMC_MODE_MASK);
return EX_USAGE;
}
}
long message_write(int fd, struct message *m)
{
long total = 0;
off_t i, j;
off_t jlimit;
char buffer[1024];
if (m->priv->flags & SPAMC_CHECK_ONLY) {
if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
return full_write(fd, 1, m->out, m->out_len);
}
else {
libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
m->is_spam);
return -1;
}
}
switch (m->type) {
case MESSAGE_NONE:
libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
return -1;
case MESSAGE_ERROR:
return full_write(fd, 1, m->raw, m->raw_len);
case MESSAGE_RAW:
return full_write(fd, 1, m->out, m->out_len);
case MESSAGE_BSMTP:
total = full_write(fd, 1, m->pre, m->pre_len);
for (i = 0; i < m->out_len;) {
jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
if (i + 1 < m->out_len && m->out[i] == '\n'
&& m->out[i + 1] == '.') {
if (j > jlimit - 4) {
break;
}
buffer[j++] = m->out[i++];
buffer[j++] = m->out[i++];
buffer[j++] = '.';
}
else {
buffer[j++] = m->out[i++];
}
}
total += full_write(fd, 1, buffer, j);
}
return total + full_write(fd, 1, m->post, m->post_len);
default:
libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
return -1;
}
}
void message_dump(int in_fd, int out_fd, struct message *m)
{
char buf[8196];
int bytes;
if (m != NULL && m->type != MESSAGE_NONE) {
message_write(out_fd, m);
}
while ((bytes = full_read(in_fd, 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",
bytes);
}
}
}
static int
_spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
char *buf, size_t *lenp, size_t bufsiz)
{
int failureval;
int bytesread = 0;
size_t len;
UNUSED_VARIABLE(m);
*lenp = 0;
for (len = 0; len < bufsiz - 1; len++) {
if (flags & SPAMC_USE_SSL) {
bytesread = ssl_timeout_read(ssl, buf + len, 1);
}
else {
bytesread = fd_timeout_read(sock, 0, buf + len, 1);
}
if (bytesread <= 0) {
failureval = EX_IOERR;
goto failure;
}
if (buf[len] == '\n') {
buf[len] = '\0';
if (len > 0 && buf[len - 1] == '\r') {
len--;
buf[len] = '\0';
}
*lenp = len;
return EX_OK;
}
}
libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
failureval = EX_TOOBIG;
failure:
return failureval;
}
static float _locale_safe_string_to_float(char *buf, int siz)
{
int is_neg;
char *cp, *dot;
int divider;
float ret, postdot;
buf[siz - 1] = '\0';
is_neg = 0;
if (*buf == '-') {
is_neg = 1;
}
ret = (float) (strtol(buf, &dot, 10));
if (dot == NULL) {
return 0.0;
}
if (dot != NULL && *dot != '.') {
return ret;
}
cp = (dot + 1);
postdot = (float) (strtol(cp, NULL, 10));
if (postdot == 0.0) {
return ret;
}
divider = 1;
while (*cp != '\0') {
divider *= 10;
cp++;
}
if (is_neg) {
ret -= (postdot / ((float) divider));
}
else {
ret += (postdot / ((float) divider));
}
return ret;
}
static int
_handle_spamd_header(struct message *m, int flags, char *buf, int len)
{
char is_spam[6];
char s_str[21], t_str[21];
UNUSED_VARIABLE(len);
if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
m->score = _locale_safe_string_to_float(s_str, 20);
m->threshold = _locale_safe_string_to_float(t_str, 20);
if (m->score > 1e10)
m->score = 1e10;
else if (m->score < -1e10)
m->score = -1e10;
if (m->threshold > 1e10)
m->threshold = 1e10;
else if (m->threshold < -1e10)
m->threshold = -1e10;
m->is_spam =
strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
if (flags & SPAMC_CHECK_ONLY) {
m->out_len = sprintf(m->out,
"%.1f/%.1f\n", m->score, m->threshold);
}
else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
|| (flags & SPAMC_REPORT)) {
m->out_len = sprintf(m->out,
"%.1f/%.1f\n", m->score, m->threshold);
}
return EX_OK;
}
else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
if (m->content_length < 0) {
libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
buf);
return EX_PROTOCOL;
}
return EX_OK;
}
libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
return EX_PROTOCOL;
}
int message_filter(struct transport *tp, const char *username,
int flags, struct message *m)
{
char buf[8192];
size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4;
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;
if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
SSLeay_add_ssl_algorithms();
meth = SSLv2_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->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
failureval = EX_OSERR;
goto failure;
}
m->out_len = 0;
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);
}
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;
}
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;
}
if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock);
SSL_connect(ssl);
#endif
}
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);
}
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;
}
if (_handle_spamd_header(m, flags, buf, len) < 0) {
failureval = EX_PROTOCOL;
goto failure;
}
}
len = 0;
if (flags & SPAMC_CHECK_ONLY) {
closesocket(sock);
sock = -1;
if (m->is_spam == EX_TOOBIG) {
failureval = EX_PROTOCOL;
goto failure;
}
return EX_OK;
}
else {
if (m->content_length < 0) {
failureval = EX_PROTOCOL;
goto failure;
}
if (m->out_len > 0) {
m->content_length += m->out_len;
}
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);
}
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);
}
if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
failureval = EX_TOOBIG;
goto failure;
}
m->out_len += len;
shutdown(sock, SHUT_RD);
closesocket(sock);
sock = -1;
}
libspamc_timeout = 0;
if (m->out_len != m->content_length) {
libspamc_log(flags, LOG_ERR,
"failed sanity check, %d bytes claimed, %d bytes seen",
m->content_length, m->out_len);
failureval = EX_PROTOCOL;
goto failure;
}
return EX_OK;
failure:
free(m->out);
m->out = m->msg;
m->out_len = m->msg_len;
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;
}
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;
m.type = MESSAGE_NONE;
m.max_len = max_size;
ret = message_read(in_fd, flags, &m);
if (ret != EX_OK)
goto FAIL;
ret = message_filter(trans, username, flags, &m);
if (ret != EX_OK)
goto FAIL;
if (message_write(out_fd, &m) < 0)
goto FAIL;
if (m.is_spam != EX_TOOBIG) {
message_cleanup(&m);
return m.is_spam;
}
message_cleanup(&m);
return ret;
FAIL:
if (flags & SPAMC_CHECK_ONLY) {
full_write(out_fd, 1, "0/0\n", 4);
message_cleanup(&m);
return EX_NOTSPAM;
}
else {
message_dump(in_fd, out_fd, &m);
message_cleanup(&m);
return ret;
}
}
void message_cleanup(struct message *m)
{
if (m->out != NULL && m->pre != NULL && m->out != m->pre+m->pre_len)
free(m->out);
if (m->raw != NULL)
free(m->raw);
if (m->priv != NULL)
free(m->priv);
_clear_message(m);
}
int process_message(struct transport *tp, char *username, int max_size,
int in_fd, int out_fd, const int my_check_only,
const int my_safe_fallback)
{
int flags;
flags = SPAMC_RAW_MODE;
if (my_check_only)
flags |= SPAMC_CHECK_ONLY;
if (my_safe_fallback)
flags |= SPAMC_SAFE_FALLBACK;
return message_process(tp, username, max_size, in_fd, out_fd, flags);
}
void transport_init(struct transport *tp)
{
assert(tp != 0);
memset(tp, 0, sizeof *tp);
tp->type = TRANSPORT_LOCALHOST;
tp->port = 783;
tp->flags = 0;
}
static void _randomize_hosts(struct transport *tp)
{
int rnum;
assert(tp != 0);
if (tp->nhosts <= 1)
return;
rnum = rand() % tp->nhosts;
while (rnum-- > 0) {
struct in_addr tmp = tp->hosts[0];
int i;
for (i = 1; i < tp->nhosts; i++)
tp->hosts[i - 1] = tp->hosts[i];
tp->hosts[i - 1] = tmp;
}
}
int transport_setup(struct transport *tp, int flags)
{
struct hostent *hp = 0;
char **addrp;
#ifdef _WIN32
WSADATA wsaData;
int nCode;
if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
printf("WSAStartup() returned error code %d\n", nCode);
return EX_OSERR;
}
#endif
tp->flags = flags;
assert(tp != 0);
switch (tp->type) {
#ifndef _WIN32
case TRANSPORT_UNIX:
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;
case TRANSPORT_TCP:
if (NULL == (hp = gethostbyname(tp->hostname))) {
int origherr = h_errno;
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 (hp->h_addr_list[0] == 0) {
return EX_NOHOST;
}
if (hp->h_length != sizeof tp->hosts[0]
|| hp->h_addrtype != AF_INET) {
return EX_NOHOST;
}
tp->nhosts = 0;
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;
}
memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
tp->nhosts++;
}
if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
_randomize_hosts(tp);
}
if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
tp->nhosts = 1;
}
}
return EX_OK;
}
#define LOG_BUFSIZ 1023
void
libspamc_log (int flags, int level, char *msg, ...)
{
va_list ap;
char buf[LOG_BUFSIZ+1];
int len = 0;
va_start(ap, msg);
if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
buf[LOG_BUFSIZ] = '\0';
(void) write (2, buf, len);
} else {
vsnprintf(buf, LOG_BUFSIZ, msg, ap);
buf[LOG_BUFSIZ] = '\0';
#ifndef _WIN32
syslog (level, "%s", buf);
#else
(void) level;
fprintf (stderr, "%s\n", buf);
#endif
}
va_end(ap);
}
#ifdef LIBSPAMC_UNIT_TESTS
static void _test_locale_safe_string_to_float_val(float input)
{
char inputstr[99], cmpbuf1[99], cmpbuf2[99];
float output;
sprintf(inputstr, "%f", input);
output = _locale_safe_string_to_float(inputstr, 99);
if (input == output) {
return;
}
sprintf(cmpbuf1, "%f", input);
sprintf(cmpbuf2, "%f", output);
if (!strcmp(cmpbuf1, cmpbuf2)) {
return;
}
printf("FAIL: input=%f != output=%f\n", input, output);
}
static void unit_test_locale_safe_string_to_float(void)
{
float statictestset[] = {
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
};
float num;
int i;
printf("starting unit_test_locale_safe_string_to_float\n");
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]);
}
for (num = -1000.0; num < 1000.0; num += 0.01) {
_test_locale_safe_string_to_float_val(num);
}
printf("finished unit_test_locale_safe_string_to_float\n");
}
void do_libspamc_unit_tests(void)
{
unit_test_locale_safe_string_to_float();
exit(0);
}
#endif