Disable SSLv3 in libspamc.c (patch from Debian spamassassin package).
[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     const 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         meth = SSLv23_client_method();
1218         SSL_load_error_strings();
1219         ctx = SSL_CTX_new(meth);
1220 #else
1221         UNUSED_VARIABLE(ssl);
1222         UNUSED_VARIABLE(meth);
1223         UNUSED_VARIABLE(ctx);
1224         libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1225         return EX_SOFTWARE;
1226 #endif
1227     }
1228
1229     m->is_spam = EX_TOOBIG;
1230
1231     if (m->outbuf != NULL)
1232         free(m->outbuf);
1233     m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
1234     if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
1235         failureval = EX_OSERR;
1236         goto failure;
1237     }
1238     m->out = m->outbuf;
1239     m->out_len = 0;
1240
1241     /* If the spamd filter takes too long and we timeout, then
1242      * retry again.  This gets us around a hung child thread 
1243      * in spamd or a problem on a spamd host in a multi-host
1244      * setup.  If there is more than one destination host
1245      * we move to the next host on each attempt.
1246      */
1247
1248     /* default values */
1249     filter_retry_sleep = tp->filter_retry_sleep;
1250     filter_retries = tp->filter_retries;
1251     if (filter_retries == 0) {
1252         filter_retries = 1;
1253     }
1254     if (filter_retry_sleep < 0) {
1255         filter_retry_sleep = 1;
1256     }
1257
1258     /* filterloop - Ensure that we run through this at least
1259      * once, and again if there are errors 
1260      */
1261     filter_retry_count = 0;
1262     while ((filter_retry_count==0) || 
1263                 ((filter_retry_count<tp->filter_retries) && (failureval == EX_IOERR)))
1264     {
1265         if (filter_retry_count != 0){
1266             /* Ensure that the old socket gets closed */
1267             if (sock != -1) {
1268                 closesocket(sock);
1269                 sock=-1;
1270             }
1271
1272             /* Move to the next host in the list, if nhosts>1 */
1273             if (tp->nhosts > 1) {
1274                 tmphost = tp->hosts[0];
1275
1276                 /* TODO: free using freeaddrinfo() */
1277                 for (nhost_counter = 1; nhost_counter < tp->nhosts; nhost_counter++) {
1278                     tp->hosts[nhost_counter - 1] = tp->hosts[nhost_counter];
1279                 }
1280         
1281                 tp->hosts[nhost_counter - 1] = tmphost;
1282             }
1283
1284             /* Now sleep the requested amount */
1285             sleep(filter_retry_sleep);
1286         }
1287
1288         filter_retry_count++;
1289     
1290         /* Build spamd protocol header */
1291         if (flags & SPAMC_CHECK_ONLY)
1292           strcpy(buf, "CHECK ");
1293         else if (flags & SPAMC_REPORT_IFSPAM)
1294           strcpy(buf, "REPORT_IFSPAM ");
1295         else if (flags & SPAMC_REPORT)
1296           strcpy(buf, "REPORT ");
1297         else if (flags & SPAMC_SYMBOLS)
1298           strcpy(buf, "SYMBOLS ");
1299         else if (flags & SPAMC_PING)
1300           strcpy(buf, "PING ");
1301         else if (flags & SPAMC_HEADERS)
1302           strcpy(buf, "HEADERS ");
1303         else
1304           strcpy(buf, "PROCESS ");
1305     
1306         len = strlen(buf);
1307         if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1308             _use_msg_for_out(m);
1309             return EX_OSERR;
1310         }
1311     
1312         strcat(buf, PROTOCOL_VERSION);
1313         strcat(buf, "\r\n");
1314         len = strlen(buf);
1315     
1316         towrite_buf = (unsigned char *) m->msg;
1317         towrite_len = (int) m->msg_len;
1318         if (zlib_on) {
1319             if (_zlib_compress(m->msg, m->msg_len, &zlib_buf, &zlib_bufsiz, flags) != EX_OK)
1320             {
1321                 _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1322                 return EX_OSERR;
1323             }
1324             towrite_buf = zlib_buf;
1325             towrite_len = zlib_bufsiz;
1326         }
1327     
1328         if (!(flags & SPAMC_PING)) {
1329           if (username != NULL) {
1330               if (strlen(username) + 8 >= (bufsiz - len)) {
1331                   _use_msg_for_out(m);
1332                   if (zlib_on) {
1333                       _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1334                   }
1335                   return EX_OSERR;
1336               }
1337               strcpy(buf + len, "User: ");
1338               strcat(buf + len, username);
1339               strcat(buf + len, "\r\n");
1340               len += strlen(buf + len);
1341           }
1342           if (zlib_on) {
1343               len += snprintf(buf + len, 8192-len, "Compress: zlib\r\n");
1344           }
1345           if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
1346               _use_msg_for_out(m);
1347               if (zlib_on) {
1348                   _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1349               }
1350               return EX_DATAERR;
1351           }
1352           len += snprintf(buf + len, 8192-len, "Content-length: %d\r\n", (int) towrite_len);
1353         }
1354         /* bug 6187, PING needs empty line too, bumps protocol version to 1.5 */
1355         len += snprintf(buf + len, 8192-len, "\r\n");
1356     
1357         libspamc_timeout = m->timeout;
1358         libspamc_connect_timeout = m->connect_timeout;  /* Sep 8, 2008 mrgus: separate connect timeout */
1359
1360         if (tp->socketpath)
1361           rc = _try_to_connect_unix(tp, &sock);
1362         else
1363           rc = _try_to_connect_tcp(tp, &sock);
1364     
1365         if (rc != EX_OK) {
1366           _use_msg_for_out(m);
1367           if (zlib_on) {
1368               _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1369           }
1370           return rc;      /* use the error code try_to_connect_*() gave us. */
1371         }
1372     
1373         if (flags & SPAMC_USE_SSL) {
1374 #ifdef SPAMC_SSL
1375             ssl = SSL_new(ctx);
1376             SSL_set_fd(ssl, sock);
1377             SSL_connect(ssl);
1378 #endif
1379         }
1380     
1381         /* Send to spamd */
1382         if (flags & SPAMC_USE_SSL) {
1383 #ifdef SPAMC_SSL
1384             SSL_write(ssl, buf, len);
1385             SSL_write(ssl, towrite_buf, towrite_len);
1386 #endif
1387         }
1388         else {
1389             full_write(sock, 0, buf, len);
1390             full_write(sock, 0, towrite_buf, towrite_len);
1391             shutdown(sock, SHUT_WR);
1392         }
1393
1394         /* free zlib buffer
1395         * bug 6025: zlib buffer not freed if compression is used
1396         */
1397         if (zlib_on) {
1398             _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
1399         }
1400     
1401         /* ok, now read and parse it.  SPAMD/1.2 line first... */
1402         failureval =
1403                 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1404     } /* end of filterloop */
1405
1406     if (failureval != EX_OK) {
1407         goto failure;
1408     }
1409
1410     if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
1411         libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
1412         failureval = EX_PROTOCOL;
1413         goto failure;
1414     }
1415
1416     versbuf[19] = '\0';
1417     version = _locale_safe_string_to_float(versbuf, 20);
1418     if (version < 1.0) {
1419         libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
1420                versbuf);
1421         failureval = EX_PROTOCOL;
1422         goto failure;
1423     }
1424
1425     if (flags & SPAMC_PING) {
1426         closesocket(sock);
1427         sock = -1;
1428         m->out_len = sprintf(m->out, "SPAMD/%s %d\n", versbuf, response);
1429         m->is_spam = EX_NOTSPAM;
1430         return EX_OK;
1431     }
1432
1433     m->score = 0;
1434     m->threshold = 0;
1435     m->is_spam = EX_TOOBIG;
1436     while (1) {
1437         failureval =
1438             _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1439         if (failureval != EX_OK) {
1440             goto failure;
1441         }
1442
1443         if (len == 0 && buf[0] == '\0') {
1444             break;              /* end of headers */
1445         }
1446
1447         if (_handle_spamd_header(m, flags, buf, len, &throwaway) < 0) {
1448             failureval = EX_PROTOCOL;
1449             goto failure;
1450         }
1451     }
1452
1453     len = 0;                    /* overwrite those headers */
1454
1455     if (flags & SPAMC_CHECK_ONLY) {
1456         closesocket(sock);
1457         sock = -1;
1458         if (m->is_spam == EX_TOOBIG) {
1459             /* We should have gotten headers back... Damnit. */
1460             failureval = EX_PROTOCOL;
1461             goto failure;
1462         }
1463         return EX_OK;
1464     }
1465     else {
1466         if (m->content_length < 0) {
1467             /* should have got a length too. */
1468             failureval = EX_PROTOCOL;
1469             goto failure;
1470         }
1471
1472         /* have we already got something in the buffer (e.g. REPORT and
1473          * REPORT_IFSPAM both create a line from the "Spam:" hdr)?  If
1474          * so, add the size of that so our sanity check passes.
1475          */
1476         if (m->out_len > 0) {
1477             m->content_length += m->out_len;
1478         }
1479
1480         if (flags & SPAMC_USE_SSL) {
1481             len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
1482                                 m->priv->alloced_size - m->out_len,
1483                                 m->priv->alloced_size - m->out_len);
1484         }
1485         else {
1486             len = full_read(sock, 0, m->out + m->out_len,
1487                             m->priv->alloced_size - m->out_len,
1488                             m->priv->alloced_size - m->out_len);
1489         }
1490
1491
1492         if ((int) len + (int) m->out_len > (m->priv->alloced_size - 1)) {
1493             failureval = EX_TOOBIG;
1494             goto failure;
1495         }
1496         m->out_len += len;
1497
1498         shutdown(sock, SHUT_RD);
1499         closesocket(sock);
1500         sock = -1;
1501     }
1502     libspamc_timeout = 0;
1503
1504     if (m->out_len != m->content_length) {
1505         libspamc_log(flags, LOG_ERR,
1506                "failed sanity check, %d bytes claimed, %d bytes seen",
1507                m->content_length, m->out_len);
1508         failureval = EX_PROTOCOL;
1509         goto failure;
1510     }
1511
1512     if (flags & SPAMC_HEADERS) {
1513         if (_append_original_body(m, flags) != EX_OK) {
1514             goto failure;
1515         }
1516     }
1517
1518     return EX_OK;
1519
1520   failure:
1521         _use_msg_for_out(m);
1522     if (sock != -1) {
1523         closesocket(sock);
1524     }
1525     libspamc_timeout = 0;
1526
1527     if (flags & SPAMC_USE_SSL) {
1528 #ifdef SPAMC_SSL
1529         SSL_free(ssl);
1530         SSL_CTX_free(ctx);
1531 #endif
1532     }
1533     return failureval;
1534 }
1535
1536 int message_process(struct transport *trans, char *username, int max_size,
1537                     int in_fd, int out_fd, const int flags)
1538 {
1539     int ret;
1540     struct message m;
1541
1542     assert(trans != NULL);
1543
1544     m.type = MESSAGE_NONE;
1545
1546     /* enforce max_size being unsigned, therefore >= 0 */
1547     if (max_size < 0) {
1548         ret = EX_SOFTWARE;
1549         goto FAIL;
1550     }
1551     m.max_len = (unsigned int) max_size;
1552
1553     ret = message_read(in_fd, flags, &m);
1554     if (ret != EX_OK)
1555         goto FAIL;
1556     ret = message_filter(trans, username, flags, &m);
1557     if (ret != EX_OK)
1558         goto FAIL;
1559     if (message_write(out_fd, &m) < 0)
1560         goto FAIL;
1561     if (m.is_spam != EX_TOOBIG) {
1562         message_cleanup(&m);
1563         return m.is_spam;
1564     }
1565     message_cleanup(&m);
1566     return ret;
1567
1568   FAIL:
1569     if (flags & SPAMC_CHECK_ONLY) {
1570         full_write(out_fd, 1, "0/0\n", 4);
1571         message_cleanup(&m);
1572         return EX_NOTSPAM;
1573     }
1574     else {
1575         message_dump(in_fd, out_fd, &m, flags);
1576         message_cleanup(&m);
1577         return ret;
1578     }
1579 }
1580
1581 int message_tell(struct transport *tp, const char *username, int flags,
1582                  struct message *m, int msg_class,
1583                  unsigned int tellflags, unsigned int *didtellflags)
1584 {
1585     char buf[8192];
1586     size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
1587     size_t len;
1588     int sock = -1;
1589     int rc;
1590     char versbuf[20];
1591     float version;
1592     int response;
1593     int failureval;
1594     SSL_CTX *ctx = NULL;
1595     SSL *ssl = NULL;
1596     const SSL_METHOD *meth;
1597
1598     assert(tp != NULL);
1599     assert(m != NULL);
1600
1601     if (flags & SPAMC_USE_SSL) {
1602 #ifdef SPAMC_SSL
1603         SSLeay_add_ssl_algorithms();
1604         meth = SSLv23_client_method();
1605         SSL_load_error_strings();
1606         ctx = SSL_CTX_new(meth);
1607 #else
1608         UNUSED_VARIABLE(ssl);
1609         UNUSED_VARIABLE(meth);
1610         UNUSED_VARIABLE(ctx);
1611         libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1612         return EX_SOFTWARE;
1613 #endif
1614     }
1615
1616     m->is_spam = EX_TOOBIG;
1617
1618     if (m->outbuf != NULL)
1619         free(m->outbuf);
1620     m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
1621     if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
1622         failureval = EX_OSERR;
1623         goto failure;
1624     }
1625     m->out = m->outbuf;
1626     m->out_len = 0;
1627
1628     /* Build spamd protocol header */
1629     strcpy(buf, "TELL ");
1630
1631     len = strlen(buf);
1632     if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1633         _use_msg_for_out(m);
1634         return EX_OSERR;
1635     }
1636
1637     strcat(buf, PROTOCOL_VERSION);
1638     strcat(buf, "\r\n");
1639     len = strlen(buf);
1640
1641     if (msg_class != 0) {
1642       strcpy(buf + len, "Message-class: ");
1643       if (msg_class == SPAMC_MESSAGE_CLASS_SPAM) {
1644         strcat(buf + len, "spam\r\n");
1645       }
1646       else {
1647         strcat(buf + len, "ham\r\n");
1648       }
1649       len += strlen(buf + len);
1650     }
1651
1652     if ((tellflags & SPAMC_SET_LOCAL) || (tellflags & SPAMC_SET_REMOTE)) {
1653       int needs_comma_p = 0;
1654       strcat(buf + len, "Set: ");
1655       if (tellflags & SPAMC_SET_LOCAL) {
1656         strcat(buf + len, "local");
1657         needs_comma_p = 1;
1658       }
1659       if (tellflags & SPAMC_SET_REMOTE) {
1660         if (needs_comma_p == 1) {
1661           strcat(buf + len, ",");
1662         }
1663         strcat(buf + len, "remote");
1664       }
1665       strcat(buf + len, "\r\n");
1666       len += strlen(buf + len);
1667     }
1668
1669     if ((tellflags & SPAMC_REMOVE_LOCAL) || (tellflags & SPAMC_REMOVE_REMOTE)) {
1670       int needs_comma_p = 0;
1671       strcat(buf + len, "Remove: ");
1672       if (tellflags & SPAMC_REMOVE_LOCAL) {
1673         strcat(buf + len, "local");
1674         needs_comma_p = 1;
1675       }
1676       if (tellflags & SPAMC_REMOVE_REMOTE) {
1677         if (needs_comma_p == 1) {
1678           strcat(buf + len, ",");
1679         }
1680         strcat(buf + len, "remote");
1681       }
1682       strcat(buf + len, "\r\n");
1683       len += strlen(buf + len);
1684     }
1685
1686     if (username != NULL) {
1687         if (strlen(username) + 8 >= (bufsiz - len)) {
1688             _use_msg_for_out(m);
1689             return EX_OSERR;
1690         }
1691         strcpy(buf + len, "User: ");
1692         strcat(buf + len, username);
1693         strcat(buf + len, "\r\n");
1694         len += strlen(buf + len);
1695     }
1696     if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
1697         _use_msg_for_out(m);
1698         return EX_DATAERR;
1699     }
1700     len += sprintf(buf + len, "Content-length: %d\r\n\r\n", (int) m->msg_len);
1701
1702     if (m->priv->spamc_header_callback != NULL) {
1703       char buf2[1024];
1704       m->priv->spamc_header_callback(m, flags, buf2, 1024);
1705       strncat(buf, buf2, bufsiz - len);
1706     }
1707
1708     libspamc_timeout = m->timeout;
1709     libspamc_connect_timeout = m->connect_timeout;      /* Sep 8, 2008 mrgus: separate connect timeout */
1710
1711     if (tp->socketpath)
1712         rc = _try_to_connect_unix(tp, &sock);
1713     else
1714         rc = _try_to_connect_tcp(tp, &sock);
1715
1716     if (rc != EX_OK) {
1717         _use_msg_for_out(m);
1718         return rc;      /* use the error code try_to_connect_*() gave us. */
1719     }
1720
1721     if (flags & SPAMC_USE_SSL) {
1722 #ifdef SPAMC_SSL
1723         ssl = SSL_new(ctx);
1724         SSL_set_fd(ssl, sock);
1725         SSL_connect(ssl);
1726 #endif
1727     }
1728
1729     /* Send to spamd */
1730     if (flags & SPAMC_USE_SSL) {
1731 #ifdef SPAMC_SSL
1732         SSL_write(ssl, buf, len);
1733         SSL_write(ssl, m->msg, m->msg_len);
1734 #endif
1735     }
1736     else {
1737         full_write(sock, 0, buf, len);
1738         full_write(sock, 0, m->msg, m->msg_len);
1739         shutdown(sock, SHUT_WR);
1740     }
1741
1742     /* ok, now read and parse it.  SPAMD/1.2 line first... */
1743     failureval =
1744         _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1745     if (failureval != EX_OK) {
1746         goto failure;
1747     }
1748
1749     if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
1750         libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
1751         failureval = EX_PROTOCOL;
1752         goto failure;
1753     }
1754
1755     versbuf[19] = '\0';
1756     version = _locale_safe_string_to_float(versbuf, 20);
1757     if (version < 1.0) {
1758         libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
1759                versbuf);
1760         failureval = EX_PROTOCOL;
1761         goto failure;
1762     }
1763
1764     m->score = 0;
1765     m->threshold = 0;
1766     m->is_spam = EX_TOOBIG;
1767     while (1) {
1768         failureval =
1769             _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1770         if (failureval != EX_OK) {
1771             goto failure;
1772         }
1773
1774         if (len == 0 && buf[0] == '\0') {
1775             break;              /* end of headers */
1776         }
1777
1778         if (_handle_spamd_header(m, flags, buf, len, didtellflags) < 0) {
1779             failureval = EX_PROTOCOL;
1780             goto failure;
1781         }
1782     }
1783
1784     len = 0;                    /* overwrite those headers */
1785
1786     shutdown(sock, SHUT_RD);
1787     closesocket(sock);
1788     sock = -1;
1789
1790     libspamc_timeout = 0;
1791
1792     return EX_OK;
1793
1794   failure:
1795     _use_msg_for_out(m);
1796     if (sock != -1) {
1797         closesocket(sock);
1798     }
1799     libspamc_timeout = 0;
1800
1801     if (flags & SPAMC_USE_SSL) {
1802 #ifdef SPAMC_SSL
1803         SSL_free(ssl);
1804         SSL_CTX_free(ctx);
1805 #endif
1806     }
1807     return failureval;
1808 }
1809
1810 void message_cleanup(struct message *m)
1811 {
1812     assert(m != NULL);
1813     if (m->outbuf != NULL)
1814         free(m->outbuf);
1815     if (m->raw != NULL)
1816         free(m->raw);
1817     if (m->priv != NULL)
1818         free(m->priv);
1819     _clear_message(m);
1820 }
1821
1822 /* Aug 14, 2002 bj: Obsolete! */
1823 int process_message(struct transport *tp, char *username, int max_size,
1824                     int in_fd, int out_fd, const int my_check_only,
1825                     const int my_safe_fallback)
1826 {
1827     int flags;
1828
1829     flags = SPAMC_RAW_MODE;
1830     if (my_check_only)
1831         flags |= SPAMC_CHECK_ONLY;
1832     if (my_safe_fallback)
1833         flags |= SPAMC_SAFE_FALLBACK;
1834
1835     return message_process(tp, username, max_size, in_fd, out_fd, flags);
1836 }
1837
1838 /*
1839 * init_transport()
1840 *
1841 *       Given a pointer to a transport structure, set it to "all empty".
1842 *       The default is a localhost connection.
1843 */
1844 void transport_init(struct transport *tp)
1845 {
1846     assert(tp != 0);
1847
1848     memset(tp, 0, sizeof *tp);
1849
1850     tp->type = TRANSPORT_LOCALHOST;
1851     tp->port = 783;
1852     tp->flags = 0;
1853     tp->retry_sleep = -1;
1854 }
1855
1856 /*
1857 * randomize_hosts()
1858 *
1859 *       Given the transport object that contains one or more IP addresses
1860 *       in this "hosts" list, rotate it by a random number of shifts to
1861 *       randomize them - this is a kind of load balancing. It's possible
1862 *       that the random number will be 0, which says not to touch. We don't
1863 *       do anything unless 
1864 */
1865
1866 static void _randomize_hosts(struct transport *tp)
1867 {
1868 #ifdef SPAMC_HAS_ADDRINFO
1869     struct addrinfo *tmp;
1870 #else
1871     struct in_addr tmp;
1872 #endif
1873     int i;
1874     int rnum;
1875
1876     assert(tp != 0);
1877
1878     if (tp->nhosts <= 1)
1879         return;
1880
1881     rnum = rand() % tp->nhosts;
1882
1883     while (rnum-- > 0) {
1884         tmp = tp->hosts[0];
1885
1886         for (i = 1; i < tp->nhosts; i++)
1887             tp->hosts[i - 1] = tp->hosts[i];
1888
1889         tp->hosts[i - 1] = tmp;
1890     }
1891 }
1892
1893 /*
1894 * transport_setup()
1895 *
1896 *       Given a "transport" object that says how we're to connect to the
1897 *       spam daemon, perform all the initial setup required to make the
1898 *       connection process a smooth one. The main work is to do the host
1899 *       name lookup and copy over all the IP addresses to make a local copy
1900 *       so they're not kept in the resolver's static state.
1901 *
1902 *       Here we also manage quasi-load balancing and failover: if we're
1903 *       doing load balancing, we randomly "rotate" the list to put it in
1904 *       a different order, and then if we're not doing failover we limit
1905 *       the hosts to just one. This way *all* connections are done with
1906 *       the intention of failover - makes the code a bit more clear.
1907 */
1908 int transport_setup(struct transport *tp, int flags)
1909 {
1910 #ifdef SPAMC_HAS_ADDRINFO
1911     struct addrinfo hints, *res, *addrp;
1912     char port[6];
1913     int origerr;
1914 #else
1915     struct hostent *hp;
1916     char **addrp;
1917 #endif
1918     char *hostlist, *hostname;
1919     int errbits;
1920
1921 #ifdef _WIN32
1922     /* Start Winsock up */
1923     WSADATA wsaData;
1924     int nCode;
1925     if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1926         printf("WSAStartup() returned error code %d\n", nCode);
1927         return EX_OSERR;
1928     }
1929
1930 #endif
1931
1932     assert(tp != NULL);
1933     tp->flags = flags;
1934
1935 #ifdef SPAMC_HAS_ADDRINFO
1936     snprintf(port, 6, "%d", tp->port);
1937
1938     memset(&hints, 0, sizeof(hints));
1939     hints.ai_flags = 0;
1940     hints.ai_socktype = SOCK_STREAM;
1941
1942     if (       (flags & SPAMC_USE_INET4) && !(flags & SPAMC_USE_INET6)) {
1943       hints.ai_family = PF_INET;
1944 #ifdef PF_INET6
1945     } else if ((flags & SPAMC_USE_INET6) && !(flags & SPAMC_USE_INET4)) {
1946       hints.ai_family = PF_INET6;
1947 #endif
1948     } else {
1949       hints.ai_family = PF_UNSPEC;
1950     }
1951 #endif
1952
1953     switch (tp->type) {
1954 #ifndef _WIN32
1955     case TRANSPORT_UNIX:
1956         assert(tp->socketpath != 0);
1957         return EX_OK;
1958 #endif
1959     case TRANSPORT_LOCALHOST:
1960 #ifdef SPAMC_HAS_ADDRINFO
1961         /* getaddrinfo(NULL) will look up the loopback address.
1962          * See also bug 5057,  ::1 will be tried before 127.0.0.1
1963          * unless overridden (through hints) by a command line option -4
1964          */
1965         if ((origerr = getaddrinfo(NULL, port, &hints, &res)) != 0) {
1966             libspamc_log(flags, LOG_ERR, 
1967                   "getaddrinfo for a loopback address failed: %s",
1968                   gai_strerror(origerr));
1969             return EX_OSERR;
1970         }
1971         tp->hosts[0] = res;
1972 #else
1973         tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1974 #endif
1975         tp->nhosts = 1;
1976         return EX_OK;
1977
1978     case TRANSPORT_TCP:
1979         if ((hostlist = strdup(tp->hostname)) == NULL)
1980             return EX_OSERR;
1981
1982         /* We want to return the least permanent error, in this bitmask we
1983          * record the errors seen with:
1984          *  0: no error
1985          *  1: EX_TEMPFAIL
1986          *  2: EX_NOHOST
1987          * EX_OSERR will return immediately.
1988          * Bits aren't reset so a check against nhosts is needed to determine
1989          * if something went wrong.
1990          */
1991         errbits = 0;
1992         tp->nhosts = 0;
1993         /* Start with char offset in front of the string because we'll add 
1994          * one in the loop
1995          */
1996         hostname = hostlist - 1;
1997         do {
1998             char *hostend;
1999             
2000             hostname += 1;
2001             hostend = strchr(hostname, ',');
2002             if (hostend != NULL) {
2003                 *hostend = '\0';
2004             }
2005 #ifdef SPAMC_HAS_ADDRINFO            
2006             if ((origerr = getaddrinfo(hostname, port, &hints, &res))) {
2007                 libspamc_log(flags, LOG_DEBUG, 
2008                       "getaddrinfo(%s) failed: %s",
2009                       hostname, gai_strerror(origerr));
2010                 switch (origerr) { 
2011                 case EAI_AGAIN:
2012                     errbits |= 1;
2013                     break;
2014                 case EAI_FAMILY: /*address family not supported*/
2015                 case EAI_SOCKTYPE: /*socket type not supported*/
2016                 case EAI_BADFLAGS: /*ai_flags is invalid*/
2017                 case EAI_NONAME: /*node or service unknown*/
2018                 case EAI_SERVICE: /*service not available*/
2019 /* work around Cygwin IPv6 patch - err codes not defined in Windows aren't in patch */
2020 #ifdef HAVE_EAI_ADDRFAMILY
2021                 case EAI_ADDRFAMILY: /*no addresses in requested family*/
2022 #endif
2023 #ifdef HAVE_EAI_SYSTEM
2024                 case EAI_SYSTEM: /*system error, check errno*/
2025 #endif
2026 #ifdef HAVE_EAI_NODATA
2027                 case EAI_NODATA: /*address exists, but no data*/
2028 #endif
2029                 case EAI_MEMORY: /*out of memory*/
2030                 case EAI_FAIL: /*name server returned permanent error*/
2031                     errbits |= 2;
2032                     break;
2033                 default:
2034                     /* should not happen, all errors are checked above */
2035                     free(hostlist);
2036                     return EX_OSERR;
2037                 }
2038                 goto nexthost; /* try next host in list */
2039             }
2040 #else
2041             if ((hp = gethostbyname(hostname)) == NULL) {
2042                 int origerr = h_errno; /* take a copy before syslog() */
2043                 libspamc_log(flags, LOG_DEBUG, "gethostbyname(%s) failed: h_errno=%d",
2044                     hostname, origerr);
2045                 switch (origerr) {
2046                 case TRY_AGAIN:
2047                     errbits |= 1;
2048                     break;
2049                 case HOST_NOT_FOUND:
2050                 case NO_ADDRESS:
2051                 case NO_RECOVERY:
2052                     errbits |= 2;
2053                     break;
2054                 default:
2055                     /* should not happen, all errors are checked above */
2056                     free(hostlist);
2057                     return EX_OSERR;
2058                 }
2059                 goto nexthost; /* try next host in list */
2060             }
2061 #endif
2062             
2063             /* If we have no hosts at all */
2064 #ifdef SPAMC_HAS_ADDRINFO
2065             if(res == NULL)
2066 #else
2067             if (hp->h_addr_list[0] == NULL
2068              || hp->h_length != sizeof tp->hosts[0]
2069              || hp->h_addrtype != AF_INET)
2070                 /* no hosts/bad size/wrong family */
2071 #endif
2072             {
2073                 errbits |= 1;
2074                 goto nexthost; /* try next host in list */
2075             }
2076
2077             /* Copy all the IP addresses into our private structure.
2078              * This gets them out of the resolver's static area and
2079              * means we won't ever walk all over the list with other
2080              * calls.
2081              */
2082 #ifdef SPAMC_HAS_ADDRINFO
2083             if(tp->nhosts == TRANSPORT_MAX_HOSTS) {
2084                libspamc_log(flags, LOG_NOTICE, 
2085                      "hit limit of %d hosts, ignoring remainder",
2086                      TRANSPORT_MAX_HOSTS);
2087                break;
2088             }
2089
2090             /* treat all A or AAAA records of each host as one entry */
2091             tp->hosts[tp->nhosts++] = res;
2092
2093             /* alternatively, treat multiple A or AAAA records
2094                of one host as individual entries */
2095 /*          for (addrp = res; addrp != NULL; ) {
2096  *              tp->hosts[tp->nhosts] = addrp;
2097  *              addrp = addrp->ai_next;     /-* before NULLing ai_next *-/
2098  *              tp->hosts[tp->nhosts]->ai_next = NULL;
2099  *              tp->nhosts++;
2100  *          }
2101  */
2102
2103 #else
2104             for (addrp = hp->h_addr_list; *addrp; addrp++) {
2105                 if (tp->nhosts == TRANSPORT_MAX_HOSTS) {
2106                     libspamc_log(flags, LOG_NOTICE, "hit limit of %d hosts, ignoring remainder",
2107                         TRANSPORT_MAX_HOSTS);
2108                     break;
2109                 }
2110                 memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length);
2111                 tp->nhosts++;
2112             }
2113 #endif            
2114 nexthost:
2115             hostname = hostend;
2116         } while (hostname != NULL);
2117         free(hostlist);
2118         
2119         if (tp->nhosts == 0) {
2120             if (errbits & 1) {
2121                 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): a temporary error occurred",
2122                     tp->hostname); 
2123                 return EX_TEMPFAIL;
2124             }
2125             else {
2126                 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): no such host",
2127                     tp->hostname); 
2128                 return EX_NOHOST;
2129             }
2130         }
2131         
2132         /* QUASI-LOAD-BALANCING
2133          *
2134          * If the user wants to do quasi load balancing, "rotate"
2135          * the list by a random amount based on the current time.
2136          * This may later be truncated to a single item. This is
2137          * meaningful only if we have more than one host.
2138          */
2139
2140         if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
2141             _randomize_hosts(tp);
2142         }
2143
2144         /* If the user wants no fallback, simply truncate the host
2145          * list to just one - this pretends that this is the extent
2146          * of our connection list - then it's not a special case.
2147          */
2148         if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
2149             /* truncating list */
2150             tp->nhosts = 1;
2151         }
2152         
2153         return EX_OK;
2154     }
2155     
2156     /* oops, unknown transport type */
2157     return EX_OSERR;
2158 }
2159
2160 /*
2161 * transport_cleanup()
2162 *
2163 *       Given a "transport" object that says how we're to connect to the
2164 *       spam daemon, delete and free any buffers allocated so that it
2165 *       can be discarded without causing a memory leak.
2166 */
2167 void transport_cleanup(struct transport *tp)
2168 {
2169
2170 #ifdef SPAMC_HAS_ADDRINFO
2171   int i;
2172
2173   for(i=0;i<tp->nhosts;i++) {
2174       if (tp->hosts[i] != NULL) {
2175           freeaddrinfo(tp->hosts[i]);
2176           tp->hosts[i] = NULL;
2177       }
2178   }
2179 #endif
2180
2181 }
2182
2183 /*
2184 * register_libspamc_log_callback()
2185 *
2186 * Register a callback handler for libspamc_log to replace the default behaviour.
2187 */
2188
2189 void register_libspamc_log_callback(void (*function)(int flags, int level, char *msg, va_list args)) {
2190   libspamc_log_callback = function;
2191 }
2192
2193 /*
2194 * register_spamc_header_callback()
2195 *
2196 * Register a callback handler to generate spamc headers for a given message
2197 */
2198
2199 void register_spamc_header_callback(const struct message *m, void (*func)(struct message *m, int flags, char *buf, int len)) {
2200   m->priv->spamc_header_callback = func;
2201 }
2202
2203 /*
2204 * register_spamd_header_callback()
2205 *
2206 * Register a callback handler to generate spamd headers for a given message
2207 */
2208
2209 void register_spamd_header_callback(const struct message *m, void (*func)(struct message *m, int flags, const char *buf, int len)) {
2210   m->priv->spamd_header_callback = func;
2211 }
2212
2213 /* --------------------------------------------------------------------------- */
2214
2215 #define LOG_BUFSIZ      1023
2216
2217 void
2218 libspamc_log (int flags, int level, char *msg, ...)
2219 {
2220     va_list ap;
2221     char buf[LOG_BUFSIZ+1];
2222     int len = 0;
2223
2224     va_start(ap, msg);
2225
2226     if ((flags & SPAMC_LOG_TO_CALLBACK) != 0 && libspamc_log_callback != NULL) {
2227       libspamc_log_callback(flags, level, msg, ap);
2228     }
2229     else if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
2230         /* create a log-line buffer */
2231         len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
2232         len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
2233
2234         /* avoid buffer overflow */
2235         if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
2236
2237         len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
2238         buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
2239         (void) write (2, buf, len);
2240
2241     } else {
2242         vsnprintf(buf, LOG_BUFSIZ, msg, ap);
2243         buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
2244 #ifndef _WIN32
2245         syslog (level, "%s", buf);
2246 #else
2247         (void) level;  /* not used. suppress compiler warning */
2248         f_printerr ("%s\n", buf);
2249 #endif
2250     }
2251
2252     va_end(ap);
2253 }
2254
2255 /* --------------------------------------------------------------------------- */
2256
2257 /*
2258 * Unit tests.  Must be built externally, e.g.:
2259 *
2260 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
2261 * ./libspamctest
2262 *
2263 */
2264 #ifdef LIBSPAMC_UNIT_TESTS
2265
2266 static void _test_locale_safe_string_to_float_val(float input)
2267 {
2268     char inputstr[99], cmpbuf1[99], cmpbuf2[99];
2269     float output;
2270
2271     /* sprintf instead of snprintf is safe here because it is only a controlled test */
2272     sprintf(inputstr, "%f", input);
2273     output = _locale_safe_string_to_float(inputstr, 99);
2274     if (input == output) {
2275         return;
2276     }
2277
2278     /* could be a rounding error.  print as string and compare those */
2279     sprintf(cmpbuf1, "%f", input);
2280     sprintf(cmpbuf2, "%f", output);
2281     if (!strcmp(cmpbuf1, cmpbuf2)) {
2282         return;
2283     }
2284
2285     printf("FAIL: input=%f != output=%f\n", input, output);
2286 }
2287
2288 static void unit_test_locale_safe_string_to_float(void)
2289 {
2290     float statictestset[] = {   /* will try both +ve and -ve */
2291         0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
2292         9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
2293         0.0                     /* end of set constant */
2294     };
2295     float num;
2296     int i;
2297
2298     printf("starting unit_test_locale_safe_string_to_float\n");
2299     /* tests of precision */
2300     for (i = 0; statictestset[i] != 0.0; i++) {
2301         _test_locale_safe_string_to_float_val(statictestset[i]);
2302         _test_locale_safe_string_to_float_val(-statictestset[i]);
2303         _test_locale_safe_string_to_float_val(1 - statictestset[i]);
2304         _test_locale_safe_string_to_float_val(1 + statictestset[i]);
2305     }
2306     /* now exhaustive, in steps of 0.01 */
2307     for (num = -1000.0; num < 1000.0; num += 0.01) {
2308         _test_locale_safe_string_to_float_val(num);
2309     }
2310     printf("finished unit_test_locale_safe_string_to_float\n");
2311 }
2312
2313 void do_libspamc_unit_tests(void)
2314 {
2315     unit_test_locale_safe_string_to_float();
2316     exit(0);
2317 }
2318
2319 #endif /* LIBSPAMC_UNIT_TESTS */