]> diplodocus.org Git - mdeliver/blob - libspamc.c
b4d1936fc446aaccb84eb4b31f3c204e2b023e5c
[mdeliver] / 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 /* FIXME: Make this configurable */
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 #ifdef _WIN32
177 int socktout;
178 #endif
179
180 assert(psock != 0);
181
182 /*----------------------------------------------------------------
183 * Create a few induction variables that are implied by the socket
184 * type given by the user. The typename is strictly used for debug
185 * reporting.
186 */
187 if (type == PF_UNIX) {
188 typename = "PF_UNIX";
189 }
190 else {
191 typename = "PF_INET";
192 proto = IPPROTO_TCP;
193 }
194
195 #ifdef DO_CONNECT_DEBUG_SYSLOGS
196 libspamc_log(flags, DEBUG_LEVEL, "dbg: create socket(%s)", typename);
197 #endif
198
199 if ((*psock = socket(type, SOCK_STREAM, proto))
200 #ifndef _WIN32
201 < 0
202 #else
203 == INVALID_SOCKET
204 #endif
205 ) {
206 int origerr;
207
208 /*--------------------------------------------------------
209 * At this point we had a failure creating the socket, and
210 * this is pretty much fatal. Translate the error reason
211 * into something the user can understand.
212 */
213 #ifndef _WIN32
214 origerr = errno; /* take a copy before syslog() */
215 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %s", typename, strerror(origerr));
216 #else
217 origerr = WSAGetLastError();
218 libspamc_log(flags, LOG_ERR, "socket(%s) to spamd failed: %d", typename, origerr);
219 #endif
220
221 switch (origerr) {
222 case EPROTONOSUPPORT:
223 case EINVAL:
224 return EX_SOFTWARE;
225
226 case EACCES:
227 return EX_NOPERM;
228
229 case ENFILE:
230 case EMFILE:
231 case ENOBUFS:
232 case ENOMEM:
233 return EX_OSERR;
234
235 default:
236 return EX_SOFTWARE;
237 }
238 }
239
240 #ifdef _WIN32
241 /* bug 4344: makes timeout functional on Win32 */
242 socktout = libspamc_timeout * 1000;
243 if (type == PF_INET
244 && setsockopt(*psock, SOL_SOCKET, SO_RCVTIMEO, (char *)&socktout, sizeof(socktout)) != 0)
245 {
246 int origerrno;
247
248 origerrno = WSAGetLastError();
249 switch (origerrno)
250 {
251 case EBADF:
252 case ENOTSOCK:
253 case ENOPROTOOPT:
254 case EFAULT:
255 libspamc_log(flags, LOG_ERR, "setsockopt(SO_RCVTIMEO) failed: %d", origerrno);
256 closesocket(*psock);
257 return EX_SOFTWARE;
258
259 default:
260 break; /* ignored */
261 }
262 }
263 #endif
264
265 /*----------------------------------------------------------------
266 * Do a bit of setup on the TCP socket if required. Notes above
267 * suggest this is probably not set
268 */
269 #ifdef USE_TCP_NODELAY
270 {
271 int one = 1;
272
273 if (type == PF_INET
274 && setsockopt(*psock, 0, TCP_NODELAY, &one, sizeof one) != 0) {
275 int origerrno;
276 #ifndef _WIN32
277 origerr = errno;
278 #else
279 origerrno = WSAGetLastError();
280 #endif
281 switch (origerr) {
282 case EBADF:
283 case ENOTSOCK:
284 case ENOPROTOOPT:
285 case EFAULT:
286 libspamc_log(flags, LOG_ERR,
287 #ifndef _WIN32
288 "setsockopt(TCP_NODELAY) failed: %s", strerror(origerr));
289 #else
290 "setsockopt(TCP_NODELAY) failed: %d", origerr);
291 #endif
292 closesocket(*psock);
293 return EX_SOFTWARE;
294
295 default:
296 break; /* ignored */
297 }
298 }
299 }
300 #endif /* USE_TCP_NODELAY */
301
302 return EX_OK; /* all is well */
303 }
304
305 /*
306 * try_to_connect_unix()
307 *
308 * Given a transport handle that implies using a UNIX domain
309 * socket, try to make a connection to it and store the resulting
310 * file descriptor in *sockptr. Return is EX_OK if we did it,
311 * and some other error code otherwise.
312 */
313 static int _try_to_connect_unix(struct transport *tp, int *sockptr)
314 {
315 #ifndef _WIN32
316 int mysock, status, origerr;
317 struct sockaddr_un addrbuf;
318 int ret;
319
320 assert(tp != 0);
321 assert(sockptr != 0);
322 assert(tp->socketpath != 0);
323
324 /*----------------------------------------------------------------
325 * If the socket itself can't be created, this is a fatal error.
326 */
327 if ((ret = _opensocket(tp->flags, PF_UNIX, &mysock)) != EX_OK)
328 return ret;
329
330 /* set up the UNIX domain socket */
331 memset(&addrbuf, 0, sizeof addrbuf);
332 addrbuf.sun_family = AF_UNIX;
333 strncpy(addrbuf.sun_path, tp->socketpath, sizeof addrbuf.sun_path - 1);
334 addrbuf.sun_path[sizeof addrbuf.sun_path - 1] = '\0';
335
336 #ifdef DO_CONNECT_DEBUG_SYSLOGS
337 libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) to spamd at %s",
338 addrbuf.sun_path);
339 #endif
340
341 status = connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
342
343 origerr = errno;
344
345 if (status >= 0) {
346 #ifdef DO_CONNECT_DEBUG_SYSLOGS
347 libspamc_log(tp->flags, DEBUG_LEVEL, "dbg: connect(AF_UNIX) ok");
348 #endif
349
350 *sockptr = mysock;
351
352 return EX_OK;
353 }
354
355 libspamc_log(tp->flags, LOG_ERR, "connect(AF_UNIX) to spamd %s failed: %s",
356 addrbuf.sun_path, strerror(origerr));
357 closesocket(mysock);
358
359 return _translate_connect_errno(origerr);
360 #else
361 (void) tp; /* not used. suppress compiler warning */
362 (void) sockptr; /* not used. suppress compiler warning */
363 return EX_OSERR;
364 #endif
365 }
366
367 /*
368 * try_to_connect_tcp()
369 *
370 * Given a transport that implies a TCP connection, either to
371 * localhost or a list of IP addresses, attempt to connect. The
372 * list of IP addresses has already been randomized (if requested)
373 * and limited to just one if fallback has been enabled.
374 */
375 static int _try_to_connect_tcp(const struct transport *tp, int *sockptr)
376 {
377 int numloops;
378 int origerr = 0;
379 int ret;
380
381 assert(tp != 0);
382 assert(sockptr != 0);
383 assert(tp->nhosts > 0);
384
385 #ifdef DO_CONNECT_DEBUG_SYSLOGS
386 for (numloops = 0; numloops < tp->nhosts; numloops++) {
387 libspamc_log(tp->flags, LOG_ERR, "dbg: %d/%d: %s",
388 numloops + 1, tp->nhosts, inet_ntoa(tp->hosts[numloops]));
389 }
390 #endif
391
392 for (numloops = 0; numloops < MAX_CONNECT_RETRIES; numloops++) {
393 struct sockaddr_in addrbuf;
394 const int hostix = numloops % tp->nhosts;
395 int status, mysock;
396 const char *ipaddr;
397
398 /*--------------------------------------------------------
399 * We always start by creating the socket, as we get only
400 * one attempt to connect() on each one. If this fails,
401 * we're done.
402 */
403 if ((ret = _opensocket(tp->flags, PF_INET, &mysock)) != EX_OK)
404 return ret;
405
406 memset(&addrbuf, 0, sizeof(addrbuf));
407
408 addrbuf.sin_family = AF_INET;
409 addrbuf.sin_port = htons(tp->port);
410 addrbuf.sin_addr = tp->hosts[hostix];
411
412 ipaddr = inet_ntoa(addrbuf.sin_addr);
413
414 #ifdef DO_CONNECT_DEBUG_SYSLOGS
415 libspamc_log(tp->flags, DEBUG_LEVEL,
416 "dbg: connect(AF_INET) to spamd at %s (try #%d of %d)",
417 ipaddr, numloops + 1, MAX_CONNECT_RETRIES);
418 #endif
419
420 status =
421 connect(mysock, (struct sockaddr *) &addrbuf, sizeof(addrbuf));
422
423 if (status != 0) {
424 #ifndef _WIN32
425 origerr = errno;
426 libspamc_log(tp->flags, LOG_ERR,
427 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %s",
428 ipaddr, numloops + 1, MAX_CONNECT_RETRIES, strerror(origerr));
429 #else
430 origerr = WSAGetLastError();
431 libspamc_log(tp->flags, LOG_ERR,
432 "connect(AF_INET) to spamd at %s failed, retrying (#%d of %d): %d",
433 ipaddr, numloops + 1, MAX_CONNECT_RETRIES, origerr);
434 #endif
435 closesocket(mysock);
436
437 sleep(CONNECT_RETRY_SLEEP);
438 }
439 else {
440 #ifdef DO_CONNECT_DEBUG_SYSLOGS
441 libspamc_log(tp->flags, DEBUG_LEVEL,
442 "dbg: connect(AF_INET) to spamd at %s done", ipaddr);
443 #endif
444 *sockptr = mysock;
445
446 return EX_OK;
447 }
448 }
449
450 libspamc_log(tp->flags, LOG_ERR, "connection attempt to spamd aborted after %d retries",
451 MAX_CONNECT_RETRIES);
452
453 return _translate_connect_errno(origerr);
454 }
455
456 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
457 * message_dump, lookup_host, message_filter, and message_process, and a bunch
458 * of helper functions.
459 */
460
461 static void _clear_message(struct message *m)
462 {
463 m->type = MESSAGE_NONE;
464 m->raw = NULL;
465 m->raw_len = 0;
466 m->pre = NULL;
467 m->pre_len = 0;
468 m->msg = NULL;
469 m->msg_len = 0;
470 m->post = NULL;
471 m->post_len = 0;
472 m->is_spam = EX_TOOBIG;
473 m->score = 0.0;
474 m->threshold = 0.0;
475 m->outbuf = NULL;
476 m->out = NULL;
477 m->out_len = 0;
478 m->content_length = -1;
479 }
480
481 static void _use_msg_for_out(struct message *m)
482 {
483 if (m->outbuf)
484 free(m->outbuf);
485 m->outbuf = NULL;
486 m->out = m->msg;
487 m->out_len = m->msg_len;
488 }
489
490 static int _message_read_raw(int fd, struct message *m)
491 {
492 struct stat st;
493 _clear_message(m);
494
495 if (fstat(fd, &st) == -1) {
496 libspamc_log(0,0, "fstat");
497 return EX_IOERR;
498 }
499
500 m->raw = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
501 if (m->raw == MAP_FAILED) {
502 libspamc_log(0,0, "mmap");
503 return EX_IOERR;
504 }
505 m->raw_len = st.st_size;
506
507 m->type = MESSAGE_RAW;
508 m->msg = m->raw;
509 m->msg_len = m->raw_len;
510 m->out = m->msg;
511 m->out_len = m->msg_len;
512 return EX_OK;
513 }
514
515 static int _message_read_bsmtp(int fd, struct message *m)
516 {
517 unsigned int i, j;
518 char prev;
519
520 _clear_message(m);
521 if ((m->raw = malloc(m->max_len + 1)) == NULL)
522 return EX_OSERR;
523
524 /* Find the DATA line */
525 m->raw_len = full_read(fd, 1, m->raw, m->max_len + 1, m->max_len + 1);
526 if (m->raw_len <= 0) {
527 free(m->raw);
528 m->raw = NULL;
529 m->raw_len = 0;
530 return EX_IOERR;
531 }
532 m->type = MESSAGE_ERROR;
533 if (m->raw_len > m->max_len)
534 return EX_TOOBIG;
535 m->pre = m->raw;
536 for (i = 0; i < m->raw_len - 6; i++) {
537 if ((m->raw[i] == '\n') &&
538 (m->raw[i + 1] == 'D' || m->raw[i + 1] == 'd') &&
539 (m->raw[i + 2] == 'A' || m->raw[i + 2] == 'a') &&
540 (m->raw[i + 3] == 'T' || m->raw[i + 3] == 't') &&
541 (m->raw[i + 4] == 'A' || m->raw[i + 4] == 'a') &&
542 ((m->raw[i + 5] == '\r' && m->raw[i + 6] == '\n')
543 || m->raw[i + 5] == '\n')) {
544 /* Found it! */
545 i += 6;
546 if (m->raw[i - 1] == '\r')
547 i++;
548 m->pre_len = i;
549 m->msg = m->raw + i;
550 m->msg_len = m->raw_len - i;
551 break;
552 }
553 }
554 if (m->msg == NULL)
555 return EX_DATAERR;
556
557 /* Find the end-of-DATA line */
558 prev = '\n';
559 for (i = j = 0; i < m->msg_len; i++) {
560 if (prev == '\n' && m->msg[i] == '.') {
561 /* Dot at the beginning of a line */
562 if ((m->msg[i + 1] == '\r' && m->msg[i + 2] == '\n')
563 || m->msg[i + 1] == '\n') {
564 /* Lone dot! That's all, folks */
565 m->post = m->msg + i;
566 m->post_len = m->msg_len - i;
567 m->msg_len = j;
568 break;
569 }
570 else if (m->msg[i + 1] == '.') {
571 /* Escaping dot, eliminate. */
572 prev = '.';
573 continue;
574 } /* Else an ordinary dot, drop down to ordinary char handler */
575 }
576 prev = m->msg[i];
577 m->msg[j++] = m->msg[i];
578 }
579
580 m->type = MESSAGE_BSMTP;
581 m->out = m->msg;
582 m->out_len = m->msg_len;
583 return EX_OK;
584 }
585
586 int message_read(int fd, int flags, struct message *m)
587 {
588 assert(m != NULL);
589
590 libspamc_timeout = 0;
591
592 /* create the "private" part of the struct message */
593 m->priv = malloc(sizeof(struct libspamc_private_message));
594 if (m->priv == NULL) {
595 libspamc_log(flags, LOG_ERR, "message_read: malloc failed");
596 return EX_OSERR;
597 }
598 m->priv->flags = flags;
599
600 switch (flags & SPAMC_MODE_MASK) {
601 case SPAMC_RAW_MODE:
602 return _message_read_raw(fd, m);
603
604 case SPAMC_BSMTP_MODE:
605 return _message_read_bsmtp(fd, m);
606
607 default:
608 libspamc_log(flags, LOG_ERR, "message_read: Unknown mode %d",
609 flags & SPAMC_MODE_MASK);
610 return EX_USAGE;
611 }
612 }
613
614 long message_write(int fd, struct message *m)
615 {
616 long total = 0;
617 off_t i, j;
618 off_t jlimit;
619 char buffer[1024];
620
621 assert(m != NULL);
622
623 if (m->priv->flags & SPAMC_CHECK_ONLY) {
624 if (m->is_spam == EX_ISSPAM || m->is_spam == EX_NOTSPAM) {
625 return full_write(fd, 1, m->out, m->out_len);
626
627 }
628 else {
629 libspamc_log(m->priv->flags, LOG_ERR, "oops! SPAMC_CHECK_ONLY is_spam: %d",
630 m->is_spam);
631 return -1;
632 }
633 }
634
635 /* else we're not in CHECK_ONLY mode */
636 switch (m->type) {
637 case MESSAGE_NONE:
638 libspamc_log(m->priv->flags, LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!");
639 return -1;
640
641 case MESSAGE_ERROR:
642 return full_write(fd, 1, m->raw, m->raw_len);
643
644 case MESSAGE_RAW:
645 return full_write(fd, 1, m->out, m->out_len);
646
647 case MESSAGE_BSMTP:
648 total = full_write(fd, 1, m->pre, m->pre_len);
649 for (i = 0; i < m->out_len;) {
650 jlimit = (off_t) (sizeof(buffer) / sizeof(*buffer) - 4);
651 for (j = 0; i < (off_t) m->out_len && j < jlimit;) {
652 if (i + 1 < m->out_len && m->out[i] == '\n'
653 && m->out[i + 1] == '.') {
654 if (j > jlimit - 4) {
655 break; /* avoid overflow */
656 }
657 buffer[j++] = m->out[i++];
658 buffer[j++] = m->out[i++];
659 buffer[j++] = '.';
660 }
661 else {
662 buffer[j++] = m->out[i++];
663 }
664 }
665 total += full_write(fd, 1, buffer, j);
666 }
667 return total + full_write(fd, 1, m->post, m->post_len);
668
669 default:
670 libspamc_log(m->priv->flags, LOG_ERR, "Unknown message type %d", m->type);
671 return -1;
672 }
673 }
674
675 void message_dump(int in_fd, int out_fd, struct message *m)
676 {
677 char buf[8196];
678 int bytes;
679
680 if (m != NULL && m->type != MESSAGE_NONE) {
681 message_write(out_fd, m);
682 }
683 while ((bytes = full_read(in_fd, 1, buf, 8192, 8192)) > 0) {
684 if (bytes != full_write(out_fd, 1, buf, bytes)) {
685 libspamc_log(m->priv->flags, LOG_ERR, "oops! message_dump of %d returned different",
686 bytes);
687 }
688 }
689 }
690
691 static int
692 _spamc_read_full_line(struct message *m, int flags, SSL * ssl, int sock,
693 char *buf, size_t *lenp, size_t bufsiz)
694 {
695 int failureval;
696 int bytesread = 0;
697 size_t len;
698
699 UNUSED_VARIABLE(m);
700
701 *lenp = 0;
702 /* Now, read from spamd */
703 for (len = 0; len < bufsiz - 1; len++) {
704 if (flags & SPAMC_USE_SSL) {
705 bytesread = ssl_timeout_read(ssl, buf + len, 1);
706 }
707 else {
708 bytesread = fd_timeout_read(sock, 0, buf + len, 1);
709 }
710
711 if (bytesread <= 0) {
712 failureval = EX_IOERR;
713 goto failure;
714 }
715
716 if (buf[len] == '\n') {
717 buf[len] = '\0';
718 if (len > 0 && buf[len - 1] == '\r') {
719 len--;
720 buf[len] = '\0';
721 }
722 *lenp = len;
723 return EX_OK;
724 }
725 }
726
727 libspamc_log(flags, LOG_ERR, "spamd responded with line of %d bytes, dying", len);
728 failureval = EX_TOOBIG;
729
730 failure:
731 return failureval;
732 }
733
734 /*
735 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
736 * work around using our own locale-independent float-parser code.
737 */
738 static float _locale_safe_string_to_float(char *buf, int siz)
739 {
740 int is_neg;
741 char *cp, *dot;
742 int divider;
743 float ret, postdot;
744
745 buf[siz - 1] = '\0'; /* ensure termination */
746
747 /* ok, let's illustrate using "100.033" as an example... */
748
749 is_neg = 0;
750 if (*buf == '-') {
751 is_neg = 1;
752 }
753
754 ret = (float) (strtol(buf, &dot, 10));
755 if (dot == NULL) {
756 return 0.0;
757 }
758 if (dot != NULL && *dot != '.') {
759 return ret;
760 }
761
762 /* ex: ret == 100.0 */
763
764 cp = (dot + 1);
765 postdot = (float) (strtol(cp, NULL, 10));
766 /* note: don't compare floats == 0.0, it's unsafe. use a range */
767 if (postdot >= -0.00001 && postdot <= 0.00001) {
768 return ret;
769 }
770
771 /* ex: postdot == 33.0, cp="033" */
772
773 /* now count the number of decimal places and figure out what power of 10 to use */
774 divider = 1;
775 while (*cp != '\0') {
776 divider *= 10;
777 cp++;
778 }
779
780 /* ex:
781 * cp="033", divider=1
782 * cp="33", divider=10
783 * cp="3", divider=100
784 * cp="", divider=1000
785 */
786
787 if (is_neg) {
788 ret -= (postdot / ((float) divider));
789 }
790 else {
791 ret += (postdot / ((float) divider));
792 }
793 /* ex: ret == 100.033, tada! ... hopefully */
794
795 return ret;
796 }
797
798 static int
799 _handle_spamd_header(struct message *m, int flags, char *buf, int len,
800 unsigned int *didtellflags)
801 {
802 char is_spam[6];
803 char s_str[21], t_str[21];
804 char didset_ret[15];
805 char didremove_ret[15];
806
807 UNUSED_VARIABLE(len);
808
809 /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
810 * let's stick with it for this parser.
811 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
812 * work around using our own locale-independent float-parser code.
813 */
814 if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) {
815 m->score = _locale_safe_string_to_float(s_str, 20);
816 m->threshold = _locale_safe_string_to_float(t_str, 20);
817
818 /* set bounds on these to ensure no buffer overflow in the sprintf */
819 if (m->score > 1e10)
820 m->score = 1e10;
821 else if (m->score < -1e10)
822 m->score = -1e10;
823 if (m->threshold > 1e10)
824 m->threshold = 1e10;
825 else if (m->threshold < -1e10)
826 m->threshold = -1e10;
827
828 /* Format is "Spam: x; y / x" */
829 m->is_spam =
830 strcasecmp("true", is_spam) == 0 ? EX_ISSPAM : EX_NOTSPAM;
831
832 if (flags & SPAMC_CHECK_ONLY) {
833 m->out_len = sprintf(m->out,
834 "%.1f/%.1f\n", m->score, m->threshold);
835 }
836 else if ((flags & SPAMC_REPORT_IFSPAM && m->is_spam == EX_ISSPAM)
837 || (flags & SPAMC_REPORT)) {
838 m->out_len = sprintf(m->out,
839 "%.1f/%.1f\n", m->score, m->threshold);
840 }
841 return EX_OK;
842
843 }
844 else if (sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
845 if (m->content_length < 0) {
846 libspamc_log(flags, LOG_ERR, "spamd responded with bad Content-length '%s'",
847 buf);
848 return EX_PROTOCOL;
849 }
850 return EX_OK;
851 }
852 else if (sscanf(buf, "DidSet: %s", didset_ret) == 1) {
853 if (strstr(didset_ret, "local")) {
854 *didtellflags |= SPAMC_SET_LOCAL;
855 }
856 if (strstr(didset_ret, "remote")) {
857 *didtellflags |= SPAMC_SET_REMOTE;
858 }
859 }
860 else if (sscanf(buf, "DidRemove: %s", didremove_ret) == 1) {
861 if (strstr(didremove_ret, "local")) {
862 *didtellflags |= SPAMC_REMOVE_LOCAL;
863 }
864 if (strstr(didremove_ret, "remote")) {
865 *didtellflags |= SPAMC_REMOVE_REMOTE;
866 }
867 }
868
869 return EX_OK;
870 }
871
872 int message_filter(struct transport *tp, const char *username,
873 int flags, struct message *m)
874 {
875 char buf[8192];
876 size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
877 size_t len;
878 int sock = -1;
879 int rc;
880 char versbuf[20];
881 float version;
882 int response;
883 int failureval;
884 unsigned int throwaway;
885 SSL_CTX *ctx = NULL;
886 SSL *ssl = NULL;
887 SSL_METHOD *meth;
888
889 assert(tp != NULL);
890 assert(m != NULL);
891
892 if (flags & SPAMC_USE_SSL) {
893 #ifdef SPAMC_SSL
894 SSLeay_add_ssl_algorithms();
895 meth = SSLv2_client_method();
896 SSL_load_error_strings();
897 ctx = SSL_CTX_new(meth);
898 #else
899 UNUSED_VARIABLE(ssl);
900 UNUSED_VARIABLE(meth);
901 UNUSED_VARIABLE(ctx);
902 libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
903 return EX_SOFTWARE;
904 #endif
905 }
906
907 m->is_spam = EX_TOOBIG;
908 if ((m->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
909 failureval = EX_OSERR;
910 goto failure;
911 }
912 m->out = m->outbuf;
913 m->out_len = 0;
914
915 /* Build spamd protocol header */
916 if (flags & SPAMC_CHECK_ONLY)
917 strcpy(buf, "CHECK ");
918 else if (flags & SPAMC_REPORT_IFSPAM)
919 strcpy(buf, "REPORT_IFSPAM ");
920 else if (flags & SPAMC_REPORT)
921 strcpy(buf, "REPORT ");
922 else if (flags & SPAMC_SYMBOLS)
923 strcpy(buf, "SYMBOLS ");
924 else
925 strcpy(buf, "PROCESS ");
926
927 len = strlen(buf);
928 if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
929 _use_msg_for_out(m);
930 return EX_OSERR;
931 }
932
933 strcat(buf, PROTOCOL_VERSION);
934 strcat(buf, "\r\n");
935 len = strlen(buf);
936
937 if (username != NULL) {
938 if (strlen(username) + 8 >= (bufsiz - len)) {
939 _use_msg_for_out(m);
940 return EX_OSERR;
941 }
942 strcpy(buf + len, "User: ");
943 strcat(buf + len, username);
944 strcat(buf + len, "\r\n");
945 len += strlen(buf + len);
946 }
947 if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
948 _use_msg_for_out(m);
949 return EX_OSERR;
950 }
951 len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
952
953 libspamc_timeout = m->timeout;
954
955 if (tp->socketpath)
956 rc = _try_to_connect_unix(tp, &sock);
957 else
958 rc = _try_to_connect_tcp(tp, &sock);
959
960 if (rc != EX_OK) {
961 _use_msg_for_out(m);
962 return rc; /* use the error code try_to_connect_*() gave us. */
963 }
964
965 if (flags & SPAMC_USE_SSL) {
966 #ifdef SPAMC_SSL
967 ssl = SSL_new(ctx);
968 SSL_set_fd(ssl, sock);
969 SSL_connect(ssl);
970 #endif
971 }
972
973 /* Send to spamd */
974 if (flags & SPAMC_USE_SSL) {
975 #ifdef SPAMC_SSL
976 SSL_write(ssl, buf, len);
977 SSL_write(ssl, m->msg, m->msg_len);
978 #endif
979 }
980 else {
981 full_write(sock, 0, buf, len);
982 full_write(sock, 0, m->msg, m->msg_len);
983 shutdown(sock, SHUT_WR);
984 }
985
986 /* ok, now read and parse it. SPAMD/1.2 line first... */
987 failureval =
988 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
989 if (failureval != EX_OK) {
990 goto failure;
991 }
992
993 if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
994 libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
995 failureval = EX_PROTOCOL;
996 goto failure;
997 }
998
999 versbuf[19] = '\0';
1000 version = _locale_safe_string_to_float(versbuf, 20);
1001 if (version < 1.0) {
1002 libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
1003 versbuf);
1004 failureval = EX_PROTOCOL;
1005 goto failure;
1006 }
1007
1008 m->score = 0;
1009 m->threshold = 0;
1010 m->is_spam = EX_TOOBIG;
1011 while (1) {
1012 failureval =
1013 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1014 if (failureval != EX_OK) {
1015 goto failure;
1016 }
1017
1018 if (len == 0 && buf[0] == '\0') {
1019 break; /* end of headers */
1020 }
1021
1022 if (_handle_spamd_header(m, flags, buf, len, &throwaway) < 0) {
1023 failureval = EX_PROTOCOL;
1024 goto failure;
1025 }
1026 }
1027
1028 len = 0; /* overwrite those headers */
1029
1030 if (flags & SPAMC_CHECK_ONLY) {
1031 closesocket(sock);
1032 sock = -1;
1033 if (m->is_spam == EX_TOOBIG) {
1034 /* We should have gotten headers back... Damnit. */
1035 failureval = EX_PROTOCOL;
1036 goto failure;
1037 }
1038 return EX_OK;
1039 }
1040 else {
1041 if (m->content_length < 0) {
1042 /* should have got a length too. */
1043 failureval = EX_PROTOCOL;
1044 goto failure;
1045 }
1046
1047 /* have we already got something in the buffer (e.g. REPORT and
1048 * REPORT_IFSPAM both create a line from the "Spam:" hdr)? If
1049 * so, add the size of that so our sanity check passes.
1050 */
1051 if (m->out_len > 0) {
1052 m->content_length += m->out_len;
1053 }
1054
1055 if (flags & SPAMC_USE_SSL) {
1056 len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
1057 m->max_len + EXPANSION_ALLOWANCE + 1 -
1058 m->out_len,
1059 m->max_len + EXPANSION_ALLOWANCE + 1 -
1060 m->out_len);
1061 }
1062 else {
1063 len = full_read(sock, 0, m->out + m->out_len,
1064 m->max_len + EXPANSION_ALLOWANCE + 1 - m->out_len,
1065 m->max_len + EXPANSION_ALLOWANCE + 1 -
1066 m->out_len);
1067 }
1068
1069
1070 if (len + m->out_len > m->max_len + EXPANSION_ALLOWANCE) {
1071 failureval = EX_TOOBIG;
1072 goto failure;
1073 }
1074 m->out_len += len;
1075
1076 shutdown(sock, SHUT_RD);
1077 closesocket(sock);
1078 sock = -1;
1079 }
1080 libspamc_timeout = 0;
1081
1082 if (m->out_len != m->content_length) {
1083 libspamc_log(flags, LOG_ERR,
1084 "failed sanity check, %d bytes claimed, %d bytes seen",
1085 m->content_length, m->out_len);
1086 failureval = EX_PROTOCOL;
1087 goto failure;
1088 }
1089
1090 return EX_OK;
1091
1092 failure:
1093 _use_msg_for_out(m);
1094 if (sock != -1) {
1095 closesocket(sock);
1096 }
1097 libspamc_timeout = 0;
1098
1099 if (flags & SPAMC_USE_SSL) {
1100 #ifdef SPAMC_SSL
1101 SSL_free(ssl);
1102 SSL_CTX_free(ctx);
1103 #endif
1104 }
1105 return failureval;
1106 }
1107
1108
1109 int message_process(struct transport *trans, char *username, int max_size,
1110 int in_fd, int out_fd, const int flags)
1111 {
1112 int ret;
1113 struct message m;
1114
1115 assert(trans != NULL);
1116
1117 m.type = MESSAGE_NONE;
1118
1119 m.max_len = max_size;
1120 ret = message_read(in_fd, flags, &m);
1121 if (ret != EX_OK)
1122 goto FAIL;
1123 ret = message_filter(trans, username, flags, &m);
1124 if (ret != EX_OK)
1125 goto FAIL;
1126 if (message_write(out_fd, &m) < 0)
1127 goto FAIL;
1128 if (m.is_spam != EX_TOOBIG) {
1129 message_cleanup(&m);
1130 return m.is_spam;
1131 }
1132 message_cleanup(&m);
1133 return ret;
1134
1135 FAIL:
1136 if (flags & SPAMC_CHECK_ONLY) {
1137 full_write(out_fd, 1, "0/0\n", 4);
1138 message_cleanup(&m);
1139 return EX_NOTSPAM;
1140 }
1141 else {
1142 message_dump(in_fd, out_fd, &m);
1143 message_cleanup(&m);
1144 return ret;
1145 }
1146 }
1147
1148 int message_tell(struct transport *tp, const char *username, int flags,
1149 struct message *m, int msg_class,
1150 unsigned int tellflags, unsigned int *didtellflags)
1151 {
1152 char buf[8192];
1153 size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
1154 size_t len;
1155 int sock = -1;
1156 int rc;
1157 char versbuf[20];
1158 float version;
1159 int response;
1160 int failureval;
1161 SSL_CTX *ctx = NULL;
1162 SSL *ssl = NULL;
1163 SSL_METHOD *meth;
1164
1165 assert(tp != NULL);
1166 assert(m != NULL);
1167
1168 if (flags & SPAMC_USE_SSL) {
1169 #ifdef SPAMC_SSL
1170 SSLeay_add_ssl_algorithms();
1171 meth = SSLv2_client_method();
1172 SSL_load_error_strings();
1173 ctx = SSL_CTX_new(meth);
1174 #else
1175 UNUSED_VARIABLE(ssl);
1176 UNUSED_VARIABLE(meth);
1177 UNUSED_VARIABLE(ctx);
1178 libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
1179 return EX_SOFTWARE;
1180 #endif
1181 }
1182
1183 m->is_spam = EX_TOOBIG;
1184 if ((m->outbuf = malloc(m->max_len + EXPANSION_ALLOWANCE + 1)) == NULL) {
1185 failureval = EX_OSERR;
1186 goto failure;
1187 }
1188 m->out = m->outbuf;
1189 m->out_len = 0;
1190
1191 /* Build spamd protocol header */
1192 strcpy(buf, "TELL ");
1193
1194 len = strlen(buf);
1195 if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
1196 _use_msg_for_out(m);
1197 return EX_OSERR;
1198 }
1199
1200 strcat(buf, PROTOCOL_VERSION);
1201 strcat(buf, "\r\n");
1202 len = strlen(buf);
1203
1204 if (msg_class != 0) {
1205 strcpy(buf + len, "Message-class: ");
1206 if (msg_class == SPAMC_MESSAGE_CLASS_SPAM) {
1207 strcat(buf + len, "spam\r\n");
1208 }
1209 else {
1210 strcat(buf + len, "ham\r\n");
1211 }
1212 len += strlen(buf + len);
1213 }
1214
1215 if ((tellflags & SPAMC_SET_LOCAL) || (tellflags & SPAMC_SET_REMOTE)) {
1216 int needs_comma_p = 0;
1217 strcat(buf + len, "Set: ");
1218 if (tellflags & SPAMC_SET_LOCAL) {
1219 strcat(buf + len, "local");
1220 needs_comma_p = 1;
1221 }
1222 if (tellflags & SPAMC_SET_REMOTE) {
1223 if (needs_comma_p == 1) {
1224 strcat(buf + len, ",");
1225 }
1226 strcat(buf + len, "remote");
1227 }
1228 strcat(buf + len, "\r\n");
1229 len += strlen(buf + len);
1230 }
1231
1232 if ((tellflags & SPAMC_REMOVE_LOCAL) || (tellflags & SPAMC_REMOVE_REMOTE)) {
1233 int needs_comma_p = 0;
1234 strcat(buf + len, "Remove: ");
1235 if (tellflags & SPAMC_REMOVE_LOCAL) {
1236 strcat(buf + len, "local");
1237 needs_comma_p = 1;
1238 }
1239 if (tellflags & SPAMC_REMOVE_REMOTE) {
1240 if (needs_comma_p == 1) {
1241 strcat(buf + len, ",");
1242 }
1243 strcat(buf + len, "remote");
1244 }
1245 strcat(buf + len, "\r\n");
1246 len += strlen(buf + len);
1247 }
1248
1249 if (username != NULL) {
1250 if (strlen(username) + 8 >= (bufsiz - len)) {
1251 _use_msg_for_out(m);
1252 return EX_OSERR;
1253 }
1254 strcpy(buf + len, "User: ");
1255 strcat(buf + len, username);
1256 strcat(buf + len, "\r\n");
1257 len += strlen(buf + len);
1258 }
1259 if ((m->msg_len > 9999999) || ((len + 27) >= (bufsiz - len))) {
1260 _use_msg_for_out(m);
1261 return EX_OSERR;
1262 }
1263 len += sprintf(buf + len, "Content-length: %d\r\n\r\n", m->msg_len);
1264
1265 libspamc_timeout = m->timeout;
1266
1267 if (tp->socketpath)
1268 rc = _try_to_connect_unix(tp, &sock);
1269 else
1270 rc = _try_to_connect_tcp(tp, &sock);
1271
1272 if (rc != EX_OK) {
1273 _use_msg_for_out(m);
1274 return rc; /* use the error code try_to_connect_*() gave us. */
1275 }
1276
1277 if (flags & SPAMC_USE_SSL) {
1278 #ifdef SPAMC_SSL
1279 ssl = SSL_new(ctx);
1280 SSL_set_fd(ssl, sock);
1281 SSL_connect(ssl);
1282 #endif
1283 }
1284
1285 /* Send to spamd */
1286 if (flags & SPAMC_USE_SSL) {
1287 #ifdef SPAMC_SSL
1288 SSL_write(ssl, buf, len);
1289 SSL_write(ssl, m->msg, m->msg_len);
1290 #endif
1291 }
1292 else {
1293 full_write(sock, 0, buf, len);
1294 full_write(sock, 0, m->msg, m->msg_len);
1295 shutdown(sock, SHUT_WR);
1296 }
1297
1298 /* ok, now read and parse it. SPAMD/1.2 line first... */
1299 failureval =
1300 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1301 if (failureval != EX_OK) {
1302 goto failure;
1303 }
1304
1305 if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
1306 libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
1307 failureval = EX_PROTOCOL;
1308 goto failure;
1309 }
1310
1311 versbuf[19] = '\0';
1312 version = _locale_safe_string_to_float(versbuf, 20);
1313 if (version < 1.0) {
1314 libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
1315 versbuf);
1316 failureval = EX_PROTOCOL;
1317 goto failure;
1318 }
1319
1320 m->score = 0;
1321 m->threshold = 0;
1322 m->is_spam = EX_TOOBIG;
1323 while (1) {
1324 failureval =
1325 _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
1326 if (failureval != EX_OK) {
1327 goto failure;
1328 }
1329
1330 if (len == 0 && buf[0] == '\0') {
1331 break; /* end of headers */
1332 }
1333
1334 if (_handle_spamd_header(m, flags, buf, len, didtellflags) < 0) {
1335 failureval = EX_PROTOCOL;
1336 goto failure;
1337 }
1338 }
1339
1340 len = 0; /* overwrite those headers */
1341
1342 shutdown(sock, SHUT_RD);
1343 closesocket(sock);
1344 sock = -1;
1345
1346 libspamc_timeout = 0;
1347
1348 return EX_OK;
1349
1350 failure:
1351 _use_msg_for_out(m);
1352 if (sock != -1) {
1353 closesocket(sock);
1354 }
1355 libspamc_timeout = 0;
1356
1357 if (flags & SPAMC_USE_SSL) {
1358 #ifdef SPAMC_SSL
1359 SSL_free(ssl);
1360 SSL_CTX_free(ctx);
1361 #endif
1362 }
1363 return failureval;
1364 }
1365
1366 void message_cleanup(struct message *m)
1367 {
1368 assert(m != NULL);
1369 if (m->outbuf != NULL)
1370 free(m->outbuf);
1371 if (m->raw != NULL) {
1372 if (munmap(m->raw, m->raw_len) != 0) {
1373 libspamc_log(0,0, "munmap");
1374 exit(EX_SOFTWARE);
1375 }
1376 }
1377 if (m->priv != NULL)
1378 free(m->priv);
1379 _clear_message(m);
1380 }
1381
1382 /* Aug 14, 2002 bj: Obsolete! */
1383 int process_message(struct transport *tp, char *username, int max_size,
1384 int in_fd, int out_fd, const int my_check_only,
1385 const int my_safe_fallback)
1386 {
1387 int flags;
1388
1389 flags = SPAMC_RAW_MODE;
1390 if (my_check_only)
1391 flags |= SPAMC_CHECK_ONLY;
1392 if (my_safe_fallback)
1393 flags |= SPAMC_SAFE_FALLBACK;
1394
1395 return message_process(tp, username, max_size, in_fd, out_fd, flags);
1396 }
1397
1398 /*
1399 * init_transport()
1400 *
1401 * Given a pointer to a transport structure, set it to "all empty".
1402 * The default is a localhost connection.
1403 */
1404 void transport_init(struct transport *tp)
1405 {
1406 assert(tp != 0);
1407
1408 memset(tp, 0, sizeof *tp);
1409
1410 tp->type = TRANSPORT_LOCALHOST;
1411 tp->port = 783;
1412 tp->flags = 0;
1413 }
1414
1415 /*
1416 * randomize_hosts()
1417 *
1418 * Given the transport object that contains one or more IP addresses
1419 * in this "hosts" list, rotate it by a random number of shifts to
1420 * randomize them - this is a kind of load balancing. It's possible
1421 * that the random number will be 0, which says not to touch. We don't
1422 * do anything unless
1423 */
1424
1425 static void _randomize_hosts(struct transport *tp)
1426 {
1427 int rnum;
1428
1429 assert(tp != 0);
1430
1431 if (tp->nhosts <= 1)
1432 return;
1433
1434 rnum = rand() % tp->nhosts;
1435
1436 while (rnum-- > 0) {
1437 struct in_addr tmp = tp->hosts[0];
1438 int i;
1439
1440 for (i = 1; i < tp->nhosts; i++)
1441 tp->hosts[i - 1] = tp->hosts[i];
1442
1443 tp->hosts[i - 1] = tmp;
1444 }
1445 }
1446
1447 /*
1448 * transport_setup()
1449 *
1450 * Given a "transport" object that says how we're to connect to the
1451 * spam daemon, perform all the initial setup required to make the
1452 * connection process a smooth one. The main work is to do the host
1453 * name lookup and copy over all the IP addresses to make a local copy
1454 * so they're not kept in the resolver's static state.
1455 *
1456 * Here we also manage quasi-load balancing and failover: if we're
1457 * doing load balancing, we randomly "rotate" the list to put it in
1458 * a different order, and then if we're not doing failover we limit
1459 * the hosts to just one. This way *all* connections are done with
1460 * the intention of failover - makes the code a bit more clear.
1461 */
1462 int transport_setup(struct transport *tp, int flags)
1463 {
1464 struct hostent *hp;
1465 char *hostlist, *hostname;
1466 int errbits;
1467 char **addrp;
1468
1469 #ifdef _WIN32
1470 // Start Winsock up
1471 WSADATA wsaData;
1472 int nCode;
1473 if ((nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0) {
1474 printf("WSAStartup() returned error code %d\n", nCode);
1475 return EX_OSERR;
1476 }
1477
1478 #endif
1479
1480 assert(tp != NULL);
1481 tp->flags = flags;
1482
1483 switch (tp->type) {
1484 #ifndef _WIN32
1485 case TRANSPORT_UNIX:
1486 assert(tp->socketpath != 0);
1487 return EX_OK;
1488 #endif
1489 case TRANSPORT_LOCALHOST:
1490 tp->hosts[0].s_addr = inet_addr("127.0.0.1");
1491 tp->nhosts = 1;
1492 return EX_OK;
1493
1494 case TRANSPORT_TCP:
1495 if ((hostlist = strdup(tp->hostname)) == NULL)
1496 return EX_OSERR;
1497
1498 /* We want to return the least permanent error, in this bitmask we
1499 * record the errors seen with:
1500 * 0: no error
1501 * 1: EX_TEMPFAIL
1502 * 2: EX_NOHOST
1503 * EX_OSERR will return immediately.
1504 * Bits aren't reset so a check against nhosts is needed to determine
1505 * if something went wrong.
1506 */
1507 errbits = 0;
1508 tp->nhosts = 0;
1509 /* Start with char offset in front of the string because we'll add
1510 * one in the loop
1511 */
1512 hostname = hostlist - 1;
1513 do {
1514 char *hostend;
1515
1516 hostname += 1;
1517 hostend = strchr(hostname, ',');
1518 if (hostend != NULL) {
1519 *hostend = '\0';
1520 }
1521
1522 if ((hp = gethostbyname(hostname)) == NULL) {
1523 int origerr = h_errno; /* take a copy before syslog() */
1524 libspamc_log(flags, LOG_DEBUG, "gethostbyname(%s) failed: h_errno=%d",
1525 hostname, origerr);
1526 switch (origerr) {
1527 case TRY_AGAIN:
1528 errbits |= 1;
1529 break;
1530 case HOST_NOT_FOUND:
1531 case NO_ADDRESS:
1532 case NO_RECOVERY:
1533 errbits |= 2;
1534 break;
1535 default:
1536 /* should not happen, all errors are checked above */
1537 free(hostlist);
1538 return EX_OSERR;
1539 }
1540 goto nexthost; /* try next host in list */
1541 }
1542
1543 /* If we have no hosts at all, or if they are some other
1544 * kind of address family besides IPv4, then we really
1545 * just have no hosts at all. TODO: IPv6
1546 */
1547 if (hp->h_addr_list[0] == NULL
1548 || hp->h_length != sizeof tp->hosts[0]
1549 || hp->h_addrtype != AF_INET) {
1550 /* no hosts/bad size/wrong family */
1551 errbits |= 1;
1552 goto nexthost; /* try next host in list */
1553 }
1554
1555 /* Copy all the IP addresses into our private structure.
1556 * This gets them out of the resolver's static area and
1557 * means we won't ever walk all over the list with other
1558 * calls.
1559 */
1560 for (addrp = hp->h_addr_list; *addrp; addrp++) {
1561 if (tp->nhosts == TRANSPORT_MAX_HOSTS) {
1562 libspamc_log(flags, LOG_NOTICE, "hit limit of %d hosts, ignoring remainder",
1563 TRANSPORT_MAX_HOSTS);
1564 break;
1565 }
1566 memcpy(&tp->hosts[tp->nhosts], *addrp, hp->h_length);
1567 tp->nhosts++;
1568 }
1569
1570 nexthost:
1571 hostname = hostend;
1572 } while (hostname != NULL);
1573 free(hostlist);
1574
1575 if (tp->nhosts == 0) {
1576 if (errbits & 1) {
1577 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): a temporary error occurred",
1578 tp->hostname);
1579 return EX_TEMPFAIL;
1580 }
1581 else {
1582 libspamc_log(flags, LOG_ERR, "could not resolve any hosts (%s): no such host",
1583 tp->hostname);
1584 return EX_NOHOST;
1585 }
1586 }
1587
1588 /* QUASI-LOAD-BALANCING
1589 *
1590 * If the user wants to do quasi load balancing, "rotate"
1591 * the list by a random amount based on the current time.
1592 * This may later be truncated to a single item. This is
1593 * meaningful only if we have more than one host.
1594 */
1595 if ((flags & SPAMC_RANDOMIZE_HOSTS) && tp->nhosts > 1) {
1596 _randomize_hosts(tp);
1597 }
1598
1599 /* If the user wants no fallback, simply truncate the host
1600 * list to just one - this pretends that this is the extent
1601 * of our connection list - then it's not a special case.
1602 */
1603 if (!(flags & SPAMC_SAFE_FALLBACK) && tp->nhosts > 1) {
1604 /* truncating list */
1605 tp->nhosts = 1;
1606 }
1607
1608 return EX_OK;
1609 }
1610
1611 /* oops, unknown transport type */
1612 return EX_OSERR;
1613 }
1614
1615 /* --------------------------------------------------------------------------- */
1616
1617 #define LOG_BUFSIZ 1023
1618
1619 #ifndef libspamc_log
1620 void
1621 libspamc_log (int flags, int level, char *msg, ...)
1622 {
1623 va_list ap;
1624 char buf[LOG_BUFSIZ+1];
1625 int len = 0;
1626
1627 va_start(ap, msg);
1628
1629 if ((flags & SPAMC_LOG_TO_STDERR) != 0) {
1630 // create a log-line buffer
1631 len = snprintf(buf, LOG_BUFSIZ, "spamc: ");
1632 len += vsnprintf(buf+len, LOG_BUFSIZ-len, msg, ap);
1633
1634 // avoid buffer overflow
1635 if (len > (LOG_BUFSIZ-2)) { len = (LOG_BUFSIZ-3); }
1636
1637 len += snprintf(buf+len, LOG_BUFSIZ-len, "\n");
1638 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
1639 (void) write (2, buf, len);
1640
1641 } else {
1642 vsnprintf(buf, LOG_BUFSIZ, msg, ap);
1643 buf[LOG_BUFSIZ] = '\0'; /* ensure termination */
1644 #ifndef _WIN32
1645 syslog (level, "%s", buf);
1646 #else
1647 (void) level; /* not used. suppress compiler warning */
1648 fprintf (stderr, "%s\n", buf);
1649 #endif
1650 }
1651
1652 va_end(ap);
1653 }
1654 #endif
1655
1656 /* --------------------------------------------------------------------------- */
1657
1658 /*
1659 * Unit tests. Must be built externally, e.g.:
1660 *
1661 * gcc -g -DLIBSPAMC_UNIT_TESTS spamd/spamc.c spamd/libspamc.c spamd/utils.c -o libspamctest
1662 * ./libspamctest
1663 *
1664 */
1665 #ifdef LIBSPAMC_UNIT_TESTS
1666
1667 static void _test_locale_safe_string_to_float_val(float input)
1668 {
1669 char inputstr[99], cmpbuf1[99], cmpbuf2[99];
1670 float output;
1671
1672 /* sprintf instead of snprintf is safe here because it is only a controlled test */
1673 sprintf(inputstr, "%f", input);
1674 output = _locale_safe_string_to_float(inputstr, 99);
1675 if (input == output) {
1676 return;
1677 }
1678
1679 /* could be a rounding error. print as string and compare those */
1680 sprintf(cmpbuf1, "%f", input);
1681 sprintf(cmpbuf2, "%f", output);
1682 if (!strcmp(cmpbuf1, cmpbuf2)) {
1683 return;
1684 }
1685
1686 printf("FAIL: input=%f != output=%f\n", input, output);
1687 }
1688
1689 static void unit_test_locale_safe_string_to_float(void)
1690 {
1691 float statictestset[] = { /* will try both +ve and -ve */
1692 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001,
1693 9.1, 9.91, 9.991, 9.9991, 9.99991, 9.999991,
1694 0.0 /* end of set constant */
1695 };
1696 float num;
1697 int i;
1698
1699 printf("starting unit_test_locale_safe_string_to_float\n");
1700 /* tests of precision */
1701 for (i = 0; statictestset[i] != 0.0; i++) {
1702 _test_locale_safe_string_to_float_val(statictestset[i]);
1703 _test_locale_safe_string_to_float_val(-statictestset[i]);
1704 _test_locale_safe_string_to_float_val(1 - statictestset[i]);
1705 _test_locale_safe_string_to_float_val(1 + statictestset[i]);
1706 }
1707 /* now exhaustive, in steps of 0.01 */
1708 for (num = -1000.0; num < 1000.0; num += 0.01) {
1709 _test_locale_safe_string_to_float_val(num);
1710 }
1711 printf("finished unit_test_locale_safe_string_to_float\n");
1712 }
1713
1714 void do_libspamc_unit_tests(void)
1715 {
1716 unit_test_locale_safe_string_to_float();
1717 exit(0);
1718 }
1719
1720 #endif /* LIBSPAMC_UNIT_TESTS */