0be68e8c3fa3ee1d4443a7b26731dd54a0d8fe76
[claws.git] / src / plugins / spamassassin / libspamc.c
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
10  * 
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>
17  */
18
19 /* 
20   Compile with extra warnings -- gcc only, not suitable for use as default:
21
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
24  */
25
26 #include "config.h"
27 #include "claws-features.h"
28 #include "libspamc.h"
29
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
52
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
71
72 /* must load *after* errno.h, Bug 6697 */
73 #include "utils.h"
74
75 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
76 /* KAM 12-4-01 */
77 /* SJF 2003/04/25 - now test for macros directly */
78 #ifndef SHUT_RD
79 #  define SHUT_RD 0             /* no more receptions */
80 #endif
81 #ifndef SHUT_WR
82 #  define SHUT_WR 1             /* no more transmissions */
83 #endif
84 #ifndef SHUT_RDWR
85 #  define SHUT_RDWR 2           /* no more receptions or transmissions */
86 #endif
87
88 #ifndef HAVE_H_ERRNO
89 #define h_errno errno
90 #endif
91
92 #ifdef _WIN32
93 #define spamc_get_errno()   WSAGetLastError()
94 #else
95 #define spamc_get_errno()   errno
96 #endif
97
98 #ifndef HAVE_OPTARG
99 extern char *optarg;
100 #endif
101
102 #ifndef HAVE_INADDR_NONE
103 #define INADDR_NONE             ((in_addr_t) 0xffffffff)
104 #endif
105
106 /* jm: turned off for now, it should not be necessary. */
107 #undef USE_TCP_NODELAY
108
109 #ifndef HAVE_EX__MAX
110 /* jm: very conservative figure, should be well out of range on almost all NIXes */
111 #define EX__MAX 200
112 #endif
113
114 #undef DO_CONNECT_DEBUG_SYSLOGS
115 /*
116 #define DO_CONNECT_DEBUG_SYSLOGS 1
117 #define CONNECT_DEBUG_LEVEL LOG_DEBUG
118 */
119
120 /* bug 4477 comment 14 */
121 #ifdef NI_MAXHOST
122 #define SPAMC_MAXHOST NI_MAXHOST
123 #else
124 #define SPAMC_MAXHOST 256
125 #endif
126
127 #ifdef NI_MAXSERV
128 #define SPAMC_MAXSERV NI_MAXSERV
129 #else
130 #define SPAMC_MAXSERV 256
131 #endif
132
133 /* static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;  No longer seems to be used */
134
135 /* set EXPANSION_ALLOWANCE to something more than might be
136    added to a message in X-headers and the report template */
137 static const int EXPANSION_ALLOWANCE = 16384;
138
139 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
140    of the data streams before and after processing by spamd 
141    Aug  7 2002 jm: no longer seems to be used
142    static const int NUM_CHECK_BYTES = 32;
143  */
144
145 /* Set the protocol version that this spamc speaks */
146 static const char *PROTOCOL_VERSION = "SPAMC/1.5";
147
148 /* "private" part of struct message.
149  * we use this instead of the struct message directly, so that we
150  * can add new members without affecting the ABI.
151  */
152 struct libspamc_private_message
153 {
154     int flags;                  /* copied from "flags" arg to message_read() */
155     int alloced_size;           /* allocated space for the "out" buffer */
156
157     void (*spamc_header_callback)(struct message *m, int flags, char *buf, int len);
158     void (*spamd_header_callback)(struct message *m, int flags, const char *buf, int len);
159 };
160
161 void (*libspamc_log_callback)(int flags, int level, char *msg, va_list args) = NULL;
162
163 int libspamc_timeout = 0;
164 int libspamc_connect_timeout = 0;       /* Sep 8, 2008 mrgus: separate connect timeout */
165
166 /*
167  * translate_connect_errno()
168  *
169  *      Given a UNIX error number obtained (probably) from "connect(2)",
170  *      translate this to a failure code. This module is shared by both
171  *      transport modules - UNIX and TCP.
172  *
173  *      This should ONLY be called when there is an error.
174  */
175 static int _translate_connect_errno(int err)
176 {
177     switch (err) {
178     case EBADF:
179     case EFAULT:
180     case ENOTSOCK:
181     case EISCONN:
182     case EADDRINUSE:
183     case EINPROGRESS:
184     case EALREADY:
185     case EAFNOSUPPORT:
186         return EX_SOFTWARE;
187
188     case ECONNREFUSED:
189     case ETIMEDOUT:
190     case ENETUNREACH:
191         return EX_UNAVAILABLE;
192
193     case EACCES:
194         return EX_NOPERM;
195
196     default:
197         return EX_SOFTWARE;
198     }
199 }
200
201 /*
202  * opensocket()
203  *
204  *      Given a socket family (PF_INET or PF_INET6 or PF_UNIX), try to
205  *      create this socket and store the FD in the pointed-to place.
206  *      If it's successful, do any other setup required to make the socket
207  *      ready to use, such as setting TCP_NODELAY mode, and in any case
208  *      we return EX_OK if all is well.
209  *
210  *      Upon failure we return one of the other EX_??? error codes.
211  */
212 #ifdef SPAMC_HAS_ADDRINFO
213 static int _opensocket(int flags, struct addrinfo *res, int *psock)
214 {
215 #else
216 static int _opensocket(int flags, int type, int *psock)
217 {
218     int proto = 0;
219 #endif
220     const char *typename;
221     int origerr;
222 #ifdef _WIN32
223     int socktout;
224 #endif
225
226     assert(psock != 0);
227
228         /*----------------------------------------------------------------
229          * Create a few induction variables that are implied by the socket
230          * type given by the user. The typename is strictly used for debug
231          * reporting.
232          */
233 #ifdef SPAMC_HAS_ADDRINFO
234     switch(res->ai_family) {
235        case PF_UNIX:
236           typename = "PF_UNIX";
237           break;
238        case PF_INET:
239           typename = "PF_INET";
240           break;
241        case PF_INET6:
242           typename = "PF_INET6";
243           break;
244        default:
245           typename = "Unknown";
246           break;
247     }
248 #else
249     if (type == PF_UNIX) {
250         typename = "PF_UNIX";
251     }
252     else {
253         typename = "PF_INET";
254         proto = IPPROTO_TCP;
255     }
256 #endif
257
258 #ifdef DO_CONNECT_DEBUG_SYSLOGS
259     libspamc_log(flags, CONNECT_DEBUG_LEVEL, "dbg: create socket(%s)", typename);
260 #endif
261
262 #ifdef SPAMC_HAS_ADDRINFO
263     if ((*psock = socket(res->ai_family, res->ai_socktype, res->ai_protocol))
264 #else
265     if ((*psock = socket(type, SOCK_STREAM, proto))
266 #endif
267 #ifndef _WIN32
268         < 0
269 #else
270         == INVALID_SOCKET
271 #endif
272         ) {
273
274                 /*--------------------------------------------------------
275                  * At this point we had a failure creating the socket, and
276                  * this is pretty much fatal. Translate the error reason
277                  * into something the user can understand.
278                  */
279         origerr = spamc_get_errno();
280 #ifndef _WIN32
281         libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
282 #else
283         libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
284 #endif
285
286         switch (origerr) {
287         case EPROTONOSUPPORT:
288         case EINVAL:
289             return EX_SOFTWARE;
290
291         case EACCES:
292             return EX_NOPERM;
293
294         case ENFILE:
295         case EMFILE:
296         case ENOBUFS:
297         case ENOMEM:
298             return EX_OSERR;
299
300         default:
301             return EX_SOFTWARE;
302         }
303     }
304
305 #ifdef _WIN32
306     /* bug 4344: makes timeout functional on Win32 */
307     socktout = libspamc_timeout * 1000;
308     if (type == PF_INET
309         && setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, (char *)&socktout, sizeof(socktout)) != 0)
310     {
311
312         origerr = spamc_get_errno();
313         switch (origerr)
314         {
315         case EBADF:
316         case ENOTSOCK:
317         case ENOPROTOOPT:
318         case EFAULT:
319             libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerr);
320             closesocket(*psock);
321             return EX_SOFTWARE;
322
323         default:
324             break;              /* ignored */
325         }
326     }
327 #endif
328
329         /*----------------------------------------------------------------
330          * Do a bit of setup on the TCP socket if required. Notes above
331          * suggest this is probably not set
332          */
333 #ifdef USE_TCP_NODELAY
334     {
335         int one = 1;
336
337         if ( (   type == PF_INET
338 #ifdef PF_INET6
339               || type == PF_INET6
340 #endif
341              ) && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
342             origerr = spamc_get_errno();
343             switch (origerr) {
344             case EBADF:
345             case ENOTSOCK:
346             case ENOPROTOOPT:
347             case EFAULT:
348                 libspamc_log(flags, LOG_ERR,
349 #ifndef _WIN32
350                        "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
351 #else
352                        "setsockopt(TCP_NODELAY) failed: %d", origerr);
353 #endif
354                 closesocket(*psock);
355                 return EX_SOFTWARE;
356
357             default:
358                 break;          /* ignored */
359             }
360         }
361     }
362 #endif /* USE_TCP_NODELAY */
363
364     return EX_OK;               /* all is well */
365 }
366
367 /*
368  * try_to_connect_unix()
369  *
370  *      Given a transport handle that implies using a UNIX domain
371  *      socket, try to make a connection to it and store the resulting
372  *      file descriptor in *sockptr. Return is EX_OK if we did it,
373  *      and some other error code otherwise.
374  */
375 static int _try_to_connect_unix(struct transport *tp, int *sockptr)
376 {
377 #ifndef _WIN32
378     int mysock, status, origerr;
379     struct sockaddr_un addrbuf;
380 #ifdef SPAMC_HAS_ADDRINFO
381     struct addrinfo hints, *res;
382 #else
383     int res = PF_UNIX;
384 #endif
385     int ret;
386
387     assert(tp != 0);
388     assert(sockptr != 0);
389     assert(tp->socketpath != 0);
390
391 #ifdef SPAMC_HAS_ADDRINFO
392     memset(&hints, 0, sizeof(hints));
393     hints.ai_family = PF_UNIX;
394     hints.ai_socktype = SOCK_STREAM;
395     hints.ai_protocol = 0;
396     res = &hints;
397 #endif
398         /*----------------------------------------------------------------
399          * If the socket itself can't be created, this is a fatal error.
400          */
401     if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK)
402         return ret;
403
404     /* set up the UNIX domain socket */
405     memset(&addrbuf, 0, sizeof addrbuf);
406     addrbuf.sun_family = AF_UNIX;
407     strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
408     addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
409
410 #ifdef DO_CONNECT_DEBUG_SYSLOGS
411     libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
412            addrbuf.sun_path);
413 #endif
414
415     status = timeout_connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
416
417     origerr = errno;
418
419     if (status >= 0) {
420 #ifdef DO_CONNECT_DEBUG_SYSLOGS
421         libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
422 #endif
423
424         *sockptr = mysock;
425
426         return EX_OK;
427     }
428
429     libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd using --socket='%s' failed: %s",
430            addrbuf.sun_path, strerror(origerr));
431     closesocket(mysock);
432
433     return _translate_connect_errno(origerr);
434 #else
435     (void) tp; /* not used. suppress compiler warning */
436     (void) sockptr; /* not used. suppress compiler warning */
437     return EX_OSERR;
438 #endif
439 }
440
441 /*
442  * try_to_connect_tcp()
443  *
444  *      Given a transport that implies a TCP connection, either to
445  *      localhost or a list of IP addresses, attempt to connect. The
446  *      list of IP addresses has already been randomized (if requested)
447  *      and limited to just one if fallback has been enabled.
448  */
449 static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
450 {
451     int numloops;
452     int origerr = 0;
453     int ret;
454 #ifdef SPAMC_HAS_ADDRINFO
455     struct addrinfo *res = NULL;
456     char port[SPAMC_MAXSERV-1]; /* port, for logging */
457 #else
458     int res = PF_INET;
459 #endif
460     char host[SPAMC_MAXHOST-1]; /* hostname, for logging */
461     int connect_retries, retry_sleep;
462
463     assert(tp != 0);
464     assert(sockptr != 0);
465     assert(tp->nhosts > 0);
466
467     /* default values */
468     retry_sleep = tp->retry_sleep;
469     connect_retries = tp->connect_retries;
470     if (connect_retries == 0) {
471       connect_retries = 3;
472     }
473     if (retry_sleep < 0) {
474       retry_sleep = 1;
475     }
476
477     for (numloops = 0; numloops < connect_retries; numloops++) {
478         const int hostix = numloops % tp->nhosts;
479         int status, mysock;
480         int innocent = 0;
481
482                 /*--------------------------------------------------------
483                 * We always start by creating the socket, as we get only
484                 * one attempt to connect() on each one. If this fails,
485                 * we're done.
486                 */
487
488 #ifdef SPAMC_HAS_ADDRINFO
489         res = tp->hosts[hostix];
490         while(res) {
491             char *family = NULL;
492             switch(res->ai_family) {
493             case AF_INET:
494                 family = "AF_INET";
495                 break;
496             case AF_INET6:
497                 family = "AF_INET6";
498                 break;
499             default:
500                 family = "Unknown";
501                 break;
502             }
503
504             if ((ret = _opensocket(tp->flags, res, &mysock)) != EX_OK) {
505                 res = res->ai_next;
506                 continue;
507             }
508
509             getnameinfo(res->ai_addr, res->ai_addrlen,
510                   host, sizeof(host),
511                   port, sizeof(port),
512                   NI_NUMERICHOST|NI_NUMERICSERV);
513
514 #ifdef DO_CONNECT_DEBUG_SYSLOGS
515             libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
516               "dbg: connect(%s) to spamd (host %s, port %s) (try #%d of %d)",
517                       family, host, port, numloops + 1, connect_retries);
518 #endif
519
520             /* this is special-cased so that we have an address we can
521              * safely use as an "always fail" test case */
522             if (!strcmp(host, "255.255.255.255")) {
523               libspamc_log(tp->flags, LOG_ERR,
524                           "connect to spamd on %s failed, broadcast addr",
525                           host);
526               status = -1;
527             }
528             else {
529               status = timeout_connect(mysock, res->ai_addr, res->ai_addrlen);
530               if (status != 0) origerr = spamc_get_errno();
531             }
532
533 #else
534             struct sockaddr_in addrbuf;
535             const char *ipaddr;
536             const char* family="AF_INET";
537             if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
538               return ret;
539             
540             memset(&addrbuf, 0, sizeof(addrbuf));
541             
542             addrbuf.sin_family = AF_INET;
543             addrbuf.sin_port = htons(tp->port);
544             addrbuf.sin_addr = tp->hosts[hostix];
545             
546             ipaddr = inet_ntoa(addrbuf.sin_addr);
547
548             /* make a copy in host, for logging (bug 5577) */
549             strncpy (host, ipaddr, sizeof(host) - 1);
550
551 #ifdef DO_CONNECT_DEBUG_SYSLOGS
552             libspamc_log(tp->flags, LOG_DEBUG,
553                          "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
554                          ipaddr, numloops + 1, connect_retries);
555 #endif
556
557             /* this is special-cased so that we have an address we can
558              * safely use as an "always fail" test case */
559             if (!strcmp(ipaddr, "255.255.255.255")) {
560               libspamc_log(tp->flags, LOG_ERR,
561                           "connect to spamd on %s failed, broadcast addr",
562                           ipaddr);
563               status = -1;
564             }
565             else {
566               status = timeout_connect(mysock, (struct sockaddr *) &addrbuf,
567                         sizeof(addrbuf));
568               if (status != 0) origerr = spamc_get_errno();
569             }
570
571 #endif
572
573             if (status != 0) {
574                   closesocket(mysock);
575
576                   innocent = origerr == ECONNREFUSED && numloops+1 < tp->nhosts;
577                   libspamc_log(tp->flags, innocent ? LOG_DEBUG : LOG_ERR,
578                       "connect to spamd on %s failed, retrying (#%d of %d): %s",
579                       host, numloops+1, connect_retries,
580 #ifdef _WIN32
581                       origerr
582 #else
583                       strerror(origerr)
584 #endif
585                   );
586
587             } else {
588 #ifdef DO_CONNECT_DEBUG_SYSLOGS
589                   libspamc_log(tp->flags, CONNECT_DEBUG_LEVEL,
590                           "dbg: connect(%s) to spamd done",family);
591 #endif
592                   *sockptr = mysock;
593
594                   return EX_OK;
595             }
596 #ifdef SPAMC_HAS_ADDRINFO
597             res = res->ai_next;
598         }
599 #endif
600         if (numloops+1 < connect_retries && !innocent) sleep(retry_sleep);
601     } /* for(numloops...) */
602
603     libspamc_log(tp->flags, LOG_ERR,
604               "connection attempt to spamd aborted after %d retries",
605               connect_retries);
606
607     return _translate_connect_errno(origerr);
608 }
609
610 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
611  * message_dump, lookup_host, message_filter, and message_process, and a bunch
612  * of helper functions.
613  */
614
615 static void _clear_message(struct message *m)
616 {
617     m->type = MESSAGE_NONE;
618     m->raw = NULL;
619     m->raw_len = 0;
620     m->pre = NULL;
621     m->pre_len = 0;
622     m->msg = NULL;
623     m->msg_len = 0;
624     m->post = NULL;
625     m->post_len = 0;
626     m->is_spam = EX_TOOBIG;
627     m->score = 0.0;
628     m->threshold = 0.0;
629     m->outbuf = NULL;
630     m->out = NULL;
631     m->out_len = 0;
632     m->content_length = -1;
633 }
634
635 static void _free_zlib_buffer(unsigned char **zlib_buf, int *zlib_bufsiz)
636 {
637         if(*zlib_buf) {
638         free(*zlib_buf);
639         *zlib_buf=NULL;
640         }
641         *zlib_bufsiz=0;
642 }
643
644 static void _use_msg_for_out(struct message *m)
645 {
646     if (m->outbuf)
647         free(m->outbuf);
648     m->outbuf = NULL;
649     m->out = m->msg;
650     m->out_len = m->msg_len;
651 }
652
653 static int _message_read_raw(int fd, struct message *m)
654 {
655     _clear_message(m);
656     if ((m->raw = malloc(m->max_len + 1)) == NULL)
657         return EX_OSERR;
658     m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
659     if (m->raw_len <= 0) {
660         free(m->raw);
661         m->raw = NULL;
662         m->raw_len = 0;
663         return EX_IOERR;
664     }
665     m->type = MESSAGE_ERROR;
666     if (m->raw_len > (int) m->max_len)
667     {
668         libspamc_log(m->priv->flags, LOG_NOTICE,
669                 "skipped message, greater than max message size (%d bytes)",
670                 m->max_len);
671         return EX_TOOBIG;
672     }
673     m->type = MESSAGE_RAW;
674     m->msg = m->raw;
675     m->msg_len = m->raw_len;
676     m->out = m->msg;
677     m->out_len = m->msg_len;
678     return EX_OK;
679 }
680
681 static int _message_read_bsmtp(int fd, struct message *m)
682 {
683     unsigned int i, j, p_len;
684     char prev;
685     char* p;
686
687     _clear_message(m);
688     if ((m->raw = malloc(m->max_len + 1)) == NULL)
689         return EX_OSERR;
690
691     /* Find the DATA line */
692     m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
693     if (m->raw_len <= 0) {
694         free(m->raw);
695         m->raw = NULL;
696         m->raw_len = 0;
697         return EX_IOERR;
698     }
699     m->type = MESSAGE_ERROR;
700     if (m->raw_len > (int) m->max_len)
701         return EX_TOOBIG;
702     p = m->pre = m->raw;
703     /* Search for \nDATA\n which marks start of actual message */
704     while ((p_len = (m->raw_len - (p - m->raw))) > 8) { /* leave room for at least \nDATA\n.\n */
705       char* q = memchr(p, '\n', p_len - 8);  /* find next \n then see if start of \nDATA\n */
706       if (q == NULL) break;
707       q++;
708       if (((q[0]|0x20) == 'd') && /* case-insensitive ASCII comparison */
709           ((q[1]|0x20) == 'a') &&
710           ((q[2]|0x20) == 't') &&
711           ((q[3]|0x20) == 'a')) {
712         q+=4;
713         if (q[0] == '\r') ++q;
714         if (*(q++) == '\n') {  /* leave q at start of message if we found it */
715           m->msg = q;
716           m->pre_len = q - m->raw;
717           m->msg_len = m->raw_len - m->pre_len;
718           break;
719         }
720       }
721       p = q; /* the above code ensures no other '\n' comes before q */
722     }
723     if (m->msg == NULL)
724         return EX_DATAERR;
725
726     /* ensure this is >= 0 */
727     if (m->msg_len < 0) {
728         return EX_SOFTWARE;
729     }
730
731     /* Find the end-of-DATA line */
732     prev = '\n';
733     for (i = j = 0; i < (unsigned int) m->msg_len; i++) {
734         if (prev == '\n' && m->msg[i] == '.') {
735             /* Dot at the beginning of a line */
736             if (((int) (i+1) == m->msg_len)
737                 || ((int) (i+1) < m->msg_len && m->msg[i + 1] == '\n')
738                 || ((int) (i+2) < m->msg_len && m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')) {
739                 /* Lone dot! That's all, folks */
740                 m->post = m->msg + i;
741                 m->post_len = m->msg_len - i;
742                 m->msg_len = j;
743                 break;
744             }
745             else if ((int) (i+1) < m->msg_len && m->msg[i + 1] == '.') {
746                 /* Escaping dot, eliminate. */
747                 prev = '.';
748                 continue;
749             }                   /* Else an ordinary dot, drop down to ordinary char handler */
750         }
751         prev = m->msg[i];
752         m->msg[j++] = m->msg[i];
753     }
754
755     /* if bad format with no end "\n.\n", error out */
756     if (m->post == NULL)
757         return EX_DATAERR;
758     m->type = MESSAGE_BSMTP;
759     m->out = m->msg;
760     m->out_len = m->msg_len;
761     return EX_OK;
762 }
763
764 int message_read(int fd, int flags, struct message *m)
765 {
766     assert(m != NULL);
767
768     libspamc_timeout = 0;
769
770     /* create the "private" part of the struct message */
771     m->priv = malloc(sizeof(struct libspamc_private_message));
772     if (m->priv == NULL) {
773         libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
774         return EX_OSERR;
775     }
776     m->priv->flags = flags;
777     m->priv->alloced_size = 0;
778     m->priv->spamc_header_callback = 0;
779     m->priv->spamd_header_callback = 0;
780
781     if (flags & SPAMC_PING) {
782       _clear_message(m);
783       return EX_OK;
784     }
785
786     switch (flags & SPAMC_MODE_MASK) {
787     case SPAMC_RAW_MODE:
788         return _message_read_raw(fd, m);
789
790     case SPAMC_BSMTP_MODE:
791         return _message_read_bsmtp(fd, m);
792
793     default:
794         libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
795                 flags & SPAMC_MODE_MASK);
796         return EX_USAGE;
797     }
798 }
799
800 long message_write(int fd, struct message *m)
801 {
802     long total = 0;
803     off_t i, j;
804     off_t jlimit;
805     char buffer[1024];
806
807     assert(m != NULL);
808
809     if (m->priv->flags & (SPAMC_CHECK_ONLY|SPAMC_PING)) {
810         if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
811             return full_write(fd, 1, m->out, m->out_len);
812
813         }
814         else {
815             libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
816                         m->is_spam);
817             return -1;
818         }
819     }
820
821     /* else we're not in CHECK_ONLY mode */
822     switch (m->type) {
823     case MESSAGE_NONE:
824         libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
825         return -1;
826
827     case MESSAGE_ERROR:
828         return full_write(fd, 1, m->raw, m->raw_len);
829
830     case MESSAGE_RAW:
831         return full_write(fd, 1, m->out, m->out_len);
832
833     case MESSAGE_BSMTP:
834         total = full_write(fd, 1, m->pre, m->pre_len);
835         for (i = 0; i < m->out_len;) {
836             jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
837             for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
838                 if (i + 1 < m->out_len && m->out[i] == '\n'
839                     && m->out[i + 1] == '.') {
840                     if (j > jlimit - 4) {
841                         break;  /* avoid overflow */
842                     }
843                     buffer[j++] = m->out[i++];
844                     buffer[j++] = m->out[i++];
845                     buffer[j++] = '.';
846                 }
847                 else {
848                     buffer[j++] = m->out[i++];
849                 }
850             }
851             total += full_write(fd, 1, buffer, j);
852         }
853         return total + full_write(fd, 1, m->post, m->post_len);
854
855     default:
856         libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
857         return -1;
858     }
859 }
860
861 void message_dump(int in_fd, int out_fd, struct message *m, int flags)
862 {
863     char buf[8196];
864     int bytes;
865
866     if (m == NULL) {
867         libspamc_log(flags, LOG_ERR, "oops! message_dump called with NULL message");
868         return;
869     }
870
871     if (m->type != MESSAGE_NONE) {
872         message_write(out_fd, m);
873     }
874
875     while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
876         if (bytes != full_write(out_fd, 1, buf, bytes)) {
877             libspamc_log(flags, LOG_ERR, "oops! message_dump of %d returned different",
878                    bytes);
879         }
880     }
881 }
882
883 static int
884 _spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
885                       char *buf, size_t *lenp, size_t bufsiz)
886 {
887     int failureval;
888     int bytesread = 0;
889     size_t len;
890
891     UNUSED_VARIABLE(m);
892
893     *lenp = 0;
894     /* Now, read from spamd */
895     for (len = 0; len < bufsiz - 1; len++) {
896         if (flags & SPAMC_USE_SSL) {
897             bytesread = ssl_timeout_read(ssl, buf + len, 1);
898         }
899         else {
900             bytesread = fd_timeout_read(sock, 0, buf + len, 1);
901         }
902
903         if (bytesread <= 0) {
904             failureval = EX_IOERR;
905             goto failure;
906         }
907
908         if (buf[len] == '\n') {
909             buf[len] = '\0';
910             if (len > 0 && buf[len - 1] == '\r') {
911                 len--;
912                 buf[len] = '\0';
913             }
914             *lenp = len;
915             return EX_OK;
916         }
917     }
918
919     libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
920     failureval = EX_TOOBIG;
921
922   failure:
923     return failureval;
924 }
925
926 /*
927  * May  7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
928  * work around using our own locale-independent float-parser code.
929  */
930 static float _locale_safe_string_to_float(char *buf, int siz)
931 {
932     int is_neg;
933     char *cp, *dot;
934     int divider;
935     float ret, postdot;
936
937     buf[siz - 1] = '\0';        /* ensure termination */
938
939     /* ok, let's illustrate using "100.033" as an example... */
940
941     is_neg = 0;
942     if (*buf == '-') {
943         is_neg = 1;
944     }
945
946     ret = (float) (strtol(buf, &dot, 10));
947     if (dot == NULL) {
948         return 0.0;
949     }
950     if (dot != NULL && *dot != '.') {
951         return ret;
952     }
953
954     /* ex: ret == 100.0 */
955
956     cp = (dot + 1);
957     postdot = (float) (strtol(cp, NULL, 10));
958     /* note: don't compare floats == 0.0, it's unsafe.  use a range */
959     if (postdot >= -0.00001 && postdot <= 0.00001) {
960         return ret;
961     }
962
963     /* ex: postdot == 33.0, cp="033" */
964
965     /* now count the number of decimal places and figure out what power of 10 to use */
966     divider = 1;
967     while (*cp != '\0') {
968         divider *= 10;
969         cp++;
970     }
971
972     /* ex:
973      * cp="033", divider=1
974      * cp="33", divider=10
975      * cp="3", divider=100
976      * cp="", divider=1000
977      */
978
979     if (is_neg) {
980         ret -= (postdot / ((float) divider));
981     }
982     else {
983         ret += (postdot / ((float) divider));
984     }
985     /* ex: ret == 100.033, tada! ... hopefully */
986
987     return ret;
988 }
989
990 static int
991 _handle_spamd_header(struct message *m, int flags, char *buf, int len,
992                      unsigned int *didtellflags)
993 {
994     char is_spam[6];
995     char s_str[21], t_str[21];
996     char didset_ret[15];
997     char didremove_ret[15];
998
999     UNUSED_VARIABLE(len);
1000
1001     /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
1002      * let's stick with it for this parser.
1003      * May  7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
1004      * work around using our own locale-independent float-parser code.
1005      */
1006     if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
1007         m->score = _locale_safe_string_to_float(s_str, 20);
1008         m->threshold = _locale_safe_string_to_float(t_str, 20);
1009
1010         /* set bounds on these to ensure no buffer overflow in the sprintf */
1011         if (m->score > 1e10)
1012             m->score = 1e10;
1013         else if (m->score < -1e10)
1014             m->score = -1e10;
1015         if (m->threshold > 1e10)
1016             m->threshold = 1e10;
1017         else if (m->threshold < -1e10)
1018             m->threshold = -1e10;
1019
1020         /* Format is "Spam: x; y / x" */
1021         m->is_spam =
1022             strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
1023
1024         if (flags & SPAMC_CHECK_ONLY) {
1025             m->out_len = sprintf(m->out,
1026                                  "%.1f/%.1f\n", m->score, m->threshold);
1027         }
1028         else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
1029                  || (flags & SPAMC_REPORT)) {
1030             m->out_len = sprintf(m->out,
1031                                  "%.1f/%.1f\n", m->score, m->threshold);
1032         }
1033         return EX_OK;
1034
1035     }
1036     else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
1037         if (m->content_length < 0) {
1038             libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
1039                    buf);
1040             return EX_PROTOCOL;
1041         }
1042         return EX_OK;
1043     }
1044     else if (sscanf(buf, "DidSet: %14s", didset_ret) == 1) {
1045       if (strstr(didset_ret, "local")) {
1046           *didtellflags |= SPAMC_SET_LOCAL;
1047         }
1048         if (strstr(didset_ret, "remote")) {
1049           *didtellflags |= SPAMC_SET_REMOTE;
1050         }
1051     }
1052     else if (sscanf(buf, "DidRemove: %14s", didremove_ret) == 1) {
1053         if (strstr(didremove_ret, "local")) {
1054           *didtellflags |= SPAMC_REMOVE_LOCAL;
1055         }
1056         if (strstr(didremove_ret, "remote")) {
1057           *didtellflags |= SPAMC_REMOVE_REMOTE;
1058         }
1059     }
1060     else if (m->priv->spamd_header_callback != NULL)
1061       m->priv->spamd_header_callback(m, flags, buf, len);
1062
1063     return EX_OK;
1064 }
1065
1066 static int
1067 _zlib_compress (char *m_msg, int m_msg_len,
1068         unsigned char **zlib_buf, int *zlib_bufsiz, int flags)
1069 {
1070     int rc;
1071     int len, totallen;
1072
1073 #ifndef HAVE_LIBZ
1074
1075     UNUSED_VARIABLE(m_msg);
1076     UNUSED_VARIABLE(m_msg_len);
1077     UNUSED_VARIABLE(zlib_buf);
1078     UNUSED_VARIABLE(zlib_bufsiz);
1079     UNUSED_VARIABLE(rc);
1080     UNUSED_VARIABLE(len);
1081     UNUSED_VARIABLE(totallen);
1082     libspamc_log(flags, LOG_ERR, "spamc not built with zlib support");
1083     return EX_SOFTWARE;
1084
1085 #else
1086     z_stream strm;
1087
1088     UNUSED_VARIABLE(flags);
1089
1090     /* worst-case, according to http://www.zlib.org/zlib_tech.html ;
1091       * same as input, plus 5 bytes per 16k, plus 6 bytes.  this should
1092       * be plenty */
1093     *zlib_bufsiz = (int) (m_msg_len * 1.0005) + 1024;
1094     *zlib_buf = (unsigned char *) malloc (*zlib_bufsiz);
1095     if (*zlib_buf == NULL) {
1096         return EX_OSERR;
1097     }
1098
1099     strm.zalloc = Z_NULL;
1100     strm.zfree = Z_NULL;
1101     strm.opaque = Z_NULL;
1102     rc = deflateInit(&strm, 3);
1103     if (rc != Z_OK) {
1104         return EX_OSERR;
1105     }
1106
1107     strm.avail_in = m_msg_len;
1108     strm.next_in = (unsigned char *) m_msg;
1109     strm.avail_out = *zlib_bufsiz;
1110     strm.next_out = (unsigned char *) *zlib_buf;
1111
1112     totallen = 0;
1113     do {
1114         rc = deflate(&strm, Z_FINISH);
1115         assert(rc != Z_STREAM_ERROR);
1116         len = (size_t) (*zlib_bufsiz - strm.avail_out);
1117         strm.next_out += len;
1118         totallen += len;
1119     } while (strm.avail_out == 0);
1120
1121     *zlib_bufsiz = totallen;
1122     return EX_OK;
1123
1124 #endif
1125 }
1126
1127 int
1128 _append_original_body (struct message *m, int flags)
1129 {
1130     char *cp, *cpend, *bodystart;
1131     int bodylen, outspaceleft, towrite;
1132
1133     /* at this stage, m->out now contains the rewritten headers.
1134      * find and append the raw message's body, up to m->priv->alloced_size
1135      * bytes.
1136      */
1137
1138 #define CRNLCRNL        "\r\n\r\n"
1139 #define CRNLCRNL_LEN    4
1140 #define NLNL            "\n\n"
1141 #define NLNL_LEN        2
1142
1143     cpend = m->raw + m->raw_len;
1144     bodystart = NULL;
1145
1146     for (cp = m->raw; cp < cpend; cp++) {
1147         if (*cp == '\r' && cpend - cp >= CRNLCRNL_LEN && 
1148                             !strncmp(cp, CRNLCRNL, CRNLCRNL_LEN))
1149         {
1150             bodystart = cp + CRNLCRNL_LEN;
1151             break;
1152         }
1153         else if (*cp == '\n' && cpend - cp >= NLNL_LEN && 
1154                            !strncmp(cp, NLNL, NLNL_LEN))
1155         {
1156             bodystart = cp + NLNL_LEN;
1157             break;
1158         }
1159     }
1160
1161     if (bodystart == NULL) {
1162         libspamc_log(flags, LOG_ERR, "failed to find end-of-headers");
1163         return EX_SOFTWARE;
1164     }
1165
1166     bodylen = cpend - bodystart;
1167     outspaceleft = (m->priv->alloced_size-1) - m->out_len;
1168     towrite = (bodylen < outspaceleft ? bodylen : outspaceleft);
1169
1170     /* copy in the body; careful not to overflow */
1171     strncpy (m->out + m->out_len, bodystart, towrite);
1172     m->out_len += towrite;
1173     return EX_OK;
1174 }
1175
1176 int message_filter(struct transport *tp, const char *username,
1177                    int flags, struct message *m)
1178 {
1179     char buf[8192];
1180     size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
1181     size_t len;
1182     int sock = -1;
1183     int rc;
1184     char versbuf[20];
1185     float version;
1186     int response;
1187     int failureval = EX_SOFTWARE;
1188     unsigned int throwaway;
1189     SSL_CTX *ctx = NULL;
1190     SSL *ssl = NULL;
1191     SSL_METHOD *meth;
1192     char zlib_on = 0;
1193     unsigned char *zlib_buf = NULL;
1194     int zlib_bufsiz = 0;
1195     unsigned char *towrite_buf;
1196     int towrite_len;
1197     int filter_retry_count;
1198     int filter_retry_sleep;
1199     int filter_retries;
1200     #ifdef SPAMC_HAS_ADDRINFO
1201         struct addrinfo *tmphost;
1202     #else
1203         struct in_addr tmphost;
1204     #endif
1205     int nhost_counter;
1206
1207     assert(tp != NULL);
1208     assert(m != NULL);
1209
1210     if ((flags & SPAMC_USE_ZLIB) != 0) {
1211       zlib_on = 1;
1212     }
1213
1214     if (flags & SPAMC_USE_SSL) {
1215 #ifdef SPAMC_SSL
1216         SSLeay_add_ssl_algorithms();
1217         if (flags & SPAMC_TLSV1) {
1218             meth = TLSv1_client_method();
1219         } else {
1220             meth = SSLv3_client_method(); /* default */
1221         }
1222         SSL_load_error_strings();
1223         ctx = SSL_CTX_new(meth);
1224 #else
1225         UNUSED_VARIABLE(ssl);
1226         UNUSED_VARIABLE(meth);
1227         UNUSED_VARIABLE(ctx);
1228         libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1229         return EX_SOFTWARE;
1230 #endif
1231     }
1232
1233     m->is_spam = EX_TOOBIG;
1234
1235     if (m->outbuf != NULL)
1236         free(m->outbuf);
1237     m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
1238     if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
1239         failureval = EX_OSERR;
1240         goto failure;
1241     }
1242     m->out = m->outbuf;
1243     m->out_len = 0;
1244
1245     /* If the spamd filter takes too long and we timeout, then
1246      * retry again.  This gets us around a hung child thread 
1247      * in spamd or a problem on a spamd host in a multi-host
1248      * setup.  If there is more than one destination host
1249      * we move to the next host on each attempt.
1250      */
1251
1252     /* default values */
1253     filter_retry_sleep = tp->filter_retry_sleep;
1254     filter_retries = tp->filter_retries;
1255     if (filter_retries == 0) {
1256         filter_retries = 1;
1257     }
1258     if (filter_retry_sleep < 0) {
1259         filter_retry_sleep = 1;
1260     }
1261
1262     /* filterloop - Ensure that we run through this at least
1263      * once, and again if there are errors 
1264      */
1265     filter_retry_count = 0;
1266     while ((filter_retry_count==0) || 
1267                 ((filter_retry_count<tp->filter_retries) && (failureval == EX_IOERR)))
1268     {
1269         if (filter_retry_count != 0){
1270             /* Ensure that the old socket gets closed */
1271             if (sock != -1) {
1272                 closesocket(sock);
1273                 sock=-1;
1274             }
1275
1276             /* Move to the next host in the list, if nhosts>1 */
1277             if (tp->nhosts > 1) {
1278                 tmphost = tp->hosts[0];
1279
1280                 /* TODO: free using freeaddrinfo() */
1281                 for (nhost_counter = 1; nhost_counter < tp->nhosts; nhost_counter++) {
1282                     tp->hosts[nhost_counter - 1] = tp->hosts[nhost_counter];
1283                 }
1284         
1285                 tp->hosts[nhost_counter - 1] = tmphost;
1286             }
1287
1288             /* Now sleep the requested amount */
1289             sleep(filter_retry_sleep);
1290         }
1291
1292         filter_retry_count++;
1293     
1294         /* Build spamd protocol header */
1295         if (flags & SPAMC_CHECK_ONLY)
1296           strcpy(buf, "CHECK ");
1297         else if (flags & SPAMC_REPORT_IFSPAM)
1298           strcpy(buf, "REPORT_IFSPAM ");
1299         else if (flags & SPAMC_REPORT)
1300           strcpy(buf, "REPORT ");
1301         else if (flags & SPAMC_SYMBOLS)
1302           strcpy(buf, "SYMBOLS ");
1303         else if (flags & SPAMC_PING)
1304           strcpy(buf, "PING ");
1305         else if (flags & SPAMC_HEADERS)
1306           strcpy(buf, "HEADERS ");
1307         else
1308           strcpy(buf, "PROCESS ");
1309     
1310         len = strlen(buf);
1311         if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1312             _use_msg_for_out(m);
1313             return EX_OSERR;
1314         }
1315     
1316         strcat(buf, PROTOCOL_VERSION);
1317         strcat(buf, "\r\n");
1318         len = strlen(buf);
1319     
1320         towrite_buf = (unsigned char *) m->msg;
1321         towrite_len = (int) m->msg_len;
1322         if (zlib_on) {
1323             if (_zlib_compress(m->msg, m->msg_len, &zlib_buf, &zlib_bufsiz, flags) != EX_OK)
1324             {
1325                 _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1326                 return EX_OSERR;
1327             }
1328             towrite_buf = zlib_buf;
1329             towrite_len = zlib_bufsiz;
1330         }
1331     
1332         if (!(flags & SPAMC_PING)) {
1333           if (username != NULL) {
1334               if (strlen(username) + 8 >= (bufsiz - len)) {
1335                   _use_msg_for_out(m);
1336                   if (zlib_on) {
1337                       _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1338                   }
1339                   return EX_OSERR;
1340               }
1341               strcpy(buf + len, "User: ");
1342               strcat(buf + len, username);
1343               strcat(buf + len, "\r\n");
1344               len += strlen(buf + len);
1345           }
1346           if (zlib_on) {
1347               len += snprintf(buf + len, 8192-len, "Compress: zlib\r\n");
1348           }
1349           if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
1350               _use_msg_for_out(m);
1351               if (zlib_on) {
1352                   _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1353               }
1354               return EX_DATAERR;
1355           }
1356           len += snprintf(buf + len, 8192-len, "Content-length: %d\r\n", (int) towrite_len);
1357         }
1358         /* bug 6187, PING needs empty line too, bumps protocol version to 1.5 */
1359         len += snprintf(buf + len, 8192-len, "\r\n");
1360     
1361         libspamc_timeout = m->timeout;
1362         libspamc_connect_timeout = m->connect_timeout;  /* Sep 8, 2008 mrgus: separate connect timeout */
1363
1364         if (tp->socketpath)
1365           rc = _try_to_connect_unix(tp, &sock);
1366         else
1367           rc = _try_to_connect_tcp(tp, &sock);
1368     
1369         if (rc != EX_OK) {
1370           _use_msg_for_out(m);
1371           if (zlib_on) {
1372               _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1373           }
1374           return rc;      /* use the error code try_to_connect_*() gave us. */
1375         }
1376     
1377         if (flags & SPAMC_USE_SSL) {
1378 #ifdef SPAMC_SSL
1379             ssl = SSL_new(ctx);
1380             SSL_set_fd(ssl, sock);
1381             SSL_connect(ssl);
1382 #endif
1383         }
1384     
1385         /* Send to spamd */
1386         if (flags & SPAMC_USE_SSL) {
1387 #ifdef SPAMC_SSL
1388             SSL_write(ssl, buf, len);
1389             SSL_write(ssl, towrite_buf, towrite_len);
1390 #endif
1391         }
1392         else {
1393             full_write(sock, 0, buf, len);
1394             full_write(sock, 0, towrite_buf, towrite_len);
1395             shutdown(sock, SHUT_WR);
1396         }
1397
1398         /* free zlib buffer
1399         * bug 6025: zlib buffer not freed if compression is used
1400         */
1401         if (zlib_on) {
1402             _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1403         }
1404     
1405         /* ok, now read and parse it.  SPAMD/1.2 line first... */
1406         failureval =
1407                 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1408     } /* end of filterloop */
1409
1410     if (failureval != EX_OK) {
1411         goto failure;
1412     }
1413
1414     if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
1415         libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
1416         failureval = EX_PROTOCOL;
1417         goto failure;
1418     }
1419
1420     versbuf[19] = '\0';
1421     version = _locale_safe_string_to_float(versbuf, 20);
1422     if (version < 1.0) {
1423         libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
1424                versbuf);
1425         failureval = EX_PROTOCOL;
1426         goto failure;
1427     }
1428
1429     if (flags & SPAMC_PING) {
1430         closesocket(sock);
1431         sock = -1;
1432         m->out_len = sprintf(m->out, "SPAMD/%s %d\n", versbuf, response);
1433         m->is_spam = EX_NOTSPAM;
1434         return EX_OK;
1435     }
1436
1437     m->score = 0;
1438     m->threshold = 0;
1439     m->is_spam = EX_TOOBIG;
1440     while (1) {
1441         failureval =
1442             _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1443         if (failureval != EX_OK) {
1444             goto failure;
1445         }
1446
1447         if (len == 0 && buf[0] == '\0') {
1448             break;              /* end of headers */
1449         }
1450
1451         if (_handle_spamd_header(m, flags, buf, len, &throwaway) < 0) {
1452             failureval = EX_PROTOCOL;
1453             goto failure;
1454         }
1455     }
1456
1457     len = 0;                    /* overwrite those headers */
1458
1459     if (flags & SPAMC_CHECK_ONLY) {
1460         closesocket(sock);
1461         sock = -1;
1462         if (m->is_spam == EX_TOOBIG) {
1463             /* We should have gotten headers back... Damnit. */
1464             failureval = EX_PROTOCOL;
1465             goto failure;
1466         }
1467         return EX_OK;
1468     }
1469     else {
1470         if (m->content_length < 0) {
1471             /* should have got a length too. */
1472             failureval = EX_PROTOCOL;
1473             goto failure;
1474         }
1475
1476         /* have we already got something in the buffer (e.g. REPORT and
1477          * REPORT_IFSPAM both create a line from the "Spam:" hdr)?  If
1478          * so, add the size of that so our sanity check passes.
1479          */
1480         if (m->out_len > 0) {
1481             m->content_length += m->out_len;
1482         }
1483
1484         if (flags & SPAMC_USE_SSL) {
1485             len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
1486                                 m->priv->alloced_size - m->out_len,
1487                                 m->priv->alloced_size - m->out_len);
1488         }
1489         else {
1490             len = full_read(sock, 0, m->out + m->out_len,
1491                             m->priv->alloced_size - m->out_len,
1492                             m->priv->alloced_size - m->out_len);
1493         }
1494
1495
1496         if ((int) len + (int) m->out_len > (m->priv->alloced_size - 1)) {
1497             failureval = EX_TOOBIG;
1498             goto failure;
1499         }
1500         m->out_len += len;
1501
1502         shutdown(sock, SHUT_RD);
1503         closesocket(sock);
1504         sock = -1;
1505     }
1506     libspamc_timeout = 0;
1507
1508     if (m->out_len != m->content_length) {
1509         libspamc_log(flags, LOG_ERR,
1510                "failed sanity check, %d bytes claimed, %d bytes seen",
1511                m->content_length, m->out_len);
1512         failureval = EX_PROTOCOL;
1513         goto failure;
1514     }
1515
1516     if (flags & SPAMC_HEADERS) {
1517         if (_append_original_body(m, flags) != EX_OK) {
1518             goto failure;
1519         }
1520     }
1521
1522     return EX_OK;
1523
1524   failure:
1525         _use_msg_for_out(m);
1526     if (sock != -1) {
1527         closesocket(sock);
1528     }
1529     libspamc_timeout = 0;
1530
1531     if (flags & SPAMC_USE_SSL) {
1532 #ifdef SPAMC_SSL
1533         SSL_free(ssl);
1534         SSL_CTX_free(ctx);
1535 #endif
1536     }
1537     return failureval;
1538 }
1539
1540 int message_process(struct transport *trans, char *username, int max_size,
1541                     int in_fd, int out_fd, const int flags)
1542 {
1543     int ret;
1544     struct message m;
1545
1546     assert(trans != NULL);
1547
1548     m.type = MESSAGE_NONE;
1549
1550     /* enforce max_size being unsigned, therefore >= 0 */
1551     if (max_size < 0) {
1552         ret = EX_SOFTWARE;
1553         goto FAIL;
1554     }
1555     m.max_len = (unsigned int) max_size;
1556
1557     ret = message_read(in_fd, flags, &m);
1558     if (ret != EX_OK)
1559         goto FAIL;
1560     ret = message_filter(trans, username, flags, &m);
1561     if (ret != EX_OK)
1562         goto FAIL;
1563     if (message_write(out_fd, &m) < 0)
1564         goto FAIL;
1565     if (m.is_spam != EX_TOOBIG) {
1566         message_cleanup(&m);
1567         return m.is_spam;
1568     }
1569     message_cleanup(&m);
1570     return ret;
1571
1572   FAIL:
1573     if (flags & SPAMC_CHECK_ONLY) {
1574         full_write(out_fd, 1, "0/0\n", 4);
1575         message_cleanup(&m);
1576         return EX_NOTSPAM;
1577     }
1578     else {
1579         message_dump(in_fd, out_fd, &m, flags);
1580         message_cleanup(&m);
1581         return ret;
1582     }
1583 }
1584
1585 int message_tell(struct transport *tp, const char *username, int flags,
1586                  struct message *m, int msg_class,
1587                  unsigned int tellflags, unsigned int *didtellflags)
1588 {
1589     char buf[8192];
1590     size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
1591     size_t len;
1592     int sock = -1;
1593     int rc;
1594     char versbuf[20];
1595     float version;
1596     int response;
1597     int failureval;
1598     SSL_CTX *ctx = NULL;
1599     SSL *ssl = NULL;
1600     SSL_METHOD *meth;
1601
1602     assert(tp != NULL);
1603     assert(m != NULL);
1604
1605     if (flags & SPAMC_USE_SSL) {
1606 #ifdef SPAMC_SSL
1607         SSLeay_add_ssl_algorithms();
1608         meth = SSLv3_client_method();
1609         SSL_load_error_strings();
1610         ctx = SSL_CTX_new(meth);
1611 #else
1612         UNUSED_VARIABLE(ssl);
1613         UNUSED_VARIABLE(meth);
1614         UNUSED_VARIABLE(ctx);
1615         libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1616         return EX_SOFTWARE;
1617 #endif
1618     }
1619
1620     m->is_spam = EX_TOOBIG;
1621
1622     if (m->outbuf != NULL)
1623         free(m->outbuf);
1624     m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
1625     if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
1626         failureval = EX_OSERR;
1627         goto failure;
1628     }
1629     m->out = m->outbuf;
1630     m->out_len = 0;
1631
1632     /* Build spamd protocol header */
1633     strcpy(buf, "TELL ");
1634
1635     len = strlen(buf);
1636     if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1637         _use_msg_for_out(m);
1638         return EX_OSERR;
1639     }
1640
1641     strcat(buf, PROTOCOL_VERSION);
1642     strcat(buf, "\r\n");
1643     len = strlen(buf);
1644
1645     if (msg_class != 0) {
1646       strcpy(buf + len, "Message-class: ");
1647       if (msg_class == SPAMC_MESSAGE_CLASS_SPAM) {
1648         strcat(buf + len, "spam\r\n");
1649       }
1650       else {
1651         strcat(buf + len, "ham\r\n");
1652       }
1653       len += strlen(buf + len);
1654     }
1655
1656     if ((tellflags & SPAMC_SET_LOCAL) || (tellflags & SPAMC_SET_REMOTE)) {
1657       int needs_comma_p = 0;
1658       strcat(buf + len, "Set: ");
1659       if (tellflags & SPAMC_SET_LOCAL) {
1660         strcat(buf + len, "local");
1661         needs_comma_p = 1;
1662       }
1663       if (tellflags & SPAMC_SET_REMOTE) {
1664         if (needs_comma_p == 1) {
1665           strcat(buf + len, ",");
1666         }
1667         strcat(buf + len, "remote");
1668       }
1669       strcat(buf + len, "\r\n");
1670       len += strlen(buf + len);
1671     }
1672
1673     if ((tellflags & SPAMC_REMOVE_LOCAL) || (tellflags & SPAMC_REMOVE_REMOTE)) {
1674       int needs_comma_p = 0;
1675       strcat(buf + len, "Remove: ");
1676       if (tellflags & SPAMC_REMOVE_LOCAL) {
1677         strcat(buf + len, "local");
1678         needs_comma_p = 1;
1679       }
1680       if (tellflags & SPAMC_REMOVE_REMOTE) {
1681         if (needs_comma_p == 1) {
1682           strcat(buf + len, ",");
1683         }
1684         strcat(buf + len, "remote");
1685       }
1686       strcat(buf + len, "\r\n");
1687       len += strlen(buf + len);
1688     }
1689
1690     if (username != NULL) {
1691         if (strlen(username) + 8 >= (bufsiz - len)) {
1692             _use_msg_for_out(m);
1693             return EX_OSERR;
1694         }
1695         strcpy(buf + len, "User: ");
1696         strcat(buf + len, username);
1697         strcat(buf + len, "\r\n");
1698         len += strlen(buf + len);
1699     }
1700     if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
1701         _use_msg_for_out(m);
1702         return EX_DATAERR;
1703     }
1704     len += sprintf(buf + len, "Content-length: %d\r\n\r\n", (int) m->msg_len);
1705
1706     if (m->priv->spamc_header_callback != NULL) {
1707       char buf2[1024];
1708       m->priv->spamc_header_callback(m, flags, buf2, 1024);
1709       strncat(buf, buf2, bufsiz - len);
1710     }
1711
1712     libspamc_timeout = m->timeout;
1713     libspamc_connect_timeout = m->connect_timeout;      /* Sep 8, 2008 mrgus: separate connect timeout */
1714
1715     if (tp->socketpath)
1716         rc = _try_to_connect_unix(tp, &sock);
1717     else
1718         rc = _try_to_connect_tcp(tp, &sock);
1719
1720     if (rc != EX_OK) {
1721         _use_msg_for_out(m);
1722         return rc;      /* use the error code try_to_connect_*() gave us. */
1723     }
1724
1725     if (flags & SPAMC_USE_SSL) {
1726 #ifdef SPAMC_SSL
1727         ssl = SSL_new(ctx);
1728         SSL_set_fd(ssl, sock);
1729         SSL_connect(ssl);
1730 #endif
1731     }
1732
1733     /* Send to spamd */
1734     if (flags & SPAMC_USE_SSL) {
1735 #ifdef SPAMC_SSL
1736         SSL_write(ssl, buf, len);
1737         SSL_write(ssl, m->msg, m->msg_len);
1738 #endif
1739     }
1740     else {
1741         full_write(sock, 0, buf, len);
1742         full_write(sock, 0, m->msg, m->msg_len);
1743         shutdown(sock, SHUT_WR);
1744     }
1745
1746     /* ok, now read and parse it.  SPAMD/1.2 line first... */
1747     failureval =
1748         _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1749     if (failureval != EX_OK) {
1750         goto failure;
1751     }
1752
1753     if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
1754         libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
1755         failureval = EX_PROTOCOL;
1756         goto failure;
1757     }
1758
1759     versbuf[19] = '\0';
1760     version = _locale_safe_string_to_float(versbuf, 20);
1761     if (version < 1.0) {
1762         libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
1763                versbuf);
1764         failureval = EX_PROTOCOL;
1765         goto failure;
1766     }
1767
1768     m->score = 0;
1769     m->threshold = 0;
1770     m->is_spam = EX_TOOBIG;
1771     while (1) {
1772         failureval =
1773             _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1774         if (failureval != EX_OK) {
1775             goto failure;
1776         }
1777
1778         if (len == 0 && buf[0] == '\0') {
1779             break;              /* end of headers */
1780         }
1781
1782         if (_handle_spamd_header(m, flags, buf, len, didtellflags) < 0) {
1783             failureval = EX_PROTOCOL;
1784             goto failure;
1785         }
1786     }
1787
1788     len = 0;                    /* overwrite those headers */
1789
1790     shutdown(sock, SHUT_RD);
1791     closesocket(sock);
1792     sock = -1;
1793
1794     libspamc_timeout = 0;
1795
1796     return EX_OK;
1797
1798   failure:
1799     _use_msg_for_out(m);
1800     if (sock != -1) {
1801         closesocket(sock);
1802     }
1803     libspamc_timeout = 0;
1804
1805     if (flags & SPAMC_USE_SSL) {
1806 #ifdef SPAMC_SSL
1807         SSL_free(ssl);
1808         SSL_CTX_free(ctx);
1809 #endif
1810     }
1811     return failureval;
1812 }
1813
1814 void message_cleanup(struct message *m)
1815 {
1816     assert(m != NULL);
1817     if (m->outbuf != NULL)
1818         free(m->outbuf);
1819     if (m->raw != NULL)
1820         free(m->raw);
1821     if (m->priv != NULL)
1822         free(m->priv);
1823     _clear_message(m);
1824 }
1825
1826 /* Aug 14, 2002 bj: Obsolete! */
1827 int process_message(struct transport *tp, char *username, int max_size,
1828                     int in_fd, int out_fd, const int my_check_only,
1829                     const int my_safe_fallback)
1830 {
1831     int flags;
1832
1833     flags = SPAMC_RAW_MODE;
1834     if (my_check_only)
1835         flags |= SPAMC_CHECK_ONLY;
1836     if (my_safe_fallback)
1837         flags |= SPAMC_SAFE_FALLBACK;
1838
1839     return message_process(tp, username, max_size, in_fd, out_fd, flags);
1840 }
1841
1842 /*
1843 * init_transport()
1844 *
1845 *       Given a pointer to a transport structure, set it to "all empty".
1846 *       The default is a localhost connection.
1847 */
1848 void transport_init(struct transport *tp)
1849 {
1850     assert(tp != 0);
1851
1852     memset(tp, 0, sizeof *tp);
1853
1854     tp->type = TRANSPORT_LOCALHOST;
1855     tp->port = 783;
1856     tp->flags = 0;
1857     tp->retry_sleep = -1;
1858 }
1859
1860 /*
1861 * randomize_hosts()
1862 *
1863 *       Given the transport object that contains one or more IP addresses
1864 *       in this "hosts" list, rotate it by a random number of shifts to
1865 *       randomize them - this is a kind of load balancing. It's possible
1866 *       that the random number will be 0, which says not to touch. We don't
1867 *       do anything unless 
1868 */
1869
1870 static void _randomize_hosts(struct transport *tp)
1871 {
1872 #ifdef SPAMC_HAS_ADDRINFO
1873     struct addrinfo *tmp;
1874 #else
1875     struct in_addr tmp;
1876 #endif
1877     int i;
1878     int rnum;
1879
1880     assert(tp != 0);
1881
1882     if (tp->nhosts <= 1)
1883         return;
1884
1885     rnum = rand() % tp->nhosts;
1886
1887     while (rnum-- > 0) {
1888         tmp = tp->hosts[0];
1889
1890         for (i = 1; i < tp->nhosts; i++)
1891             tp->hosts[i - 1] = tp->hosts[i];
1892
1893         tp->hosts[i - 1] = tmp;
1894     }
1895 }
1896
1897 /*
1898 * transport_setup()
1899 *
1900 *       Given a "transport" object that says how we're to connect to the
1901 *       spam daemon, perform all the initial setup required to make the
1902 *       connection process a smooth one. The main work is to do the host
1903 *       name lookup and copy over all the IP addresses to make a local copy
1904 *       so they're not kept in the resolver's static state.
1905 *
1906 *       Here we also manage quasi-load balancing and failover: if we're
1907 *       doing load balancing, we randomly "rotate" the list to put it in
1908 *       a different order, and then if we're not doing failover we limit
1909 *       the hosts to just one. This way *all* connections are done with
1910 *       the intention of failover - makes the code a bit more clear.
1911 */
1912 int transport_setup(struct transport *tp, int flags)
1913 {
1914 #ifdef SPAMC_HAS_ADDRINFO
1915     struct addrinfo hints, *res, *addrp;
1916     char port[6];
1917     int origerr;
1918 #else
1919     struct hostent *hp;
1920     char **addrp;
1921 #endif
1922     char *hostlist, *hostname;
1923     int errbits;
1924
1925 #ifdef _WIN32
1926     /* Start Winsock up */
1927     WSADATA wsaData;
1928     int nCode;
1929     if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1930         printf("WSAStartup() returned error code %d\n", nCode);
1931         return EX_OSERR;
1932     }
1933
1934 #endif
1935
1936     assert(tp != NULL);
1937     tp->flags = flags;
1938
1939 #ifdef SPAMC_HAS_ADDRINFO
1940     snprintf(port, 6, "%d", tp->port);
1941
1942     memset(&hints, 0, sizeof(hints));
1943     hints.ai_flags = 0;
1944     hints.ai_socktype = SOCK_STREAM;
1945
1946     if (       (flags & SPAMC_USE_INET4) && !(flags & SPAMC_USE_INET6)) {
1947       hints.ai_family = PF_INET;
1948 #ifdef PF_INET6
1949     } else if ((flags & SPAMC_USE_INET6) && !(flags & SPAMC_USE_INET4)) {
1950       hints.ai_family = PF_INET6;
1951 #endif
1952     } else {
1953       hints.ai_family = PF_UNSPEC;
1954     }
1955 #endif
1956
1957     switch (tp->type) {
1958 #ifndef _WIN32
1959     case TRANSPORT_UNIX:
1960         assert(tp->socketpath != 0);
1961         return EX_OK;
1962 #endif
1963     case TRANSPORT_LOCALHOST:
1964 #ifdef SPAMC_HAS_ADDRINFO
1965         /* getaddrinfo(NULL) will look up the loopback address.
1966          * See also bug 5057,  ::1 will be tried before 127.0.0.1
1967          * unless overridden (through hints) by a command line option -4
1968          */
1969         if ((origerr = getaddrinfo(NULL, port, &hints, &res)) != 0) {
1970             libspamc_log(flags, LOG_ERR, 
1971                   "getaddrinfo for a loopback address failed: %s",
1972                   gai_strerror(origerr));
1973             return EX_OSERR;
1974         }
1975         tp->hosts[0] = res;
1976 #else
1977         tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1978 #endif
1979         tp->nhosts = 1;
1980         return EX_OK;
1981
1982     case TRANSPORT_TCP:
1983         if ((hostlist = strdup(tp->hostname)) == NULL)
1984             return EX_OSERR;
1985
1986         /* We want to return the least permanent error, in this bitmask we
1987          * record the errors seen with:
1988          *  0: no error
1989          *  1: EX_TEMPFAIL
1990          *  2: EX_NOHOST
1991          * EX_OSERR will return immediately.
1992          * Bits aren't reset so a check against nhosts is needed to determine
1993          * if something went wrong.
1994          */
1995         errbits = 0;
1996         tp->nhosts = 0;
1997         /* Start with char offset in front of the string because we'll add 
1998          * one in the loop
1999          */
2000         hostname = hostlist - 1;
2001         do {
2002             char *hostend;
2003             
2004             hostname += 1;
2005             hostend = strchr(hostname, ',');
2006             if (hostend != NULL) {
2007                 *hostend = '\0';
2008             }
2009 #ifdef SPAMC_HAS_ADDRINFO            
2010             if ((origerr = getaddrinfo(hostname, port, &hints, &res))) {
2011                 libspamc_log(flags, LOG_DEBUG, 
2012                       "getaddrinfo(%s) failed: %s",
2013                       hostname, gai_strerror(origerr));
2014                 switch (origerr) { 
2015                 case EAI_AGAIN:
2016                     errbits |= 1;
2017                     break;
2018                 case EAI_FAMILY: /*address family not supported*/
2019                 case EAI_SOCKTYPE: /*socket type not supported*/
2020                 case EAI_BADFLAGS: /*ai_flags is invalid*/
2021                 case EAI_NONAME: /*node or service unknown*/
2022                 case EAI_SERVICE: /*service not available*/
2023 /* work around Cygwin IPv6 patch - err codes not defined in Windows aren't in patch */
2024 #ifdef HAVE_EAI_ADDRFAMILY
2025                 case EAI_ADDRFAMILY: /*no addresses in requested family*/
2026 #endif
2027 #ifdef HAVE_EAI_SYSTEM
2028                 case EAI_SYSTEM: /*system error, check errno*/
2029 #endif
2030 #ifdef HAVE_EAI_NODATA
2031                 case EAI_NODATA: /*address exists, but no data*/
2032 #endif
2033                 case EAI_MEMORY: /*out of memory*/
2034                 case EAI_FAIL: /*name server returned permanent error*/
2035                     errbits |= 2;
2036                     break;
2037                 default:
2038                     /* should not happen, all errors are checked above */
2039                     free(hostlist);
2040                     return EX_OSERR;
2041                 }
2042                 goto nexthost; /* try next host in list */
2043             }
2044 #else
2045             if ((hp = gethostbyname(hostname)) == NULL) {
2046                 int origerr = h_errno; /* take a copy before syslog() */
2047                 libspamc_log(flags, LOG_DEBUG, "gethostbyname(%s) failed: h_errno=%d",
2048                     hostname, origerr);
2049                 switch (origerr) {
2050                 case TRY_AGAIN:
2051                     errbits |= 1;
2052                     break;
2053                 case HOST_NOT_FOUND:
2054                 case NO_ADDRESS:
2055                 case NO_RECOVERY:
2056                     errbits |= 2;
2057                     break;
2058                 default:
2059                     /* should not happen, all errors are checked above */
2060                     free(hostlist);
2061                     return EX_OSERR;
2062                 }
2063                 goto nexthost; /* try next host in list */
2064             }
2065 #endif
2066             
2067             /* If we have no hosts at all */
2068 #ifdef SPAMC_HAS_ADDRINFO
2069             if(res == NULL)
2070 #else
2071             if (hp->h_addr_list[0] == NULL
2072              || hp->h_length != sizeof tp->hosts[0]
2073              || hp->h_addrtype != AF_INET)
2074                 /* no hosts/bad size/wrong family */
2075 #endif
2076             {
2077                 errbits |= 1;
2078                 goto nexthost; /* try next host in list */
2079             }
2080
2081             /* Copy all the IP addresses into our private structure.
2082              * This gets them out of the resolver's static area and
2083              * means we won't ever walk all over the list with other
2084              * calls.
2085              */
2086 #ifdef SPAMC_HAS_ADDRINFO
2087             if(tp->nhosts == TRANSPORT_MAX_HOSTS) {
2088                libspamc_log(flags, LOG_NOTICE, 
2089                      "hit limit of %d hosts, ignoring remainder",
2090                      TRANSPORT_MAX_HOSTS);
2091                break;
2092             }
2093
2094             /* treat all A or AAAA records of each host as one entry */
2095             tp->hosts[tp->nhosts++] = res;
2096
2097             /* alternatively, treat multiple A or AAAA records
2098                of one host as individual entries */
2099 /*          for (addrp = res; addrp != NULL; ) {
2100  *              tp->hosts[tp->nhosts] = addrp;
2101  *              addrp = addrp->ai_next;     /-* before NULLing ai_next *-/
2102  *              tp->hosts[tp->nhosts]->ai_next = NULL;
2103  *              tp->nhosts++;
2104  *          }
2105  */
2106
2107 #else
2108             for (addrp = hp->h_addr_list; *addrp; addrp++) {
2109                 if (tp->nhosts == TRANSPORT_MAX_HOSTS) {
2110                     libspamc_log(flags, LOG_NOTICE, "hit limit of %d hosts, ignoring remainder",
2111                         TRANSPORT_MAX_HOSTS);
2112                     break;
2113                 }
2114                 memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length);
2115                 tp->nhosts++;
2116             }
2117 #endif            
2118 nexthost:
2119             hostname = hostend;
2120         } while (hostname != NULL);
2121         free(hostlist);
2122         
2123         if (tp->nhosts == 0) {
2124             if (errbits & 1) {
2125                 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): a temporary error occurred",
2126                     tp->hostname); 
2127                 return EX_TEMPFAIL;
2128             }
2129             else {
2130                 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): no such host",
2131                     tp->hostname); 
2132                 return EX_NOHOST;
2133             }
2134         }
2135         
2136         /* QUASI-LOAD-BALANCING
2137          *
2138          * If the user wants to do quasi load balancing, "rotate"
2139          * the list by a random amount based on the current time.
2140          * This may later be truncated to a single item. This is
2141          * meaningful only if we have more than one host.
2142          */
2143
2144         if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
2145             _randomize_hosts(tp);
2146         }
2147
2148         /* If the user wants no fallback, simply truncate the host
2149          * list to just one - this pretends that this is the extent
2150          * of our connection list - then it's not a special case.
2151          */
2152         if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
2153             /* truncating list */
2154             tp->nhosts = 1;
2155         }
2156         
2157         return EX_OK;
2158     }
2159     
2160     /* oops, unknown transport type */
2161     return EX_OSERR;
2162 }
2163
2164 /*
2165 * transport_cleanup()
2166 *
2167 *       Given a "transport" object that says how we're to connect to the
2168 *       spam daemon, delete and free any buffers allocated so that it
2169 *       can be discarded without causing a memory leak.
2170 */
2171 void transport_cleanup(struct transport *tp)
2172 {
2173
2174 #ifdef SPAMC_HAS_ADDRINFO
2175   int i;
2176
2177   for(i=0;i<tp->nhosts;i++) {
2178       if (tp->hosts[i] != NULL) {
2179           freeaddrinfo(tp->hosts[i]);
2180           tp->hosts[i] = NULL;
2181       }
2182   }
2183 #endif
2184
2185 }
2186
2187 /*
2188 * register_libspamc_log_callback()
2189 *
2190 * Register a callback handler for libspamc_log to replace the default behaviour.
2191 */
2192
2193 void register_libspamc_log_callback(void (*function)(int flags, int level, char *msg, va_list args)) {
2194   libspamc_log_callback = function;
2195 }
2196
2197 /*
2198 * register_spamc_header_callback()
2199 *
2200 * Register a callback handler to generate spamc headers for a given message
2201 */
2202
2203 void register_spamc_header_callback(const struct message *m, void (*func)(struct message *m, int flags, char *buf, int len)) {
2204   m->priv->spamc_header_callback = func;
2205 }
2206
2207 /*
2208 * register_spamd_header_callback()
2209 *
2210 * Register a callback handler to generate spamd headers for a given message
2211 */
2212
2213 void register_spamd_header_callback(const struct message *m, void (*func)(struct message *m, int flags, const char *buf, int len)) {
2214   m->priv->spamd_header_callback = func;
2215 }
2216
2217 /* --------------------------------------------------------------------------- */
2218
2219 #define LOG_BUFSIZ      1023
2220
2221 void
2222 libspamc_log (int flags, int level, char *msg, ...)
2223 {
2224     va_list ap;
2225     char buf[LOG_BUFSIZ+1];
2226     int len = 0;
2227
2228     va_start(ap, msg);
2229
2230     if ((flags & SPAMC_LOG_TO_CALLBACK) != 0 && libspamc_log_callback != NULL) {
2231       libspamc_log_callback(flags, level, msg, ap);
2232     }
2233     else if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
2234         /* create a log-line buffer */
2235         len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
2236         len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
2237
2238         /* avoid buffer overflow */
2239         if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
2240
2241         len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
2242         buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
2243         (void) write (2, buf, len);
2244
2245     } else {
2246         vsnprintf(buf, LOG_BUFSIZ, msg, ap);
2247         buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
2248 #ifndef _WIN32
2249         syslog (level, "%s", buf);
2250 #else
2251         (void) level;  /* not used. suppress compiler warning */
2252         f_printerr ("%s\n", buf);
2253 #endif
2254     }
2255
2256     va_end(ap);
2257 }
2258
2259 /* --------------------------------------------------------------------------- */
2260
2261 /*
2262 * Unit tests.  Must be built externally, e.g.:
2263 *
2264 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
2265 * ./libspamctest
2266 *
2267 */
2268 #ifdef LIBSPAMC_UNIT_TESTS
2269
2270 static void _test_locale_safe_string_to_float_val(float input)
2271 {
2272     char inputstr[99], cmpbuf1[99], cmpbuf2[99];
2273     float output;
2274
2275     /* sprintf instead of snprintf is safe here because it is only a controlled test */
2276     sprintf(inputstr, "%f", input);
2277     output = _locale_safe_string_to_float(inputstr, 99);
2278     if (input == output) {
2279         return;
2280     }
2281
2282     /* could be a rounding error.  print as string and compare those */
2283     sprintf(cmpbuf1, "%f", input);
2284     sprintf(cmpbuf2, "%f", output);
2285     if (!strcmp(cmpbuf1, cmpbuf2)) {
2286         return;
2287     }
2288
2289     printf("FAIL: input=%f != output=%f\n", input, output);
2290 }
2291
2292 static void unit_test_locale_safe_string_to_float(void)
2293 {
2294     float statictestset[] = {   /* will try both +ve and -ve */
2295         0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
2296         9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
2297         0.0                     /* end of set constant */
2298     };
2299     float num;
2300     int i;
2301
2302     printf("starting unit_test_locale_safe_string_to_float\n");
2303     /* tests of precision */
2304     for (i = 0; statictestset[i] != 0.0; i++) {
2305         _test_locale_safe_string_to_float_val(statictestset[i]);
2306         _test_locale_safe_string_to_float_val(-statictestset[i]);
2307         _test_locale_safe_string_to_float_val(1 - statictestset[i]);
2308         _test_locale_safe_string_to_float_val(1 + statictestset[i]);
2309     }
2310     /* now exhaustive, in steps of 0.01 */
2311     for (num = -1000.0; num < 1000.0; num += 0.01) {
2312         _test_locale_safe_string_to_float_val(num);
2313     }
2314     printf("finished unit_test_locale_safe_string_to_float\n");
2315 }
2316
2317 void do_libspamc_unit_tests(void)
2318 {
2319     unit_test_locale_safe_string_to_float();
2320     exit(0);
2321 }
2322
2323 #endif /* LIBSPAMC_UNIT_TESTS */