Improve some sieve-related translations
[claws.git] / src / plugins / spamassassin / libspamc.c
blob47b8c0c2cdfd5f92700425424fee575eca63ccfc
1 /* <@LICENSE>
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:
8 *
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.
16 * </@LICENSE>
19 /*
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
26 #include "config.h"
27 #include "claws-features.h"
28 #include "libspamc.h"
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <assert.h>
33 #include <stdio.h>
34 #include <string.h>
35 #ifdef _WIN32
36 #define snprintf _snprintf
37 #define vsnprintf _vsnprintf
38 #define strcasecmp stricmp
39 #define sleep Sleep
40 #include <io.h>
41 #else
42 #include <syslog.h>
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <sys/socket.h>
46 #include <netinet/in.h>
47 #include <sys/un.h>
48 #include <netinet/tcp.h>
49 #include <arpa/inet.h>
50 #define closesocket(x) close(x)
51 #endif
53 #ifdef HAVE_SYSEXITS_H
54 #include <sysexits.h>
55 #endif
56 #ifdef HAVE_ERRNO_H
57 #include <errno.h>
58 #endif
59 #ifdef HAVE_SYS_ERRNO_H
60 #include <sys/errno.h>
61 #endif
62 #ifdef HAVE_TIME_H
63 #include <time.h>
64 #endif
65 #ifdef HAVE_SYS_TIME_H
66 #include <sys/time.h>
67 #endif
68 #ifdef HAVE_ZLIB_H
69 #include <zlib.h>
70 #endif
72 /* must load *after* errno.h, Bug 6697 */
73 #include "file-utils.h"
74 #include "utils.h"
76 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
77 /* KAM 12-4-01 */
78 /* SJF 2003/04/25 - now test for macros directly */
79 #ifndef SHUT_RD
80 # define SHUT_RD 0 /* no more receptions */
81 #endif
82 #ifndef SHUT_WR
83 # define SHUT_WR 1 /* no more transmissions */
84 #endif
85 #ifndef SHUT_RDWR
86 # define SHUT_RDWR 2 /* no more receptions or transmissions */
87 #endif
89 #ifndef HAVE_H_ERRNO
90 #define h_errno errno
91 #endif
93 #ifdef _WIN32
94 #define spamc_get_errno() WSAGetLastError()
95 #else
96 #define spamc_get_errno() errno
97 #endif
99 #ifndef HAVE_OPTARG
100 extern char *optarg;
101 #endif
103 #ifndef HAVE_INADDR_NONE
104 #define INADDR_NONE ((in_addr_t) 0xffffffff)
105 #endif
107 /* jm: turned off for now, it should not be necessary. */
108 #undef USE_TCP_NODELAY
110 #ifndef HAVE_EX__MAX
111 /* jm: very conservative figure, should be well out of range on almost all NIXes */
112 #define EX__MAX 200
113 #endif
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 */
122 #ifdef NI_MAXHOST
123 #define SPAMC_MAXHOST NI_MAXHOST
124 #else
125 #define SPAMC_MAXHOST 256
126 #endif
128 #ifdef NI_MAXSERV
129 #define SPAMC_MAXSERV NI_MAXSERV
130 #else
131 #define SPAMC_MAXSERV 256
132 #endif
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)
178 switch (err) {
179 case EBADF:
180 case EFAULT:
181 case ENOTSOCK:
182 case EISCONN:
183 case EADDRINUSE:
184 case EINPROGRESS:
185 case EALREADY:
186 case EAFNOSUPPORT:
187 return EX_SOFTWARE;
189 case ECONNREFUSED:
190 case ETIMEDOUT:
191 case ENETUNREACH:
192 return EX_UNAVAILABLE;
194 case EACCES:
195 return EX_NOPERM;
197 default:
198 return EX_SOFTWARE;
203 * opensocket()
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)
216 #else
217 static int _opensocket(int flags, int type, int *psock)
219 int proto = 0;
220 #endif
221 const char *typename;
222 int origerr;
223 #ifdef _WIN32
224 int socktout;
225 #endif
227 assert(psock != 0);
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
232 * reporting.
234 #ifdef SPAMC_HAS_ADDRINFO
235 switch(res->ai_family) {
236 case PF_UNIX:
237 typename = "PF_UNIX";
238 break;
239 case PF_INET:
240 typename = "PF_INET";
241 break;
242 case PF_INET6:
243 typename = "PF_INET6";
244 break;
245 default:
246 typename = "Unknown";
247 break;
249 #else
250 if (type == PF_UNIX) {
251 typename = "PF_UNIX";
253 else {
254 typename = "PF_INET";
255 proto = IPPROTO_TCP;
257 #endif
259 #ifdef DO_CONNECT_DEBUG_SYSLOGS
260 libspamc_log(flags, CONNECT_DEBUG_LEVEL, "dbg: create socket(%s)", typename);
261 #endif
263 #ifdef SPAMC_HAS_ADDRINFO
264 if ((*psock = socket(res->ai_family, res->ai_socktype, res->ai_protocol))
265 #else
266 if ((*psock = socket(type, SOCK_STREAM, proto))
267 #endif
268 #ifndef _WIN32
270 #else
271 == INVALID_SOCKET
272 #endif
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();
281 #ifndef _WIN32
282 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
283 #else
284 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
285 #endif
287 switch (origerr) {
288 case EPROTONOSUPPORT:
289 case EINVAL:
290 return EX_SOFTWARE;
292 case EACCES:
293 return EX_NOPERM;
295 case ENFILE:
296 case EMFILE:
297 case ENOBUFS:
298 case ENOMEM:
299 return EX_OSERR;
301 default:
302 return EX_SOFTWARE;
306 #ifdef _WIN32
307 /* bug 4344: makes timeout functional on Win32 */
308 socktout = libspamc_timeout * 1000;
309 if (type == PF_INET
310 && setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, (char *)&socktout, sizeof(socktout)) != 0)
313 origerr = spamc_get_errno();
314 switch (origerr)
316 case EBADF:
317 case ENOTSOCK:
318 case ENOPROTOOPT:
319 case EFAULT:
320 libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerr);
321 closesocket(*psock);
322 return EX_SOFTWARE;
324 default:
325 break; /* ignored */
328 #endif
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
336 int one = 1;
338 if ( ( type == PF_INET
339 #ifdef PF_INET6
340 || type == PF_INET6
341 #endif
342 ) && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
343 origerr = spamc_get_errno();
344 switch (origerr) {
345 case EBADF:
346 case ENOTSOCK:
347 case ENOPROTOOPT:
348 case EFAULT:
349 libspamc_log(flags, LOG_ERR,
350 #ifndef _WIN32
351 "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
352 #else
353 "setsockopt(TCP_NODELAY) failed: %d", origerr);
354 #endif
355 closesocket(*psock);
356 return EX_SOFTWARE;
358 default:
359 break; /* ignored */
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)
378 #ifndef _WIN32
379 int mysock, status, origerr;
380 struct sockaddr_un addrbuf;
381 #ifdef SPAMC_HAS_ADDRINFO
382 struct addrinfo hints, *res;
383 #else
384 int res = PF_UNIX;
385 #endif
386 int ret;
388 assert(tp != 0);
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;
397 res = &hints;
398 #endif
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)
403 return ret;
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",
413 addrbuf.sun_path);
414 #endif
416 status = timeout_connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
418 origerr = errno;
420 if (status >= 0) {
421 #ifdef DO_CONNECT_DEBUG_SYSLOGS
422 libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
423 #endif
425 *sockptr = mysock;
427 return EX_OK;
430 libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd using --socket='%s' failed: %s",
431 addrbuf.sun_path, strerror(origerr));
432 closesocket(mysock);
434 return _translate_connect_errno(origerr);
435 #else
436 (void) tp; /* not used. suppress compiler warning */
437 (void) sockptr; /* not used. suppress compiler warning */
438 return EX_OSERR;
439 #endif
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)
452 int numloops;
453 int origerr = 0;
454 int ret;
455 #ifdef SPAMC_HAS_ADDRINFO
456 struct addrinfo *res = NULL;
457 char port[SPAMC_MAXSERV-1]; /* port, for logging */
458 #else
459 int res = PF_INET;
460 #endif
461 char host[SPAMC_MAXHOST-1]; /* hostname, for logging */
462 int connect_retries, retry_sleep;
464 assert(tp != 0);
465 assert(sockptr != 0);
466 assert(tp->nhosts > 0);
468 /* default values */
469 retry_sleep = tp->retry_sleep;
470 connect_retries = tp->connect_retries;
471 if (connect_retries == 0) {
472 connect_retries = 3;
474 if (retry_sleep < 0) {
475 retry_sleep = 1;
478 for (numloops = 0; numloops < connect_retries; numloops++) {
479 const int hostix = numloops % tp->nhosts;
480 int status, mysock;
481 int innocent = 0;
483 /*--------------------------------------------------------
484 * We always start by creating the socket, as we get only
485 * one attempt to connect() on each one. If this fails,
486 * we're done.
489 #ifdef SPAMC_HAS_ADDRINFO
490 res = tp->hosts[hostix];
491 while(res) {
492 #ifdef DO_CONNECT_DEBUG_SYSLOGS
493 char *family = NULL;
494 switch(res->ai_family) {
495 case AF_INET:
496 family = "AF_INET";
497 break;
498 case AF_INET6:
499 family = "AF_INET6";
500 break;
501 default:
502 family = "Unknown";
503 break;
505 #endif
507 if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK) {
508 res = res->ai_next;
509 continue;
512 getnameinfo(res->ai_addr, res->ai_addrlen,
513 host, sizeof(host),
514 port, sizeof(port),
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);
521 #endif
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",
528 host);
529 status = -1;
531 else {
532 status = timeout_connect(mysock, res->ai_addr, res->ai_addrlen);
533 if (status != 0) origerr = spamc_get_errno();
536 #else
537 struct sockaddr_in addrbuf;
538 const char *ipaddr;
539 const char* family="AF_INET";
540 if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
541 return ret;
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);
558 #endif
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",
565 ipaddr);
566 status = -1;
568 else {
569 status = timeout_connect(mysock, (struct sockaddr *) &addrbuf,
570 sizeof(addrbuf));
571 if (status != 0) origerr = spamc_get_errno();
574 #endif
576 if (status != 0) {
577 closesocket(mysock);
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,
583 #ifdef _WIN32
584 origerr
585 #else
586 strerror(origerr)
587 #endif
590 } else {
591 #ifdef DO_CONNECT_DEBUG_SYSLOGS
592 libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
593 "dbg: connect(%s) to spamd done",family);
594 #endif
595 *sockptr = mysock;
597 return EX_OK;
599 #ifdef SPAMC_HAS_ADDRINFO
600 res = res->ai_next;
602 #endif
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",
608 connect_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;
621 m->raw = NULL;
622 m->raw_len = 0;
623 m->pre = NULL;
624 m->pre_len = 0;
625 m->msg = NULL;
626 m->msg_len = 0;
627 m->post = NULL;
628 m->post_len = 0;
629 m->is_spam = EX_TOOBIG;
630 m->score = 0.0;
631 m->threshold = 0.0;
632 m->outbuf = NULL;
633 m->out = NULL;
634 m->out_len = 0;
635 m->content_length = -1;
638 static void _free_zlib_buffer(unsigned char **zlib_buf, int *zlib_bufsiz)
640 if(*zlib_buf) {
641 free(*zlib_buf);
642 *zlib_buf=NULL;
644 *zlib_bufsiz=0;
647 static void _use_msg_for_out(struct message *m)
649 if (m->outbuf)
650 free(m->outbuf);
651 m->outbuf = NULL;
652 m->out = m->msg;
653 m->out_len = m->msg_len;
656 static int _message_read_raw(int fd, struct message *m)
658 _clear_message(m);
659 if ((m->raw = malloc(m->max_len + 1)) == NULL)
660 return EX_OSERR;
661 m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
662 if (m->raw_len <= 0) {
663 free(m->raw);
664 m->raw = NULL;
665 m->raw_len = 0;
666 return EX_IOERR;
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)",
673 m->max_len);
674 return EX_TOOBIG;
676 m->type = MESSAGE_RAW;
677 m->msg = m->raw;
678 m->msg_len = m->raw_len;
679 m->out = m->msg;
680 m->out_len = m->msg_len;
681 return EX_OK;
684 static int _message_read_bsmtp(int fd, struct message *m)
686 unsigned int i, j, p_len;
687 char prev;
688 char* p;
690 _clear_message(m);
691 if ((m->raw = malloc(m->max_len + 1)) == NULL)
692 return EX_OSERR;
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) {
697 free(m->raw);
698 m->raw = NULL;
699 m->raw_len = 0;
700 return EX_IOERR;
702 m->type = MESSAGE_ERROR;
703 if (m->raw_len > (int) m->max_len)
704 return EX_TOOBIG;
705 p = m->pre = m->raw;
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;
710 q++;
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')) {
715 q+=4;
716 if (q[0] == '\r') ++q;
717 if (*(q++) == '\n') { /* leave q at start of message if we found it */
718 m->msg = q;
719 m->pre_len = q - m->raw;
720 m->msg_len = m->raw_len - m->pre_len;
721 break;
724 p = q; /* the above code ensures no other '\n' comes before q */
726 if (m->msg == NULL)
727 return EX_DATAERR;
729 /* ensure this is >= 0 */
730 if (m->msg_len < 0) {
731 return EX_SOFTWARE;
734 /* Find the end-of-DATA line */
735 prev = '\n';
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;
745 m->msg_len = j;
746 break;
748 else if ((int) (i+1) < m->msg_len && m->msg[i + 1] == '.') {
749 /* Escaping dot, eliminate. */
750 prev = '.';
751 continue;
752 } /* Else an ordinary dot, drop down to ordinary char handler */
754 prev = m->msg[i];
755 m->msg[j++] = m->msg[i];
758 /* if bad format with no end "\n.\n", error out */
759 if (m->post == NULL)
760 return EX_DATAERR;
761 m->type = MESSAGE_BSMTP;
762 m->out = m->msg;
763 m->out_len = m->msg_len;
764 return EX_OK;
767 int message_read(int fd, int flags, struct message *m)
769 assert(m != NULL);
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");
777 return EX_OSERR;
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) {
785 _clear_message(m);
786 return EX_OK;
789 switch (flags & SPAMC_MODE_MASK) {
790 case SPAMC_RAW_MODE:
791 return _message_read_raw(fd, m);
793 case SPAMC_BSMTP_MODE:
794 return _message_read_bsmtp(fd, m);
796 default:
797 libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
798 flags & SPAMC_MODE_MASK);
799 return EX_USAGE;
803 long message_write(int fd, struct message *m)
805 long total = 0;
806 off_t i, j;
807 off_t jlimit;
808 char buffer[1024];
810 assert(m != NULL);
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);
817 else {
818 libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
819 m->is_spam);
820 return -1;
824 /* else we're not in CHECK_ONLY mode */
825 switch (m->type) {
826 case MESSAGE_NONE:
827 libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
828 return -1;
830 case MESSAGE_ERROR:
831 return full_write(fd, 1, m->raw, m->raw_len);
833 case MESSAGE_RAW:
834 return full_write(fd, 1, m->out, m->out_len);
836 case MESSAGE_BSMTP:
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++];
848 buffer[j++] = '.';
850 else {
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);
858 default:
859 libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
860 return -1;
864 void message_dump(int in_fd, int out_fd, struct message *m, int flags)
866 char buf[8196];
867 int bytes;
869 if (m == NULL) {
870 libspamc_log(flags, LOG_ERR, "oops! message_dump called with NULL message");
871 return;
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",
881 bytes);
886 static int
887 _spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
888 char *buf, size_t *lenp, size_t bufsiz)
890 int failureval;
891 int bytesread = 0;
892 size_t len;
894 UNUSED_VARIABLE(m);
896 *lenp = 0;
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);
902 else {
903 bytesread = fd_timeout_read(sock, 0, buf + len, 1);
906 if (bytesread <= 0) {
907 failureval = EX_IOERR;
908 goto failure;
911 if (buf[len] == '\n') {
912 buf[len] = '\0';
913 if (len > 0 && buf[len - 1] == '\r') {
914 len--;
915 buf[len] = '\0';
917 *lenp = len;
918 return EX_OK;
922 libspamc_log(flags, LOG_ERR, "spamd responded with line of %lu bytes, dying", len);
923 failureval = EX_TOOBIG;
925 failure:
926 return failureval;
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)
935 int is_neg;
936 char *cp, *dot;
937 int divider;
938 float ret, postdot;
940 buf[siz - 1] = '\0'; /* ensure termination */
942 /* ok, let's illustrate using "100.033" as an example... */
944 is_neg = 0;
945 if (*buf == '-') {
946 is_neg = 1;
949 ret = (float) (strtol(buf, &dot, 10));
950 if (dot == NULL) {
951 return 0.0;
953 if (dot != NULL && *dot != '.') {
954 return ret;
957 /* ex: ret == 100.0 */
959 cp = (dot + 1);
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) {
963 return ret;
966 /* ex: postdot == 33.0, cp="033" */
968 /* now count the number of decimal places and figure out what power of 10 to use */
969 divider = 1;
970 while (*cp != '\0') {
971 divider *= 10;
972 cp++;
975 /* ex:
976 * cp="033", divider=1
977 * cp="33", divider=10
978 * cp="3", divider=100
979 * cp="", divider=1000
982 if (is_neg) {
983 ret -= (postdot / ((float) divider));
985 else {
986 ret += (postdot / ((float) divider));
988 /* ex: ret == 100.033, tada! ... hopefully */
990 return ret;
993 static int
994 _handle_spamd_header(struct message *m, int flags, char *buf, int len,
995 unsigned int *didtellflags)
997 char is_spam[6];
998 char s_str[21], t_str[21];
999 char didset_ret[15];
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)
1015 m->score = 1e10;
1016 else if (m->score < -1e10)
1017 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" */
1024 m->is_spam =
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);
1036 return EX_OK;
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'",
1042 buf);
1043 return EX_PROTOCOL;
1045 return EX_OK;
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);
1066 return EX_OK;
1069 static int
1070 _zlib_compress (char *m_msg, int m_msg_len,
1071 unsigned char **zlib_buf, int *zlib_bufsiz, int flags)
1073 int rc;
1074 int len, totallen;
1076 #ifndef HAVE_LIBZ
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");
1086 return EX_SOFTWARE;
1088 #else
1089 z_stream strm;
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
1095 * be plenty */
1096 *zlib_bufsiz = (int) (m_msg_len * 1.0005) + 1024;
1097 *zlib_buf = (unsigned char *) malloc (*zlib_bufsiz);
1098 if (*zlib_buf == NULL) {
1099 return EX_OSERR;
1102 strm.zalloc = Z_NULL;
1103 strm.zfree = Z_NULL;
1104 strm.opaque = Z_NULL;
1105 rc = deflateInit(&strm, 3);
1106 if (rc != Z_OK) {
1107 return EX_OSERR;
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;
1115 totallen = 0;
1116 do {
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;
1121 totallen += len;
1122 } while (strm.avail_out == 0);
1124 *zlib_bufsiz = totallen;
1125 return EX_OK;
1127 #endif
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
1138 * bytes.
1141 #define CRNLCRNL "\r\n\r\n"
1142 #define CRNLCRNL_LEN 4
1143 #define NLNL "\n\n"
1144 #define NLNL_LEN 2
1146 cpend = m->raw + m->raw_len;
1147 bodystart = NULL;
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;
1154 break;
1156 else if (*cp == '\n' && cpend - cp >= NLNL_LEN &&
1157 !strncmp(cp, NLNL, NLNL_LEN))
1159 bodystart = cp + NLNL_LEN;
1160 break;
1164 if (bodystart == NULL) {
1165 libspamc_log(flags, LOG_ERR, "failed to find end-of-headers");
1166 return EX_SOFTWARE;
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;
1176 return EX_OK;
1179 int message_filter(struct transport *tp, const char *username,
1180 int flags, struct message *m)
1182 char buf[8192];
1183 size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
1184 size_t len;
1185 int sock = -1;
1186 int rc;
1187 char versbuf[20];
1188 float version;
1189 int response;
1190 int failureval = EX_SOFTWARE;
1191 unsigned int throwaway;
1192 SSL_CTX *ctx = NULL;
1193 SSL *ssl = NULL;
1194 const SSL_METHOD *meth;
1195 char zlib_on = 0;
1196 unsigned char *zlib_buf = NULL;
1197 int zlib_bufsiz = 0;
1198 unsigned char *towrite_buf;
1199 int towrite_len;
1200 int filter_retry_count;
1201 int filter_retry_sleep;
1202 int filter_retries;
1203 #ifdef SPAMC_HAS_ADDRINFO
1204 struct addrinfo *tmphost;
1205 #else
1206 struct in_addr tmphost;
1207 #endif
1208 int nhost_counter;
1210 assert(tp != NULL);
1211 assert(m != NULL);
1213 if ((flags & SPAMC_USE_ZLIB) != 0) {
1214 zlib_on = 1;
1217 if (flags & SPAMC_USE_SSL) {
1218 #ifdef SPAMC_SSL
1219 SSLeay_add_ssl_algorithms();
1220 meth = SSLv23_client_method();
1221 SSL_load_error_strings();
1222 ctx = SSL_CTX_new(meth);
1223 #else
1224 UNUSED_VARIABLE(ssl);
1225 UNUSED_VARIABLE(meth);
1226 UNUSED_VARIABLE(ctx);
1227 libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1228 return EX_SOFTWARE;
1229 #endif
1232 m->is_spam = EX_TOOBIG;
1234 if (m->outbuf != NULL)
1235 free(m->outbuf);
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;
1239 goto failure;
1241 m->out = m->outbuf;
1242 m->out_len = 0;
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) {
1255 filter_retries = 1;
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 */
1270 if (sock != -1) {
1271 closesocket(sock);
1272 sock=-1;
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 ");
1306 else
1307 strcpy(buf, "PROCESS ");
1309 len = strlen(buf);
1310 if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1311 _use_msg_for_out(m);
1312 return EX_OSERR;
1315 strcat(buf, PROTOCOL_VERSION);
1316 strcat(buf, "\r\n");
1317 len = strlen(buf);
1319 towrite_buf = (unsigned char *) m->msg;
1320 towrite_len = (int) m->msg_len;
1321 if (zlib_on) {
1322 if (_zlib_compress(m->msg, m->msg_len, &zlib_buf, &zlib_bufsiz, flags) != EX_OK)
1324 _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1325 return EX_OSERR;
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);
1335 if (zlib_on) {
1336 _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1338 return EX_OSERR;
1340 strcpy(buf + len, "User: ");
1341 strcat(buf + len, username);
1342 strcat(buf + len, "\r\n");
1343 len += strlen(buf + len);
1345 if (zlib_on) {
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);
1350 if (zlib_on) {
1351 _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1353 return EX_DATAERR;
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 */
1363 if (tp->socketpath)
1364 rc = _try_to_connect_unix(tp, &sock);
1365 else
1366 rc = _try_to_connect_tcp(tp, &sock);
1368 if (rc != EX_OK) {
1369 _use_msg_for_out(m);
1370 if (zlib_on) {
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) {
1377 #ifdef SPAMC_SSL
1378 ssl = SSL_new(ctx);
1379 SSL_set_fd(ssl, sock);
1380 SSL_connect(ssl);
1381 #endif
1384 /* Send to spamd */
1385 if (flags & SPAMC_USE_SSL) {
1386 #ifdef SPAMC_SSL
1387 SSL_write(ssl, buf, len);
1388 SSL_write(ssl, towrite_buf, towrite_len);
1389 #endif
1391 else {
1392 full_write(sock, 0, buf, len);
1393 full_write(sock, 0, towrite_buf, towrite_len);
1394 shutdown(sock, SHUT_WR);
1397 /* free zlib buffer
1398 * bug 6025: zlib buffer not freed if compression is used
1400 if (zlib_on) {
1401 _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1404 /* ok, now read and parse it. SPAMD/1.2 line first... */
1405 failureval =
1406 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1407 } /* end of filterloop */
1409 if (failureval != EX_OK) {
1410 goto failure;
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;
1416 goto failure;
1419 versbuf[19] = '\0';
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'",
1423 versbuf);
1424 failureval = EX_PROTOCOL;
1425 goto failure;
1428 if (flags & SPAMC_PING) {
1429 closesocket(sock);
1430 sock = -1;
1431 m->out_len = sprintf(m->out, "SPAMD/%s %d\n", versbuf, response);
1432 m->is_spam = EX_NOTSPAM;
1433 return EX_OK;
1436 m->score = 0;
1437 m->threshold = 0;
1438 m->is_spam = EX_TOOBIG;
1439 while (1) {
1440 failureval =
1441 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1442 if (failureval != EX_OK) {
1443 goto failure;
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;
1452 goto failure;
1456 len = 0; /* overwrite those headers */
1458 if (flags & SPAMC_CHECK_ONLY) {
1459 closesocket(sock);
1460 sock = -1;
1461 if (m->is_spam == EX_TOOBIG) {
1462 /* We should have gotten headers back... Damnit. */
1463 failureval = EX_PROTOCOL;
1464 goto failure;
1466 return EX_OK;
1468 else {
1469 if (m->content_length < 0) {
1470 /* should have got a length too. */
1471 failureval = EX_PROTOCOL;
1472 goto failure;
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);
1488 else {
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;
1497 goto failure;
1499 m->out_len += len;
1501 shutdown(sock, SHUT_RD);
1502 closesocket(sock);
1503 sock = -1;
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;
1512 goto failure;
1515 if (flags & SPAMC_HEADERS) {
1516 if (_append_original_body(m, flags) != EX_OK) {
1517 goto failure;
1521 return EX_OK;
1523 failure:
1524 _use_msg_for_out(m);
1525 if (sock != -1) {
1526 closesocket(sock);
1528 libspamc_timeout = 0;
1530 if (flags & SPAMC_USE_SSL) {
1531 #ifdef SPAMC_SSL
1532 SSL_free(ssl);
1533 SSL_CTX_free(ctx);
1534 #endif
1536 return failureval;
1539 int message_process(struct transport *trans, char *username, int max_size,
1540 int in_fd, int out_fd, const int flags)
1542 int ret;
1543 struct message m;
1545 assert(trans != NULL);
1547 m.type = MESSAGE_NONE;
1549 /* enforce max_size being unsigned, therefore >= 0 */
1550 if (max_size < 0) {
1551 ret = EX_SOFTWARE;
1552 goto FAIL;
1554 m.max_len = (unsigned int) max_size;
1556 ret = message_read(in_fd, flags, &m);
1557 if (ret != EX_OK)
1558 goto FAIL;
1559 ret = message_filter(trans, username, flags, &m);
1560 if (ret != EX_OK)
1561 goto FAIL;
1562 if (message_write(out_fd, &m) < 0)
1563 goto FAIL;
1564 if (m.is_spam != EX_TOOBIG) {
1565 message_cleanup(&m);
1566 return m.is_spam;
1568 message_cleanup(&m);
1569 return ret;
1571 FAIL:
1572 if (flags & SPAMC_CHECK_ONLY) {
1573 full_write(out_fd, 1, "0/0\n", 4);
1574 message_cleanup(&m);
1575 return EX_NOTSPAM;
1577 else {
1578 message_dump(in_fd, out_fd, &m, flags);
1579 message_cleanup(&m);
1580 return ret;
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)
1588 char buf[8192];
1589 size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
1590 size_t len;
1591 int sock = -1;
1592 int rc;
1593 char versbuf[20];
1594 float version;
1595 int response;
1596 int failureval;
1597 SSL_CTX *ctx = NULL;
1598 SSL *ssl = NULL;
1599 const SSL_METHOD *meth;
1601 assert(tp != NULL);
1602 assert(m != NULL);
1604 if (flags & SPAMC_USE_SSL) {
1605 #ifdef SPAMC_SSL
1606 SSLeay_add_ssl_algorithms();
1607 meth = SSLv23_client_method();
1608 SSL_load_error_strings();
1609 ctx = SSL_CTX_new(meth);
1610 #else
1611 UNUSED_VARIABLE(ssl);
1612 UNUSED_VARIABLE(meth);
1613 UNUSED_VARIABLE(ctx);
1614 libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1615 return EX_SOFTWARE;
1616 #endif
1619 m->is_spam = EX_TOOBIG;
1621 if (m->outbuf != NULL)
1622 free(m->outbuf);
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;
1626 goto failure;
1628 m->out = m->outbuf;
1629 m->out_len = 0;
1631 /* Build spamd protocol header */
1632 strcpy(buf, "TELL ");
1634 len = strlen(buf);
1635 if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1636 _use_msg_for_out(m);
1637 return EX_OSERR;
1640 strcat(buf, PROTOCOL_VERSION);
1641 strcat(buf, "\r\n");
1642 len = strlen(buf);
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");
1649 else {
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");
1660 needs_comma_p = 1;
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");
1677 needs_comma_p = 1;
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);
1692 return EX_OSERR;
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);
1701 return EX_DATAERR;
1703 len += sprintf(buf + len, "Content-length: %d\r\n\r\n", (int) m->msg_len);
1705 if (m->priv->spamc_header_callback != NULL) {
1706 char buf2[1024];
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 */
1714 if (tp->socketpath)
1715 rc = _try_to_connect_unix(tp, &sock);
1716 else
1717 rc = _try_to_connect_tcp(tp, &sock);
1719 if (rc != EX_OK) {
1720 _use_msg_for_out(m);
1721 return rc; /* use the error code try_to_connect_*() gave us. */
1724 if (flags & SPAMC_USE_SSL) {
1725 #ifdef SPAMC_SSL
1726 ssl = SSL_new(ctx);
1727 SSL_set_fd(ssl, sock);
1728 SSL_connect(ssl);
1729 #endif
1732 /* Send to spamd */
1733 if (flags & SPAMC_USE_SSL) {
1734 #ifdef SPAMC_SSL
1735 SSL_write(ssl, buf, len);
1736 SSL_write(ssl, m->msg, m->msg_len);
1737 #endif
1739 else {
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... */
1746 failureval =
1747 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1748 if (failureval != EX_OK) {
1749 goto failure;
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;
1755 goto failure;
1758 versbuf[19] = '\0';
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'",
1762 versbuf);
1763 failureval = EX_PROTOCOL;
1764 goto failure;
1767 m->score = 0;
1768 m->threshold = 0;
1769 m->is_spam = EX_TOOBIG;
1770 while (1) {
1771 failureval =
1772 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1773 if (failureval != EX_OK) {
1774 goto failure;
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;
1783 goto failure;
1787 len = 0; /* overwrite those headers */
1789 shutdown(sock, SHUT_RD);
1790 closesocket(sock);
1791 sock = -1;
1793 libspamc_timeout = 0;
1795 return EX_OK;
1797 failure:
1798 _use_msg_for_out(m);
1799 if (sock != -1) {
1800 closesocket(sock);
1802 libspamc_timeout = 0;
1804 if (flags & SPAMC_USE_SSL) {
1805 #ifdef SPAMC_SSL
1806 SSL_free(ssl);
1807 SSL_CTX_free(ctx);
1808 #endif
1810 return failureval;
1813 void message_cleanup(struct message *m)
1815 assert(m != NULL);
1816 if (m->outbuf != NULL)
1817 free(m->outbuf);
1818 if (m->raw != NULL)
1819 free(m->raw);
1820 if (m->priv != NULL)
1821 free(m->priv);
1822 _clear_message(m);
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)
1830 int flags;
1832 flags = SPAMC_RAW_MODE;
1833 if (my_check_only)
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);
1842 * init_transport()
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)
1849 assert(tp != 0);
1851 memset(tp, 0, sizeof *tp);
1853 tp->type = TRANSPORT_LOCALHOST;
1854 tp->port = 783;
1855 tp->flags = 0;
1856 tp->retry_sleep = -1;
1860 * randomize_hosts()
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;
1873 #else
1874 struct in_addr tmp;
1875 #endif
1876 int i;
1877 int rnum;
1879 assert(tp != 0);
1881 if (tp->nhosts <= 1)
1882 return;
1884 rnum = rand() % tp->nhosts;
1886 while (rnum-- > 0) {
1887 tmp = tp->hosts[0];
1889 for (i = 1; i < tp->nhosts; i++)
1890 tp->hosts[i - 1] = tp->hosts[i];
1892 tp->hosts[i - 1] = tmp;
1897 * transport_setup()
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; */
1915 char port[6];
1916 int origerr;
1917 #else
1918 struct hostent *hp;
1919 char **addrp;
1920 #endif
1921 char *hostlist, *hostname;
1922 int errbits;
1924 #ifdef _WIN32
1925 /* Start Winsock up */
1926 WSADATA wsaData;
1927 int nCode;
1928 if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1929 printf("WSAStartup() returned error code %d\n", nCode);
1930 return EX_OSERR;
1933 #endif
1935 assert(tp != NULL);
1936 tp->flags = flags;
1938 #ifdef SPAMC_HAS_ADDRINFO
1939 snprintf(port, 6, "%d", tp->port);
1941 memset(&hints, 0, sizeof(hints));
1942 hints.ai_flags = 0;
1943 hints.ai_socktype = SOCK_STREAM;
1945 if ( (flags & SPAMC_USE_INET4) && !(flags & SPAMC_USE_INET6)) {
1946 hints.ai_family = PF_INET;
1947 #ifdef PF_INET6
1948 } else if ((flags & SPAMC_USE_INET6) && !(flags & SPAMC_USE_INET4)) {
1949 hints.ai_family = PF_INET6;
1950 #endif
1951 } else {
1952 hints.ai_family = PF_UNSPEC;
1954 #endif
1956 switch (tp->type) {
1957 #ifndef _WIN32
1958 case TRANSPORT_UNIX:
1959 assert(tp->socketpath != 0);
1960 return EX_OK;
1961 #endif
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));
1972 return EX_OSERR;
1974 tp->hosts[0] = res;
1975 #else
1976 tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1977 #endif
1978 tp->nhosts = 1;
1979 return EX_OK;
1981 case TRANSPORT_TCP:
1982 if ((hostlist = strdup(tp->hostname)) == NULL)
1983 return EX_OSERR;
1985 /* We want to return the least permanent error, in this bitmask we
1986 * record the errors seen with:
1987 * 0: no error
1988 * 1: EX_TEMPFAIL
1989 * 2: EX_NOHOST
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.
1994 errbits = 0;
1995 tp->nhosts = 0;
1996 /* Start with char offset in front of the string because we'll add
1997 * one in the loop
1999 hostname = hostlist - 1;
2000 do {
2001 char *hostend;
2003 hostname += 1;
2004 hostend = strchr(hostname, ',');
2005 if (hostend != NULL) {
2006 *hostend = '\0';
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));
2013 switch (origerr) {
2014 case EAI_AGAIN:
2015 errbits |= 1;
2016 break;
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*/
2025 #endif
2026 #ifdef HAVE_EAI_SYSTEM
2027 case EAI_SYSTEM: /*system error, check errno*/
2028 #endif
2029 #ifdef HAVE_EAI_NODATA
2030 case EAI_NODATA: /*address exists, but no data*/
2031 #endif
2032 case EAI_MEMORY: /*out of memory*/
2033 case EAI_FAIL: /*name server returned permanent error*/
2034 errbits |= 2;
2035 break;
2036 default:
2037 /* should not happen, all errors are checked above */
2038 free(hostlist);
2039 return EX_OSERR;
2041 goto nexthost; /* try next host in list */
2043 #else
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",
2047 hostname, origerr);
2048 switch (origerr) {
2049 case TRY_AGAIN:
2050 errbits |= 1;
2051 break;
2052 case HOST_NOT_FOUND:
2053 case NO_ADDRESS:
2054 case NO_RECOVERY:
2055 errbits |= 2;
2056 break;
2057 default:
2058 /* should not happen, all errors are checked above */
2059 free(hostlist);
2060 return EX_OSERR;
2062 goto nexthost; /* try next host in list */
2064 #endif
2066 /* If we have no hosts at all */
2067 #ifdef SPAMC_HAS_ADDRINFO
2068 if(res == NULL)
2069 #else
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 */
2074 #endif
2076 errbits |= 1;
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
2083 * calls.
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);
2090 break;
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;
2102 * tp->nhosts++;
2106 #else
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);
2111 break;
2113 memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length);
2114 tp->nhosts++;
2116 #endif
2117 nexthost:
2118 hostname = hostend;
2119 } while (hostname != NULL);
2120 free(hostlist);
2122 if (tp->nhosts == 0) {
2123 if (errbits & 1) {
2124 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): a temporary error occurred",
2125 tp->hostname);
2126 return EX_TEMPFAIL;
2128 else {
2129 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): no such host",
2130 tp->hostname);
2131 return EX_NOHOST;
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 */
2153 tp->nhosts = 1;
2156 return EX_OK;
2159 /* oops, unknown transport type */
2160 return EX_OSERR;
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
2174 int i;
2176 for(i=0;i<tp->nhosts;i++) {
2177 if (tp->hosts[i] != NULL) {
2178 freeaddrinfo(tp->hosts[i]);
2179 tp->hosts[i] = NULL;
2182 #endif
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
2220 void
2221 libspamc_log (int flags, int level, char *msg, ...)
2223 va_list ap;
2224 char buf[LOG_BUFSIZ+1];
2225 int len = 0;
2227 va_start(ap, msg);
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);
2244 } else {
2245 vsnprintf(buf, LOG_BUFSIZ, msg, ap);
2246 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
2247 #ifndef _WIN32
2248 syslog (level, "%s", buf);
2249 #else
2250 (void) level; /* not used. suppress compiler warning */
2251 f_printerr ("%s\n", buf);
2252 #endif
2255 va_end(ap);
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
2264 * ./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];
2272 float output;
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) {
2278 return;
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)) {
2285 return;
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 */
2298 float num;
2299 int i;
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();
2319 exit(0);
2322 #endif /* LIBSPAMC_UNIT_TESTS */