]>
diplodocus.org Git - mdeliver/blob - libspamc.c
b4d1936fc446aaccb84eb4b31f3c204e2b023e5c
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.
27 #define snprintf _snprintf
28 #define vsnprintf _vsnprintf
29 #define strcasecmp stricmp
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
39 #include <netinet/tcp.h>
40 #include <arpa/inet.h>
41 #define closesocket(x) close(x)
44 #ifdef HAVE_SYSEXITS_H
50 #ifdef HAVE_SYS_ERRNO_H
51 #include <sys/errno.h>
56 #ifdef HAVE_SYS_TIME_H
60 /* FIXME: Make this configurable */
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
;
182 /*----------------------------------------------------------------
183 * Create a few induction variables that are implied by the socket
184 * type given by the user. The typename is strictly used for debug
187 if (type
== PF_UNIX
) {
188 typename
= "PF_UNIX";
191 typename
= "PF_INET";
195 #ifdef DO_CONNECT_DEBUG_SYSLOGS
196 libspamc_log(flags
, DEBUG_LEVEL
, "dbg: create socket(%s)", typename
);
199 if ((*psock
= socket(type
, SOCK_STREAM
, proto
))
208 /*--------------------------------------------------------
209 * At this point we had a failure creating the socket, and
210 * this is pretty much fatal. Translate the error reason
211 * into something the user can understand.
214 origerr
= errno
; /* take a copy before syslog() */
215 libspamc_log(flags
, LOG_ERR
, "socket(%s) to spamd failed: %s", typename
, strerror(origerr
));
217 origerr
= WSAGetLastError();
218 libspamc_log(flags
, LOG_ERR
, "socket(%s) to spamd failed: %d", typename
, origerr
);
222 case EPROTONOSUPPORT
:
241 /* bug 4344: makes timeout functional on Win32 */
242 socktout
= libspamc_timeout
* 1000;
244 && setsockopt(*psock
, SOL_SOCKET
, SO_RCVTIMEO
, (char *)&socktout
, sizeof(socktout
)) != 0)
248 origerrno
= WSAGetLastError();
255 libspamc_log(flags
, LOG_ERR
, "setsockopt(SO_RCVTIMEO) failed: %d", origerrno
);
265 /*----------------------------------------------------------------
266 * Do a bit of setup on the TCP socket if required. Notes above
267 * suggest this is probably not set
269 #ifdef USE_TCP_NODELAY
274 && setsockopt(*psock
, 0, TCP_NODELAY
, &one
, sizeof one
) != 0) {
279 origerrno
= WSAGetLastError();
286 libspamc_log(flags
, LOG_ERR
,
288 "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr
));
290 "setsockopt(TCP_NODELAY) failed: %d", origerr
);
300 #endif /* USE_TCP_NODELAY */
302 return EX_OK
; /* all is well */
306 * try_to_connect_unix()
308 * Given a transport handle that implies using a UNIX domain
309 * socket, try to make a connection to it and store the resulting
310 * file descriptor in *sockptr. Return is EX_OK if we did it,
311 * and some other error code otherwise.
313 static int _try_to_connect_unix(struct transport
*tp
, int *sockptr
)
316 int mysock
, status
, origerr
;
317 struct sockaddr_un addrbuf
;
321 assert(sockptr
!= 0);
322 assert(tp
->socketpath
!= 0);
324 /*----------------------------------------------------------------
325 * If the socket itself can't be created, this is a fatal error.
327 if ((ret
= _opensocket(tp
->flags
, PF_UNIX
, &mysock
)) != EX_OK
)
330 /* set up the UNIX domain socket */
331 memset(&addrbuf
, 0, sizeof addrbuf
);
332 addrbuf
.sun_family
= AF_UNIX
;
333 strncpy(addrbuf
.sun_path
, tp
->socketpath
, sizeof addrbuf
.sun_path
- 1);
334 addrbuf
.sun_path
[sizeof addrbuf
.sun_path
- 1] = '\0';
336 #ifdef DO_CONNECT_DEBUG_SYSLOGS
337 libspamc_log(tp
->flags
, DEBUG_LEVEL
, "dbg: connect(AF_UNIX) to spamd at %s",
341 status
= connect(mysock
, (struct sockaddr
*) &addrbuf
, sizeof(addrbuf
));
346 #ifdef DO_CONNECT_DEBUG_SYSLOGS
347 libspamc_log(tp
->flags
, DEBUG_LEVEL
, "dbg: connect(AF_UNIX) ok");
355 libspamc_log(tp
->flags
, LOG_ERR
, "connect(AF_UNIX) to spamd %s failed: %s",
356 addrbuf
.sun_path
, strerror(origerr
));
359 return _translate_connect_errno(origerr
);
361 (void) tp
; /* not used. suppress compiler warning */
362 (void) sockptr
; /* not used. suppress compiler warning */
368 * try_to_connect_tcp()
370 * Given a transport that implies a TCP connection, either to
371 * localhost or a list of IP addresses, attempt to connect. The
372 * list of IP addresses has already been randomized (if requested)
373 * and limited to just one if fallback has been enabled.
375 static int _try_to_connect_tcp(const struct transport
*tp
, int *sockptr
)
382 assert(sockptr
!= 0);
383 assert(tp
->nhosts
> 0);
385 #ifdef DO_CONNECT_DEBUG_SYSLOGS
386 for (numloops
= 0; numloops
< tp
->nhosts
; numloops
++) {
387 libspamc_log(tp
->flags
, LOG_ERR
, "dbg: %d/%d: %s",
388 numloops
+ 1, tp
->nhosts
, inet_ntoa(tp
->hosts
[numloops
]));
392 for (numloops
= 0; numloops
< MAX_CONNECT_RETRIES
; numloops
++) {
393 struct sockaddr_in addrbuf
;
394 const int hostix
= numloops
% tp
->nhosts
;
398 /*--------------------------------------------------------
399 * We always start by creating the socket, as we get only
400 * one attempt to connect() on each one. If this fails,
403 if ((ret
= _opensocket(tp
->flags
, PF_INET
, &mysock
)) != EX_OK
)
406 memset(&addrbuf
, 0, sizeof(addrbuf
));
408 addrbuf
.sin_family
= AF_INET
;
409 addrbuf
.sin_port
= htons(tp
->port
);
410 addrbuf
.sin_addr
= tp
->hosts
[hostix
];
412 ipaddr
= inet_ntoa(addrbuf
.sin_addr
);
414 #ifdef DO_CONNECT_DEBUG_SYSLOGS
415 libspamc_log(tp
->flags
, DEBUG_LEVEL
,
416 "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
417 ipaddr
, numloops
+ 1, MAX_CONNECT_RETRIES
);
421 connect(mysock
, (struct sockaddr
*) &addrbuf
, sizeof(addrbuf
));
426 libspamc_log(tp
->flags
, LOG_ERR
,
427 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
428 ipaddr
, numloops
+ 1, MAX_CONNECT_RETRIES
, strerror(origerr
));
430 origerr
= WSAGetLastError();
431 libspamc_log(tp
->flags
, LOG_ERR
,
432 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
433 ipaddr
, numloops
+ 1, MAX_CONNECT_RETRIES
, origerr
);
437 sleep(CONNECT_RETRY_SLEEP
);
440 #ifdef DO_CONNECT_DEBUG_SYSLOGS
441 libspamc_log(tp
->flags
, DEBUG_LEVEL
,
442 "dbg: connect(AF_INET) to spamd at %s done", ipaddr
);
450 libspamc_log(tp
->flags
, LOG_ERR
, "connection attempt to spamd aborted after %d retries",
451 MAX_CONNECT_RETRIES
);
453 return _translate_connect_errno(origerr
);
456 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
457 * message_dump, lookup_host, message_filter, and message_process, and a bunch
458 * of helper functions.
461 static void _clear_message(struct message
*m
)
463 m
->type
= MESSAGE_NONE
;
472 m
->is_spam
= EX_TOOBIG
;
478 m
->content_length
= -1;
481 static void _use_msg_for_out(struct message
*m
)
487 m
->out_len
= m
->msg_len
;
490 static int _message_read_raw(int fd
, struct message
*m
)
495 if (fstat(fd
, &st
) == -1) {
496 libspamc_log(0,0, "fstat");
500 m
->raw
= mmap(0, st
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
501 if (m
->raw
== MAP_FAILED
) {
502 libspamc_log(0,0, "mmap");
505 m
->raw_len
= st
.st_size
;
507 m
->type
= MESSAGE_RAW
;
509 m
->msg_len
= m
->raw_len
;
511 m
->out_len
= m
->msg_len
;
515 static int _message_read_bsmtp(int fd
, struct message
*m
)
521 if ((m
->raw
= malloc(m
->max_len
+ 1)) == NULL
)
524 /* Find the DATA line */
525 m
->raw_len
= full_read(fd
, 1, m
->raw
, m
->max_len
+ 1, m
->max_len
+ 1);
526 if (m
->raw_len
<= 0) {
532 m
->type
= MESSAGE_ERROR
;
533 if (m
->raw_len
> m
->max_len
)
536 for (i
= 0; i
< m
->raw_len
- 6; i
++) {
537 if ((m
->raw
[i
] == '\n') &&
538 (m
->raw
[i
+ 1] == 'D' || m
->raw
[i
+ 1] == 'd') &&
539 (m
->raw
[i
+ 2] == 'A' || m
->raw
[i
+ 2] == 'a') &&
540 (m
->raw
[i
+ 3] == 'T' || m
->raw
[i
+ 3] == 't') &&
541 (m
->raw
[i
+ 4] == 'A' || m
->raw
[i
+ 4] == 'a') &&
542 ((m
->raw
[i
+ 5] == '\r' && m
->raw
[i
+ 6] == '\n')
543 || m
->raw
[i
+ 5] == '\n')) {
546 if (m
->raw
[i
- 1] == '\r')
550 m
->msg_len
= m
->raw_len
- i
;
557 /* Find the end-of-DATA line */
559 for (i
= j
= 0; i
< m
->msg_len
; i
++) {
560 if (prev
== '\n' && m
->msg
[i
] == '.') {
561 /* Dot at the beginning of a line */
562 if ((m
->msg
[i
+ 1] == '\r' && m
->msg
[i
+ 2] == '\n')
563 || m
->msg
[i
+ 1] == '\n') {
564 /* Lone dot! That's all, folks */
565 m
->post
= m
->msg
+ i
;
566 m
->post_len
= m
->msg_len
- i
;
570 else if (m
->msg
[i
+ 1] == '.') {
571 /* Escaping dot, eliminate. */
574 } /* Else an ordinary dot, drop down to ordinary char handler */
577 m
->msg
[j
++] = m
->msg
[i
];
580 m
->type
= MESSAGE_BSMTP
;
582 m
->out_len
= m
->msg_len
;
586 int message_read(int fd
, int flags
, struct message
*m
)
590 libspamc_timeout
= 0;
592 /* create the "private" part of the struct message */
593 m
->priv
= malloc(sizeof(struct libspamc_private_message
));
594 if (m
->priv
== NULL
) {
595 libspamc_log(flags
, LOG_ERR
, "message_read: malloc failed");
598 m
->priv
->flags
= flags
;
600 switch (flags
& SPAMC_MODE_MASK
) {
602 return _message_read_raw(fd
, m
);
604 case SPAMC_BSMTP_MODE
:
605 return _message_read_bsmtp(fd
, m
);
608 libspamc_log(flags
, LOG_ERR
, "message_read: Unknown mode %d",
609 flags
& SPAMC_MODE_MASK
);
614 long message_write(int fd
, struct message
*m
)
623 if (m
->priv
->flags
& SPAMC_CHECK_ONLY
) {
624 if (m
->is_spam
== EX_ISSPAM
|| m
->is_spam
== EX_NOTSPAM
) {
625 return full_write(fd
, 1, m
->out
, m
->out_len
);
629 libspamc_log(m
->priv
->flags
, LOG_ERR
, "oops! SPAMC_CHECK_ONLY is_spam: %d",
635 /* else we're not in CHECK_ONLY mode */
638 libspamc_log(m
->priv
->flags
, LOG_ERR
, "Cannot write this message, it's MESSAGE_NONE!");
642 return full_write(fd
, 1, m
->raw
, m
->raw_len
);
645 return full_write(fd
, 1, m
->out
, m
->out_len
);
648 total
= full_write(fd
, 1, m
->pre
, m
->pre_len
);
649 for (i
= 0; i
< m
->out_len
;) {
650 jlimit
= (off_t
) (sizeof(buffer
) / sizeof(*buffer
) - 4);
651 for (j
= 0; i
< (off_t
) m
->out_len
&& j
< jlimit
;) {
652 if (i
+ 1 < m
->out_len
&& m
->out
[i
] == '\n'
653 && m
->out
[i
+ 1] == '.') {
654 if (j
> jlimit
- 4) {
655 break; /* avoid overflow */
657 buffer
[j
++] = m
->out
[i
++];
658 buffer
[j
++] = m
->out
[i
++];
662 buffer
[j
++] = m
->out
[i
++];
665 total
+= full_write(fd
, 1, buffer
, j
);
667 return total
+ full_write(fd
, 1, m
->post
, m
->post_len
);
670 libspamc_log(m
->priv
->flags
, LOG_ERR
, "Unknown message type %d", m
->type
);
675 void message_dump(int in_fd
, int out_fd
, struct message
*m
)
680 if (m
!= NULL
&& m
->type
!= MESSAGE_NONE
) {
681 message_write(out_fd
, m
);
683 while ((bytes
= full_read(in_fd
, 1, buf
, 8192, 8192)) > 0) {
684 if (bytes
!= full_write(out_fd
, 1, buf
, bytes
)) {
685 libspamc_log(m
->priv
->flags
, LOG_ERR
, "oops! message_dump of %d returned different",
692 _spamc_read_full_line(struct message
*m
, int flags
, SSL
* ssl
, int sock
,
693 char *buf
, size_t *lenp
, size_t bufsiz
)
702 /* Now, read from spamd */
703 for (len
= 0; len
< bufsiz
- 1; len
++) {
704 if (flags
& SPAMC_USE_SSL
) {
705 bytesread
= ssl_timeout_read(ssl
, buf
+ len
, 1);
708 bytesread
= fd_timeout_read(sock
, 0, buf
+ len
, 1);
711 if (bytesread
<= 0) {
712 failureval
= EX_IOERR
;
716 if (buf
[len
] == '\n') {
718 if (len
> 0 && buf
[len
- 1] == '\r') {
727 libspamc_log(flags
, LOG_ERR
, "spamd responded with line of %d bytes, dying", len
);
728 failureval
= EX_TOOBIG
;
735 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
736 * work around using our own locale-independent float-parser code.
738 static float _locale_safe_string_to_float(char *buf
, int siz
)
745 buf
[siz
- 1] = '\0'; /* ensure termination */
747 /* ok, let's illustrate using "100.033" as an example... */
754 ret
= (float) (strtol(buf
, &dot
, 10));
758 if (dot
!= NULL
&& *dot
!= '.') {
762 /* ex: ret == 100.0 */
765 postdot
= (float) (strtol(cp
, NULL
, 10));
766 /* note: don't compare floats == 0.0, it's unsafe. use a range */
767 if (postdot
>= -0.00001 && postdot
<= 0.00001) {
771 /* ex: postdot == 33.0, cp="033" */
773 /* now count the number of decimal places and figure out what power of 10 to use */
775 while (*cp
!= '\0') {
781 * cp="033", divider=1
782 * cp="33", divider=10
783 * cp="3", divider=100
784 * cp="", divider=1000
788 ret
-= (postdot
/ ((float) divider
));
791 ret
+= (postdot
/ ((float) divider
));
793 /* ex: ret == 100.033, tada! ... hopefully */
799 _handle_spamd_header(struct message
*m
, int flags
, char *buf
, int len
,
800 unsigned int *didtellflags
)
803 char s_str
[21], t_str
[21];
805 char didremove_ret
[15];
807 UNUSED_VARIABLE(len
);
809 /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
810 * let's stick with it for this parser.
811 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
812 * work around using our own locale-independent float-parser code.
814 if (sscanf(buf
, "Spam: %5s ; %20s / %20s", is_spam
, s_str
, t_str
) == 3) {
815 m
->score
= _locale_safe_string_to_float(s_str
, 20);
816 m
->threshold
= _locale_safe_string_to_float(t_str
, 20);
818 /* set bounds on these to ensure no buffer overflow in the sprintf */
821 else if (m
->score
< -1e10
)
823 if (m
->threshold
> 1e10
)
825 else if (m
->threshold
< -1e10
)
826 m
->threshold
= -1e10
;
828 /* Format is "Spam: x; y / x" */
830 strcasecmp("true", is_spam
) == 0 ? EX_ISSPAM
: EX_NOTSPAM
;
832 if (flags
& SPAMC_CHECK_ONLY
) {
833 m
->out_len
= sprintf(m
->out
,
834 "%.1f/%.1f\n", m
->score
, m
->threshold
);
836 else if ((flags
& SPAMC_REPORT_IFSPAM
&& m
->is_spam
== EX_ISSPAM
)
837 || (flags
& SPAMC_REPORT
)) {
838 m
->out_len
= sprintf(m
->out
,
839 "%.1f/%.1f\n", m
->score
, m
->threshold
);
844 else if (sscanf(buf
, "Content-length: %d", &m
->content_length
) == 1) {
845 if (m
->content_length
< 0) {
846 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad Content-length '%s'",
852 else if (sscanf(buf
, "DidSet: %s", didset_ret
) == 1) {
853 if (strstr(didset_ret
, "local")) {
854 *didtellflags
|= SPAMC_SET_LOCAL
;
856 if (strstr(didset_ret
, "remote")) {
857 *didtellflags
|= SPAMC_SET_REMOTE
;
860 else if (sscanf(buf
, "DidRemove: %s", didremove_ret
) == 1) {
861 if (strstr(didremove_ret
, "local")) {
862 *didtellflags
|= SPAMC_REMOVE_LOCAL
;
864 if (strstr(didremove_ret
, "remote")) {
865 *didtellflags
|= SPAMC_REMOVE_REMOTE
;
872 int message_filter(struct transport
*tp
, const char *username
,
873 int flags
, struct message
*m
)
876 size_t bufsiz
= (sizeof(buf
) / sizeof(*buf
)) - 4; /* bit of breathing room */
884 unsigned int throwaway
;
892 if (flags
& SPAMC_USE_SSL
) {
894 SSLeay_add_ssl_algorithms();
895 meth
= SSLv2_client_method();
896 SSL_load_error_strings();
897 ctx
= SSL_CTX_new(meth
);
899 UNUSED_VARIABLE(ssl
);
900 UNUSED_VARIABLE(meth
);
901 UNUSED_VARIABLE(ctx
);
902 libspamc_log(flags
, LOG_ERR
, "spamc not built with SSL support");
907 m
->is_spam
= EX_TOOBIG
;
908 if ((m
->outbuf
= malloc(m
->max_len
+ EXPANSION_ALLOWANCE
+ 1)) == NULL
) {
909 failureval
= EX_OSERR
;
915 /* Build spamd protocol header */
916 if (flags
& SPAMC_CHECK_ONLY
)
917 strcpy(buf
, "CHECK ");
918 else if (flags
& SPAMC_REPORT_IFSPAM
)
919 strcpy(buf
, "REPORT_IFSPAM ");
920 else if (flags
& SPAMC_REPORT
)
921 strcpy(buf
, "REPORT ");
922 else if (flags
& SPAMC_SYMBOLS
)
923 strcpy(buf
, "SYMBOLS ");
925 strcpy(buf
, "PROCESS ");
928 if (len
+ strlen(PROTOCOL_VERSION
) + 2 >= bufsiz
) {
933 strcat(buf
, PROTOCOL_VERSION
);
937 if (username
!= NULL
) {
938 if (strlen(username
) + 8 >= (bufsiz
- len
)) {
942 strcpy(buf
+ len
, "User: ");
943 strcat(buf
+ len
, username
);
944 strcat(buf
+ len
, "\r\n");
945 len
+= strlen(buf
+ len
);
947 if ((m
->msg_len
> 9999999) || ((len
+ 27) >= (bufsiz
- len
))) {
951 len
+= sprintf(buf
+ len
, "Content-length: %d\r\n\r\n", m
->msg_len
);
953 libspamc_timeout
= m
->timeout
;
956 rc
= _try_to_connect_unix(tp
, &sock
);
958 rc
= _try_to_connect_tcp(tp
, &sock
);
962 return rc
; /* use the error code try_to_connect_*() gave us. */
965 if (flags
& SPAMC_USE_SSL
) {
968 SSL_set_fd(ssl
, sock
);
974 if (flags
& SPAMC_USE_SSL
) {
976 SSL_write(ssl
, buf
, len
);
977 SSL_write(ssl
, m
->msg
, m
->msg_len
);
981 full_write(sock
, 0, buf
, len
);
982 full_write(sock
, 0, m
->msg
, m
->msg_len
);
983 shutdown(sock
, SHUT_WR
);
986 /* ok, now read and parse it. SPAMD/1.2 line first... */
988 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
989 if (failureval
!= EX_OK
) {
993 if (sscanf(buf
, "SPAMD/%18s %d %*s", versbuf
, &response
) != 2) {
994 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad string '%s'", buf
);
995 failureval
= EX_PROTOCOL
;
1000 version
= _locale_safe_string_to_float(versbuf
, 20);
1001 if (version
< 1.0) {
1002 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad version string '%s'",
1004 failureval
= EX_PROTOCOL
;
1010 m
->is_spam
= EX_TOOBIG
;
1013 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1014 if (failureval
!= EX_OK
) {
1018 if (len
== 0 && buf
[0] == '\0') {
1019 break; /* end of headers */
1022 if (_handle_spamd_header(m
, flags
, buf
, len
, &throwaway
) < 0) {
1023 failureval
= EX_PROTOCOL
;
1028 len
= 0; /* overwrite those headers */
1030 if (flags
& SPAMC_CHECK_ONLY
) {
1033 if (m
->is_spam
== EX_TOOBIG
) {
1034 /* We should have gotten headers back... Damnit. */
1035 failureval
= EX_PROTOCOL
;
1041 if (m
->content_length
< 0) {
1042 /* should have got a length too. */
1043 failureval
= EX_PROTOCOL
;
1047 /* have we already got something in the buffer (e.g. REPORT and
1048 * REPORT_IFSPAM both create a line from the "Spam:" hdr)? If
1049 * so, add the size of that so our sanity check passes.
1051 if (m
->out_len
> 0) {
1052 m
->content_length
+= m
->out_len
;
1055 if (flags
& SPAMC_USE_SSL
) {
1056 len
= full_read_ssl(ssl
, (unsigned char *) m
->out
+ m
->out_len
,
1057 m
->max_len
+ EXPANSION_ALLOWANCE
+ 1 -
1059 m
->max_len
+ EXPANSION_ALLOWANCE
+ 1 -
1063 len
= full_read(sock
, 0, m
->out
+ m
->out_len
,
1064 m
->max_len
+ EXPANSION_ALLOWANCE
+ 1 - m
->out_len
,
1065 m
->max_len
+ EXPANSION_ALLOWANCE
+ 1 -
1070 if (len
+ m
->out_len
> m
->max_len
+ EXPANSION_ALLOWANCE
) {
1071 failureval
= EX_TOOBIG
;
1076 shutdown(sock
, SHUT_RD
);
1080 libspamc_timeout
= 0;
1082 if (m
->out_len
!= m
->content_length
) {
1083 libspamc_log(flags
, LOG_ERR
,
1084 "failed sanity check, %d bytes claimed, %d bytes seen",
1085 m
->content_length
, m
->out_len
);
1086 failureval
= EX_PROTOCOL
;
1093 _use_msg_for_out(m
);
1097 libspamc_timeout
= 0;
1099 if (flags
& SPAMC_USE_SSL
) {
1109 int message_process(struct transport
*trans
, char *username
, int max_size
,
1110 int in_fd
, int out_fd
, const int flags
)
1115 assert(trans
!= NULL
);
1117 m
.type
= MESSAGE_NONE
;
1119 m
.max_len
= max_size
;
1120 ret
= message_read(in_fd
, flags
, &m
);
1123 ret
= message_filter(trans
, username
, flags
, &m
);
1126 if (message_write(out_fd
, &m
) < 0)
1128 if (m
.is_spam
!= EX_TOOBIG
) {
1129 message_cleanup(&m
);
1132 message_cleanup(&m
);
1136 if (flags
& SPAMC_CHECK_ONLY
) {
1137 full_write(out_fd
, 1, "0/0\n", 4);
1138 message_cleanup(&m
);
1142 message_dump(in_fd
, out_fd
, &m
);
1143 message_cleanup(&m
);
1148 int message_tell(struct transport
*tp
, const char *username
, int flags
,
1149 struct message
*m
, int msg_class
,
1150 unsigned int tellflags
, unsigned int *didtellflags
)
1153 size_t bufsiz
= (sizeof(buf
) / sizeof(*buf
)) - 4; /* bit of breathing room */
1161 SSL_CTX
*ctx
= NULL
;
1168 if (flags
& SPAMC_USE_SSL
) {
1170 SSLeay_add_ssl_algorithms();
1171 meth
= SSLv2_client_method();
1172 SSL_load_error_strings();
1173 ctx
= SSL_CTX_new(meth
);
1175 UNUSED_VARIABLE(ssl
);
1176 UNUSED_VARIABLE(meth
);
1177 UNUSED_VARIABLE(ctx
);
1178 libspamc_log(flags
, LOG_ERR
, "spamc not built with SSL support");
1183 m
->is_spam
= EX_TOOBIG
;
1184 if ((m
->outbuf
= malloc(m
->max_len
+ EXPANSION_ALLOWANCE
+ 1)) == NULL
) {
1185 failureval
= EX_OSERR
;
1191 /* Build spamd protocol header */
1192 strcpy(buf
, "TELL ");
1195 if (len
+ strlen(PROTOCOL_VERSION
) + 2 >= bufsiz
) {
1196 _use_msg_for_out(m
);
1200 strcat(buf
, PROTOCOL_VERSION
);
1201 strcat(buf
, "\r\n");
1204 if (msg_class
!= 0) {
1205 strcpy(buf
+ len
, "Message-class: ");
1206 if (msg_class
== SPAMC_MESSAGE_CLASS_SPAM
) {
1207 strcat(buf
+ len
, "spam\r\n");
1210 strcat(buf
+ len
, "ham\r\n");
1212 len
+= strlen(buf
+ len
);
1215 if ((tellflags
& SPAMC_SET_LOCAL
) || (tellflags
& SPAMC_SET_REMOTE
)) {
1216 int needs_comma_p
= 0;
1217 strcat(buf
+ len
, "Set: ");
1218 if (tellflags
& SPAMC_SET_LOCAL
) {
1219 strcat(buf
+ len
, "local");
1222 if (tellflags
& SPAMC_SET_REMOTE
) {
1223 if (needs_comma_p
== 1) {
1224 strcat(buf
+ len
, ",");
1226 strcat(buf
+ len
, "remote");
1228 strcat(buf
+ len
, "\r\n");
1229 len
+= strlen(buf
+ len
);
1232 if ((tellflags
& SPAMC_REMOVE_LOCAL
) || (tellflags
& SPAMC_REMOVE_REMOTE
)) {
1233 int needs_comma_p
= 0;
1234 strcat(buf
+ len
, "Remove: ");
1235 if (tellflags
& SPAMC_REMOVE_LOCAL
) {
1236 strcat(buf
+ len
, "local");
1239 if (tellflags
& SPAMC_REMOVE_REMOTE
) {
1240 if (needs_comma_p
== 1) {
1241 strcat(buf
+ len
, ",");
1243 strcat(buf
+ len
, "remote");
1245 strcat(buf
+ len
, "\r\n");
1246 len
+= strlen(buf
+ len
);
1249 if (username
!= NULL
) {
1250 if (strlen(username
) + 8 >= (bufsiz
- len
)) {
1251 _use_msg_for_out(m
);
1254 strcpy(buf
+ len
, "User: ");
1255 strcat(buf
+ len
, username
);
1256 strcat(buf
+ len
, "\r\n");
1257 len
+= strlen(buf
+ len
);
1259 if ((m
->msg_len
> 9999999) || ((len
+ 27) >= (bufsiz
- len
))) {
1260 _use_msg_for_out(m
);
1263 len
+= sprintf(buf
+ len
, "Content-length: %d\r\n\r\n", m
->msg_len
);
1265 libspamc_timeout
= m
->timeout
;
1268 rc
= _try_to_connect_unix(tp
, &sock
);
1270 rc
= _try_to_connect_tcp(tp
, &sock
);
1273 _use_msg_for_out(m
);
1274 return rc
; /* use the error code try_to_connect_*() gave us. */
1277 if (flags
& SPAMC_USE_SSL
) {
1280 SSL_set_fd(ssl
, sock
);
1286 if (flags
& SPAMC_USE_SSL
) {
1288 SSL_write(ssl
, buf
, len
);
1289 SSL_write(ssl
, m
->msg
, m
->msg_len
);
1293 full_write(sock
, 0, buf
, len
);
1294 full_write(sock
, 0, m
->msg
, m
->msg_len
);
1295 shutdown(sock
, SHUT_WR
);
1298 /* ok, now read and parse it. SPAMD/1.2 line first... */
1300 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1301 if (failureval
!= EX_OK
) {
1305 if (sscanf(buf
, "SPAMD/%18s %d %*s", versbuf
, &response
) != 2) {
1306 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad string '%s'", buf
);
1307 failureval
= EX_PROTOCOL
;
1312 version
= _locale_safe_string_to_float(versbuf
, 20);
1313 if (version
< 1.0) {
1314 libspamc_log(flags
, LOG_ERR
, "spamd responded with bad version string '%s'",
1316 failureval
= EX_PROTOCOL
;
1322 m
->is_spam
= EX_TOOBIG
;
1325 _spamc_read_full_line(m
, flags
, ssl
, sock
, buf
, &len
, bufsiz
);
1326 if (failureval
!= EX_OK
) {
1330 if (len
== 0 && buf
[0] == '\0') {
1331 break; /* end of headers */
1334 if (_handle_spamd_header(m
, flags
, buf
, len
, didtellflags
) < 0) {
1335 failureval
= EX_PROTOCOL
;
1340 len
= 0; /* overwrite those headers */
1342 shutdown(sock
, SHUT_RD
);
1346 libspamc_timeout
= 0;
1351 _use_msg_for_out(m
);
1355 libspamc_timeout
= 0;
1357 if (flags
& SPAMC_USE_SSL
) {
1366 void message_cleanup(struct message
*m
)
1369 if (m
->outbuf
!= NULL
)
1371 if (m
->raw
!= NULL
) {
1372 if (munmap(m
->raw
, m
->raw_len
) != 0) {
1373 libspamc_log(0,0, "munmap");
1377 if (m
->priv
!= NULL
)
1382 /* Aug 14, 2002 bj: Obsolete! */
1383 int process_message(struct transport
*tp
, char *username
, int max_size
,
1384 int in_fd
, int out_fd
, const int my_check_only
,
1385 const int my_safe_fallback
)
1389 flags
= SPAMC_RAW_MODE
;
1391 flags
|= SPAMC_CHECK_ONLY
;
1392 if (my_safe_fallback
)
1393 flags
|= SPAMC_SAFE_FALLBACK
;
1395 return message_process(tp
, username
, max_size
, in_fd
, out_fd
, flags
);
1401 * Given a pointer to a transport structure, set it to "all empty".
1402 * The default is a localhost connection.
1404 void transport_init(struct transport
*tp
)
1408 memset(tp
, 0, sizeof *tp
);
1410 tp
->type
= TRANSPORT_LOCALHOST
;
1418 * Given the transport object that contains one or more IP addresses
1419 * in this "hosts" list, rotate it by a random number of shifts to
1420 * randomize them - this is a kind of load balancing. It's possible
1421 * that the random number will be 0, which says not to touch. We don't
1422 * do anything unless
1425 static void _randomize_hosts(struct transport
*tp
)
1431 if (tp
->nhosts
<= 1)
1434 rnum
= rand() % tp
->nhosts
;
1436 while (rnum
-- > 0) {
1437 struct in_addr tmp
= tp
->hosts
[0];
1440 for (i
= 1; i
< tp
->nhosts
; i
++)
1441 tp
->hosts
[i
- 1] = tp
->hosts
[i
];
1443 tp
->hosts
[i
- 1] = tmp
;
1450 * Given a "transport" object that says how we're to connect to the
1451 * spam daemon, perform all the initial setup required to make the
1452 * connection process a smooth one. The main work is to do the host
1453 * name lookup and copy over all the IP addresses to make a local copy
1454 * so they're not kept in the resolver's static state.
1456 * Here we also manage quasi-load balancing and failover: if we're
1457 * doing load balancing, we randomly "rotate" the list to put it in
1458 * a different order, and then if we're not doing failover we limit
1459 * the hosts to just one. This way *all* connections are done with
1460 * the intention of failover - makes the code a bit more clear.
1462 int transport_setup(struct transport
*tp
, int flags
)
1465 char *hostlist
, *hostname
;
1473 if ((nCode
= WSAStartup(MAKEWORD(1, 1), &wsaData
)) != 0) {
1474 printf("WSAStartup() returned error code %d\n", nCode
);
1485 case TRANSPORT_UNIX
:
1486 assert(tp
->socketpath
!= 0);
1489 case TRANSPORT_LOCALHOST
:
1490 tp
->hosts
[0].s_addr
= inet_addr("127.0.0.1");
1495 if ((hostlist
= strdup(tp
->hostname
)) == NULL
)
1498 /* We want to return the least permanent error, in this bitmask we
1499 * record the errors seen with:
1503 * EX_OSERR will return immediately.
1504 * Bits aren't reset so a check against nhosts is needed to determine
1505 * if something went wrong.
1509 /* Start with char offset in front of the string because we'll add
1512 hostname
= hostlist
- 1;
1517 hostend
= strchr(hostname
, ',');
1518 if (hostend
!= NULL
) {
1522 if ((hp
= gethostbyname(hostname
)) == NULL
) {
1523 int origerr
= h_errno
; /* take a copy before syslog() */
1524 libspamc_log(flags
, LOG_DEBUG
, "gethostbyname(%s) failed: h_errno=%d",
1530 case HOST_NOT_FOUND
:
1536 /* should not happen, all errors are checked above */
1540 goto nexthost
; /* try next host in list */
1543 /* If we have no hosts at all, or if they are some other
1544 * kind of address family besides IPv4, then we really
1545 * just have no hosts at all. TODO: IPv6
1547 if (hp
->h_addr_list
[0] == NULL
1548 || hp
->h_length
!= sizeof tp
->hosts
[0]
1549 || hp
->h_addrtype
!= AF_INET
) {
1550 /* no hosts/bad size/wrong family */
1552 goto nexthost
; /* try next host in list */
1555 /* Copy all the IP addresses into our private structure.
1556 * This gets them out of the resolver's static area and
1557 * means we won't ever walk all over the list with other
1560 for (addrp
= hp
->h_addr_list
; *addrp
; addrp
++) {
1561 if (tp
->nhosts
== TRANSPORT_MAX_HOSTS
) {
1562 libspamc_log(flags
, LOG_NOTICE
, "hit limit of %d hosts, ignoring remainder",
1563 TRANSPORT_MAX_HOSTS
);
1566 memcpy(&tp
->hosts
[tp
->nhosts
], *addrp
, hp
->h_length
);
1572 } while (hostname
!= NULL
);
1575 if (tp
->nhosts
== 0) {
1577 libspamc_log(flags
, LOG_ERR
, "could not resolve any hosts (%s): a temporary error occurred",
1582 libspamc_log(flags
, LOG_ERR
, "could not resolve any hosts (%s): no such host",
1588 /* QUASI-LOAD-BALANCING
1590 * If the user wants to do quasi load balancing, "rotate"
1591 * the list by a random amount based on the current time.
1592 * This may later be truncated to a single item. This is
1593 * meaningful only if we have more than one host.
1595 if ((flags
& SPAMC_RANDOMIZE_HOSTS
) && tp
->nhosts
> 1) {
1596 _randomize_hosts(tp
);
1599 /* If the user wants no fallback, simply truncate the host
1600 * list to just one - this pretends that this is the extent
1601 * of our connection list - then it's not a special case.
1603 if (!(flags
& SPAMC_SAFE_FALLBACK
) && tp
->nhosts
> 1) {
1604 /* truncating list */
1611 /* oops, unknown transport type */
1615 /* --------------------------------------------------------------------------- */
1617 #define LOG_BUFSIZ 1023
1619 #ifndef libspamc_log
1621 libspamc_log (int flags
, int level
, char *msg
, ...)
1624 char buf
[LOG_BUFSIZ
+1];
1629 if ((flags
& SPAMC_LOG_TO_STDERR
) != 0) {
1630 // create a log-line buffer
1631 len
= snprintf(buf
, LOG_BUFSIZ
, "spamc: ");
1632 len
+= vsnprintf(buf
+len
, LOG_BUFSIZ
-len
, msg
, ap
);
1634 // avoid buffer overflow
1635 if (len
> (LOG_BUFSIZ
-2)) { len
= (LOG_BUFSIZ
-3); }
1637 len
+= snprintf(buf
+len
, LOG_BUFSIZ
-len
, "\n");
1638 buf
[LOG_BUFSIZ
] = '\0'; /* ensure termination */
1639 (void) write (2, buf
, len
);
1642 vsnprintf(buf
, LOG_BUFSIZ
, msg
, ap
);
1643 buf
[LOG_BUFSIZ
] = '\0'; /* ensure termination */
1645 syslog (level
, "%s", buf
);
1647 (void) level
; /* not used. suppress compiler warning */
1648 fprintf (stderr
, "%s\n", buf
);
1656 /* --------------------------------------------------------------------------- */
1659 * Unit tests. Must be built externally, e.g.:
1661 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
1665 #ifdef LIBSPAMC_UNIT_TESTS
1667 static void _test_locale_safe_string_to_float_val(float input
)
1669 char inputstr
[99], cmpbuf1
[99], cmpbuf2
[99];
1672 /* sprintf instead of snprintf is safe here because it is only a controlled test */
1673 sprintf(inputstr
, "%f", input
);
1674 output
= _locale_safe_string_to_float(inputstr
, 99);
1675 if (input
== output
) {
1679 /* could be a rounding error. print as string and compare those */
1680 sprintf(cmpbuf1
, "%f", input
);
1681 sprintf(cmpbuf2
, "%f", output
);
1682 if (!strcmp(cmpbuf1
, cmpbuf2
)) {
1686 printf("FAIL: input=%f != output=%f\n", input
, output
);
1689 static void unit_test_locale_safe_string_to_float(void)
1691 float statictestset
[] = { /* will try both +ve and -ve */
1692 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
1693 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
1694 0.0 /* end of set constant */
1699 printf("starting unit_test_locale_safe_string_to_float\n");
1700 /* tests of precision */
1701 for (i
= 0; statictestset
[i
] != 0.0; i
++) {
1702 _test_locale_safe_string_to_float_val(statictestset
[i
]);
1703 _test_locale_safe_string_to_float_val(-statictestset
[i
]);
1704 _test_locale_safe_string_to_float_val(1 - statictestset
[i
]);
1705 _test_locale_safe_string_to_float_val(1 + statictestset
[i
]);
1707 /* now exhaustive, in steps of 0.01 */
1708 for (num
= -1000.0; num
< 1000.0; num
+= 0.01) {
1709 _test_locale_safe_string_to_float_val(num
);
1711 printf("finished unit_test_locale_safe_string_to_float\n");
1714 void do_libspamc_unit_tests(void)
1716 unit_test_locale_safe_string_to_float();
1720 #endif /* LIBSPAMC_UNIT_TESTS */