#include "config.h"
#include "libspamc.h"
#include "utils.h"
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <syslog.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>
#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 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
syslog (DEBUG_LEVEL, "dbg: create socket(%s)", typename);
#endif
if ( (*psock = socket(type, SOCK_STREAM, proto)) < 0 )
{
int origerr;
origerr = errno;
syslog (LOG_ERR, "socket(%s) to spamd failed: %m", typename);
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 )
{
switch(errno)
{
case EBADF:
case ENOTSOCK:
case ENOPROTOOPT:
case EFAULT:
syslog(LOG_ERR,
"setsockopt(TCP_NODELAY) failed: %m");
close (*psock);
return EX_SOFTWARE;
default:
break;
}
}
}
#endif
return EX_OK;
}
static int
try_to_connect_unix (struct transport *tp, int *sockptr)
{
int mysock, status, origerr;
struct sockaddr_un addrbuf;
int ret;
assert(tp != 0);
assert(sockptr != 0);
assert(tp->socketpath != 0);
if ( (ret = opensocket(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
syslog (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
syslog(DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
#endif
*sockptr = mysock;
return EX_OK;
}
syslog(LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %m",
addrbuf.sun_path);
close(mysock);
return translate_connect_errno(origerr);
}
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++)
{
syslog(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(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
syslog (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)
{
syslog (LOG_ERR,
"connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %m",
ipaddr, numloops+1, MAX_CONNECT_RETRIES);
close(mysock);
sleep(CONNECT_RETRY_SLEEP);
}
else
{
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog(DEBUG_LEVEL,
"dbg: connect(AF_INET) to spamd at %s done",
ipaddr);
#endif
*sockptr = mysock;
return EX_OK;
}
}
syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
MAX_CONNECT_RETRIES);
return translate_connect_errno(origerr);
}
#if 0
static int
try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
int hent_port, int *sockptr)
{
#ifdef USE_TCP_NODELAY
int value;
#endif
int mysock = -1;
int status = -1;
int origerr;
int numloops;
int hostnum = 0;
struct sockaddr_in addrbuf, *addr;
struct in_addr inaddrlist[256];
#ifdef DO_CONNECT_DEBUG_SYSLOGS
int dbgiter; char dbgbuf[2048]; int dbgbuflen = 0;
#endif
if (argaddr == NULL && hent == NULL) {
syslog (LOG_ERR, "oops! both NULL in try_to_connect");
return EX_SOFTWARE;
} else if (argaddr != NULL && hent != NULL) {
syslog (LOG_ERR, "oops! both non-NULL in try_to_connect");
return EX_SOFTWARE;
}
if (hent != NULL) {
memset (inaddrlist, 0, sizeof(inaddrlist));
for (hostnum=0; hent->h_addr_list[hostnum] != 0; hostnum++) {
#ifdef DO_CONNECT_DEBUG_SYSLOGS
dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen,
"[%d %lx: %d.%d.%d.%d]",
hostnum, hent->h_addr_list[hostnum],
hent->h_addr_list[hostnum][0],
hent->h_addr_list[hostnum][1],
hent->h_addr_list[hostnum][2],
hent->h_addr_list[hostnum][3]);
#endif
if (hostnum > 255) {
syslog (LOG_ERR, "too many address in hostent (%d), ignoring others",
hostnum);
break;
}
if (hent->h_addr_list[hostnum] == NULL) {
syslog (LOG_ERR, "hent->h_addr_list[hostnum] == NULL! foo!");
return EX_SOFTWARE;
}
#ifdef DO_CONNECT_DEBUG_SYSLOGS
dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen,
"[%d: %d.%d.%d.%d] ", sizeof (struct in_addr),
hent->h_addr_list[hostnum][0],
hent->h_addr_list[hostnum][1],
hent->h_addr_list[hostnum][2],
hent->h_addr_list[hostnum][3]);
#endif
memcpy ((void *) &(inaddrlist[hostnum]),
(void *) hent->h_addr_list[hostnum],
sizeof (struct in_addr));
}
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0;
#endif
}
#ifdef DO_CONNECT_DEBUG_SYSLOGS
for (dbgiter = 0; dbgiter < hostnum; dbgiter++) {
syslog (LOG_DEBUG, "dbg: host addr %d/%d = %lx at %lx", dbgiter, hostnum,
inaddrlist[dbgiter].s_addr, &(inaddrlist[dbgiter]));
}
#endif
hent = NULL;
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: socket");
#endif
if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
{
origerr = errno;
syslog (LOG_ERR, "socket() to spamd failed: %m");
switch(origerr)
{
case EPROTONOSUPPORT:
case EINVAL:
return EX_SOFTWARE;
case EACCES:
return EX_NOPERM;
case ENFILE:
case EMFILE:
case ENOBUFS:
case ENOMEM:
return EX_OSERR;
default:
return EX_SOFTWARE;
}
}
#ifdef USE_TCP_NODELAY
value = 1;
if(-1 == setsockopt(mysock,0,TCP_NODELAY,&value,sizeof(value)))
{
switch(errno)
{
case EBADF:
case ENOTSOCK:
case ENOPROTOOPT:
case EFAULT:
syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
close (mysock);
return EX_SOFTWARE;
default:
break;
}
}
#endif
for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: connect() to spamd %d", numloops);
#endif
if (argaddr != NULL) {
addr = (struct sockaddr_in *) argaddr;
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: using argaddr");
#endif
} else {
memset(&addrbuf, 0, sizeof(addrbuf));
addrbuf.sin_family=AF_INET;
addrbuf.sin_port=htons(hent_port);
if (sizeof(addrbuf.sin_addr) != sizeof(struct in_addr)) {
syslog (LOG_ERR,
"foo! sizeof(sockaddr.sin_addr) != sizeof(struct in_addr)");
return EX_SOFTWARE;
}
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx",
numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum]));
#endif
memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]),
sizeof(addrbuf.sin_addr));
addr = &addrbuf;
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: conn addr %d/%d = %lx",
numloops%hostnum, hostnum, addrbuf.sin_addr.s_addr);
#endif
}
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: connect() to spamd at %s",
inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
#endif
status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
#ifdef DO_CONNECT_DEBUG_SYSLOGS
syslog (LOG_DEBUG, "dbg: connect() to spamd at %s done",
inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
#endif
if (status < 0)
{
origerr = errno;
syslog (LOG_ERR, "connect() to spamd at %s failed, retrying (%d/%d): %m",
inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
numloops+1, MAX_CONNECT_RETRIES);
sleep(CONNECT_RETRY_SLEEP);
} else {
*sockptr = mysock;
return EX_OK;
}
}
close (mysock);
syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
MAX_CONNECT_RETRIES);
switch(origerr)
{
case EBADF:
case EFAULT:
case ENOTSOCK:
case EISCONN:
case EADDRINUSE:
case EINPROGRESS:
case EALREADY:
case EAFNOSUPPORT:
return EX_SOFTWARE;
case ECONNREFUSED:
case ETIMEDOUT:
case ENETUNREACH:
return EX_UNAVAILABLE;
case EACCES:
return EX_NOPERM;
default:
return EX_SOFTWARE;
}
}
#endif
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, m->raw, m->max_len+1, m->max_len+1);
if(m->raw_len<=0){
free(m->raw); m->raw=NULL; m->raw_len=0;
return EX_IOERR;
}
m->type=MESSAGE_ERROR;
if(m->raw_len>m->max_len) return EX_TOOBIG;
m->type=MESSAGE_RAW;
m->msg=m->raw;
m->msg_len=m->raw_len;
m->out=m->msg;
m->out_len=m->msg_len;
return EX_OK;
}
static int message_read_bsmtp(int fd, struct message *m){
off_t i, j;
char prev;
clear_message(m);
if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
m->raw_len=full_read(fd, m->raw, m->max_len+1, m->max_len+1);
if(m->raw_len<=0){
free(m->raw); m->raw=NULL; m->raw_len=0;
return EX_IOERR;
}
m->type=MESSAGE_ERROR;
if(m->raw_len>m->max_len) return EX_TOOBIG;
m->pre=m->raw;
for(i=0; i<m->raw_len-6; i++){
if((m->raw[i]=='\n') &&
(m->raw[i+1]=='D' || m->raw[i+1]=='d') &&
(m->raw[i+2]=='A' || m->raw[i+2]=='a') &&
(m->raw[i+3]=='T' || m->raw[i+3]=='t') &&
(m->raw[i+4]=='A' || m->raw[i+4]=='a') &&
((m->raw[i+5]=='\r' && m->raw[i+6]=='\n') || m->raw[i+5]=='\n')){
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) {
syslog(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:
syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK);
return EX_USAGE;
}
}
long message_write(int fd, struct message *m){
long total=0;
off_t i, j;
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, m->out, m->out_len);
} else {
syslog(LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d\n", m->is_spam);
return -1;
}
}
switch(m->type){
case MESSAGE_NONE:
syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n");
return -1;
case MESSAGE_ERROR:
return full_write(fd, m->raw, m->raw_len);
case MESSAGE_RAW:
return full_write(fd, m->out, m->out_len);
case MESSAGE_BSMTP:
total=full_write(fd, m->pre, m->pre_len);
for(i=0; i<m->out_len; ){
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, buffer, j);
}
return total+full_write(fd, m->post, m->post_len);
default:
syslog(LOG_ERR, "Unknown message type %d\n", m->type);
return -1;
}
}
void message_dump(int in_fd, int out_fd, struct message *m){
char buf[8196];
int bytes;
if(m!=NULL && m->type!=MESSAGE_NONE) {
message_write(out_fd, m);
}
while((bytes=full_read(in_fd, buf, 8192, 8192))>0){
if (bytes!=full_write(out_fd, buf, bytes)) {
syslog(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, int *lenp, int bufsiz)
{
int failureval;
int bytesread = 0;
int len;
UNUSED_VARIABLE(m);
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, buf+len, 1);
}
if(buf[len]=='\n') {
buf[len]='\0';
if (len > 0 && buf[len-1] == '\r') {
len--;
buf[len]='\0';
}
*lenp = len;
return EX_OK;
}
if(bytesread<=0){
failureval = EX_IOERR; goto failure;
}
}
syslog(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);
m->is_spam=strcasecmp("true", is_spam) == 0 ? EX_ISSPAM: EX_NOTSPAM;
if(flags&SPAMC_CHECK_ONLY) {
m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE,
"%.1f/%.1f\n", m->score, m->threshold);
}
else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
|| (flags & SPAMC_REPORT))
{
m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE,
"%.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) {
syslog(LOG_ERR, "spamd responded with bad Content-length '%s'", buf);
return EX_PROTOCOL;
}
return EX_OK;
}
syslog(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];
int bufsiz = (sizeof(buf) / sizeof(*buf)) - 4;
int len, i;
int sock = -1;
int rc;
char versbuf[20];
float version;
int response;
int failureval;
SSL_CTX* ctx;
SSL* ssl;
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);
syslog(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)
len=snprintf(buf, bufsiz, "CHECK %s\r\n", PROTOCOL_VERSION);
else if(flags & SPAMC_REPORT_IFSPAM)
len=snprintf(buf, bufsiz, "REPORT_IFSPAM %s\r\n", PROTOCOL_VERSION);
else if(flags & SPAMC_REPORT)
len=snprintf(buf, bufsiz, "REPORT %s\r\n", PROTOCOL_VERSION);
else if(flags & SPAMC_SYMBOLS)
len=snprintf(buf, bufsiz, "SYMBOLS %s\r\n", PROTOCOL_VERSION);
else
len=snprintf(buf, bufsiz, "PROCESS %s\r\n", PROTOCOL_VERSION);
if(len<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
if(username!=NULL){
len+=i=snprintf(buf+len, bufsiz-len, "User: %s\r\n", username);
if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
}
len+=i=snprintf(buf+len, bufsiz-len, "Content-length: %d\r\n", m->msg_len);
if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
len+=i=snprintf(buf+len, bufsiz-len, "\r\n");
if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
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 i;
}
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, buf, len);
full_write(sock, 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) {
syslog(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) {
syslog(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) {
close(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, 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);
close(sock); sock = -1;
}
libspamc_timeout = 0;
if(m->out_len!=m->content_length) {
syslog(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) {
close(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, "0/0\n", 4);
message_cleanup(&m);
return EX_NOTSPAM;
} else {
message_dump(in_fd, out_fd, &m);
message_cleanup(&m);
return ret;
}
}
void message_cleanup(struct message *m) {
if (m->out != NULL && m->out != m->raw) free(m->out);
if (m->raw != NULL) free(m->raw);
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;
}
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;
assert(tp != 0);
switch ( tp->type )
{
case TRANSPORT_UNIX:
assert(tp->socketpath != 0);
return EX_OK;
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;
syslog (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) {
syslog (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;
}
#ifdef LIBSPAMC_UNIT_TESTS
static void
_test_locale_safe_string_to_float_val (float input) {
char inputstr[99], cmpbuf1[99], cmpbuf2[99];
float output;
snprintf (inputstr, 99, "%f", input);
output = _locale_safe_string_to_float (inputstr, 99);
if (input == output) { return; }
snprintf (cmpbuf1, 98, "%f", input);
snprintf (cmpbuf2, 98, "%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