2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to you under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at:
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
20 Compile with extra warnings -- gcc only, not suitable for use as default:
22 gcc -Wextra -Wdeclaration-after-statement -Wall -g -O2 spamc/spamc.c \
23 spamc/getopt.c spamc/libspamc.c spamc/utils.c -o spamc/spamc -ldl -lz
27 #include "claws-features.h"
36 #define snprintf _snprintf
37 #define vsnprintf _vsnprintf
38 #define strcasecmp stricmp
44 #include <sys/types.h>
45 #include <sys/socket.h>
46 #include <netinet/in.h>
48 #include <netinet/tcp.h>
49 #include <arpa/inet.h>
50 #define closesocket(x) close(x)
53 #ifdef HAVE_SYSEXITS_H
59 #ifdef HAVE_SYS_ERRNO_H
60 #include <sys/errno.h>
65 #ifdef HAVE_SYS_TIME_H
72 /* must load *after* errno.h, Bug 6697 */
73 #include "file-utils.h"
76 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
78 /* SJF 2003/04/25 - now test for macros directly */
80 # define SHUT_RD 0 /* no more receptions */
83 # define SHUT_WR 1 /* no more transmissions */
86 # define SHUT_RDWR 2 /* no more receptions or transmissions */
94 #define spamc_get_errno() WSAGetLastError()
96 #define spamc_get_errno() errno
103 #ifndef HAVE_INADDR_NONE
104 #define INADDR_NONE ((in_addr_t) 0xffffffff)
107 /* jm: turned off for now, it should not be necessary. */
108 #undef USE_TCP_NODELAY
111 /* jm: very conservative figure, should be well out of range on almost all NIXes */
115 #undef DO_CONNECT_DEBUG_SYSLOGS
117 #define DO_CONNECT_DEBUG_SYSLOGS 1
118 #define CONNECT_DEBUG_LEVEL LOG_DEBUG
121 /* bug 4477 comment 14 */
123 #define SPAMC_MAXHOST NI_MAXHOST
125 #define SPAMC_MAXHOST 256
129 #define SPAMC_MAXSERV NI_MAXSERV
131 #define SPAMC_MAXSERV 256
134 /* static const int ESC_PASSTHROUGHRAW = EX__MAX + 666; No longer seems to be used */
136 /* set EXPANSION_ALLOWANCE to something more than might be
137 added to a message in X-headers and the report template */
138 static const int EXPANSION_ALLOWANCE
= 16384;
140 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
141 of the data streams before and after processing by spamd
142 Aug 7 2002 jm: no longer seems to be used
143 static const int NUM_CHECK_BYTES = 32;
146 /* Set the protocol version that this spamc speaks */
147 static const char *PROTOCOL_VERSION
= "SPAMC/1.5";
149 /* "private" part of struct message.
150 * we use this instead of the struct message directly, so that we
151 * can add new members without affecting the ABI.
153 struct libspamc_private_message
155 int flags
; /* copied from "flags" arg to message_read() */
156 int alloced_size
; /* allocated space for the "out" buffer */
158 void (*spamc_header_callback
)(struct message
*m
, int flags
, char *buf
, int len
);
159 void (*spamd_header_callback
)(struct message
*m
, int flags
, const char *buf
, int len
);
162 void (*libspamc_log_callback
)(int flags
, int level
, char *msg
, va_list args
) = NULL
;
164 int libspamc_timeout
= 0;
165 int libspamc_connect_timeout
= 0; /* Sep 8, 2008 mrgus: separate connect timeout */
168 * translate_connect_errno()
170 * Given a UNIX error number obtained (probably) from "connect(2)",
171 * translate this to a failure code. This module is shared by both
172 * transport modules - UNIX and TCP.
174 * This should ONLY be called when there is an error.
176 static int _translate_connect_errno(int err
)
192 return EX_UNAVAILABLE
;
205 * Given a socket family (PF_INET or PF_INET6 or PF_UNIX), try to
206 * create this socket and store the FD in the pointed-to place.
207 * If it's successful, do any other setup required to make the socket
208 * ready to use, such as setting TCP_NODELAY mode, and in any case
209 * we return EX_OK if all is well.
211 * Upon failure we return one of the other EX_??? error codes.
213 #ifdef SPAMC_HAS_ADDRINFO
214 static int _opensocket(int flags
, struct addrinfo
*res
, int *psock
)
217 static int _opensocket(int flags
, int type
, int *psock
)
221 const char *typename
;
229 /*----------------------------------------------------------------
230 * Create a few induction variables that are implied by the socket
231 * type given by the user. The typename is strictly used for debug
234 #ifdef SPAMC_HAS_ADDRINFO
235 switch(res
->ai_family
) {
237 typename
= "PF_UNIX";
240 typename
= "PF_INET";
243 typename
= "PF_INET6";
246 typename
= "Unknown";
250 if (type
== PF_UNIX
) {
251 typename
= "PF_UNIX";
254 typename
= "PF_INET";
259 #ifdef DO_CONNECT_DEBUG_SYSLOGS
260 libspamc_log(flags
, CONNECT_DEBUG_LEVEL
, "dbg: create socket(%s)", typename
);
263 #ifdef SPAMC_HAS_ADDRINFO
264 if ((*psock
= socket(res
->ai_family
, res
->ai_socktype
, res
->ai_protocol
))
266 if ((*psock
= socket(type
, SOCK_STREAM
, proto
))
275 /*--------------------------------------------------------
276 * At this point we had a failure creating the socket, and
277 * this is pretty much fatal. Translate the error reason
278 * into something the user can understand.
280 origerr
= spamc_get_errno();
282 libspamc_log(flags
, LOG_ERR
, "socket(%s) to spamd failed: %s", typename
, strerror(origerr
));
284 libspamc_log(flags
, LOG_ERR
, "socket(%s) to spamd failed: %d", typename
, origerr
);
288 case EPROTONOSUPPORT
:
307 /* bug 4344: makes timeout functional on Win32 */
308 socktout
= libspamc_timeout
* 1000;
310 && setsockopt(*psock
, SOL_SOCKET
, SO_RCVTIMEO
, (char *)&socktout
, sizeof(socktout
)) != 0)
313 origerr
= spamc_get_errno();
320 libspamc_log(flags
, LOG_ERR
, "setsockopt(SO_RCVTIMEO) failed: %d", origerr
);
330 /*----------------------------------------------------------------
331 * Do a bit of setup on the TCP socket if required. Notes above
332 * suggest this is probably not set
334 #ifdef USE_TCP_NODELAY
338 if ( ( type
== PF_INET
342 ) && setsockopt(*psock
, 0, TCP_NODELAY
, &one
, sizeof one
) != 0) {
343 origerr
= spamc_get_errno();
349 libspamc_log(flags
, LOG_ERR
,
351 "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr
));
353 "setsockopt(TCP_NODELAY) failed: %d", origerr
);
363 #endif /* USE_TCP_NODELAY */
365 return EX_OK
; /* all is well */
369 * try_to_connect_unix()
371 * Given a transport handle that implies using a UNIX domain
372 * socket, try to make a connection to it and store the resulting
373 * file descriptor in *sockptr. Return is EX_OK if we did it,
374 * and some other error code otherwise.
376 static int _try_to_connect_unix(struct transport
*tp
, int *sockptr
)
379 int mysock
, status
, origerr
;
380 struct sockaddr_un addrbuf
;
381 #ifdef SPAMC_HAS_ADDRINFO
382 struct addrinfo hints
, *res
;
389 assert(sockptr
!= 0);
390 assert(tp
->socketpath
!= 0);
392 #ifdef SPAMC_HAS_ADDRINFO
393 memset(&hints
, 0, sizeof(hints
));
394 hints
.ai_family
= PF_UNIX
;
395 hints
.ai_socktype
= SOCK_STREAM
;
396 hints
.ai_protocol
= 0;
399 /*----------------------------------------------------------------
400 * If the socket itself can't be created, this is a fatal error.
402 if ((ret
= _opensocket(tp
->flags
, res
, &mysock
)) != EX_OK
)
405 /* set up the UNIX domain socket */
406 memset(&addrbuf
, 0, sizeof addrbuf
);
407 addrbuf
.sun_family
= AF_UNIX
;
408 strncpy(addrbuf
.sun_path
, tp
->socketpath
, sizeof addrbuf
.sun_path
- 1);
409 addrbuf
.sun_path
[sizeof addrbuf
.sun_path
- 1] = '\0';
411 #ifdef DO_CONNECT_DEBUG_SYSLOGS
412 libspamc_log(tp
->flags
, CONNECT_DEBUG_LEVEL
, "dbg: connect(AF_UNIX) to spamd at %s",
416 status
= timeout_connect(mysock
, (struct sockaddr
*) &addrbuf
, sizeof(addrbuf
));
421 #ifdef DO_CONNECT_DEBUG_SYSLOGS
422 libspamc_log(tp
->flags
, CONNECT_DEBUG_LEVEL
, "dbg: connect(AF_UNIX) ok");
430 libspamc_log(tp
->flags
, LOG_ERR
, "connect(AF_UNIX) to spamd using --socket='%s' failed: %s",
431 addrbuf
.sun_path
, strerror(origerr
));
434 return _translate_connect_errno(origerr
);
436 (void) tp
; /* not used. suppress compiler warning */
437 (void) sockptr
; /* not used. suppress compiler warning */
443 * try_to_connect_tcp()
445 * Given a transport that implies a TCP connection, either to
446 * localhost or a list of IP addresses, attempt to connect. The
447 * list of IP addresses has already been randomized (if requested)
448 * and limited to just one if fallback has been enabled.
450 static int _try_to_connect_tcp(const struct transport
*tp
, int *sockptr
)
455 #ifdef SPAMC_HAS_ADDRINFO
456 struct addrinfo
*res
= NULL
;
457 char port
[SPAMC_MAXSERV
-1]; /* port, for logging */
461 char host
[SPAMC_MAXHOST
-1]; /* hostname, for logging */
462 int connect_retries
, retry_sleep
;
465 assert(sockptr
!= 0);
466 assert(tp
->nhosts
> 0);
469 retry_sleep
= tp
->retry_sleep
;
470 connect_retries
= tp
->connect_retries
;
471 if (connect_retries
== 0) {
474 if (retry_sleep
< 0) {
478 for (numloops
= 0; numloops
< connect_retries
; numloops
++) {
479 const int hostix
= numloops
% tp
->nhosts
;
483 /*--------------------------------------------------------
484 * We always start by creating the socket, as we get only
485 * one attempt to connect() on each one. If this fails,
489 #ifdef SPAMC_HAS_ADDRINFO
490 res
= tp
->hosts
[hostix
];
492 #ifdef DO_CONNECT_DEBUG_SYSLOGS
494 switch(res
->ai_family
) {
507 if ((ret
= _opensocket(tp
->flags
, res
, &mysock
)) != EX_OK
) {
512 getnameinfo(res
->ai_addr
, res
->ai_addrlen
,
515 NI_NUMERICHOST
|NI_NUMERICSERV
);
517 #ifdef DO_CONNECT_DEBUG_SYSLOGS
518 libspamc_log(tp
->flags
, CONNECT_DEBUG_LEVEL
,
519 "dbg: connect(%s) to spamd (host %s, port %s) (try #%d of %d)",
520 family
, host
, port
, numloops
+ 1, connect_retries
);
523 /* this is special-cased so that we have an address we can
524 * safely use as an "always fail" test case */
525 if (!strcmp(host
, "255.255.255.255")) {
526 libspamc_log(tp
->flags
, LOG_ERR
,
527 "connect to spamd on %s failed, broadcast addr",
532 status
= timeout_connect(mysock
, res
->ai_addr
, res
->ai_addrlen
);
533 if (status
!= 0) origerr
= spamc_get_errno();
537 struct sockaddr_in addrbuf
;
539 const char* family
="AF_INET";
540 if ((ret
= _opensocket(tp
->flags
, PF_INET
, &mysock
)) != EX_OK
)
543 memset(&addrbuf
, 0, sizeof(addrbuf
));
545 addrbuf
.sin_family
= AF_INET
;
546 addrbuf
.sin_port
= htons(tp
->port
);
547 addrbuf
.sin_addr
= tp
->hosts
[hostix
];
549 ipaddr
= inet_ntoa(addrbuf
.sin_addr
);
551 /* make a copy in host, for logging (bug 5577) */
552 strncpy (host
, ipaddr
, sizeof(host
) - 1);
554 #ifdef DO_CONNECT_DEBUG_SYSLOGS
555 libspamc_log(tp
->flags
, LOG_DEBUG
,
556 "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
557 ipaddr
, numloops
+ 1, connect_retries
);
560 /* this is special-cased so that we have an address we can
561 * safely use as an "always fail" test case */
562 if (!strcmp(ipaddr
, "255.255.255.255")) {
563 libspamc_log(tp
->flags
, LOG_ERR
,
564 "connect to spamd on %s failed, broadcast addr",
569 status
= timeout_connect(mysock
, (struct sockaddr
*) &addrbuf
,
571 if (status
!= 0) origerr
= spamc_get_errno();
579 innocent
= origerr
== ECONNREFUSED
&& numloops
+1 < tp
->nhosts
;
580 libspamc_log(tp
->flags
, innocent
? LOG_DEBUG
: LOG_ERR
,
581 "connect to spamd on %s failed, retrying (#%d of %d): %s",
582 host
, numloops
+1, connect_retries
,
591 #ifdef DO_CONNECT_DEBUG_SYSLOGS
592 libspamc_log(tp
->flags
, CONNECT_DEBUG_LEVEL
,
593 "dbg: connect(%s) to spamd done",family
);
599 #ifdef SPAMC_HAS_ADDRINFO
603 if (numloops
+1 < connect_retries
&& !innocent
) sleep(retry_sleep
);
604 } /* for(numloops...) */
606 libspamc_log(tp
->flags
, LOG_ERR
,
607 "connection attempt to spamd aborted after %d retries",
610 return _translate_connect_errno(origerr
);
613 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
614 * message_dump, lookup_host, message_filter, and message_process, and a bunch
615 * of helper functions.
618 static void _clear_message(struct message
*m
)
620 m
->type
= MESSAGE_NONE
;
629 m
->is_spam
= EX_TOOBIG
;
635 m
->content_length
= -1;
638 static void _free_zlib_buffer(unsigned char **zlib_buf
, int *zlib_bufsiz
)
647 static void _use_msg_for_out(struct message
*m
)
653 m
->out_len
= m
->msg_len
;
656 static int _message_read_raw(int fd
, struct message
*m
)
659 if ((m
->raw
= malloc(m
->max_len
+ 1)) == NULL
)
661 m
->raw_len
= full_read(fd
, 1, m
->raw
, m
->max_len
+ 1, m
->max_len
+ 1);
662 if (m
->raw_len
<= 0) {
668 m
->type
= MESSAGE_ERROR
;
669 if (m
->raw_len
> (int) m
->max_len
)
671 libspamc_log(m
->priv
->flags
, LOG_NOTICE
,
672 "skipped message, greater than max message size (%d bytes)",
676 m
->type
= MESSAGE_RAW
;
678 m
->msg_len
= m
->raw_len
;
680 m
->out_len
= m
->msg_len
;
684 static int _message_read_bsmtp(int fd
, struct message
*m
)
686 unsigned int i
, j
, p_len
;
691 if ((m
->raw
= malloc(m
->max_len
+ 1)) == NULL
)
694 /* Find the DATA line */
695 m
->raw_len
= full_read(fd
, 1, m
->raw
, m
->max_len
+ 1, m
->max_len
+ 1);
696 if (m
->raw_len
<= 0) {
702 m
->type
= MESSAGE_ERROR
;
703 if (m
->raw_len
> (int) m
->max_len
)
706 /* Search for \nDATA\n which marks start of actual message */
707 while ((p_len
= (m
->raw_len
- (p
- m
->raw
))) > 8) { /* leave room for at least \nDATA\n.\n */
708 char* q
= memchr(p
, '\n', p_len
- 8); /* find next \n then see if start of \nDATA\n */
709 if (q
== NULL
) break;
711 if (((q
[0]|0x20) == 'd') && /* case-insensitive ASCII comparison */
712 ((q
[1]|0x20) == 'a') &&
713 ((q
[2]|0x20) == 't') &&
714 ((q
[3]|0x20) == 'a')) {
716 if (q
[0] == '\r') ++q
;
717 if (*(q
++) == '\n') { /* leave q at start of message if we found it */
719 m
->pre_len
= q
- m
->raw
;
720 m
->msg_len
= m
->raw_len
- m
->pre_len
;
724 p
= q
; /* the above code ensures no other '\n' comes before q */
729 /* ensure this is >= 0 */
730 if (m
->msg_len
< 0) {
734 /* Find the end-of-DATA line */
736 for (i
= j
= 0; i
< (unsigned int) m
->msg_len
; i
++) {
737 if (prev
== '\n' && m
->msg
[i
] == '.') {
738 /* Dot at the beginning of a line */
739 if (((int) (i
+1) == m
->msg_len
)
740 || ((int) (i
+1) < m
->msg_len
&& m
->msg
[i
+ 1] == '\n')
741 || ((int) (i
+2) < m
->msg_len
&& m
->msg
[i
+ 1] == '\r' && m
->msg
[i
+ 2] == '\n')) {
742 /* Lone dot! That's all, folks */
743 m
->post
= m
->msg
+ i
;
744 m
->post_len
= m
->msg_len
- i
;
748 else if ((int) (i
+1) < m
->msg_len
&& m
->msg
[i
+ 1] == '.') {
749 /* Escaping dot, eliminate. */
752 } /* Else an ordinary dot, drop down to ordinary char handler */
755 m
->msg
[j
++] = m
->msg
[i
];
758 /* if bad format with no end "\n.\n", error out */
761 m
->type
= MESSAGE_BSMTP
;
763 m
->out_len
= m
->msg_len
;
767 int message_read(int fd
, int flags
, struct message
*m
)
771 libspamc_timeout
= 0;
773 /* create the "private" part of the struct message */
774 m
->priv
= malloc(sizeof(struct libspamc_private_message
));
775 if (m
->priv
== NULL
) {
776 libspamc_log(flags
, LOG_ERR
, "message_read: malloc failed");
779 m
->priv
->flags
= flags
;
780 m
->priv
->alloced_size
= 0;
781 m
->priv
->spamc_header_callback
= 0;
782 m
->priv
->spamd_header_callback
= 0;
784 if (flags
& SPAMC_PING
) {
789 switch (flags
& SPAMC_MODE_MASK
) {
791 return _message_read_raw(fd
, m
);
793 case SPAMC_BSMTP_MODE
:
794 return _message_read_bsmtp(fd
, m
);
797 libspamc_log(flags
, LOG_ERR
, "message_read: Unknown mode %d",
798 flags
& SPAMC_MODE_MASK
);
803 long message_write(int fd
, struct message
*m
)
812 if (m
->priv
->flags
& (SPAMC_CHECK_ONLY
|SPAMC_PING
)) {
813 if (m
->is_spam
== EX_ISSPAM
|| m
->is_spam
== EX_NOTSPAM
) {
814 return full_write(fd
, 1, m
->out
, m
->out_len
);
818 libspamc_log(m
->priv
->flags
, LOG_ERR
, "oops! SPAMC_CHECK_ONLY is_spam: %d",
824 /* else we're not in CHECK_ONLY mode */
827 libspamc_log(m
->priv
->flags
, LOG_ERR
, "Cannot write this message, it's MESSAGE_NONE!");
831 return full_write(fd
, 1, m
->raw
, m
->raw_len
);
834 return full_write(fd
, 1, m
->out
, m
->out_len
);
837 total
= full_write(fd
, 1, m
->pre
, m
->pre_len
);
838 for (i
= 0; i
< m
->out_len
;) {
839 jlimit
= (off_t
) (sizeof(buffer
) / sizeof(*buffer
) - 4);
840 for (j
= 0; i
< (off_t
) m
->out_len
&& j
< jlimit
;) {
841 if (i
+ 1 < m
->out_len
&& m
->out
[i
] == '\n'
842 && m
->out
[i
+ 1] == '.') {
843 if (j
> jlimit
- 4) {
844 break; /* avoid overflow */
846 buffer
[j
++] = m
->out
[i
++];
847 buffer
[j
++] = m
->out
[i
++];
851 buffer
[j
++] = m
->out
[i
++];
854 total
+= full_write(fd
, 1, buffer
, j
);
856 return total
+ full_write(fd
, 1, m
->post
, m
->post_len
);
859 libspamc_log(m
->priv
->flags
, LOG_ERR
, "Unknown message type %d", m
->type
);
864 void message_dump(int in_fd
, int out_fd
, struct message
*m
, int flags
)
870 libspamc_log(flags
, LOG_ERR
, "oops! message_dump called with NULL message");
874 if (m
->type
!= MESSAGE_NONE
) {
875 message_write(out_fd
, m
);
878 while ((bytes
= full_read(in_fd
, 1, buf
, 8192, 8192)) > 0) {
879 if (bytes
!= full_write(out_fd
, 1, buf
, bytes
)) {
880 libspamc_log(flags
, LOG_ERR
, "oops! message_dump of %d returned different",
887 _spamc_read_full_line(struct message
*m
, int flags
, SSL
* ssl
, int sock
,
888 char *buf
, size_t *lenp
, size_t bufsiz
)
897 /* Now, read from spamd */
898 for (len
= 0; len
< bufsiz
- 1; len
++) {
899 if (flags
& SPAMC_USE_SSL
) {
900 bytesread
= ssl_timeout_read(ssl
, buf
+ len
, 1);
903 bytesread
= fd_timeout_read(sock
, 0, buf
+ len
, 1);
906 if (bytesread
<= 0) {
907 failureval
= EX_IOERR
;
911 if (buf
[len
] == '\n') {
913 if (len
> 0 && buf
[len
- 1] == '\r') {
922 libspamc_log(flags
, LOG_ERR
, "spamd responded with line of %lu bytes, dying", len
);
923 failureval
= EX_TOOBIG
;
930 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
931 * work around using our own locale-independent float-parser code.
933 static float _locale_safe_string_to_float(char *buf
, int siz
)
940 buf
[siz
- 1] = '\0'; /* ensure termination */
942 /* ok, let's illustrate using "100.033" as an example... */
949 ret
= (float) (strtol(buf
, &dot
, 10));
953 if (dot
!= NULL
&& *dot
!= '.') {
957 /* ex: ret == 100.0 */
960 postdot
= (float) (strtol(cp
, NULL
, 10));
961 /* note: don't compare floats == 0.0, it's unsafe. use a range */
962 if (postdot
>= -0.00001 && postdot
<= 0.00001) {
966 /* ex: postdot == 33.0, cp="033" */
968 /* now count the number of decimal places and figure out what power of 10 to use */
970 while (*cp
!= '\0') {
976 * cp="033", divider=1
977 * cp="33", divider=10
978 * cp="3", divider=100
979 * cp="", divider=1000
983 ret
-= (postdot
/ ((float) divider
));
986 ret
+= (postdot
/ ((float) divider
));
988 /* ex: ret == 100.033, tada! ... hopefully */
994 _handle_spamd_header(struct message
*m
, int flags
, char *buf
, int len
,
995 unsigned int *didtellflags
)
998 char s_str
[21], t_str
[21];
1000 char didremove_ret
[15];
1002 UNUSED_VARIABLE(len
);
1004 /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
1005 * let's stick with it for this parser.
1006 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
1007 * work around using our own locale-independent float-parser code.
1009 if (sscanf(buf
, "Spam: %5s ; %20s / %20s", is_spam
, s_str
, t_str
) == 3) {
1010 m
->score
= _locale_safe_string_to_float(s_str
, 20);
1011 m
->threshold
= _locale_safe_string_to_float(t_str
, 20);
1013 /* set bounds on these to ensure no buffer overflow in the sprintf */
1014 if (m
->score
> 1e10
)
1016 else if (m
->score
< -1e10
)
1018 if (m
->threshold
> 1e10
)
1019 m
->threshold
= 1e10
;
1020 else if (m
->threshold
< -1e10
)
1021 m
->threshold
= -1e10
;
1023 /* Format is "Spam: x; y / x" */
1025 strcasecmp("true", is_spam
) == 0 ? EX_ISSPAM
: EX_NOTSPAM
;
1027 if (flags
& SPAMC_CHECK_ONLY
) {
1028 m
->out_len
= sprintf(m
->out
,
1029 "%.1f/%.1f\n", m
->score
, m
->threshold
);
1031 else if ((flags
& SPAMC_REPORT_IFSPAM
&& m
->is_spam
== EX_ISSPAM
)
1032 || (flags
& SPAMC_REPORT
)) {
1033 m
->out_len
= sprintf(m
->out
,
1034 "%.1f/%.1f\n", m
->score
, m
->threshold
);
1039 else if (sscanf(buf
, "Content-length: %d", &m
->content_length
) == 1) {
1040 if (m
->content_length
< 0) {
1041 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad Content-length '%s'",
1047 else if (sscanf(buf
, "DidSet: %14s", didset_ret
) == 1) {
1048 if (strstr(didset_ret
, "local")) {
1049 *didtellflags
|= SPAMC_SET_LOCAL
;
1051 if (strstr(didset_ret
, "remote")) {
1052 *didtellflags
|= SPAMC_SET_REMOTE
;
1055 else if (sscanf(buf
, "DidRemove: %14s", didremove_ret
) == 1) {
1056 if (strstr(didremove_ret
, "local")) {
1057 *didtellflags
|= SPAMC_REMOVE_LOCAL
;
1059 if (strstr(didremove_ret
, "remote")) {
1060 *didtellflags
|= SPAMC_REMOVE_REMOTE
;
1063 else if (m
->priv
->spamd_header_callback
!= NULL
)
1064 m
->priv
->spamd_header_callback(m
, flags
, buf
, len
);
1070 _zlib_compress (char *m_msg
, int m_msg_len
,
1071 unsigned char **zlib_buf
, int *zlib_bufsiz
, int flags
)
1078 UNUSED_VARIABLE(m_msg
);
1079 UNUSED_VARIABLE(m_msg_len
);
1080 UNUSED_VARIABLE(zlib_buf
);
1081 UNUSED_VARIABLE(zlib_bufsiz
);
1082 UNUSED_VARIABLE(rc
);
1083 UNUSED_VARIABLE(len
);
1084 UNUSED_VARIABLE(totallen
);
1085 libspamc_log(flags
, LOG_ERR
, "spamc not built with zlib support");
1091 UNUSED_VARIABLE(flags
);
1093 /* worst-case, according to http://www.zlib.org/zlib_tech.html ;
1094 * same as input, plus 5 bytes per 16k, plus 6 bytes. this should
1096 *zlib_bufsiz
= (int) (m_msg_len
* 1.0005) + 1024;
1097 *zlib_buf
= (unsigned char *) malloc (*zlib_bufsiz
);
1098 if (*zlib_buf
== NULL
) {
1102 strm
.zalloc
= Z_NULL
;
1103 strm
.zfree
= Z_NULL
;
1104 strm
.opaque
= Z_NULL
;
1105 rc
= deflateInit(&strm
, 3);
1110 strm
.avail_in
= m_msg_len
;
1111 strm
.next_in
= (unsigned char *) m_msg
;
1112 strm
.avail_out
= *zlib_bufsiz
;
1113 strm
.next_out
= (unsigned char *) *zlib_buf
;
1117 rc
= deflate(&strm
, Z_FINISH
);
1118 assert(rc
!= Z_STREAM_ERROR
);
1119 len
= (size_t) (*zlib_bufsiz
- strm
.avail_out
);
1120 strm
.next_out
+= len
;
1122 } while (strm
.avail_out
== 0);
1124 *zlib_bufsiz
= totallen
;
1131 _append_original_body (struct message
*m
, int flags
)
1133 char *cp
, *cpend
, *bodystart
;
1134 int bodylen
, outspaceleft
, towrite
;
1136 /* at this stage, m->out now contains the rewritten headers.
1137 * find and append the raw message's body, up to m->priv->alloced_size
1141 #define CRNLCRNL "\r\n\r\n"
1142 #define CRNLCRNL_LEN 4
1146 cpend
= m
->raw
+ m
->raw_len
;
1149 for (cp
= m
->raw
; cp
< cpend
; cp
++) {
1150 if (*cp
== '\r' && cpend
- cp
>= CRNLCRNL_LEN
&&
1151 !strncmp(cp
, CRNLCRNL
, CRNLCRNL_LEN
))
1153 bodystart
= cp
+ CRNLCRNL_LEN
;
1156 else if (*cp
== '\n' && cpend
- cp
>= NLNL_LEN
&&
1157 !strncmp(cp
, NLNL
, NLNL_LEN
))
1159 bodystart
= cp
+ NLNL_LEN
;
1164 if (bodystart
== NULL
) {
1165 libspamc_log(flags
, LOG_ERR
, "failed to find end-of-headers");
1169 bodylen
= cpend
- bodystart
;
1170 outspaceleft
= (m
->priv
->alloced_size
-1) - m
->out_len
;
1171 towrite
= (bodylen
< outspaceleft
? bodylen
: outspaceleft
);
1173 /* copy in the body; careful not to overflow */
1174 strncpy (m
->out
+ m
->out_len
, bodystart
, towrite
);
1175 m
->out_len
+= towrite
;
1179 int message_filter(struct transport
*tp
, const char *username
,
1180 int flags
, struct message
*m
)
1183 size_t bufsiz
= (sizeof(buf
) / sizeof(*buf
)) - 4; /* bit of breathing room */
1190 int failureval
= EX_SOFTWARE
;
1191 unsigned int throwaway
;
1192 SSL_CTX
*ctx
= NULL
;
1194 const SSL_METHOD
*meth
;
1196 unsigned char *zlib_buf
= NULL
;
1197 int zlib_bufsiz
= 0;
1198 unsigned char *towrite_buf
;
1200 int filter_retry_count
;
1201 int filter_retry_sleep
;
1203 #ifdef SPAMC_HAS_ADDRINFO
1204 struct addrinfo
*tmphost
;
1206 struct in_addr tmphost
;
1213 if ((flags
& SPAMC_USE_ZLIB
) != 0) {
1217 if (flags
& SPAMC_USE_SSL
) {
1219 SSLeay_add_ssl_algorithms();
1220 meth
= SSLv23_client_method();
1221 SSL_load_error_strings();
1222 ctx
= SSL_CTX_new(meth
);
1224 UNUSED_VARIABLE(ssl
);
1225 UNUSED_VARIABLE(meth
);
1226 UNUSED_VARIABLE(ctx
);
1227 libspamc_log(flags
, LOG_ERR
, "spamc not built with SSL support");
1232 m
->is_spam
= EX_TOOBIG
;
1234 if (m
->outbuf
!= NULL
)
1236 m
->priv
->alloced_size
= m
->max_len
+ EXPANSION_ALLOWANCE
+ 1;
1237 if ((m
->outbuf
= malloc(m
->priv
->alloced_size
)) == NULL
) {
1238 failureval
= EX_OSERR
;
1244 /* If the spamd filter takes too long and we timeout, then
1245 * retry again. This gets us around a hung child thread
1246 * in spamd or a problem on a spamd host in a multi-host
1247 * setup. If there is more than one destination host
1248 * we move to the next host on each attempt.
1251 /* default values */
1252 filter_retry_sleep
= tp
->filter_retry_sleep
;
1253 filter_retries
= tp
->filter_retries
;
1254 if (filter_retries
== 0) {
1257 if (filter_retry_sleep
< 0) {
1258 filter_retry_sleep
= 1;
1261 /* filterloop - Ensure that we run through this at least
1262 * once, and again if there are errors
1264 filter_retry_count
= 0;
1265 while ((filter_retry_count
==0) ||
1266 ((filter_retry_count
<tp
->filter_retries
) && (failureval
== EX_IOERR
)))
1268 if (filter_retry_count
!= 0){
1269 /* Ensure that the old socket gets closed */
1275 /* Move to the next host in the list, if nhosts>1 */
1276 if (tp
->nhosts
> 1) {
1277 tmphost
= tp
->hosts
[0];
1279 /* TODO: free using freeaddrinfo() */
1280 for (nhost_counter
= 1; nhost_counter
< tp
->nhosts
; nhost_counter
++) {
1281 tp
->hosts
[nhost_counter
- 1] = tp
->hosts
[nhost_counter
];
1284 tp
->hosts
[nhost_counter
- 1] = tmphost
;
1287 /* Now sleep the requested amount */
1288 sleep(filter_retry_sleep
);
1291 filter_retry_count
++;
1293 /* Build spamd protocol header */
1294 if (flags
& SPAMC_CHECK_ONLY
)
1295 strcpy(buf
, "CHECK ");
1296 else if (flags
& SPAMC_REPORT_IFSPAM
)
1297 strcpy(buf
, "REPORT_IFSPAM ");
1298 else if (flags
& SPAMC_REPORT
)
1299 strcpy(buf
, "REPORT ");
1300 else if (flags
& SPAMC_SYMBOLS
)
1301 strcpy(buf
, "SYMBOLS ");
1302 else if (flags
& SPAMC_PING
)
1303 strcpy(buf
, "PING ");
1304 else if (flags
& SPAMC_HEADERS
)
1305 strcpy(buf
, "HEADERS ");
1307 strcpy(buf
, "PROCESS ");
1310 if (len
+ strlen(PROTOCOL_VERSION
) + 2 >= bufsiz
) {
1311 _use_msg_for_out(m
);
1315 strcat(buf
, PROTOCOL_VERSION
);
1316 strcat(buf
, "\r\n");
1319 towrite_buf
= (unsigned char *) m
->msg
;
1320 towrite_len
= (int) m
->msg_len
;
1322 if (_zlib_compress(m
->msg
, m
->msg_len
, &zlib_buf
, &zlib_bufsiz
, flags
) != EX_OK
)
1324 _free_zlib_buffer(&zlib_buf
, &zlib_bufsiz
);
1327 towrite_buf
= zlib_buf
;
1328 towrite_len
= zlib_bufsiz
;
1331 if (!(flags
& SPAMC_PING
)) {
1332 if (username
!= NULL
) {
1333 if (strlen(username
) + 8 >= (bufsiz
- len
)) {
1334 _use_msg_for_out(m
);
1336 _free_zlib_buffer(&zlib_buf
, &zlib_bufsiz
);
1340 strcpy(buf
+ len
, "User: ");
1341 strcat(buf
+ len
, username
);
1342 strcat(buf
+ len
, "\r\n");
1343 len
+= strlen(buf
+ len
);
1346 len
+= snprintf(buf
+ len
, 8192-len
, "Compress: zlib\r\n");
1348 if ((m
->msg_len
> SPAMC_MAX_MESSAGE_LEN
) || ((len
+ 27) >= (bufsiz
- len
))) {
1349 _use_msg_for_out(m
);
1351 _free_zlib_buffer(&zlib_buf
, &zlib_bufsiz
);
1355 len
+= snprintf(buf
+ len
, 8192-len
, "Content-length: %d\r\n", (int) towrite_len
);
1357 /* bug 6187, PING needs empty line too, bumps protocol version to 1.5 */
1358 len
+= snprintf(buf
+ len
, 8192-len
, "\r\n");
1360 libspamc_timeout
= m
->timeout
;
1361 libspamc_connect_timeout
= m
->connect_timeout
; /* Sep 8, 2008 mrgus: separate connect timeout */
1364 rc
= _try_to_connect_unix(tp
, &sock
);
1366 rc
= _try_to_connect_tcp(tp
, &sock
);
1369 _use_msg_for_out(m
);
1371 _free_zlib_buffer(&zlib_buf
, &zlib_bufsiz
);
1373 return rc
; /* use the error code try_to_connect_*() gave us. */
1376 if (flags
& SPAMC_USE_SSL
) {
1379 SSL_set_fd(ssl
, sock
);
1385 if (flags
& SPAMC_USE_SSL
) {
1387 SSL_write(ssl
, buf
, len
);
1388 SSL_write(ssl
, towrite_buf
, towrite_len
);
1392 full_write(sock
, 0, buf
, len
);
1393 full_write(sock
, 0, towrite_buf
, towrite_len
);
1394 shutdown(sock
, SHUT_WR
);
1398 * bug 6025: zlib buffer not freed if compression is used
1401 _free_zlib_buffer(&zlib_buf
, &zlib_bufsiz
);
1404 /* ok, now read and parse it. SPAMD/1.2 line first... */
1406 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1407 } /* end of filterloop */
1409 if (failureval
!= EX_OK
) {
1413 if (sscanf(buf
, "SPAMD/%18s %d %*s", versbuf
, &response
) != 2) {
1414 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad string '%s'", buf
);
1415 failureval
= EX_PROTOCOL
;
1420 version
= _locale_safe_string_to_float(versbuf
, 20);
1421 if (version
< 1.0) {
1422 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad version string '%s'",
1424 failureval
= EX_PROTOCOL
;
1428 if (flags
& SPAMC_PING
) {
1431 m
->out_len
= sprintf(m
->out
, "SPAMD/%s %d\n", versbuf
, response
);
1432 m
->is_spam
= EX_NOTSPAM
;
1438 m
->is_spam
= EX_TOOBIG
;
1441 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1442 if (failureval
!= EX_OK
) {
1446 if (len
== 0 && buf
[0] == '\0') {
1447 break; /* end of headers */
1450 if (_handle_spamd_header(m
, flags
, buf
, len
, &throwaway
) < 0) {
1451 failureval
= EX_PROTOCOL
;
1456 len
= 0; /* overwrite those headers */
1458 if (flags
& SPAMC_CHECK_ONLY
) {
1461 if (m
->is_spam
== EX_TOOBIG
) {
1462 /* We should have gotten headers back... Damnit. */
1463 failureval
= EX_PROTOCOL
;
1469 if (m
->content_length
< 0) {
1470 /* should have got a length too. */
1471 failureval
= EX_PROTOCOL
;
1475 /* have we already got something in the buffer (e.g. REPORT and
1476 * REPORT_IFSPAM both create a line from the "Spam:" hdr)? If
1477 * so, add the size of that so our sanity check passes.
1479 if (m
->out_len
> 0) {
1480 m
->content_length
+= m
->out_len
;
1483 if (flags
& SPAMC_USE_SSL
) {
1484 len
= full_read_ssl(ssl
, (unsigned char *) m
->out
+ m
->out_len
,
1485 m
->priv
->alloced_size
- m
->out_len
,
1486 m
->priv
->alloced_size
- m
->out_len
);
1489 len
= full_read(sock
, 0, m
->out
+ m
->out_len
,
1490 m
->priv
->alloced_size
- m
->out_len
,
1491 m
->priv
->alloced_size
- m
->out_len
);
1495 if ((int) len
+ (int) m
->out_len
> (m
->priv
->alloced_size
- 1)) {
1496 failureval
= EX_TOOBIG
;
1501 shutdown(sock
, SHUT_RD
);
1505 libspamc_timeout
= 0;
1507 if (m
->out_len
!= m
->content_length
) {
1508 libspamc_log(flags
, LOG_ERR
,
1509 "failed sanity check, %d bytes claimed, %d bytes seen",
1510 m
->content_length
, m
->out_len
);
1511 failureval
= EX_PROTOCOL
;
1515 if (flags
& SPAMC_HEADERS
) {
1516 if (_append_original_body(m
, flags
) != EX_OK
) {
1524 _use_msg_for_out(m
);
1528 libspamc_timeout
= 0;
1530 if (flags
& SPAMC_USE_SSL
) {
1539 int message_process(struct transport
*trans
, char *username
, int max_size
,
1540 int in_fd
, int out_fd
, const int flags
)
1545 assert(trans
!= NULL
);
1547 m
.type
= MESSAGE_NONE
;
1549 /* enforce max_size being unsigned, therefore >= 0 */
1554 m
.max_len
= (unsigned int) max_size
;
1556 ret
= message_read(in_fd
, flags
, &m
);
1559 ret
= message_filter(trans
, username
, flags
, &m
);
1562 if (message_write(out_fd
, &m
) < 0)
1564 if (m
.is_spam
!= EX_TOOBIG
) {
1565 message_cleanup(&m
);
1568 message_cleanup(&m
);
1572 if (flags
& SPAMC_CHECK_ONLY
) {
1573 full_write(out_fd
, 1, "0/0\n", 4);
1574 message_cleanup(&m
);
1578 message_dump(in_fd
, out_fd
, &m
, flags
);
1579 message_cleanup(&m
);
1584 int message_tell(struct transport
*tp
, const char *username
, int flags
,
1585 struct message
*m
, int msg_class
,
1586 unsigned int tellflags
, unsigned int *didtellflags
)
1589 size_t bufsiz
= (sizeof(buf
) / sizeof(*buf
)) - 4; /* bit of breathing room */
1597 SSL_CTX
*ctx
= NULL
;
1599 const SSL_METHOD
*meth
;
1604 if (flags
& SPAMC_USE_SSL
) {
1606 SSLeay_add_ssl_algorithms();
1607 meth
= SSLv23_client_method();
1608 SSL_load_error_strings();
1609 ctx
= SSL_CTX_new(meth
);
1611 UNUSED_VARIABLE(ssl
);
1612 UNUSED_VARIABLE(meth
);
1613 UNUSED_VARIABLE(ctx
);
1614 libspamc_log(flags
, LOG_ERR
, "spamc not built with SSL support");
1619 m
->is_spam
= EX_TOOBIG
;
1621 if (m
->outbuf
!= NULL
)
1623 m
->priv
->alloced_size
= m
->max_len
+ EXPANSION_ALLOWANCE
+ 1;
1624 if ((m
->outbuf
= malloc(m
->priv
->alloced_size
)) == NULL
) {
1625 failureval
= EX_OSERR
;
1631 /* Build spamd protocol header */
1632 strcpy(buf
, "TELL ");
1635 if (len
+ strlen(PROTOCOL_VERSION
) + 2 >= bufsiz
) {
1636 _use_msg_for_out(m
);
1640 strcat(buf
, PROTOCOL_VERSION
);
1641 strcat(buf
, "\r\n");
1644 if (msg_class
!= 0) {
1645 strcpy(buf
+ len
, "Message-class: ");
1646 if (msg_class
== SPAMC_MESSAGE_CLASS_SPAM
) {
1647 strcat(buf
+ len
, "spam\r\n");
1650 strcat(buf
+ len
, "ham\r\n");
1652 len
+= strlen(buf
+ len
);
1655 if ((tellflags
& SPAMC_SET_LOCAL
) || (tellflags
& SPAMC_SET_REMOTE
)) {
1656 int needs_comma_p
= 0;
1657 strcat(buf
+ len
, "Set: ");
1658 if (tellflags
& SPAMC_SET_LOCAL
) {
1659 strcat(buf
+ len
, "local");
1662 if (tellflags
& SPAMC_SET_REMOTE
) {
1663 if (needs_comma_p
== 1) {
1664 strcat(buf
+ len
, ",");
1666 strcat(buf
+ len
, "remote");
1668 strcat(buf
+ len
, "\r\n");
1669 len
+= strlen(buf
+ len
);
1672 if ((tellflags
& SPAMC_REMOVE_LOCAL
) || (tellflags
& SPAMC_REMOVE_REMOTE
)) {
1673 int needs_comma_p
= 0;
1674 strcat(buf
+ len
, "Remove: ");
1675 if (tellflags
& SPAMC_REMOVE_LOCAL
) {
1676 strcat(buf
+ len
, "local");
1679 if (tellflags
& SPAMC_REMOVE_REMOTE
) {
1680 if (needs_comma_p
== 1) {
1681 strcat(buf
+ len
, ",");
1683 strcat(buf
+ len
, "remote");
1685 strcat(buf
+ len
, "\r\n");
1686 len
+= strlen(buf
+ len
);
1689 if (username
!= NULL
) {
1690 if (strlen(username
) + 8 >= (bufsiz
- len
)) {
1691 _use_msg_for_out(m
);
1694 strcpy(buf
+ len
, "User: ");
1695 strcat(buf
+ len
, username
);
1696 strcat(buf
+ len
, "\r\n");
1697 len
+= strlen(buf
+ len
);
1699 if ((m
->msg_len
> SPAMC_MAX_MESSAGE_LEN
) || ((len
+ 27) >= (bufsiz
- len
))) {
1700 _use_msg_for_out(m
);
1703 len
+= sprintf(buf
+ len
, "Content-length: %d\r\n\r\n", (int) m
->msg_len
);
1705 if (m
->priv
->spamc_header_callback
!= NULL
) {
1707 m
->priv
->spamc_header_callback(m
, flags
, buf2
, 1024);
1708 strncat(buf
, buf2
, bufsiz
- len
);
1711 libspamc_timeout
= m
->timeout
;
1712 libspamc_connect_timeout
= m
->connect_timeout
; /* Sep 8, 2008 mrgus: separate connect timeout */
1715 rc
= _try_to_connect_unix(tp
, &sock
);
1717 rc
= _try_to_connect_tcp(tp
, &sock
);
1720 _use_msg_for_out(m
);
1721 return rc
; /* use the error code try_to_connect_*() gave us. */
1724 if (flags
& SPAMC_USE_SSL
) {
1727 SSL_set_fd(ssl
, sock
);
1733 if (flags
& SPAMC_USE_SSL
) {
1735 SSL_write(ssl
, buf
, len
);
1736 SSL_write(ssl
, m
->msg
, m
->msg_len
);
1740 full_write(sock
, 0, buf
, len
);
1741 full_write(sock
, 0, m
->msg
, m
->msg_len
);
1742 shutdown(sock
, SHUT_WR
);
1745 /* ok, now read and parse it. SPAMD/1.2 line first... */
1747 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1748 if (failureval
!= EX_OK
) {
1752 if (sscanf(buf
, "SPAMD/%18s %d %*s", versbuf
, &response
) != 2) {
1753 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad string '%s'", buf
);
1754 failureval
= EX_PROTOCOL
;
1759 version
= _locale_safe_string_to_float(versbuf
, 20);
1760 if (version
< 1.0) {
1761 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad version string '%s'",
1763 failureval
= EX_PROTOCOL
;
1769 m
->is_spam
= EX_TOOBIG
;
1772 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1773 if (failureval
!= EX_OK
) {
1777 if (len
== 0 && buf
[0] == '\0') {
1778 break; /* end of headers */
1781 if (_handle_spamd_header(m
, flags
, buf
, len
, didtellflags
) < 0) {
1782 failureval
= EX_PROTOCOL
;
1787 len
= 0; /* overwrite those headers */
1789 shutdown(sock
, SHUT_RD
);
1793 libspamc_timeout
= 0;
1798 _use_msg_for_out(m
);
1802 libspamc_timeout
= 0;
1804 if (flags
& SPAMC_USE_SSL
) {
1813 void message_cleanup(struct message
*m
)
1816 if (m
->outbuf
!= NULL
)
1820 if (m
->priv
!= NULL
)
1825 /* Aug 14, 2002 bj: Obsolete! */
1826 int process_message(struct transport
*tp
, char *username
, int max_size
,
1827 int in_fd
, int out_fd
, const int my_check_only
,
1828 const int my_safe_fallback
)
1832 flags
= SPAMC_RAW_MODE
;
1834 flags
|= SPAMC_CHECK_ONLY
;
1835 if (my_safe_fallback
)
1836 flags
|= SPAMC_SAFE_FALLBACK
;
1838 return message_process(tp
, username
, max_size
, in_fd
, out_fd
, flags
);
1844 * Given a pointer to a transport structure, set it to "all empty".
1845 * The default is a localhost connection.
1847 void transport_init(struct transport
*tp
)
1851 memset(tp
, 0, sizeof *tp
);
1853 tp
->type
= TRANSPORT_LOCALHOST
;
1856 tp
->retry_sleep
= -1;
1862 * Given the transport object that contains one or more IP addresses
1863 * in this "hosts" list, rotate it by a random number of shifts to
1864 * randomize them - this is a kind of load balancing. It's possible
1865 * that the random number will be 0, which says not to touch. We don't
1866 * do anything unless
1869 static void _randomize_hosts(struct transport
*tp
)
1871 #ifdef SPAMC_HAS_ADDRINFO
1872 struct addrinfo
*tmp
;
1881 if (tp
->nhosts
<= 1)
1884 rnum
= rand() % tp
->nhosts
;
1886 while (rnum
-- > 0) {
1889 for (i
= 1; i
< tp
->nhosts
; i
++)
1890 tp
->hosts
[i
- 1] = tp
->hosts
[i
];
1892 tp
->hosts
[i
- 1] = tmp
;
1899 * Given a "transport" object that says how we're to connect to the
1900 * spam daemon, perform all the initial setup required to make the
1901 * connection process a smooth one. The main work is to do the host
1902 * name lookup and copy over all the IP addresses to make a local copy
1903 * so they're not kept in the resolver's static state.
1905 * Here we also manage quasi-load balancing and failover: if we're
1906 * doing load balancing, we randomly "rotate" the list to put it in
1907 * a different order, and then if we're not doing failover we limit
1908 * the hosts to just one. This way *all* connections are done with
1909 * the intention of failover - makes the code a bit more clear.
1911 int transport_setup(struct transport
*tp
, int flags
)
1913 #ifdef SPAMC_HAS_ADDRINFO
1914 struct addrinfo hints
, *res
; /* , *addrp; */
1921 char *hostlist
, *hostname
;
1925 /* Start Winsock up */
1928 if ((nCode
= WSAStartup(MAKEWORD(1, 1), &wsaData
)) != 0) {
1929 printf("WSAStartup() returned error code %d\n", nCode
);
1938 #ifdef SPAMC_HAS_ADDRINFO
1939 snprintf(port
, 6, "%d", tp
->port
);
1941 memset(&hints
, 0, sizeof(hints
));
1943 hints
.ai_socktype
= SOCK_STREAM
;
1945 if ( (flags
& SPAMC_USE_INET4
) && !(flags
& SPAMC_USE_INET6
)) {
1946 hints
.ai_family
= PF_INET
;
1948 } else if ((flags
& SPAMC_USE_INET6
) && !(flags
& SPAMC_USE_INET4
)) {
1949 hints
.ai_family
= PF_INET6
;
1952 hints
.ai_family
= PF_UNSPEC
;
1958 case TRANSPORT_UNIX
:
1959 assert(tp
->socketpath
!= 0);
1962 case TRANSPORT_LOCALHOST
:
1963 #ifdef SPAMC_HAS_ADDRINFO
1964 /* getaddrinfo(NULL) will look up the loopback address.
1965 * See also bug 5057, ::1 will be tried before 127.0.0.1
1966 * unless overridden (through hints) by a command line option -4
1968 if ((origerr
= getaddrinfo(NULL
, port
, &hints
, &res
)) != 0) {
1969 libspamc_log(flags
, LOG_ERR
,
1970 "getaddrinfo for a loopback address failed: %s",
1971 gai_strerror(origerr
));
1976 tp
->hosts
[0].s_addr
= inet_addr("127.0.0.1");
1982 if ((hostlist
= strdup(tp
->hostname
)) == NULL
)
1985 /* We want to return the least permanent error, in this bitmask we
1986 * record the errors seen with:
1990 * EX_OSERR will return immediately.
1991 * Bits aren't reset so a check against nhosts is needed to determine
1992 * if something went wrong.
1996 /* Start with char offset in front of the string because we'll add
1999 hostname
= hostlist
- 1;
2004 hostend
= strchr(hostname
, ',');
2005 if (hostend
!= NULL
) {
2008 #ifdef SPAMC_HAS_ADDRINFO
2009 if ((origerr
= getaddrinfo(hostname
, port
, &hints
, &res
))) {
2010 libspamc_log(flags
, LOG_DEBUG
,
2011 "getaddrinfo(%s) failed: %s",
2012 hostname
, gai_strerror(origerr
));
2017 case EAI_FAMILY
: /*address family not supported*/
2018 case EAI_SOCKTYPE
: /*socket type not supported*/
2019 case EAI_BADFLAGS
: /*ai_flags is invalid*/
2020 case EAI_NONAME
: /*node or service unknown*/
2021 case EAI_SERVICE
: /*service not available*/
2022 /* work around Cygwin IPv6 patch - err codes not defined in Windows aren't in patch */
2023 #ifdef HAVE_EAI_ADDRFAMILY
2024 case EAI_ADDRFAMILY
: /*no addresses in requested family*/
2026 #ifdef HAVE_EAI_SYSTEM
2027 case EAI_SYSTEM
: /*system error, check errno*/
2029 #ifdef HAVE_EAI_NODATA
2030 case EAI_NODATA
: /*address exists, but no data*/
2032 case EAI_MEMORY
: /*out of memory*/
2033 case EAI_FAIL
: /*name server returned permanent error*/
2037 /* should not happen, all errors are checked above */
2041 goto nexthost
; /* try next host in list */
2044 if ((hp
= gethostbyname(hostname
)) == NULL
) {
2045 int origerr
= h_errno
; /* take a copy before syslog() */
2046 libspamc_log(flags
, LOG_DEBUG
, "gethostbyname(%s) failed: h_errno=%d",
2052 case HOST_NOT_FOUND
:
2058 /* should not happen, all errors are checked above */
2062 goto nexthost
; /* try next host in list */
2066 /* If we have no hosts at all */
2067 #ifdef SPAMC_HAS_ADDRINFO
2070 if (hp
->h_addr_list
[0] == NULL
2071 || hp
->h_length
!= sizeof tp
->hosts
[0]
2072 || hp
->h_addrtype
!= AF_INET
)
2073 /* no hosts/bad size/wrong family */
2077 goto nexthost
; /* try next host in list */
2080 /* Copy all the IP addresses into our private structure.
2081 * This gets them out of the resolver's static area and
2082 * means we won't ever walk all over the list with other
2085 #ifdef SPAMC_HAS_ADDRINFO
2086 if(tp
->nhosts
== TRANSPORT_MAX_HOSTS
) {
2087 libspamc_log(flags
, LOG_NOTICE
,
2088 "hit limit of %d hosts, ignoring remainder",
2089 TRANSPORT_MAX_HOSTS
);
2093 /* treat all A or AAAA records of each host as one entry */
2094 tp
->hosts
[tp
->nhosts
++] = res
;
2096 /* alternatively, treat multiple A or AAAA records
2097 of one host as individual entries */
2098 /* for (addrp = res; addrp != NULL; ) {
2099 * tp->hosts[tp->nhosts] = addrp;
2100 * addrp = addrp->ai_next; /-* before NULLing ai_next *-/
2101 * tp->hosts[tp->nhosts]->ai_next = NULL;
2107 for (addrp
= hp
->h_addr_list
; *addrp
; addrp
++) {
2108 if (tp
->nhosts
== TRANSPORT_MAX_HOSTS
) {
2109 libspamc_log(flags
, LOG_NOTICE
, "hit limit of %d hosts, ignoring remainder",
2110 TRANSPORT_MAX_HOSTS
);
2113 memcpy(&tp
->hosts
[tp
->nhosts
], *addrp
, hp
->h_length
);
2119 } while (hostname
!= NULL
);
2122 if (tp
->nhosts
== 0) {
2124 libspamc_log(flags
, LOG_ERR
, "could not resolve any hosts (%s): a temporary error occurred",
2129 libspamc_log(flags
, LOG_ERR
, "could not resolve any hosts (%s): no such host",
2135 /* QUASI-LOAD-BALANCING
2137 * If the user wants to do quasi load balancing, "rotate"
2138 * the list by a random amount based on the current time.
2139 * This may later be truncated to a single item. This is
2140 * meaningful only if we have more than one host.
2143 if ((flags
& SPAMC_RANDOMIZE_HOSTS
) && tp
->nhosts
> 1) {
2144 _randomize_hosts(tp
);
2147 /* If the user wants no fallback, simply truncate the host
2148 * list to just one - this pretends that this is the extent
2149 * of our connection list - then it's not a special case.
2151 if (!(flags
& SPAMC_SAFE_FALLBACK
) && tp
->nhosts
> 1) {
2152 /* truncating list */
2159 /* oops, unknown transport type */
2164 * transport_cleanup()
2166 * Given a "transport" object that says how we're to connect to the
2167 * spam daemon, delete and free any buffers allocated so that it
2168 * can be discarded without causing a memory leak.
2170 void transport_cleanup(struct transport
*tp
)
2173 #ifdef SPAMC_HAS_ADDRINFO
2176 for(i
=0;i
<tp
->nhosts
;i
++) {
2177 if (tp
->hosts
[i
] != NULL
) {
2178 freeaddrinfo(tp
->hosts
[i
]);
2179 tp
->hosts
[i
] = NULL
;
2187 * register_libspamc_log_callback()
2189 * Register a callback handler for libspamc_log to replace the default behaviour.
2192 void register_libspamc_log_callback(void (*function
)(int flags
, int level
, char *msg
, va_list args
)) {
2193 libspamc_log_callback
= function
;
2197 * register_spamc_header_callback()
2199 * Register a callback handler to generate spamc headers for a given message
2202 void register_spamc_header_callback(const struct message
*m
, void (*func
)(struct message
*m
, int flags
, char *buf
, int len
)) {
2203 m
->priv
->spamc_header_callback
= func
;
2207 * register_spamd_header_callback()
2209 * Register a callback handler to generate spamd headers for a given message
2212 void register_spamd_header_callback(const struct message
*m
, void (*func
)(struct message
*m
, int flags
, const char *buf
, int len
)) {
2213 m
->priv
->spamd_header_callback
= func
;
2216 /* --------------------------------------------------------------------------- */
2218 #define LOG_BUFSIZ 1023
2221 libspamc_log (int flags
, int level
, char *msg
, ...)
2224 char buf
[LOG_BUFSIZ
+1];
2229 if ((flags
& SPAMC_LOG_TO_CALLBACK
) != 0 && libspamc_log_callback
!= NULL
) {
2230 libspamc_log_callback(flags
, level
, msg
, ap
);
2232 else if ((flags
& SPAMC_LOG_TO_STDERR
) != 0) {
2233 /* create a log-line buffer */
2234 len
= snprintf(buf
, LOG_BUFSIZ
, "spamc: ");
2235 len
+= vsnprintf(buf
+len
, LOG_BUFSIZ
-len
, msg
, ap
);
2237 /* avoid buffer overflow */
2238 if (len
> (LOG_BUFSIZ
-2)) { len
= (LOG_BUFSIZ
-3); }
2240 len
+= snprintf(buf
+len
, LOG_BUFSIZ
-len
, "\n");
2241 buf
[LOG_BUFSIZ
] = '\0'; /* ensure termination */
2242 (void) write (2, buf
, len
);
2245 vsnprintf(buf
, LOG_BUFSIZ
, msg
, ap
);
2246 buf
[LOG_BUFSIZ
] = '\0'; /* ensure termination */
2248 syslog (level
, "%s", buf
);
2250 (void) level
; /* not used. suppress compiler warning */
2251 f_printerr ("%s\n", buf
);
2258 /* --------------------------------------------------------------------------- */
2261 * Unit tests. Must be built externally, e.g.:
2263 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
2267 #ifdef LIBSPAMC_UNIT_TESTS
2269 static void _test_locale_safe_string_to_float_val(float input
)
2271 char inputstr
[99], cmpbuf1
[99], cmpbuf2
[99];
2274 /* sprintf instead of snprintf is safe here because it is only a controlled test */
2275 sprintf(inputstr
, "%f", input
);
2276 output
= _locale_safe_string_to_float(inputstr
, 99);
2277 if (input
== output
) {
2281 /* could be a rounding error. print as string and compare those */
2282 sprintf(cmpbuf1
, "%f", input
);
2283 sprintf(cmpbuf2
, "%f", output
);
2284 if (!strcmp(cmpbuf1
, cmpbuf2
)) {
2288 printf("FAIL: input=%f != output=%f\n", input
, output
);
2291 static void unit_test_locale_safe_string_to_float(void)
2293 float statictestset
[] = { /* will try both +ve and -ve */
2294 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
2295 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
2296 0.0 /* end of set constant */
2301 printf("starting unit_test_locale_safe_string_to_float\n");
2302 /* tests of precision */
2303 for (i
= 0; statictestset
[i
] != 0.0; i
++) {
2304 _test_locale_safe_string_to_float_val(statictestset
[i
]);
2305 _test_locale_safe_string_to_float_val(-statictestset
[i
]);
2306 _test_locale_safe_string_to_float_val(1 - statictestset
[i
]);
2307 _test_locale_safe_string_to_float_val(1 + statictestset
[i
]);
2309 /* now exhaustive, in steps of 0.01 */
2310 for (num
= -1000.0; num
< 1000.0; num
+= 0.01) {
2311 _test_locale_safe_string_to_float_val(num
);
2313 printf("finished unit_test_locale_safe_string_to_float\n");
2316 void do_libspamc_unit_tests(void)
2318 unit_test_locale_safe_string_to_float();
2322 #endif /* LIBSPAMC_UNIT_TESTS */