2005-12-02 [paul] 1.9.100cvs55
[claws.git] / src / plugins / spamassassin / libspamc.c
1 /* <@LICENSE>
2  * Copyright 2004 Apache Software Foundation
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  * </@LICENSE>
16  */
17
18 #include "config.h"
19 #include "libspamc.h"
20 #include "utils.h"
21
22 #include <stdlib.h>
23 #include <assert.h>
24 #include <stdio.h>
25 #include <string.h>
26 #ifdef _WIN32
27 #define snprintf _snprintf
28 #define vsnprintf _vsnprintf
29 #define strcasecmp stricmp
30 #define sleep Sleep
31 #include <io.h>
32 #else
33 #include <syslog.h>
34 #include <unistd.h>
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <sys/un.h>
39 #include <netinet/tcp.h>
40 #include <arpa/inet.h>
41 #define closesocket(x) close(x)
42 #endif
43
44 #ifdef HAVE_SYSEXITS_H
45 #include <sysexits.h>
46 #endif
47 #ifdef HAVE_ERRNO_H
48 #include <errno.h>
49 #endif
50 #ifdef HAVE_SYS_ERRNO_H
51 #include <sys/errno.h>
52 #endif
53 #ifdef HAVE_TIME_H
54 #include <time.h>
55 #endif
56 #ifdef HAVE_SYS_TIME_H
57 #include <sys/time.h>
58 #endif
59
60 #define MAX_CONNECT_RETRIES 3
61 #define CONNECT_RETRY_SLEEP 1
62
63 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
64 /* KAM 12-4-01 */
65 /* SJF 2003/04/25 - now test for macros directly */
66 #ifndef SHUT_RD
67 #  define SHUT_RD 0             /* no more receptions */
68 #endif
69 #ifndef SHUT_WR
70 #  define SHUT_WR 1             /* no more transmissions */
71 #endif
72 #ifndef SHUT_RDWR
73 #  define SHUT_RDWR 2           /* no more receptions or transmissions */
74 #endif
75
76 #ifndef HAVE_H_ERRNO
77 #define h_errno errno
78 #endif
79
80 #ifndef HAVE_OPTARG
81 extern char *optarg;
82 #endif
83
84 #ifndef HAVE_INADDR_NONE
85 #define INADDR_NONE             ((in_addr_t) 0xffffffff)
86 #endif
87
88 /* jm: turned off for now, it should not be necessary. */
89 #undef USE_TCP_NODELAY
90
91 #ifndef HAVE_EX__MAX
92 /* jm: very conservative figure, should be well out of range on almost all NIXes */
93 #define EX__MAX 200
94 #endif
95
96 #undef DO_CONNECT_DEBUG_SYSLOGS
97 /* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
98
99 static const int ESC_PASSTHROUGHRAW = EX__MAX + 666;
100
101 /* set EXPANSION_ALLOWANCE to something more than might be
102    added to a message in X-headers and the report template */
103 static const int EXPANSION_ALLOWANCE = 16384;
104
105 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
106    of the data streams before and after processing by spamd 
107    Aug  7 2002 jm: no longer seems to be used
108    static const int NUM_CHECK_BYTES = 32;
109  */
110
111 /* Set the protocol version that this spamc speaks */
112 static const char *PROTOCOL_VERSION = "SPAMC/1.3";
113
114 /* "private" part of struct message.
115  * we use this instead of the struct message directly, so that we
116  * can add new members without affecting the ABI.
117  */
118 struct libspamc_private_message
119 {
120     int flags;                  /* copied from "flags" arg to message_read() */
121 };
122
123 int libspamc_timeout = 0;
124
125 /*
126  * translate_connect_errno()
127  *
128  *      Given a UNIX error number obtained (probably) from "connect(2)",
129  *      translate this to a failure code. This module is shared by both
130  *      transport modules - UNIX and TCP.
131  *
132  *      This should ONLY be called when there is an error.
133  */
134 static int _translate_connect_errno(int err)
135 {
136     switch (err) {
137     case EBADF:
138     case EFAULT:
139     case ENOTSOCK:
140     case EISCONN:
141     case EADDRINUSE:
142     case EINPROGRESS:
143     case EALREADY:
144     case EAFNOSUPPORT:
145         return EX_SOFTWARE;
146
147     case ECONNREFUSED:
148     case ETIMEDOUT:
149     case ENETUNREACH:
150         return EX_UNAVAILABLE;
151
152     case EACCES:
153         return EX_NOPERM;
154
155     default:
156         return EX_SOFTWARE;
157     }
158 }
159
160 /*
161  * opensocket()
162  *
163  *      Given a socket type (PF_INET or PF_UNIX), try to create this socket
164  *      and store the FD in the pointed-to place. If it's successful, do any
165  *      other setup required to make the socket ready to use, such as setting
166  *      TCP_NODELAY mode, and in any case we return EX_OK if all is well.
167  *
168  *      Upon failure we return one of the other EX_??? error codes.
169  */
170 static int _opensocket(int flags, int type, int *psock)
171 {
172     const char *typename;
173     int proto = 0;
174
175     assert(psock != 0);
176
177         /*----------------------------------------------------------------
178          * Create a few induction variables that are implied by the socket
179          * type given by the user. The typename is strictly used for debug
180          * reporting.
181          */
182     if (type == PF_UNIX) {
183         typename = "PF_UNIX";
184     }
185     else {
186         typename = "PF_INET";
187         proto = IPPROTO_TCP;
188     }
189
190 #ifdef DO_CONNECT_DEBUG_SYSLOGS
191     libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
192 #endif
193
194     if ((*psock = socket(type, SOCK_STREAM, proto))
195 #ifndef _WIN32
196         < 0
197 #else
198         == INVALID_SOCKET
199 #endif
200         ) {
201         int origerr;
202
203                 /*--------------------------------------------------------
204                  * At this point we had a failure creating the socket, and
205                  * this is pretty much fatal. Translate the error reason
206                  * into something the user can understand.
207                  */
208 #ifndef _WIN32
209         origerr = errno;        /* take a copy before syslog() */
210         libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
211 #else
212         origerr = WSAGetLastError();
213         libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
214 #endif
215
216         switch (origerr) {
217         case EPROTONOSUPPORT:
218         case EINVAL:
219             return EX_SOFTWARE;
220
221         case EACCES:
222             return EX_NOPERM;
223
224         case ENFILE:
225         case EMFILE:
226         case ENOBUFS:
227         case ENOMEM:
228             return EX_OSERR;
229
230         default:
231             return EX_SOFTWARE;
232         }
233     }
234
235
236         /*----------------------------------------------------------------
237          * Do a bit of setup on the TCP socket if required. Notes above
238          * suggest this is probably not set
239          */
240 #ifdef USE_TCP_NODELAY
241     {
242         int one = 1;
243
244         if (type == PF_INET
245             && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
246             int origerrno;
247 #ifndef _WIN32
248             origerr = errno;
249 #else
250             origerrno = WSAGetLastError();
251 #endif
252             switch (origerr) {
253             case EBADF:
254             case ENOTSOCK:
255             case ENOPROTOOPT:
256             case EFAULT:
257                 libspamc_log(flags, LOG_ERR,
258 #ifndef _WIN32
259                        "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
260 #else
261                        "setsockopt(TCP_NODELAY) failed: %d", origerr);
262 #endif
263                 closesocket(*psock);
264                 return EX_SOFTWARE;
265
266             default:
267                 break;          /* ignored */
268             }
269         }
270     }
271 #endif /* USE_TCP_NODELAY */
272
273     return EX_OK;               /* all is well */
274 }
275
276 /*
277  * try_to_connect_unix()
278  *
279  *      Given a transport handle that implies using a UNIX domain
280  *      socket, try to make a connection to it and store the resulting
281  *      file descriptor in *sockptr. Return is EX_OK if we did it,
282  *      and some other error code otherwise.
283  */
284 static int _try_to_connect_unix(struct transport *tp, int *sockptr)
285 {
286 #ifndef _WIN32
287     int mysock, status, origerr;
288     struct sockaddr_un addrbuf;
289     int ret;
290
291     assert(tp != 0);
292     assert(sockptr != 0);
293     assert(tp->socketpath != 0);
294
295         /*----------------------------------------------------------------
296          * If the socket itself can't be created, this is a fatal error.
297          */
298     if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
299         return ret;
300
301     /* set up the UNIX domain socket */
302     memset(&addrbuf, 0, sizeof addrbuf);
303     addrbuf.sun_family = AF_UNIX;
304     strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
305     addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
306
307 #ifdef DO_CONNECT_DEBUG_SYSLOGS
308     libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
309            addrbuf.sun_path);
310 #endif
311
312     status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
313
314     origerr = errno;
315
316     if (status >= 0) {
317 #ifdef DO_CONNECT_DEBUG_SYSLOGS
318         libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
319 #endif
320
321         *sockptr = mysock;
322
323         return EX_OK;
324     }
325
326     libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
327            addrbuf.sun_path, strerror(origerr));
328     closesocket(mysock);
329
330     return _translate_connect_errno(origerr);
331 #else
332     (void) tp; /* not used. suppress compiler warning */
333     (void) sockptr; /* not used. suppress compiler warning */
334     return EX_OSERR;
335 #endif
336 }
337
338 /*
339  * try_to_connect_tcp()
340  *
341  *      Given a transport that implies a TCP connection, either to
342  *      localhost or a list of IP addresses, attempt to connect. The
343  *      list of IP addresses has already been randomized (if requested)
344  *      and limited to just one if fallback has been enabled.
345  */
346 static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
347 {
348     int numloops;
349     int origerr = 0;
350     int ret;
351
352     assert(tp != 0);
353     assert(sockptr != 0);
354     assert(tp->nhosts > 0);
355
356 #ifdef DO_CONNECT_DEBUG_SYSLOGS
357     for (numloops = 0; numloops < tp->nhosts; numloops++) {
358         libspamc_log(tp->flags, LOG_ERR, "dbg: %d/%d: %s",
359                 numloops + 1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
360     }
361 #endif
362
363     for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
364         struct sockaddr_in addrbuf;
365         const int hostix = numloops % tp->nhosts;
366         int status, mysock;
367         const char *ipaddr;
368
369                 /*--------------------------------------------------------
370                  * We always start by creating the socket, as we get only
371                  * one attempt to connect() on each one. If this fails,
372                  * we're done.
373                  */
374         if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
375             return ret;
376
377         memset(&addrbuf, 0, sizeof(addrbuf));
378
379         addrbuf.sin_family = AF_INET;
380         addrbuf.sin_port = htons(tp->port);
381         addrbuf.sin_addr = tp->hosts[hostix];
382
383         ipaddr = inet_ntoa(addrbuf.sin_addr);
384
385 #ifdef DO_CONNECT_DEBUG_SYSLOGS
386         libspamc_log(tp->flags, DEBUG_LEVEL,
387                "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
388                 ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
389 #endif
390
391         status =
392             connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
393
394         if (status != 0) {
395 #ifndef _WIN32
396             origerr = errno;
397             libspamc_log(tp->flags, LOG_ERR,
398                    "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
399                    ipaddr, numloops + 1, MAX_CONNECT_RETRIES, strerror(origerr));
400 #else
401             origerr = WSAGetLastError();
402             libspamc_log(tp->flags, LOG_ERR,
403                    "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
404                    ipaddr, numloops + 1, MAX_CONNECT_RETRIES, origerr);
405 #endif
406             closesocket(mysock);
407
408             sleep(CONNECT_RETRY_SLEEP);
409         }
410         else {
411 #ifdef DO_CONNECT_DEBUG_SYSLOGS
412             libspamc_log(tp->flags, DEBUG_LEVEL,
413                    "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
414 #endif
415             *sockptr = mysock;
416
417             return EX_OK;
418         }
419     }
420
421     libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
422             MAX_CONNECT_RETRIES);
423
424     return _translate_connect_errno(origerr);
425 }
426
427 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
428  * message_dump, lookup_host, message_filter, and message_process, and a bunch
429  * of helper functions.
430  */
431
432 static void _clear_message(struct message *m)
433 {
434     m->type = MESSAGE_NONE;
435     m->raw = NULL;
436     m->raw_len = 0;
437     m->pre = NULL;
438     m->pre_len = 0;
439     m->msg = NULL;
440     m->msg_len = 0;
441     m->post = NULL;
442     m->post_len = 0;
443     m->is_spam = EX_TOOBIG;
444     m->score = 0.0;
445     m->threshold = 0.0;
446     m->out = NULL;
447     m->out_len = 0;
448     m->content_length = -1;
449 }
450
451 static int _message_read_raw(int fd, struct message *m)
452 {
453     _clear_message(m);
454     if ((m->raw = malloc(m->max_len + 1)) == NULL)
455         return EX_OSERR;
456     m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
457     if (m->raw_len <= 0) {
458         free(m->raw);
459         m->raw = NULL;
460         m->raw_len = 0;
461         return EX_IOERR;
462     }
463     m->type = MESSAGE_ERROR;
464     if (m->raw_len > m->max_len)
465         return EX_TOOBIG;
466     m->type = MESSAGE_RAW;
467     m->msg = m->raw;
468     m->msg_len = m->raw_len;
469     m->out = m->msg;
470     m->out_len = m->msg_len;
471     return EX_OK;
472 }
473
474 static int _message_read_bsmtp(int fd, struct message *m)
475 {
476     unsigned int i, j;
477     char prev;
478
479     _clear_message(m);
480     if ((m->raw = malloc(m->max_len + 1)) == NULL)
481         return EX_OSERR;
482
483     /* Find the DATA line */
484     m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
485     if (m->raw_len <= 0) {
486         free(m->raw);
487         m->raw = NULL;
488         m->raw_len = 0;
489         return EX_IOERR;
490     }
491     m->type = MESSAGE_ERROR;
492     if (m->raw_len > m->max_len)
493         return EX_TOOBIG;
494     m->pre = m->raw;
495     for (i = 0; i < m->raw_len - 6; i++) {
496         if ((m->raw[i] == '\n') &&
497             (m->raw[i + 1] == 'D' || m->raw[i + 1] == 'd') &&
498             (m->raw[i + 2] == 'A' || m->raw[i + 2] == 'a') &&
499             (m->raw[i + 3] == 'T' || m->raw[i + 3] == 't') &&
500             (m->raw[i + 4] == 'A' || m->raw[i + 4] == 'a') &&
501             ((m->raw[i + 5] == '\r' && m->raw[i + 6] == '\n')
502              || m->raw[i + 5] == '\n')) {
503             /* Found it! */
504             i += 6;
505             if (m->raw[i - 1] == '\r')
506                 i++;
507             m->pre_len = i;
508             m->msg = m->raw + i;
509             m->msg_len = m->raw_len - i;
510             break;
511         }
512     }
513     if (m->msg == NULL)
514         return EX_DATAERR;
515
516     /* Find the end-of-DATA line */
517     prev = '\n';
518     for (i = j = 0; i < m->msg_len; i++) {
519         if (prev == '\n' && m->msg[i] == '.') {
520             /* Dot at the beginning of a line */
521             if ((m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')
522                 || m->msg[i + 1] == '\n') {
523                 /* Lone dot! That's all, folks */
524                 m->post = m->msg + i;
525                 m->post_len = m->msg_len - i;
526                 m->msg_len = j;
527                 break;
528             }
529             else if (m->msg[i + 1] == '.') {
530                 /* Escaping dot, eliminate. */
531                 prev = '.';
532                 continue;
533             }                   /* Else an ordinary dot, drop down to ordinary char handler */
534         }
535         prev = m->msg[i];
536         m->msg[j++] = m->msg[i];
537     }
538
539     m->type = MESSAGE_BSMTP;
540     m->out = m->msg;
541     m->out_len = m->msg_len;
542     return EX_OK;
543 }
544
545 int message_read(int fd, int flags, struct message *m)
546 {
547     libspamc_timeout = 0;
548
549     /* create the "private" part of the struct message */
550     m->priv = malloc(sizeof(struct libspamc_private_message));
551     if (m->priv == NULL) {
552         libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
553         return EX_OSERR;
554     }
555     m->priv->flags = flags;
556
557     switch (flags & SPAMC_MODE_MASK) {
558     case SPAMC_RAW_MODE:
559         return _message_read_raw(fd, m);
560
561     case SPAMC_BSMTP_MODE:
562         return _message_read_bsmtp(fd, m);
563
564     default:
565         libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
566                 flags & SPAMC_MODE_MASK);
567         return EX_USAGE;
568     }
569 }
570
571 long message_write(int fd, struct message *m)
572 {
573     long total = 0;
574     off_t i, j;
575     off_t jlimit;
576     char buffer[1024];
577
578     if (m->priv->flags & SPAMC_CHECK_ONLY) {
579         if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
580             return full_write(fd, 1, m->out, m->out_len);
581
582         }
583         else {
584             libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
585                         m->is_spam);
586             return -1;
587         }
588     }
589
590     /* else we're not in CHECK_ONLY mode */
591     switch (m->type) {
592     case MESSAGE_NONE:
593         libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
594         return -1;
595
596     case MESSAGE_ERROR:
597         return full_write(fd, 1, m->raw, m->raw_len);
598
599     case MESSAGE_RAW:
600         return full_write(fd, 1, m->out, m->out_len);
601
602     case MESSAGE_BSMTP:
603         total = full_write(fd, 1, m->pre, m->pre_len);
604         for (i = 0; i < m->out_len;) {
605             jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
606             for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
607                 if (i + 1 < m->out_len && m->out[i] == '\n'
608                     && m->out[i + 1] == '.') {
609                     if (j > jlimit - 4) {
610                         break;  /* avoid overflow */
611                     }
612                     buffer[j++] = m->out[i++];
613                     buffer[j++] = m->out[i++];
614                     buffer[j++] = '.';
615                 }
616                 else {
617                     buffer[j++] = m->out[i++];
618                 }
619             }
620             total += full_write(fd, 1, buffer, j);
621         }
622         return total + full_write(fd, 1, m->post, m->post_len);
623
624     default:
625         libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
626         return -1;
627     }
628 }
629
630 void message_dump(int in_fd, int out_fd, struct message *m)
631 {
632     char buf[8196];
633     int bytes;
634
635     if (m != NULL && m->type != MESSAGE_NONE) {
636         message_write(out_fd, m);
637     }
638     while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
639         if (bytes != full_write(out_fd, 1, buf, bytes)) {
640             libspamc_log(m->priv->flags, LOG_ERR, "oops! message_dump of %d returned different",
641                    bytes);
642         }
643     }
644 }
645
646 static int
647 _spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
648                       char *buf, size_t *lenp, size_t bufsiz)
649 {
650     int failureval;
651     int bytesread = 0;
652     size_t len;
653
654     UNUSED_VARIABLE(m);
655
656     *lenp = 0;
657     /* Now, read from spamd */
658     for (len = 0; len < bufsiz - 1; len++) {
659         if (flags & SPAMC_USE_SSL) {
660             bytesread = ssl_timeout_read(ssl, buf + len, 1);
661         }
662         else {
663             bytesread = fd_timeout_read(sock, 0, buf + len, 1);
664         }
665
666         if (bytesread <= 0) {
667             failureval = EX_IOERR;
668             goto failure;
669         }
670
671         if (buf[len] == '\n') {
672             buf[len] = '\0';
673             if (len > 0 && buf[len - 1] == '\r') {
674                 len--;
675                 buf[len] = '\0';
676             }
677             *lenp = len;
678             return EX_OK;
679         }
680     }
681
682     libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
683     failureval = EX_TOOBIG;
684
685   failure:
686     return failureval;
687 }
688
689 /*
690  * May  7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
691  * work around using our own locale-independent float-parser code.
692  */
693 static float _locale_safe_string_to_float(char *buf, int siz)
694 {
695     int is_neg;
696     char *cp, *dot;
697     int divider;
698     float ret, postdot;
699
700     buf[siz - 1] = '\0';        /* ensure termination */
701
702     /* ok, let's illustrate using "100.033" as an example... */
703
704     is_neg = 0;
705     if (*buf == '-') {
706         is_neg = 1;
707     }
708
709     ret = (float) (strtol(buf, &dot, 10));
710     if (dot == NULL) {
711         return 0.0;
712     }
713     if (dot != NULL && *dot != '.') {
714         return ret;
715     }
716
717     /* ex: ret == 100.0 */
718
719     cp = (dot + 1);
720     postdot = (float) (strtol(cp, NULL, 10));
721     if (postdot == 0.0) {
722         return ret;
723     }
724
725     /* ex: postdot == 33.0, cp="033" */
726
727     /* now count the number of decimal places and figure out what power of 10 to use */
728     divider = 1;
729     while (*cp != '\0') {
730         divider *= 10;
731         cp++;
732     }
733
734     /* ex:
735      * cp="033", divider=1
736      * cp="33", divider=10
737      * cp="3", divider=100
738      * cp="", divider=1000
739      */
740
741     if (is_neg) {
742         ret -= (postdot / ((float) divider));
743     }
744     else {
745         ret += (postdot / ((float) divider));
746     }
747     /* ex: ret == 100.033, tada! ... hopefully */
748
749     return ret;
750 }
751
752 static int
753 _handle_spamd_header(struct message *m, int flags, char *buf, int len)
754 {
755     char is_spam[6];
756     char s_str[21], t_str[21];
757
758     UNUSED_VARIABLE(len);
759
760     /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
761      * let's stick with it for this parser.
762      * May  7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
763      * work around using our own locale-independent float-parser code.
764      */
765     if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
766         m->score = _locale_safe_string_to_float(s_str, 20);
767         m->threshold = _locale_safe_string_to_float(t_str, 20);
768
769         /* set bounds on these to ensure no buffer overflow in the sprintf */
770         if (m->score > 1e10)
771             m->score = 1e10;
772         else if (m->score < -1e10)
773             m->score = -1e10;
774         if (m->threshold > 1e10)
775             m->threshold = 1e10;
776         else if (m->threshold < -1e10)
777             m->threshold = -1e10;
778
779         /* Format is "Spam: x; y / x" */
780         m->is_spam =
781             strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
782
783         if (flags & SPAMC_CHECK_ONLY) {
784             m->out_len = sprintf(m->out,
785                                  "%.1f/%.1f\n", m->score, m->threshold);
786         }
787         else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
788                  || (flags & SPAMC_REPORT)) {
789             m->out_len = sprintf(m->out,
790                                  "%.1f/%.1f\n", m->score, m->threshold);
791         }
792         return EX_OK;
793
794     }
795     else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
796         if (m->content_length < 0) {
797             libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
798                    buf);
799             return EX_PROTOCOL;
800         }
801         return EX_OK;
802     }
803
804     libspamc_log(flags, LOG_ERR, "spamd responded with bad header '%s'", buf);
805     return EX_PROTOCOL;
806 }
807
808 int message_filter(struct transport *tp, const char *username,
809                    int flags, struct message *m)
810 {
811     char buf[8192];
812     size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
813     size_t len;
814     int sock = -1;
815     int rc;
816     char versbuf[20];
817     float version;
818     int response;
819     int failureval;
820     SSL_CTX *ctx = NULL;
821     SSL *ssl = NULL;
822     SSL_METHOD *meth;
823
824     if (flags & SPAMC_USE_SSL) {
825 #ifdef SPAMC_SSL
826         SSLeay_add_ssl_algorithms();
827         meth = SSLv2_client_method();
828         SSL_load_error_strings();
829         ctx = SSL_CTX_new(meth);
830 #else
831         UNUSED_VARIABLE(ssl);
832         UNUSED_VARIABLE(meth);
833         UNUSED_VARIABLE(ctx);
834         libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
835         return EX_SOFTWARE;
836 #endif
837     }
838
839     m->is_spam = EX_TOOBIG;
840     if ((m->out = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
841         failureval = EX_OSERR;
842         goto failure;
843     }
844     m->out_len = 0;
845
846
847     /* Build spamd protocol header */
848     if (flags & SPAMC_CHECK_ONLY)
849         strcpy(buf, "CHECK ");
850     else if (flags & SPAMC_REPORT_IFSPAM)
851         strcpy(buf, "REPORT_IFSPAM ");
852     else if (flags & SPAMC_REPORT)
853         strcpy(buf, "REPORT ");
854     else if (flags & SPAMC_SYMBOLS)
855         strcpy(buf, "SYMBOLS ");
856     else
857         strcpy(buf, "PROCESS ");
858
859     len = strlen(buf);
860     if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
861         free(m->out);
862         m->out = m->msg;
863         m->out_len = m->msg_len;
864         return EX_OSERR;
865     }
866
867     strcat(buf, PROTOCOL_VERSION);
868     strcat(buf, "\r\n");
869     len = strlen(buf);
870
871     if (username != NULL) {
872         if (strlen(username) + 8 >= (bufsiz - len)) {
873             free(m->out);
874             m->out = m->msg;
875             m->out_len = m->msg_len;
876             return EX_OSERR;
877         }
878         strcpy(buf + len, "User: ");
879         strcat(buf + len, username);
880         strcat(buf + len, "\r\n");
881         len += strlen(buf + len);
882     }
883     if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
884         free(m->out);
885         m->out = m->msg;
886         m->out_len = m->msg_len;
887         return EX_OSERR;
888     }
889     len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
890
891     libspamc_timeout = m->timeout;
892
893     if (tp->socketpath)
894         rc = _try_to_connect_unix(tp, &sock);
895     else
896         rc = _try_to_connect_tcp(tp, &sock);
897
898     if (rc != EX_OK) {
899         free(m->out);
900         m->out = m->msg;
901         m->out_len = m->msg_len;
902         return rc;      /* use the error code try_to_connect_*() gave us. */
903     }
904
905     if (flags & SPAMC_USE_SSL) {
906 #ifdef SPAMC_SSL
907         ssl = SSL_new(ctx);
908         SSL_set_fd(ssl, sock);
909         SSL_connect(ssl);
910 #endif
911     }
912
913     /* Send to spamd */
914     if (flags & SPAMC_USE_SSL) {
915 #ifdef SPAMC_SSL
916         SSL_write(ssl, buf, len);
917         SSL_write(ssl, m->msg, m->msg_len);
918 #endif
919     }
920     else {
921         full_write(sock, 0, buf, len);
922         full_write(sock, 0, m->msg, m->msg_len);
923         shutdown(sock, SHUT_WR);
924     }
925
926     /* ok, now read and parse it.  SPAMD/1.2 line first... */
927     failureval =
928         _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
929     if (failureval != EX_OK) {
930         goto failure;
931     }
932
933     if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
934         libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
935         failureval = EX_PROTOCOL;
936         goto failure;
937     }
938
939     versbuf[19] = '\0';
940     version = _locale_safe_string_to_float(versbuf, 20);
941     if (version < 1.0) {
942         libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
943                versbuf);
944         failureval = EX_PROTOCOL;
945         goto failure;
946     }
947
948     m->score = 0;
949     m->threshold = 0;
950     m->is_spam = EX_TOOBIG;
951     while (1) {
952         failureval =
953             _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
954         if (failureval != EX_OK) {
955             goto failure;
956         }
957
958         if (len == 0 && buf[0] == '\0') {
959             break;              /* end of headers */
960         }
961
962         if (_handle_spamd_header(m, flags, buf, len) < 0) {
963             failureval = EX_PROTOCOL;
964             goto failure;
965         }
966     }
967
968     len = 0;                    /* overwrite those headers */
969
970     if (flags & SPAMC_CHECK_ONLY) {
971         closesocket(sock);
972         sock = -1;
973         if (m->is_spam == EX_TOOBIG) {
974             /* We should have gotten headers back... Damnit. */
975             failureval = EX_PROTOCOL;
976             goto failure;
977         }
978         return EX_OK;
979     }
980     else {
981         if (m->content_length < 0) {
982             /* should have got a length too. */
983             failureval = EX_PROTOCOL;
984             goto failure;
985         }
986
987         /* have we already got something in the buffer (e.g. REPORT and
988          * REPORT_IFSPAM both create a line from the "Spam:" hdr)?  If
989          * so, add the size of that so our sanity check passes.
990          */
991         if (m->out_len > 0) {
992             m->content_length += m->out_len;
993         }
994
995         if (flags & SPAMC_USE_SSL) {
996             len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
997                                 m->max_len + EXPANSION_ALLOWANCE + 1 -
998                                 m->out_len,
999                                 m->max_len + EXPANSION_ALLOWANCE + 1 -
1000                                 m->out_len);
1001         }
1002         else {
1003             len = full_read(sock, 0, m->out + m->out_len,
1004                             m->max_len + EXPANSION_ALLOWANCE + 1 - m->out_len,
1005                             m->max_len + EXPANSION_ALLOWANCE + 1 -
1006                             m->out_len);
1007         }
1008
1009
1010         if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
1011             failureval = EX_TOOBIG;
1012             goto failure;
1013         }
1014         m->out_len += len;
1015
1016         shutdown(sock, SHUT_RD);
1017         closesocket(sock);
1018         sock = -1;
1019     }
1020     libspamc_timeout = 0;
1021
1022     if (m->out_len != m->content_length) {
1023         libspamc_log(flags, LOG_ERR,
1024                "failed sanity check, %d bytes claimed, %d bytes seen",
1025                m->content_length, m->out_len);
1026         failureval = EX_PROTOCOL;
1027         goto failure;
1028     }
1029
1030     return EX_OK;
1031
1032   failure:
1033     free(m->out);
1034     m->out = m->msg;
1035     m->out_len = m->msg_len;
1036     if (sock != -1) {
1037         closesocket(sock);
1038     }
1039     libspamc_timeout = 0;
1040
1041     if (flags & SPAMC_USE_SSL) {
1042 #ifdef SPAMC_SSL
1043         SSL_free(ssl);
1044         SSL_CTX_free(ctx);
1045 #endif
1046     }
1047     return failureval;
1048 }
1049
1050
1051 int message_process(struct transport *trans, char *username, int max_size,
1052                     int in_fd, int out_fd, const int flags)
1053 {
1054     int ret;
1055     struct message m;
1056
1057     m.type = MESSAGE_NONE;
1058
1059     m.max_len = max_size;
1060     ret = message_read(in_fd, flags, &m);
1061     if (ret != EX_OK)
1062         goto FAIL;
1063     ret = message_filter(trans, username, flags, &m);
1064     if (ret != EX_OK)
1065         goto FAIL;
1066     if (message_write(out_fd, &m) < 0)
1067         goto FAIL;
1068     if (m.is_spam != EX_TOOBIG) {
1069         message_cleanup(&m);
1070         return m.is_spam;
1071     }
1072     message_cleanup(&m);
1073     return ret;
1074
1075   FAIL:
1076     if (flags & SPAMC_CHECK_ONLY) {
1077         full_write(out_fd, 1, "0/0\n", 4);
1078         message_cleanup(&m);
1079         return EX_NOTSPAM;
1080     }
1081     else {
1082         message_dump(in_fd, out_fd, &m);
1083         message_cleanup(&m);
1084         return ret;
1085     }
1086 }
1087
1088 void message_cleanup(struct message *m)
1089 {
1090     if (m->out != NULL)
1091         free(m->out);
1092     if (m->raw != NULL && m->raw != m->out)
1093         free(m->raw);
1094     if (m->priv != NULL && (char*)m->priv != m->out && (char*)m->priv != m->raw)
1095         free(m->priv);
1096     _clear_message(m);
1097 }
1098
1099 /* Aug 14, 2002 bj: Obsolete! */
1100 int process_message(struct transport *tp, char *username, int max_size,
1101                     int in_fd, int out_fd, const int my_check_only,
1102                     const int my_safe_fallback)
1103 {
1104     int flags;
1105
1106     flags = SPAMC_RAW_MODE;
1107     if (my_check_only)
1108         flags |= SPAMC_CHECK_ONLY;
1109     if (my_safe_fallback)
1110         flags |= SPAMC_SAFE_FALLBACK;
1111
1112     return message_process(tp, username, max_size, in_fd, out_fd, flags);
1113 }
1114
1115 /*
1116  * init_transport()
1117  *
1118  *      Given a pointer to a transport structure, set it to "all empty".
1119  *      The default is a localhost connection.
1120  */
1121 void transport_init(struct transport *tp)
1122 {
1123     assert(tp != 0);
1124
1125     memset(tp, 0, sizeof *tp);
1126
1127     tp->type = TRANSPORT_LOCALHOST;
1128     tp->port = 783;
1129     tp->flags = 0;
1130 }
1131
1132 /*
1133  * randomize_hosts()
1134  *
1135  *      Given the transport object that contains one or more IP addresses
1136  *      in this "hosts" list, rotate it by a random number of shifts to
1137  *      randomize them - this is a kind of load balancing. It's possible
1138  *      that the random number will be 0, which says not to touch. We don't
1139  *      do anything unless 
1140  */
1141
1142 static void _randomize_hosts(struct transport *tp)
1143 {
1144     int rnum;
1145
1146     assert(tp != 0);
1147
1148     if (tp->nhosts <= 1)
1149         return;
1150
1151     rnum = rand() % tp->nhosts;
1152
1153     while (rnum-- > 0) {
1154         struct in_addr tmp = tp->hosts[0];
1155         int i;
1156
1157         for (i = 1; i < tp->nhosts; i++)
1158             tp->hosts[i - 1] = tp->hosts[i];
1159
1160         tp->hosts[i - 1] = tmp;
1161     }
1162 }
1163
1164 /*
1165  * transport_setup()
1166  *
1167  *      Given a "transport" object that says how we're to connect to the
1168  *      spam daemon, perform all the initial setup required to make the
1169  *      connection process a smooth one. The main work is to do the host
1170  *      name lookup and copy over all the IP addresses to make a local copy
1171  *      so they're not kept in the resolver's static state.
1172  *
1173  *      Here we also manage quasi-load balancing and failover: if we're
1174  *      doing load balancing, we randomly "rotate" the list to put it in
1175  *      a different order, and then if we're not doing failover we limit
1176  *      the hosts to just one. This way *all* connections are done with
1177  *      the intention of failover - makes the code a bit more clear.
1178  */
1179 int transport_setup(struct transport *tp, int flags)
1180 {
1181     struct hostent *hp = 0;
1182     char **addrp;
1183
1184 #ifdef _WIN32
1185     /* Start Winsock up */
1186     WSADATA wsaData;
1187     int nCode;
1188     if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1189         printf("WSAStartup() returned error code %d\n", nCode);
1190         return EX_OSERR;
1191     }
1192
1193 #endif
1194
1195     tp->flags = flags;
1196
1197     assert(tp != 0);
1198
1199     switch (tp->type) {
1200 #ifndef _WIN32
1201     case TRANSPORT_UNIX:
1202         assert(tp->socketpath != 0);
1203         return EX_OK;
1204 #endif
1205     case TRANSPORT_LOCALHOST:
1206         tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1207         tp->nhosts = 1;
1208         return EX_OK;
1209
1210     case TRANSPORT_TCP:
1211         if (NULL == (hp = gethostbyname(tp->hostname))) {
1212             int origherr = h_errno;     /* take a copy before syslog() */
1213
1214             libspamc_log(flags, LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
1215                     tp->hostname, origherr);
1216             switch (origherr) {
1217             case HOST_NOT_FOUND:
1218             case NO_ADDRESS:
1219             case NO_RECOVERY:
1220                 return EX_NOHOST;
1221             case TRY_AGAIN:
1222                 return EX_TEMPFAIL;
1223             default:
1224                 return EX_OSERR;
1225             }
1226         }
1227
1228                 /*--------------------------------------------------------
1229                  * If we have no hosts at all, or if they are some other
1230                  * kind of address family besides IPv4, then we really
1231                  * just have no hosts at all.
1232                  */
1233         if (hp->h_addr_list[0] == 0) {
1234             /* no hosts in this list */
1235             return EX_NOHOST;
1236         }
1237
1238         if (hp->h_length != sizeof tp->hosts[0]
1239             || hp->h_addrtype != AF_INET) {
1240             /* FAIL - bad size/protocol/family? */
1241             return EX_NOHOST;
1242         }
1243
1244                 /*--------------------------------------------------------
1245                  * Copy all the IP addresses into our private structure.
1246                  * This gets them out of the resolver's static area and
1247                  * means we won't ever walk all over the list with other
1248                  * calls.
1249                  */
1250         tp->nhosts = 0;
1251
1252         for (addrp = hp->h_addr_list; *addrp; addrp++) {
1253             if (tp->nhosts >= TRANSPORT_MAX_HOSTS - 1) {
1254                 libspamc_log(flags, LOG_ERR, "hit limit of %d hosts, ignoring remainder",
1255                        TRANSPORT_MAX_HOSTS - 1);
1256                 break;
1257             }
1258
1259             memcpy(&tp->hosts[tp->nhosts], *addrp, sizeof tp->hosts[0]);
1260
1261             tp->nhosts++;
1262         }
1263
1264                 /*--------------------------------------------------------
1265                  * QUASI-LOAD-BALANCING
1266                  *
1267                  * If the user wants to do quasi load balancing, "rotate"
1268                  * the list by a random amount based on the current time.
1269                  * This may later be truncated to a single item. This is
1270                  * meaningful only if we have more than one host.
1271                  */
1272         if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
1273             _randomize_hosts(tp);
1274         }
1275
1276                 /*--------------------------------------------------------
1277                  * If the user wants no fallback, simply truncate the host
1278                  * list to just one - this pretends that this is the extent
1279                  * of our connection list - then it's not a special case.
1280                  */
1281         if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
1282             /* truncating list */
1283             tp->nhosts = 1;
1284         }
1285     }
1286     return EX_OK;
1287 }
1288
1289 /* --------------------------------------------------------------------------- */
1290
1291 #define LOG_BUFSIZ      1023
1292
1293 void
1294 libspamc_log (int flags, int level, char *msg, ...)
1295 {
1296     va_list ap;
1297     char buf[LOG_BUFSIZ+1];
1298     int len = 0;
1299
1300     va_start(ap, msg);
1301
1302     if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
1303         /* create a log-line buffer */
1304         len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
1305         len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
1306
1307         /* avoid buffer overflow */
1308         if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
1309
1310         len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
1311         buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
1312         (void) write (2, buf, len);
1313
1314     } else {
1315         vsnprintf(buf, LOG_BUFSIZ, msg, ap);
1316         buf[LOG_BUFSIZ] = '\0';     /* ensure termination */
1317 #ifndef _WIN32
1318         syslog (level, "%s", buf);
1319 #else
1320         (void) level;  /* not used. suppress compiler warning */
1321         fprintf (stderr, "%s\n", buf);
1322 #endif
1323     }
1324
1325     va_end(ap);
1326 }
1327
1328 /* --------------------------------------------------------------------------- */
1329
1330 /*
1331  * Unit tests.  Must be built externally, e.g.:
1332  *
1333  * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
1334  * ./libspamctest
1335  *
1336  */
1337 #ifdef LIBSPAMC_UNIT_TESTS
1338
1339 static void _test_locale_safe_string_to_float_val(float input)
1340 {
1341     char inputstr[99], cmpbuf1[99], cmpbuf2[99];
1342     float output;
1343
1344     /* sprintf instead of snprintf is safe here because it is only a controlled test */
1345     sprintf(inputstr, "%f", input);
1346     output = _locale_safe_string_to_float(inputstr, 99);
1347     if (input == output) {
1348         return;
1349     }
1350
1351     /* could be a rounding error.  print as string and compare those */
1352     sprintf(cmpbuf1, "%f", input);
1353     sprintf(cmpbuf2, "%f", output);
1354     if (!strcmp(cmpbuf1, cmpbuf2)) {
1355         return;
1356     }
1357
1358     printf("FAIL: input=%f != output=%f\n", input, output);
1359 }
1360
1361 static void unit_test_locale_safe_string_to_float(void)
1362 {
1363     float statictestset[] = {   /* will try both +ve and -ve */
1364         0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
1365         9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
1366         0.0                     /* end of set constant */
1367     };
1368     float num;
1369     int i;
1370
1371     printf("starting unit_test_locale_safe_string_to_float\n");
1372     /* tests of precision */
1373     for (i = 0; statictestset[i] != 0.0; i++) {
1374         _test_locale_safe_string_to_float_val(statictestset[i]);
1375         _test_locale_safe_string_to_float_val(-statictestset[i]);
1376         _test_locale_safe_string_to_float_val(1 - statictestset[i]);
1377         _test_locale_safe_string_to_float_val(1 + statictestset[i]);
1378     }
1379     /* now exhaustive, in steps of 0.01 */
1380     for (num = -1000.0; num < 1000.0; num += 0.01) {
1381         _test_locale_safe_string_to_float_val(num);
1382     }
1383     printf("finished unit_test_locale_safe_string_to_float\n");
1384 }
1385
1386 void do_libspamc_unit_tests(void)
1387 {
1388     unit_test_locale_safe_string_to_float();
1389     exit(0);
1390 }
1391
1392 #endif /* LIBSPAMC_UNIT_TESTS */