--- /dev/null
+/* <@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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </@LICENSE>
+ */
+
+#include "libspamc.h"
+#include "utils.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.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)
+
+#include <sysexits.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+
+void _err(int, bool, char *, int, char *, ...);
+#define libspamc_log(flags, level, ...) _err(-1, false, __FILE__, __LINE__, __VA_ARGS__)
+
+enum { LOG_ERR, LOG_DEBUG, LOG_NOTICE };
+
+/* FIXME: Make this configurable */
+#define MAX_CONNECT_RETRIES 3
+#define CONNECT_RETRY_SLEEP 1
+
+/* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
+/* KAM 12-4-01 */
+/* SJF 2003/04/25 - now test for macros directly */
+#ifndef SHUT_RD
+# define SHUT_RD 0 /* no more receptions */
+#endif
+#ifndef SHUT_WR
+# define SHUT_WR 1 /* no more transmissions */
+#endif
+#ifndef SHUT_RDWR
+# define SHUT_RDWR 2 /* no more receptions or transmissions */
+#endif
+
+#ifndef HAVE_OPTARG
+extern char *optarg;
+#endif
+
+#ifndef HAVE_INADDR_NONE
+#define INADDR_NONE ((in_addr_t) 0xffffffff)
+#endif
+
+/* jm: turned off for now, it should not be necessary. */
+#undef USE_TCP_NODELAY
+
+#undef DO_CONNECT_DEBUG_SYSLOGS
+/* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
+
+static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
+
+/* set EXPANSION_ALLOWANCE to something more than might be
+ added to a message in X-headers and the report template */
+static const int EXPANSION_ALLOWANCE = 16384;
+
+/* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
+ of the data streams before and after processing by spamd
+ Aug 7 2002 jm: no longer seems to be used
+ static const int NUM_CHECK_BYTES = 32;
+ */
+
+/* Set the protocol version that this spamc speaks */
+static const char *PROTOCOL_VERSION = "SPAMC/1.3";
+
+/* "private" part of struct message.
+ * we use this instead of the struct message directly, so that we
+ * can add new members without affecting the ABI.
+ */
+struct libspamc_private_message
+{
+ int flags; /* copied from "flags" arg to message_read() */
+};
+
+int libspamc_timeout = 0;
+
+/*
+ * translate_connect_errno()
+ *
+ * Given a UNIX error number obtained (probably) from "connect(2)",
+ * translate this to a failure code. This module is shared by both
+ * transport modules - UNIX and TCP.
+ *
+ * This should ONLY be called when there is an error.
+ */
+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;
+ }
+}
+
+/*
+ * 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.
+ *
+ * Upon failure we return one of the other EX_??? error codes.
+ */
+static int _opensocket(int flags, int type, int *psock)
+{
+ const char *typename;
+ int proto = 0;
+
+#ifdef _WIN32
+ int socktout;
+#endif
+
+ assert(psock != 0);
+
+ /*----------------------------------------------------------------
+ * Create a few induction variables that are implied by the socket
+ * type given by the user. The typename is strictly used for debug
+ * reporting.
+ */
+ 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;
+
+ /*--------------------------------------------------------
+ * 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.
+ */
+#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
+
+ 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 _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)
+ {
+ int origerrno;
+
+ origerrno = WSAGetLastError();
+ switch (origerrno)
+ {
+ case EBADF:
+ case ENOTSOCK:
+ case ENOPROTOOPT:
+ case EFAULT:
+ libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerrno);
+ closesocket(*psock);
+ return EX_SOFTWARE;
+
+ default:
+ break; /* ignored */
+ }
+ }
+#endif
+
+ /*----------------------------------------------------------------
+ * Do a bit of setup on the TCP socket if required. Notes above
+ * suggest this is probably not set
+ */
+#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; /* ignored */
+ }
+ }
+ }
+#endif /* USE_TCP_NODELAY */
+
+ return EX_OK; /* all is well */
+}
+
+/*
+ * try_to_connect_unix()
+ *
+ * Given a transport handle that implies using a UNIX domain
+ * socket, try to make a connection to it and store the resulting
+ * file descriptor in *sockptr. Return is EX_OK if we did it,
+ * and some other error code otherwise.
+ */
+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 the socket itself can't be created, this is a fatal error.
+ */
+ if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
+ return ret;
+
+ /* set up the UNIX domain socket */
+ 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; /* not used. suppress compiler warning */
+ (void) sockptr; /* not used. suppress compiler warning */
+ return EX_OSERR;
+#endif
+}
+
+/*
+ * try_to_connect_tcp()
+ *
+ * Given a transport that implies a TCP connection, either to
+ * localhost or a list of IP addresses, attempt to connect. The
+ * list of IP addresses has already been randomized (if requested)
+ * and limited to just one if fallback has been enabled.
+ */
+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;
+
+ /*--------------------------------------------------------
+ * 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));
+
+ 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);
+}
+
+/* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
+ * message_dump, lookup_host, message_filter, and message_process, and a bunch
+ * of helper functions.
+ */
+
+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->outbuf = NULL;
+ m->out = NULL;
+ m->out_len = 0;
+ m->content_length = -1;
+}
+
+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)
+{
+ struct stat st;
+ _clear_message(m);
+
+ if (fstat(fd, &st) == -1) {
+ libspamc_log(0,0, "fstat");
+ return EX_IOERR;
+ }
+
+ m->raw = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (m->raw == MAP_FAILED) {
+ libspamc_log(0,0, "mmap");
+ return EX_IOERR;
+ }
+ m->raw_len = st.st_size;
+
+ 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;
+
+ /* Find the DATA line */
+ 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')) {
+ /* 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;
+ }
+ }
+ if (m->msg == NULL)
+ return EX_DATAERR;
+
+ /* Find the end-of-DATA line */
+ prev = '\n';
+ for (i = j = 0; i < 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') {
+ /* 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] == '.') {
+ /* Escaping dot, eliminate. */
+ prev = '.';
+ continue;
+ } /* Else an ordinary dot, drop down to ordinary char handler */
+ }
+ 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)
+{
+ assert(m != NULL);
+
+ libspamc_timeout = 0;
+
+ /* create the "private" part of the struct message */
+ 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];
+
+ assert(m != NULL);
+
+ 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;
+ }
+ }
+
+ /* else we're not in CHECK_ONLY mode */
+ 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; /* avoid overflow */
+ }
+ 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;
+ /* Now, read from spamd */
+ 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;
+}
+
+/*
+ * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
+ * work around using our own locale-independent float-parser code.
+ */
+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'; /* ensure termination */
+
+ /* ok, let's illustrate using "100.033" as an example... */
+
+ 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;
+ }
+
+ /* ex: ret == 100.0 */
+
+ cp = (dot + 1);
+ postdot = (float) (strtol(cp, NULL, 10));
+ /* note: don't compare floats == 0.0, it's unsafe. use a range */
+ if (postdot >= -0.00001 && postdot <= 0.00001) {
+ return ret;
+ }
+
+ /* ex: postdot == 33.0, cp="033" */
+
+ /* now count the number of decimal places and figure out what power of 10 to use */
+ divider = 1;
+ while (*cp != '\0') {
+ divider *= 10;
+ cp++;
+ }
+
+ /* ex:
+ * cp="033", divider=1
+ * cp="33", divider=10
+ * cp="3", divider=100
+ * cp="", divider=1000
+ */
+
+ if (is_neg) {
+ ret -= (postdot / ((float) divider));
+ }
+ else {
+ ret += (postdot / ((float) divider));
+ }
+ /* ex: ret == 100.033, tada! ... hopefully */
+
+ return ret;
+}
+
+static int
+_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);
+
+ /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
+ * let's stick with it for this parser.
+ * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
+ * work around using our own locale-independent float-parser code.
+ */
+ 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);
+
+ /* set bounds on these to ensure no buffer overflow in the sprintf */
+ 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;
+
+ /* Format is "Spam: x; y / x" */
+ 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;
+ }
+ else if (sscanf(buf, "DidSet: %s", 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: %s", didremove_ret) == 1) {
+ if (strstr(didremove_ret, "local")) {
+ *didtellflags |= SPAMC_REMOVE_LOCAL;
+ }
+ if (strstr(didremove_ret, "remote")) {
+ *didtellflags |= SPAMC_REMOVE_REMOTE;
+ }
+ }
+
+ return EX_OK;
+}
+
+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; /* bit of breathing room */
+ size_t len;
+ int sock = -1;
+ int rc;
+ char versbuf[20];
+ float version;
+ int response;
+ int failureval;
+ unsigned int throwaway;
+ 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 = 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->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
+ failureval = EX_OSERR;
+ goto failure;
+ }
+ m->out = m->outbuf;
+ m->out_len = 0;
+
+ /* 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) {
+ _use_msg_for_out(m);
+ return EX_OSERR;
+ }
+
+ strcat(buf, PROTOCOL_VERSION);
+ strcat(buf, "\r\n");
+ len = strlen(buf);
+
+ 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 > 9999999) || ((len + 27) >= (bufsiz - len))) {
+ _use_msg_for_out(m);
+ 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) {
+ _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, &throwaway) < 0) {
+ failureval = EX_PROTOCOL;
+ goto failure;
+ }
+ }
+
+ len = 0; /* overwrite those headers */
+
+ if (flags & SPAMC_CHECK_ONLY) {
+ closesocket(sock);
+ sock = -1;
+ if (m->is_spam == EX_TOOBIG) {
+ /* We should have gotten headers back... Damnit. */
+ failureval = EX_PROTOCOL;
+ goto failure;
+ }
+ return EX_OK;
+ }
+ else {
+ if (m->content_length < 0) {
+ /* should have got a length too. */
+ failureval = EX_PROTOCOL;
+ goto failure;
+ }
+
+ /* have we already got something in the buffer (e.g. REPORT and
+ * REPORT_IFSPAM both create a line from the "Spam:" hdr)? If
+ * so, add the size of that so our sanity check passes.
+ */
+ 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:
+ _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;
+}
+
+
+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;
+ 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;
+ }
+}
+
+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 = 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->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == 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 > 9999999) || ((len + 27) >= (bufsiz - len))) {
+ _use_msg_for_out(m);
+ 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) {
+ _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)
+{
+ assert(m != NULL);
+ if (m->outbuf != NULL)
+ free(m->outbuf);
+ if (m->raw != NULL) {
+ if (munmap(m->raw, m->raw_len) != 0) {
+ libspamc_log(0,0, "munmap");
+ exit(EX_SOFTWARE);
+ }
+ }
+ if (m->priv != NULL)
+ free(m->priv);
+ _clear_message(m);
+}
+
+/* Aug 14, 2002 bj: Obsolete! */
+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);
+}
+
+/*
+* 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);
+
+ memset(tp, 0, sizeof *tp);
+
+ tp->type = TRANSPORT_LOCALHOST;
+ tp->port = 783;
+ tp->flags = 0;
+}
+
+/*
+* 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)
+{
+ 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;
+ }
+}
+
+/*
+* 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;
+ char *hostlist, *hostname;
+ int errbits;
+ char **addrp;
+
+#ifdef _WIN32
+ // Start Winsock up
+ WSADATA wsaData;
+ int nCode;
+ if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
+ printf("WSAStartup() returned error code %d\n", nCode);
+ return EX_OSERR;
+ }
+
+#endif
+
+ assert(tp != NULL);
+ tp->flags = flags;
+
+ 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 ((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';
+ }
+
+ 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 */
+ }
+
+ /* 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 (hp->h_addr_list[0] == NULL
+ || hp->h_length != sizeof tp->hosts[0]
+ || hp->h_addrtype != AF_INET) {
+ /* no hosts/bad size/wrong family */
+ 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.
+ */
+ 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++;
+ }
+
+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 ((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;
+}
+
+/* --------------------------------------------------------------------------- */
+
+#define LOG_BUFSIZ 1023
+
+#ifndef libspamc_log
+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) {
+ // create a log-line buffer
+ len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
+ len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
+
+ // avoid buffer overflow
+ if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
+
+ len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
+ buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
+ (void) write (2, buf, len);
+
+ } else {
+ vsnprintf(buf, LOG_BUFSIZ, msg, ap);
+ buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
+#ifndef _WIN32
+ syslog (level, "%s", buf);
+#else
+ (void) level; /* not used. suppress compiler warning */
+ fprintf (stderr, "%s\n", buf);
+#endif
+ }
+
+ va_end(ap);
+}
+#endif
+
+/* --------------------------------------------------------------------------- */
+
+/*
+* 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)
+{
+ char inputstr[99], cmpbuf1[99], cmpbuf2[99];
+ float output;
+
+ /* sprintf instead of snprintf is safe here because it is only a controlled test */
+ sprintf(inputstr, "%f", input);
+ output = _locale_safe_string_to_float(inputstr, 99);
+ if (input == output) {
+ 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;
+ }
+
+ 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 */
+ };
+ float num;
+ int i;
+
+ 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]);
+ }
+ /* 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);
+ }
+ 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 /* LIBSPAMC_UNIT_TESTS */
--- /dev/null
+/* <@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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </@LICENSE>
+ */
+#ifndef LIBSPAMC_H
+#define LIBSPAMC_H 1
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#ifdef _WIN32
+#ifdef _MSC_VER
+/* ignore MSVC++ warnings that are annoying and hard to remove:
+ 4115 named type definition in parentheses
+ 4127 conditional expression is constant
+ 4514 unreferenced inline function removed
+ */
+#pragma warning( disable : 4115 4127 4514 )
+#endif
+#include <winsock.h>
+#else
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#ifdef _WIN32
+/* FIXME: This stuff has to go somewhere else */
+
+#define EX_OK 0
+#define EX_USAGE 64
+#define EX_DATAERR 65
+#define EX_NOINPUT 66
+#define EX_NOUSER 67
+#define EX_NOHOST 68
+#define EX_UNAVAILABLE 69
+#define EX_SOFTWARE 70
+#define EX_OSERR 71
+#define EX_OSFILE 72
+#define EX_CANTCREAT 73
+#define EX_IOERR 74
+#define EX_TEMPFAIL 75
+#define EX_PROTOCOL 76
+#define EX_NOPERM 77
+#define EX_CONFIG 78
+
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+
+/* FIXME: This doesn't belong here either */
+#define LOG_EMERG 0 /* system is unusable */
+#define LOG_ALERT 1 /* action must be taken immediately */
+#define LOG_CRIT 2 /* critical conditions */
+#define LOG_ERR 3 /* error conditions */
+#define LOG_WARNING 4 /* warning conditions */
+#define LOG_NOTICE 5 /* normal but significant condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+
+#endif
+
+#define EX_NOTSPAM 0
+#define EX_ISSPAM 1
+#define EX_TOOBIG 866
+
+/* Aug 14, 2002 bj: Bitflags instead of lots of bool parameters */
+#define SPAMC_MODE_MASK 1
+#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)
+
+/* Jan 30, 2003 ym: added reporting options */
+#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)
+
+/* 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)
+
+/* 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)
+
+
+#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
+
+/* Aug 14, 2002 bj: A struct for storing a message-in-progress */
+typedef enum
+{
+ MESSAGE_NONE,
+ MESSAGE_ERROR,
+ MESSAGE_RAW,
+ MESSAGE_BSMTP,
+ MAX_MESSAGE_TYPE
+} message_type_t;
+
+struct libspamc_private_message;
+
+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 */
+
+ /* Filled in by message_read */
+ message_type_t type;
+ char *raw;
+ unsigned int raw_len; /* Raw message buffer */
+ char *pre;
+ int pre_len; /* Pre-message data (e.g. SMTP commands) */
+ char *msg;
+ unsigned int msg_len; /* The message */
+ char *post;
+ int post_len; /* Post-message data (e.g. SMTP commands) */
+ int content_length;
+
+ /* Filled in by filter_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,
+ a pointer to msg above. */
+
+ /* these members added in SpamAssassin version 2.60: */
+ struct libspamc_private_message *priv;
+};
+
+/*------------------------------------------------------------------------
+ * TRANSPORT (2004/04/16 - SJF)
+ *
+ * The code to connect with the daemon has gotten more complicated: support
+ * for SSL, fallback to multiple hosts, and using UNIX domain sockets. The
+ * code has gotten ugly with way too many parameters being passed all around.
+ *
+ * So we've created this object to hold all the info required to connect with
+ * the remote site, including a self-contained list of all the IP addresses
+ * in the event this is using TCP sockets. These multiple IPs can be obtained
+ * only from DNS returning more than one A record for a single name, and
+ * this allows for fallback.
+ *
+ * We also allow a kind of quasi-load balancing, where we take the list of
+ * A records from DNS and randomize them before starting out - this lets
+ * us spread the load out among multiple servers if desired. The idea for
+ * load balancing goes to Jeremy Zawodny.
+ *
+ * By putting all our data here, we remove "fallback" from being a special
+ * case. We may find ourselves with several IP addresses, but if the user
+ * disables fallback, we set the IP address count to one. Now the connect
+ * code just loops over that same address.
+ */
+#define TRANSPORT_LOCALHOST 0x01 /* TCP to localhost only */
+#define TRANSPORT_TCP 0x02 /* standard TCP socket */
+#define TRANSPORT_UNIX 0x03 /* UNIX domain socket */
+
+#define TRANSPORT_MAX_HOSTS 256 /* max hosts we can failover between */
+
+struct transport
+{
+ int type;
+
+ const char *socketpath; /* for UNIX dommain socket */
+ const char *hostname; /* for TCP sockets */
+
+ unsigned short port; /* for TCP sockets */
+
+ struct in_addr hosts[TRANSPORT_MAX_HOSTS];
+ int nhosts;
+ int flags;
+};
+
+extern void transport_init(struct transport *tp);
+extern int transport_setup(struct transport *tp, int flags);
+
+/* Aug 14, 2002 bj: New interface functions */
+
+/* Read in a message from the fd, with the mode specified in the flags.
+ * Returns EX_OK on success, EX_otherwise on failure. On failure, m may be
+ * either MESSAGE_NONE or MESSAGE_ERROR. */
+int message_read(int in_fd, int flags, struct message *m);
+
+/* Write out a message to the fd, as specified by m->type. Note that
+ * MESSAGE_NONE messages have nothing to write. Also note that if you ran the
+ * message through message_filter with SPAMC_CHECK_ONLY, it will only output
+ * the "score/threshold" line. */
+long message_write(int out_fd, struct message *m);
+
+/* Process the message through the spamd filter, 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_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);
+
+/* Do a message_read->message_filter->message_write sequence, handling errors
+ * appropriately with dump_message or appropriate CHECK_ONLY output. Returns
+ * EX_OK or EX_ISSPAM/EX_NOTSPAM on success, some error EX on error. */
+int message_process(struct transport *trans, char *username, int max_size,
+ int in_fd, int out_fd, const int flags);
+
+/* Cleanup the resources we allocated for storing the message. Call after
+ * you're done processing. */
+void message_cleanup(struct message *m);
+
+/* Aug 14, 2002 bj: This is now legacy, don't use it. */
+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 libspamc_log(int flags, int level, char *msg, ...);
+
+#endif