2 * Copyright 2004 Apache Software Foundation
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "claws-features.h"
28 #define snprintf _snprintf
29 #define vsnprintf _vsnprintf
30 #define strcasecmp stricmp
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <netinet/in.h>
40 #include <netinet/tcp.h>
41 #include <arpa/inet.h>
42 #define closesocket(x) close(x)
45 #ifdef HAVE_SYSEXITS_H
51 #ifdef HAVE_SYS_ERRNO_H
52 #include <sys/errno.h>
57 #ifdef HAVE_SYS_TIME_H
61 #define MAX_CONNECT_RETRIES 3
62 #define CONNECT_RETRY_SLEEP 1
64 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
66 /* SJF 2003/04/25 - now test for macros directly */
68 # define SHUT_RD 0 /* no more receptions */
71 # define SHUT_WR 1 /* no more transmissions */
74 # define SHUT_RDWR 2 /* no more receptions or transmissions */
85 #ifndef HAVE_INADDR_NONE
86 #define INADDR_NONE ((in_addr_t) 0xffffffff)
89 /* jm: turned off for now, it should not be necessary. */
90 #undef USE_TCP_NODELAY
93 /* jm: very conservative figure, should be well out of range on almost all NIXes */
97 #undef DO_CONNECT_DEBUG_SYSLOGS
98 /* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
100 static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
102 /* set EXPANSION_ALLOWANCE to something more than might be
103 added to a message in X-headers and the report template */
104 static const int EXPANSION_ALLOWANCE = 16384;
106 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
107 of the data streams before and after processing by spamd
108 Aug 7 2002 jm: no longer seems to be used
109 static const int NUM_CHECK_BYTES = 32;
112 /* Set the protocol version that this spamc speaks */
113 static const char *PROTOCOL_VERSION = "SPAMC/1.3";
115 /* "private" part of struct message.
116 * we use this instead of the struct message directly, so that we
117 * can add new members without affecting the ABI.
119 struct libspamc_private_message
121 int flags; /* copied from "flags" arg to message_read() */
124 int libspamc_timeout = 0;
127 * translate_connect_errno()
129 * Given a UNIX error number obtained (probably) from "connect(2)",
130 * translate this to a failure code. This module is shared by both
131 * transport modules - UNIX and TCP.
133 * This should ONLY be called when there is an error.
135 static int _translate_connect_errno(int err)
151 return EX_UNAVAILABLE;
164 * Given a socket type (PF_INET or PF_UNIX), try to create this socket
165 * and store the FD in the pointed-to place. If it's successful, do any
166 * other setup required to make the socket ready to use, such as setting
167 * TCP_NODELAY mode, and in any case we return EX_OK if all is well.
169 * Upon failure we return one of the other EX_??? error codes.
171 static int _opensocket(int flags, int type, int *psock)
173 const char *typename;
178 /*----------------------------------------------------------------
179 * Create a few induction variables that are implied by the socket
180 * type given by the user. The typename is strictly used for debug
183 if (type == PF_UNIX) {
184 typename = "PF_UNIX";
187 typename = "PF_INET";
191 #ifdef DO_CONNECT_DEBUG_SYSLOGS
192 libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
195 if ((*psock = socket(type, SOCK_STREAM, proto))
204 /*--------------------------------------------------------
205 * At this point we had a failure creating the socket, and
206 * this is pretty much fatal. Translate the error reason
207 * into something the user can understand.
210 origerr = errno; /* take a copy before syslog() */
211 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
213 origerr = WSAGetLastError();
214 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
218 case EPROTONOSUPPORT:
237 /*----------------------------------------------------------------
238 * Do a bit of setup on the TCP socket if required. Notes above
239 * suggest this is probably not set
241 #ifdef USE_TCP_NODELAY
246 && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
251 origerrno = WSAGetLastError();
258 libspamc_log(flags, LOG_ERR,
260 "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
262 "setsockopt(TCP_NODELAY) failed: %d", origerr);
272 #endif /* USE_TCP_NODELAY */
274 return EX_OK; /* all is well */
278 * try_to_connect_unix()
280 * Given a transport handle that implies using a UNIX domain
281 * socket, try to make a connection to it and store the resulting
282 * file descriptor in *sockptr. Return is EX_OK if we did it,
283 * and some other error code otherwise.
285 static int _try_to_connect_unix(struct transport *tp, int *sockptr)
288 int mysock, status, origerr;
289 struct sockaddr_un addrbuf;
293 assert(sockptr != 0);
294 assert(tp->socketpath != 0);
296 /*----------------------------------------------------------------
297 * If the socket itself can't be created, this is a fatal error.
299 if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
302 /* set up the UNIX domain socket */
303 memset(&addrbuf, 0, sizeof addrbuf);
304 addrbuf.sun_family = AF_UNIX;
305 strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
306 addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
308 #ifdef DO_CONNECT_DEBUG_SYSLOGS
309 libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
313 status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
318 #ifdef DO_CONNECT_DEBUG_SYSLOGS
319 libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
327 libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
328 addrbuf.sun_path, strerror(origerr));
331 return _translate_connect_errno(origerr);
333 (void) tp; /* not used. suppress compiler warning */
334 (void) sockptr; /* not used. suppress compiler warning */
340 * try_to_connect_tcp()
342 * Given a transport that implies a TCP connection, either to
343 * localhost or a list of IP addresses, attempt to connect. The
344 * list of IP addresses has already been randomized (if requested)
345 * and limited to just one if fallback has been enabled.
347 static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
354 assert(sockptr != 0);
355 assert(tp->nhosts > 0);
357 #ifdef DO_CONNECT_DEBUG_SYSLOGS
358 for (numloops = 0; numloops < tp->nhosts; numloops++) {
359 libspamc_log(tp->flags, LOG_ERR, "dbg: %d/%d: %s",
360 numloops + 1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
364 for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
365 struct sockaddr_in addrbuf;
366 const int hostix = numloops % tp->nhosts;
370 /*--------------------------------------------------------
371 * We always start by creating the socket, as we get only
372 * one attempt to connect() on each one. If this fails,
375 if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
378 memset(&addrbuf, 0, sizeof(addrbuf));
380 addrbuf.sin_family = AF_INET;
381 addrbuf.sin_port = htons(tp->port);
382 addrbuf.sin_addr = tp->hosts[hostix];
384 ipaddr = inet_ntoa(addrbuf.sin_addr);
386 #ifdef DO_CONNECT_DEBUG_SYSLOGS
387 libspamc_log(tp->flags, DEBUG_LEVEL,
388 "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
389 ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
393 connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
398 libspamc_log(tp->flags, LOG_ERR,
399 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
400 ipaddr, numloops + 1, MAX_CONNECT_RETRIES, strerror(origerr));
402 origerr = WSAGetLastError();
403 libspamc_log(tp->flags, LOG_ERR,
404 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
405 ipaddr, numloops + 1, MAX_CONNECT_RETRIES, origerr);
409 sleep(CONNECT_RETRY_SLEEP);
412 #ifdef DO_CONNECT_DEBUG_SYSLOGS
413 libspamc_log(tp->flags, DEBUG_LEVEL,
414 "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
422 libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
423 MAX_CONNECT_RETRIES);
425 return _translate_connect_errno(origerr);
428 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
429 * message_dump, lookup_host, message_filter, and message_process, and a bunch
430 * of helper functions.
433 static void _clear_message(struct message *m)
435 m->type = MESSAGE_NONE;
444 m->is_spam = EX_TOOBIG;
449 m->content_length = -1;
452 static int _message_read_raw(int fd, struct message *m)
455 if ((m->raw = malloc(m->max_len + 1)) == NULL)
457 m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
458 if (m->raw_len <= 0) {
464 m->type = MESSAGE_ERROR;
465 if (m->raw_len > m->max_len)
467 m->type = MESSAGE_RAW;
469 m->msg_len = m->raw_len;
471 m->out_len = m->msg_len;
475 static int _message_read_bsmtp(int fd, struct message *m)
481 if ((m->raw = malloc(m->max_len + 1)) == NULL)
484 /* Find the DATA line */
485 m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
486 if (m->raw_len <= 0) {
492 m->type = MESSAGE_ERROR;
493 if (m->raw_len > m->max_len)
496 for (i = 0; i < m->raw_len - 6; i++) {
497 if ((m->raw[i] == '\n') &&
498 (m->raw[i + 1] == 'D' || m->raw[i + 1] == 'd') &&
499 (m->raw[i + 2] == 'A' || m->raw[i + 2] == 'a') &&
500 (m->raw[i + 3] == 'T' || m->raw[i + 3] == 't') &&
501 (m->raw[i + 4] == 'A' || m->raw[i + 4] == 'a') &&
502 ((m->raw[i + 5] == '\r' && m->raw[i + 6] == '\n')
503 || m->raw[i + 5] == '\n')) {
506 if (m->raw[i - 1] == '\r')
510 m->msg_len = m->raw_len - i;
517 /* Find the end-of-DATA line */
519 for (i = j = 0; i < m->msg_len; i++) {
520 if (prev == '\n' && m->msg[i] == '.') {
521 /* Dot at the beginning of a line */
522 if ((m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')
523 || m->msg[i + 1] == '\n') {
524 /* Lone dot! That's all, folks */
525 m->post = m->msg + i;
526 m->post_len = m->msg_len - i;
530 else if (m->msg[i + 1] == '.') {
531 /* Escaping dot, eliminate. */
534 } /* Else an ordinary dot, drop down to ordinary char handler */
537 m->msg[j++] = m->msg[i];
540 m->type = MESSAGE_BSMTP;
542 m->out_len = m->msg_len;
546 int message_read(int fd, int flags, struct message *m)
548 libspamc_timeout = 0;
550 /* create the "private" part of the struct message */
551 m->priv = malloc(sizeof(struct libspamc_private_message));
552 if (m->priv == NULL) {
553 libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
556 m->priv->flags = flags;
558 switch (flags & SPAMC_MODE_MASK) {
560 return _message_read_raw(fd, m);
562 case SPAMC_BSMTP_MODE:
563 return _message_read_bsmtp(fd, m);
566 libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
567 flags & SPAMC_MODE_MASK);
572 long message_write(int fd, struct message *m)
579 if (m->priv->flags & SPAMC_CHECK_ONLY) {
580 if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
581 return full_write(fd, 1, m->out, m->out_len);
585 libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
591 /* else we're not in CHECK_ONLY mode */
594 libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
598 return full_write(fd, 1, m->raw, m->raw_len);
601 return full_write(fd, 1, m->out, m->out_len);
604 total = full_write(fd, 1, m->pre, m->pre_len);
605 for (i = 0; i < m->out_len;) {
606 jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
607 for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
608 if (i + 1 < m->out_len && m->out[i] == '\n'
609 && m->out[i + 1] == '.') {
610 if (j > jlimit - 4) {
611 break; /* avoid overflow */
613 buffer[j++] = m->out[i++];
614 buffer[j++] = m->out[i++];
618 buffer[j++] = m->out[i++];
621 total += full_write(fd, 1, buffer, j);
623 return total + full_write(fd, 1, m->post, m->post_len);
626 libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
631 void message_dump(int in_fd, int out_fd, struct message *m)
636 if (m != NULL && m->type != MESSAGE_NONE) {
637 message_write(out_fd, m);
639 while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
640 if (bytes != full_write(out_fd, 1, buf, bytes)) {
641 libspamc_log(m->priv->flags, LOG_ERR, "oops! message_dump of %d returned different",
648 _spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
649 char *buf, size_t *lenp, size_t bufsiz)
658 /* Now, read from spamd */
659 for (len = 0; len < bufsiz - 1; len++) {
660 if (flags & SPAMC_USE_SSL) {
661 bytesread = ssl_timeout_read(ssl, buf + len, 1);
664 bytesread = fd_timeout_read(sock, 0, buf + len, 1);
667 if (bytesread <= 0) {
668 failureval = EX_IOERR;
672 if (buf[len] == '\n') {
674 if (len > 0 && buf[len - 1] == '\r') {
683 libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
684 failureval = EX_TOOBIG;
691 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
692 * work around using our own locale-independent float-parser code.
694 static float _locale_safe_string_to_float(char *buf, int siz)
701 buf[siz - 1] = '\0'; /* ensure termination */
703 /* ok, let's illustrate using "100.033" as an example... */
710 ret = (float) (strtol(buf, &dot, 10));
714 if (dot != NULL && *dot != '.') {
718 /* ex: ret == 100.0 */
721 postdot = (float) (strtol(cp, NULL, 10));
722 if (postdot == 0.0) {
726 /* ex: postdot == 33.0, cp="033" */
728 /* now count the number of decimal places and figure out what power of 10 to use */
730 while (*cp != '\0') {
736 * cp="033", divider=1
737 * cp="33", divider=10
738 * cp="3", divider=100
739 * cp="", divider=1000
743 ret -= (postdot / ((float) divider));
746 ret += (postdot / ((float) divider));
748 /* ex: ret == 100.033, tada! ... hopefully */
754 _handle_spamd_header(struct message *m, int flags, char *buf, int len)
757 char s_str[21], t_str[21];
759 UNUSED_VARIABLE(len);
761 /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
762 * let's stick with it for this parser.
763 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
764 * work around using our own locale-independent float-parser code.
766 if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
767 m->score = _locale_safe_string_to_float(s_str, 20);
768 m->threshold = _locale_safe_string_to_float(t_str, 20);
770 /* set bounds on these to ensure no buffer overflow in the sprintf */
773 else if (m->score < -1e10)
775 if (m->threshold > 1e10)
777 else if (m->threshold < -1e10)
778 m->threshold = -1e10;
780 /* Format is "Spam: x; y / x" */
782 strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
784 if (flags & SPAMC_CHECK_ONLY) {
785 m->out_len = sprintf(m->out,
786 "%.1f/%.1f\n", m->score, m->threshold);
788 else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
789 || (flags & SPAMC_REPORT)) {
790 m->out_len = sprintf(m->out,
791 "%.1f/%.1f\n", m->score, m->threshold);
796 else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
797 if (m->content_length < 0) {
798 libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
805 libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
809 int message_filter(struct transport *tp, const char *username,
810 int flags, struct message *m)
813 size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
825 if (flags & SPAMC_USE_SSL) {
827 SSLeay_add_ssl_algorithms();
828 meth = SSLv2_client_method();
829 SSL_load_error_strings();
830 ctx = SSL_CTX_new(meth);
832 UNUSED_VARIABLE(ssl);
833 UNUSED_VARIABLE(meth);
834 UNUSED_VARIABLE(ctx);
835 libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
840 m->is_spam = EX_TOOBIG;
841 if ((m->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
842 failureval = EX_OSERR;
848 /* Build spamd protocol header */
849 if (flags & SPAMC_CHECK_ONLY)
850 strcpy(buf, "CHECK ");
851 else if (flags & SPAMC_REPORT_IFSPAM)
852 strcpy(buf, "REPORT_IFSPAM ");
853 else if (flags & SPAMC_REPORT)
854 strcpy(buf, "REPORT ");
855 else if (flags & SPAMC_SYMBOLS)
856 strcpy(buf, "SYMBOLS ");
858 strcpy(buf, "PROCESS ");
861 if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
864 m->out_len = m->msg_len;
868 strcat(buf, PROTOCOL_VERSION);
872 if (username != NULL) {
873 if (strlen(username) + 8 >= (bufsiz - len)) {
876 m->out_len = m->msg_len;
879 strcpy(buf + len, "User: ");
880 strcat(buf + len, username);
881 strcat(buf + len, "\r\n");
882 len += strlen(buf + len);
884 if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
887 m->out_len = m->msg_len;
890 len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
892 libspamc_timeout = m->timeout;
895 rc = _try_to_connect_unix(tp, &sock);
897 rc = _try_to_connect_tcp(tp, &sock);
902 m->out_len = m->msg_len;
903 return rc; /* use the error code try_to_connect_*() gave us. */
906 if (flags & SPAMC_USE_SSL) {
909 SSL_set_fd(ssl, sock);
915 if (flags & SPAMC_USE_SSL) {
917 SSL_write(ssl, buf, len);
918 SSL_write(ssl, m->msg, m->msg_len);
922 full_write(sock, 0, buf, len);
923 full_write(sock, 0, m->msg, m->msg_len);
924 shutdown(sock, SHUT_WR);
927 /* ok, now read and parse it. SPAMD/1.2 line first... */
929 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
930 if (failureval != EX_OK) {
934 if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
935 libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
936 failureval = EX_PROTOCOL;
941 version = _locale_safe_string_to_float(versbuf, 20);
943 libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
945 failureval = EX_PROTOCOL;
951 m->is_spam = EX_TOOBIG;
954 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
955 if (failureval != EX_OK) {
959 if (len == 0 && buf[0] == '\0') {
960 break; /* end of headers */
963 if (_handle_spamd_header(m, flags, buf, len) < 0) {
964 failureval = EX_PROTOCOL;
969 len = 0; /* overwrite those headers */
971 if (flags & SPAMC_CHECK_ONLY) {
974 if (m->is_spam == EX_TOOBIG) {
975 /* We should have gotten headers back... Damnit. */
976 failureval = EX_PROTOCOL;
982 if (m->content_length < 0) {
983 /* should have got a length too. */
984 failureval = EX_PROTOCOL;
988 /* have we already got something in the buffer (e.g. REPORT and
989 * REPORT_IFSPAM both create a line from the "Spam:" hdr)? If
990 * so, add the size of that so our sanity check passes.
992 if (m->out_len > 0) {
993 m->content_length += m->out_len;
996 if (flags & SPAMC_USE_SSL) {
997 len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
998 m->max_len + EXPANSION_ALLOWANCE + 1 -
1000 m->max_len + EXPANSION_ALLOWANCE + 1 -
1004 len = full_read(sock, 0, m->out + m->out_len,
1005 m->max_len + EXPANSION_ALLOWANCE + 1 - m->out_len,
1006 m->max_len + EXPANSION_ALLOWANCE + 1 -
1011 if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
1012 failureval = EX_TOOBIG;
1017 shutdown(sock, SHUT_RD);
1021 libspamc_timeout = 0;
1023 if (m->out_len != m->content_length) {
1024 libspamc_log(flags, LOG_ERR,
1025 "failed sanity check, %d bytes claimed, %d bytes seen",
1026 m->content_length, m->out_len);
1027 failureval = EX_PROTOCOL;
1036 m->out_len = m->msg_len;
1040 libspamc_timeout = 0;
1042 if (flags & SPAMC_USE_SSL) {
1052 int message_process(struct transport *trans, char *username, int max_size,
1053 int in_fd, int out_fd, const int flags)
1058 m.type = MESSAGE_NONE;
1060 m.max_len = max_size;
1061 ret = message_read(in_fd, flags, &m);
1064 ret = message_filter(trans, username, flags, &m);
1067 if (message_write(out_fd, &m) < 0)
1069 if (m.is_spam != EX_TOOBIG) {
1070 message_cleanup(&m);
1073 message_cleanup(&m);
1077 if (flags & SPAMC_CHECK_ONLY) {
1078 full_write(out_fd, 1, "0/0\n", 4);
1079 message_cleanup(&m);
1083 message_dump(in_fd, out_fd, &m);
1084 message_cleanup(&m);
1089 void message_cleanup(struct message *m)
1093 if (m->raw != NULL && m->raw != m->out)
1095 if (m->priv != NULL && (char*)m->priv != m->out && (char*)m->priv != m->raw)
1100 /* Aug 14, 2002 bj: Obsolete! */
1101 int process_message(struct transport *tp, char *username, int max_size,
1102 int in_fd, int out_fd, const int my_check_only,
1103 const int my_safe_fallback)
1107 flags = SPAMC_RAW_MODE;
1109 flags |= SPAMC_CHECK_ONLY;
1110 if (my_safe_fallback)
1111 flags |= SPAMC_SAFE_FALLBACK;
1113 return message_process(tp, username, max_size, in_fd, out_fd, flags);
1119 * Given a pointer to a transport structure, set it to "all empty".
1120 * The default is a localhost connection.
1122 void transport_init(struct transport *tp)
1126 memset(tp, 0, sizeof *tp);
1128 tp->type = TRANSPORT_LOCALHOST;
1136 * Given the transport object that contains one or more IP addresses
1137 * in this "hosts" list, rotate it by a random number of shifts to
1138 * randomize them - this is a kind of load balancing. It's possible
1139 * that the random number will be 0, which says not to touch. We don't
1140 * do anything unless
1143 static void _randomize_hosts(struct transport *tp)
1149 if (tp->nhosts <= 1)
1152 rnum = rand() % tp->nhosts;
1154 while (rnum-- > 0) {
1155 struct in_addr tmp = tp->hosts[0];
1158 for (i = 1; i < tp->nhosts; i++)
1159 tp->hosts[i - 1] = tp->hosts[i];
1161 tp->hosts[i - 1] = tmp;
1168 * Given a "transport" object that says how we're to connect to the
1169 * spam daemon, perform all the initial setup required to make the
1170 * connection process a smooth one. The main work is to do the host
1171 * name lookup and copy over all the IP addresses to make a local copy
1172 * so they're not kept in the resolver's static state.
1174 * Here we also manage quasi-load balancing and failover: if we're
1175 * doing load balancing, we randomly "rotate" the list to put it in
1176 * a different order, and then if we're not doing failover we limit
1177 * the hosts to just one. This way *all* connections are done with
1178 * the intention of failover - makes the code a bit more clear.
1180 int transport_setup(struct transport *tp, int flags)
1182 struct hostent *hp = 0;
1186 /* Start Winsock up */
1189 if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1190 g_print("WSAStartup() returned error code %d\n", nCode);
1202 case TRANSPORT_UNIX:
1203 assert(tp->socketpath != 0);
1206 case TRANSPORT_LOCALHOST:
1207 tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1212 if (NULL == (hp = gethostbyname(tp->hostname))) {
1213 int origherr = h_errno; /* take a copy before syslog() */
1215 libspamc_log(flags, LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
1216 tp->hostname, origherr);
1218 case HOST_NOT_FOUND:
1229 /*--------------------------------------------------------
1230 * If we have no hosts at all, or if they are some other
1231 * kind of address family besides IPv4, then we really
1232 * just have no hosts at all.
1234 if (hp->h_addr_list[0] == 0) {
1235 /* no hosts in this list */
1239 if (hp->h_length != sizeof tp->hosts[0]
1240 || hp->h_addrtype != AF_INET) {
1241 /* FAIL - bad size/protocol/family? */
1245 /*--------------------------------------------------------
1246 * Copy all the IP addresses into our private structure.
1247 * This gets them out of the resolver's static area and
1248 * means we won't ever walk all over the list with other
1253 for (addrp = hp->h_addr_list; *addrp; addrp++) {
1254 if (tp->nhosts >= TRANSPORT_MAX_HOSTS - 1) {
1255 libspamc_log(flags, LOG_ERR, "hit limit of %d hosts, ignoring remainder",
1256 TRANSPORT_MAX_HOSTS - 1);
1260 memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
1265 /*--------------------------------------------------------
1266 * QUASI-LOAD-BALANCING
1268 * If the user wants to do quasi load balancing, "rotate"
1269 * the list by a random amount based on the current time.
1270 * This may later be truncated to a single item. This is
1271 * meaningful only if we have more than one host.
1273 if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
1274 _randomize_hosts(tp);
1277 /*--------------------------------------------------------
1278 * If the user wants no fallback, simply truncate the host
1279 * list to just one - this pretends that this is the extent
1280 * of our connection list - then it's not a special case.
1282 if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
1283 /* truncating list */
1290 /* --------------------------------------------------------------------------- */
1292 #define LOG_BUFSIZ 1023
1295 libspamc_log (int flags, int level, char *msg, ...)
1298 char buf[LOG_BUFSIZ+1];
1303 if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
1304 /* create a log-line buffer */
1305 len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
1306 len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
1308 /* avoid buffer overflow */
1309 if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
1311 len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
1312 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
1313 if (write (2, buf, len) != len)
1316 vsnprintf(buf, LOG_BUFSIZ, msg, ap);
1317 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
1319 syslog (level, "%s", buf);
1321 (void) level; /* not used. suppress compiler warning */
1322 g_printerr ("%s\n", buf);
1329 /* --------------------------------------------------------------------------- */
1332 * Unit tests. Must be built externally, e.g.:
1334 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
1338 #ifdef LIBSPAMC_UNIT_TESTS
1340 static void _test_locale_safe_string_to_float_val(float input)
1342 char inputstr[99], cmpbuf1[99], cmpbuf2[99];
1345 /* sprintf instead of snprintf is safe here because it is only a controlled test */
1346 sprintf(inputstr, "%f", input);
1347 output = _locale_safe_string_to_float(inputstr, 99);
1348 if (input == output) {
1352 /* could be a rounding error. print as string and compare those */
1353 sprintf(cmpbuf1, "%f", input);
1354 sprintf(cmpbuf2, "%f", output);
1355 if (!strcmp(cmpbuf1, cmpbuf2)) {
1359 g_print("FAIL: input=%f != output=%f\n", input, output);
1362 static void unit_test_locale_safe_string_to_float(void)
1364 float statictestset[] = { /* will try both +ve and -ve */
1365 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
1366 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
1367 0.0 /* end of set constant */
1372 g_print("starting unit_test_locale_safe_string_to_float\n");
1373 /* tests of precision */
1374 for (i = 0; statictestset[i] != 0.0; i++) {
1375 _test_locale_safe_string_to_float_val(statictestset[i]);
1376 _test_locale_safe_string_to_float_val(-statictestset[i]);
1377 _test_locale_safe_string_to_float_val(1 - statictestset[i]);
1378 _test_locale_safe_string_to_float_val(1 + statictestset[i]);
1380 /* now exhaustive, in steps of 0.01 */
1381 for (num = -1000.0; num < 1000.0; num += 0.01) {
1382 _test_locale_safe_string_to_float_val(num);
1384 g_print("finished unit_test_locale_safe_string_to_float\n");
1387 void do_libspamc_unit_tests(void)
1389 unit_test_locale_safe_string_to_float();
1393 #endif /* LIBSPAMC_UNIT_TESTS */