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 #define MAX_CONNECT_RETRIES 3
61 #define CONNECT_RETRY_SLEEP 1
63 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
65 /* SJF 2003/04/25 - now test for macros directly */
67 # define SHUT_RD 0 /* no more receptions */
70 # define SHUT_WR 1 /* no more transmissions */
73 # define SHUT_RDWR 2 /* no more receptions or transmissions */
84 #ifndef HAVE_INADDR_NONE
85 #define INADDR_NONE ((in_addr_t) 0xffffffff)
88 /* jm: turned off for now, it should not be necessary. */
89 #undef USE_TCP_NODELAY
92 /* jm: very conservative figure, should be well out of range on almost all NIXes */
96 #undef DO_CONNECT_DEBUG_SYSLOGS
97 /* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
99 static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
101 /* set EXPANSION_ALLOWANCE to something more than might be
102 added to a message in X-headers and the report template */
103 static const int EXPANSION_ALLOWANCE = 16384;
105 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
106 of the data streams before and after processing by spamd
107 Aug 7 2002 jm: no longer seems to be used
108 static const int NUM_CHECK_BYTES = 32;
111 /* Set the protocol version that this spamc speaks */
112 static const char *PROTOCOL_VERSION = "SPAMC/1.3";
114 /* "private" part of struct message.
115 * we use this instead of the struct message directly, so that we
116 * can add new members without affecting the ABI.
118 struct libspamc_private_message
120 int flags; /* copied from "flags" arg to message_read() */
123 int libspamc_timeout = 0;
126 * translate_connect_errno()
128 * Given a UNIX error number obtained (probably) from "connect(2)",
129 * translate this to a failure code. This module is shared by both
130 * transport modules - UNIX and TCP.
132 * This should ONLY be called when there is an error.
134 static int _translate_connect_errno(int err)
150 return EX_UNAVAILABLE;
163 * Given a socket type (PF_INET or PF_UNIX), try to create this socket
164 * and store the FD in the pointed-to place. If it's successful, do any
165 * other setup required to make the socket ready to use, such as setting
166 * TCP_NODELAY mode, and in any case we return EX_OK if all is well.
168 * Upon failure we return one of the other EX_??? error codes.
170 static int _opensocket(int flags, int type, int *psock)
172 const char *typename;
177 /*----------------------------------------------------------------
178 * Create a few induction variables that are implied by the socket
179 * type given by the user. The typename is strictly used for debug
182 if (type == PF_UNIX) {
183 typename = "PF_UNIX";
186 typename = "PF_INET";
190 #ifdef DO_CONNECT_DEBUG_SYSLOGS
191 libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
194 if ((*psock = socket(type, SOCK_STREAM, proto))
203 /*--------------------------------------------------------
204 * At this point we had a failure creating the socket, and
205 * this is pretty much fatal. Translate the error reason
206 * into something the user can understand.
209 origerr = errno; /* take a copy before syslog() */
210 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
212 origerr = WSAGetLastError();
213 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
217 case EPROTONOSUPPORT:
236 /*----------------------------------------------------------------
237 * Do a bit of setup on the TCP socket if required. Notes above
238 * suggest this is probably not set
240 #ifdef USE_TCP_NODELAY
245 && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
250 origerrno = WSAGetLastError();
257 libspamc_log(flags, LOG_ERR,
259 "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
261 "setsockopt(TCP_NODELAY) failed: %d", origerr);
271 #endif /* USE_TCP_NODELAY */
273 return EX_OK; /* all is well */
277 * try_to_connect_unix()
279 * Given a transport handle that implies using a UNIX domain
280 * socket, try to make a connection to it and store the resulting
281 * file descriptor in *sockptr. Return is EX_OK if we did it,
282 * and some other error code otherwise.
284 static int _try_to_connect_unix(struct transport *tp, int *sockptr)
287 int mysock, status, origerr;
288 struct sockaddr_un addrbuf;
292 assert(sockptr != 0);
293 assert(tp->socketpath != 0);
295 /*----------------------------------------------------------------
296 * If the socket itself can't be created, this is a fatal error.
298 if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
301 /* set up the UNIX domain socket */
302 memset(&addrbuf, 0, sizeof addrbuf);
303 addrbuf.sun_family = AF_UNIX;
304 strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
305 addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
307 #ifdef DO_CONNECT_DEBUG_SYSLOGS
308 libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
312 status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
317 #ifdef DO_CONNECT_DEBUG_SYSLOGS
318 libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
326 libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
327 addrbuf.sun_path, strerror(origerr));
330 return _translate_connect_errno(origerr);
332 (void) tp; /* not used. suppress compiler warning */
333 (void) sockptr; /* not used. suppress compiler warning */
339 * try_to_connect_tcp()
341 * Given a transport that implies a TCP connection, either to
342 * localhost or a list of IP addresses, attempt to connect. The
343 * list of IP addresses has already been randomized (if requested)
344 * and limited to just one if fallback has been enabled.
346 static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
353 assert(sockptr != 0);
354 assert(tp->nhosts > 0);
356 #ifdef DO_CONNECT_DEBUG_SYSLOGS
357 for (numloops = 0; numloops < tp->nhosts; numloops++) {
358 libspamc_log(tp->flags, LOG_ERR, "dbg: %d/%d: %s",
359 numloops + 1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
363 for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
364 struct sockaddr_in addrbuf;
365 const int hostix = numloops % tp->nhosts;
369 /*--------------------------------------------------------
370 * We always start by creating the socket, as we get only
371 * one attempt to connect() on each one. If this fails,
374 if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
377 memset(&addrbuf, 0, sizeof(addrbuf));
379 addrbuf.sin_family = AF_INET;
380 addrbuf.sin_port = htons(tp->port);
381 addrbuf.sin_addr = tp->hosts[hostix];
383 ipaddr = inet_ntoa(addrbuf.sin_addr);
385 #ifdef DO_CONNECT_DEBUG_SYSLOGS
386 libspamc_log(tp->flags, DEBUG_LEVEL,
387 "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
388 ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
392 connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
397 libspamc_log(tp->flags, LOG_ERR,
398 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
399 ipaddr, numloops + 1, MAX_CONNECT_RETRIES, strerror(origerr));
401 origerr = WSAGetLastError();
402 libspamc_log(tp->flags, LOG_ERR,
403 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
404 ipaddr, numloops + 1, MAX_CONNECT_RETRIES, origerr);
408 sleep(CONNECT_RETRY_SLEEP);
411 #ifdef DO_CONNECT_DEBUG_SYSLOGS
412 libspamc_log(tp->flags, DEBUG_LEVEL,
413 "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
421 libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
422 MAX_CONNECT_RETRIES);
424 return _translate_connect_errno(origerr);
427 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
428 * message_dump, lookup_host, message_filter, and message_process, and a bunch
429 * of helper functions.
432 static void _clear_message(struct message *m)
434 m->type = MESSAGE_NONE;
443 m->is_spam = EX_TOOBIG;
448 m->content_length = -1;
451 static int _message_read_raw(int fd, struct message *m)
454 if ((m->raw = malloc(m->max_len + 1)) == NULL)
456 m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
457 if (m->raw_len <= 0) {
463 m->type = MESSAGE_ERROR;
464 if (m->raw_len > m->max_len)
466 m->type = MESSAGE_RAW;
468 m->msg_len = m->raw_len;
470 m->out_len = m->msg_len;
474 static int _message_read_bsmtp(int fd, struct message *m)
480 if ((m->raw = malloc(m->max_len + 1)) == NULL)
483 /* Find the DATA line */
484 m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
485 if (m->raw_len <= 0) {
491 m->type = MESSAGE_ERROR;
492 if (m->raw_len > m->max_len)
495 for (i = 0; i < m->raw_len - 6; i++) {
496 if ((m->raw[i] == '\n') &&
497 (m->raw[i + 1] == 'D' || m->raw[i + 1] == 'd') &&
498 (m->raw[i + 2] == 'A' || m->raw[i + 2] == 'a') &&
499 (m->raw[i + 3] == 'T' || m->raw[i + 3] == 't') &&
500 (m->raw[i + 4] == 'A' || m->raw[i + 4] == 'a') &&
501 ((m->raw[i + 5] == '\r' && m->raw[i + 6] == '\n')
502 || m->raw[i + 5] == '\n')) {
505 if (m->raw[i - 1] == '\r')
509 m->msg_len = m->raw_len - i;
516 /* Find the end-of-DATA line */
518 for (i = j = 0; i < m->msg_len; i++) {
519 if (prev == '\n' && m->msg[i] == '.') {
520 /* Dot at the beginning of a line */
521 if ((m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')
522 || m->msg[i + 1] == '\n') {
523 /* Lone dot! That's all, folks */
524 m->post = m->msg + i;
525 m->post_len = m->msg_len - i;
529 else if (m->msg[i + 1] == '.') {
530 /* Escaping dot, eliminate. */
533 } /* Else an ordinary dot, drop down to ordinary char handler */
536 m->msg[j++] = m->msg[i];
539 m->type = MESSAGE_BSMTP;
541 m->out_len = m->msg_len;
545 int message_read(int fd, int flags, struct message *m)
547 libspamc_timeout = 0;
549 /* create the "private" part of the struct message */
550 m->priv = malloc(sizeof(struct libspamc_private_message));
551 if (m->priv == NULL) {
552 libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
555 m->priv->flags = flags;
557 switch (flags & SPAMC_MODE_MASK) {
559 return _message_read_raw(fd, m);
561 case SPAMC_BSMTP_MODE:
562 return _message_read_bsmtp(fd, m);
565 libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
566 flags & SPAMC_MODE_MASK);
571 long message_write(int fd, struct message *m)
578 if (m->priv->flags & SPAMC_CHECK_ONLY) {
579 if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
580 return full_write(fd, 1, m->out, m->out_len);
584 libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
590 /* else we're not in CHECK_ONLY mode */
593 libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
597 return full_write(fd, 1, m->raw, m->raw_len);
600 return full_write(fd, 1, m->out, m->out_len);
603 total = full_write(fd, 1, m->pre, m->pre_len);
604 for (i = 0; i < m->out_len;) {
605 jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
606 for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
607 if (i + 1 < m->out_len && m->out[i] == '\n'
608 && m->out[i + 1] == '.') {
609 if (j > jlimit - 4) {
610 break; /* avoid overflow */
612 buffer[j++] = m->out[i++];
613 buffer[j++] = m->out[i++];
617 buffer[j++] = m->out[i++];
620 total += full_write(fd, 1, buffer, j);
622 return total + full_write(fd, 1, m->post, m->post_len);
625 libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
630 void message_dump(int in_fd, int out_fd, struct message *m)
635 if (m != NULL && m->type != MESSAGE_NONE) {
636 message_write(out_fd, m);
638 while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
639 if (bytes != full_write(out_fd, 1, buf, bytes)) {
640 libspamc_log(m->priv->flags, LOG_ERR, "oops! message_dump of %d returned different",
647 _spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
648 char *buf, size_t *lenp, size_t bufsiz)
657 /* Now, read from spamd */
658 for (len = 0; len < bufsiz - 1; len++) {
659 if (flags & SPAMC_USE_SSL) {
660 bytesread = ssl_timeout_read(ssl, buf + len, 1);
663 bytesread = fd_timeout_read(sock, 0, buf + len, 1);
666 if (bytesread <= 0) {
667 failureval = EX_IOERR;
671 if (buf[len] == '\n') {
673 if (len > 0 && buf[len - 1] == '\r') {
682 libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
683 failureval = EX_TOOBIG;
690 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
691 * work around using our own locale-independent float-parser code.
693 static float _locale_safe_string_to_float(char *buf, int siz)
700 buf[siz - 1] = '\0'; /* ensure termination */
702 /* ok, let's illustrate using "100.033" as an example... */
709 ret = (float) (strtol(buf, &dot, 10));
713 if (dot != NULL && *dot != '.') {
717 /* ex: ret == 100.0 */
720 postdot = (float) (strtol(cp, NULL, 10));
721 if (postdot == 0.0) {
725 /* ex: postdot == 33.0, cp="033" */
727 /* now count the number of decimal places and figure out what power of 10 to use */
729 while (*cp != '\0') {
735 * cp="033", divider=1
736 * cp="33", divider=10
737 * cp="3", divider=100
738 * cp="", divider=1000
742 ret -= (postdot / ((float) divider));
745 ret += (postdot / ((float) divider));
747 /* ex: ret == 100.033, tada! ... hopefully */
753 _handle_spamd_header(struct message *m, int flags, char *buf, int len)
756 char s_str[21], t_str[21];
758 UNUSED_VARIABLE(len);
760 /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
761 * let's stick with it for this parser.
762 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
763 * work around using our own locale-independent float-parser code.
765 if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
766 m->score = _locale_safe_string_to_float(s_str, 20);
767 m->threshold = _locale_safe_string_to_float(t_str, 20);
769 /* set bounds on these to ensure no buffer overflow in the sprintf */
772 else if (m->score < -1e10)
774 if (m->threshold > 1e10)
776 else if (m->threshold < -1e10)
777 m->threshold = -1e10;
779 /* Format is "Spam: x; y / x" */
781 strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
783 if (flags & SPAMC_CHECK_ONLY) {
784 m->out_len = sprintf(m->out,
785 "%.1f/%.1f\n", m->score, m->threshold);
787 else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
788 || (flags & SPAMC_REPORT)) {
789 m->out_len = sprintf(m->out,
790 "%.1f/%.1f\n", m->score, m->threshold);
795 else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
796 if (m->content_length < 0) {
797 libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
804 libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
808 int message_filter(struct transport *tp, const char *username,
809 int flags, struct message *m)
812 size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
824 if (flags & SPAMC_USE_SSL) {
826 SSLeay_add_ssl_algorithms();
827 meth = SSLv2_client_method();
828 SSL_load_error_strings();
829 ctx = SSL_CTX_new(meth);
831 UNUSED_VARIABLE(ssl);
832 UNUSED_VARIABLE(meth);
833 UNUSED_VARIABLE(ctx);
834 libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
839 m->is_spam = EX_TOOBIG;
840 if ((m->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
841 failureval = EX_OSERR;
847 /* Build spamd protocol header */
848 if (flags & SPAMC_CHECK_ONLY)
849 strcpy(buf, "CHECK ");
850 else if (flags & SPAMC_REPORT_IFSPAM)
851 strcpy(buf, "REPORT_IFSPAM ");
852 else if (flags & SPAMC_REPORT)
853 strcpy(buf, "REPORT ");
854 else if (flags & SPAMC_SYMBOLS)
855 strcpy(buf, "SYMBOLS ");
857 strcpy(buf, "PROCESS ");
860 if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
863 m->out_len = m->msg_len;
867 strcat(buf, PROTOCOL_VERSION);
871 if (username != NULL) {
872 if (strlen(username) + 8 >= (bufsiz - len)) {
875 m->out_len = m->msg_len;
878 strcpy(buf + len, "User: ");
879 strcat(buf + len, username);
880 strcat(buf + len, "\r\n");
881 len += strlen(buf + len);
883 if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
886 m->out_len = m->msg_len;
889 len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
891 libspamc_timeout = m->timeout;
894 rc = _try_to_connect_unix(tp, &sock);
896 rc = _try_to_connect_tcp(tp, &sock);
901 m->out_len = m->msg_len;
902 return rc; /* use the error code try_to_connect_*() gave us. */
905 if (flags & SPAMC_USE_SSL) {
908 SSL_set_fd(ssl, sock);
914 if (flags & SPAMC_USE_SSL) {
916 SSL_write(ssl, buf, len);
917 SSL_write(ssl, m->msg, m->msg_len);
921 full_write(sock, 0, buf, len);
922 full_write(sock, 0, m->msg, m->msg_len);
923 shutdown(sock, SHUT_WR);
926 /* ok, now read and parse it. SPAMD/1.2 line first... */
928 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
929 if (failureval != EX_OK) {
933 if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
934 libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
935 failureval = EX_PROTOCOL;
940 version = _locale_safe_string_to_float(versbuf, 20);
942 libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
944 failureval = EX_PROTOCOL;
950 m->is_spam = EX_TOOBIG;
953 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
954 if (failureval != EX_OK) {
958 if (len == 0 && buf[0] == '\0') {
959 break; /* end of headers */
962 if (_handle_spamd_header(m, flags, buf, len) < 0) {
963 failureval = EX_PROTOCOL;
968 len = 0; /* overwrite those headers */
970 if (flags & SPAMC_CHECK_ONLY) {
973 if (m->is_spam == EX_TOOBIG) {
974 /* We should have gotten headers back... Damnit. */
975 failureval = EX_PROTOCOL;
981 if (m->content_length < 0) {
982 /* should have got a length too. */
983 failureval = EX_PROTOCOL;
987 /* have we already got something in the buffer (e.g. REPORT and
988 * REPORT_IFSPAM both create a line from the "Spam:" hdr)? If
989 * so, add the size of that so our sanity check passes.
991 if (m->out_len > 0) {
992 m->content_length += m->out_len;
995 if (flags & SPAMC_USE_SSL) {
996 len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
997 m->max_len + EXPANSION_ALLOWANCE + 1 -
999 m->max_len + EXPANSION_ALLOWANCE + 1 -
1003 len = full_read(sock, 0, m->out + m->out_len,
1004 m->max_len + EXPANSION_ALLOWANCE + 1 - m->out_len,
1005 m->max_len + EXPANSION_ALLOWANCE + 1 -
1010 if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
1011 failureval = EX_TOOBIG;
1016 shutdown(sock, SHUT_RD);
1020 libspamc_timeout = 0;
1022 if (m->out_len != m->content_length) {
1023 libspamc_log(flags, LOG_ERR,
1024 "failed sanity check, %d bytes claimed, %d bytes seen",
1025 m->content_length, m->out_len);
1026 failureval = EX_PROTOCOL;
1035 m->out_len = m->msg_len;
1039 libspamc_timeout = 0;
1041 if (flags & SPAMC_USE_SSL) {
1051 int message_process(struct transport *trans, char *username, int max_size,
1052 int in_fd, int out_fd, const int flags)
1057 m.type = MESSAGE_NONE;
1059 m.max_len = max_size;
1060 ret = message_read(in_fd, flags, &m);
1063 ret = message_filter(trans, username, flags, &m);
1066 if (message_write(out_fd, &m) < 0)
1068 if (m.is_spam != EX_TOOBIG) {
1069 message_cleanup(&m);
1072 message_cleanup(&m);
1076 if (flags & SPAMC_CHECK_ONLY) {
1077 full_write(out_fd, 1, "0/0\n", 4);
1078 message_cleanup(&m);
1082 message_dump(in_fd, out_fd, &m);
1083 message_cleanup(&m);
1088 void message_cleanup(struct message *m)
1092 if (m->raw != NULL && m->raw != m->out)
1094 if (m->priv != NULL && (char*)m->priv != m->out && (char*)m->priv != m->raw)
1099 /* Aug 14, 2002 bj: Obsolete! */
1100 int process_message(struct transport *tp, char *username, int max_size,
1101 int in_fd, int out_fd, const int my_check_only,
1102 const int my_safe_fallback)
1106 flags = SPAMC_RAW_MODE;
1108 flags |= SPAMC_CHECK_ONLY;
1109 if (my_safe_fallback)
1110 flags |= SPAMC_SAFE_FALLBACK;
1112 return message_process(tp, username, max_size, in_fd, out_fd, flags);
1118 * Given a pointer to a transport structure, set it to "all empty".
1119 * The default is a localhost connection.
1121 void transport_init(struct transport *tp)
1125 memset(tp, 0, sizeof *tp);
1127 tp->type = TRANSPORT_LOCALHOST;
1135 * Given the transport object that contains one or more IP addresses
1136 * in this "hosts" list, rotate it by a random number of shifts to
1137 * randomize them - this is a kind of load balancing. It's possible
1138 * that the random number will be 0, which says not to touch. We don't
1139 * do anything unless
1142 static void _randomize_hosts(struct transport *tp)
1148 if (tp->nhosts <= 1)
1151 rnum = rand() % tp->nhosts;
1153 while (rnum-- > 0) {
1154 struct in_addr tmp = tp->hosts[0];
1157 for (i = 1; i < tp->nhosts; i++)
1158 tp->hosts[i - 1] = tp->hosts[i];
1160 tp->hosts[i - 1] = tmp;
1167 * Given a "transport" object that says how we're to connect to the
1168 * spam daemon, perform all the initial setup required to make the
1169 * connection process a smooth one. The main work is to do the host
1170 * name lookup and copy over all the IP addresses to make a local copy
1171 * so they're not kept in the resolver's static state.
1173 * Here we also manage quasi-load balancing and failover: if we're
1174 * doing load balancing, we randomly "rotate" the list to put it in
1175 * a different order, and then if we're not doing failover we limit
1176 * the hosts to just one. This way *all* connections are done with
1177 * the intention of failover - makes the code a bit more clear.
1179 int transport_setup(struct transport *tp, int flags)
1181 struct hostent *hp = 0;
1185 /* Start Winsock up */
1188 if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1189 printf("WSAStartup() returned error code %d\n", nCode);
1201 case TRANSPORT_UNIX:
1202 assert(tp->socketpath != 0);
1205 case TRANSPORT_LOCALHOST:
1206 tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1211 if (NULL == (hp = gethostbyname(tp->hostname))) {
1212 int origherr = h_errno; /* take a copy before syslog() */
1214 libspamc_log(flags, LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
1215 tp->hostname, origherr);
1217 case HOST_NOT_FOUND:
1228 /*--------------------------------------------------------
1229 * If we have no hosts at all, or if they are some other
1230 * kind of address family besides IPv4, then we really
1231 * just have no hosts at all.
1233 if (hp->h_addr_list[0] == 0) {
1234 /* no hosts in this list */
1238 if (hp->h_length != sizeof tp->hosts[0]
1239 || hp->h_addrtype != AF_INET) {
1240 /* FAIL - bad size/protocol/family? */
1244 /*--------------------------------------------------------
1245 * Copy all the IP addresses into our private structure.
1246 * This gets them out of the resolver's static area and
1247 * means we won't ever walk all over the list with other
1252 for (addrp = hp->h_addr_list; *addrp; addrp++) {
1253 if (tp->nhosts >= TRANSPORT_MAX_HOSTS - 1) {
1254 libspamc_log(flags, LOG_ERR, "hit limit of %d hosts, ignoring remainder",
1255 TRANSPORT_MAX_HOSTS - 1);
1259 memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
1264 /*--------------------------------------------------------
1265 * QUASI-LOAD-BALANCING
1267 * If the user wants to do quasi load balancing, "rotate"
1268 * the list by a random amount based on the current time.
1269 * This may later be truncated to a single item. This is
1270 * meaningful only if we have more than one host.
1272 if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
1273 _randomize_hosts(tp);
1276 /*--------------------------------------------------------
1277 * If the user wants no fallback, simply truncate the host
1278 * list to just one - this pretends that this is the extent
1279 * of our connection list - then it's not a special case.
1281 if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
1282 /* truncating list */
1289 /* --------------------------------------------------------------------------- */
1291 #define LOG_BUFSIZ 1023
1294 libspamc_log (int flags, int level, char *msg, ...)
1297 char buf[LOG_BUFSIZ+1];
1302 if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
1303 /* create a log-line buffer */
1304 len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
1305 len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
1307 /* avoid buffer overflow */
1308 if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
1310 len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
1311 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
1312 (void) write (2, buf, len);
1315 vsnprintf(buf, LOG_BUFSIZ, msg, ap);
1316 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
1318 syslog (level, "%s", buf);
1320 (void) level; /* not used. suppress compiler warning */
1321 fprintf (stderr, "%s\n", buf);
1328 /* --------------------------------------------------------------------------- */
1331 * Unit tests. Must be built externally, e.g.:
1333 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
1337 #ifdef LIBSPAMC_UNIT_TESTS
1339 static void _test_locale_safe_string_to_float_val(float input)
1341 char inputstr[99], cmpbuf1[99], cmpbuf2[99];
1344 /* sprintf instead of snprintf is safe here because it is only a controlled test */
1345 sprintf(inputstr, "%f", input);
1346 output = _locale_safe_string_to_float(inputstr, 99);
1347 if (input == output) {
1351 /* could be a rounding error. print as string and compare those */
1352 sprintf(cmpbuf1, "%f", input);
1353 sprintf(cmpbuf2, "%f", output);
1354 if (!strcmp(cmpbuf1, cmpbuf2)) {
1358 printf("FAIL: input=%f != output=%f\n", input, output);
1361 static void unit_test_locale_safe_string_to_float(void)
1363 float statictestset[] = { /* will try both +ve and -ve */
1364 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
1365 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
1366 0.0 /* end of set constant */
1371 printf("starting unit_test_locale_safe_string_to_float\n");
1372 /* tests of precision */
1373 for (i = 0; statictestset[i] != 0.0; i++) {
1374 _test_locale_safe_string_to_float_val(statictestset[i]);
1375 _test_locale_safe_string_to_float_val(-statictestset[i]);
1376 _test_locale_safe_string_to_float_val(1 - statictestset[i]);
1377 _test_locale_safe_string_to_float_val(1 + statictestset[i]);
1379 /* now exhaustive, in steps of 0.01 */
1380 for (num = -1000.0; num < 1000.0; num += 0.01) {
1381 _test_locale_safe_string_to_float_val(num);
1383 printf("finished unit_test_locale_safe_string_to_float\n");
1386 void do_libspamc_unit_tests(void)
1388 unit_test_locale_safe_string_to_float();
1392 #endif /* LIBSPAMC_UNIT_TESTS */