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