Debianization.
[libspamc] / libspamc.c
1 /*
2 * This code is copyright 2001 by Craig Hughes
3 * Portions copyright 2002 by Brad Jorsch
4 * It is licensed under the same license as Perl itself. The text of this
5 * license is included in the SpamAssassin distribution in the file named
6 * "License".
7 */
8
9 #include "config.h"
10 #include "libspamc.h"
11 #include "utils.h"
12
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <syslog.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <netinet/in.h>
21 #include <netinet/tcp.h>
22 #include <arpa/inet.h>
23
24 #ifdef HAVE_SYSEXITS_H
25 #include <sysexits.h>
26 #endif
27 #ifdef HAVE_ERRNO_H
28 #include <errno.h>
29 #endif
30 #ifdef HAVE_SYS_ERRNO_H
31 #include <sys/errno.h>
32 #endif
33 #ifdef HAVE_TIME_H
34 #include <time.h>
35 #endif
36 #ifdef HAVE_SYS_TIME_H
37 #include <sys/time.h>
38 #endif
39
40 #define MAX_CONNECT_RETRIES 3
41 #define CONNECT_RETRY_SLEEP 1
42
43 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
44 /* KAM 12-4-01 */
45 #ifndef HAVE_SHUT_RD
46 #define SHUT_RD (0) /* No more receptions. */
47 #define SHUT_WR (1) /* No more transmissions. */
48 #define SHUT_RDWR (2) /* No more receptions or transmissions. */
49 #endif
50
51 #ifndef HAVE_H_ERRNO
52 #define h_errno errno
53 #endif
54
55 #ifndef HAVE_OPTARG
56 extern char *optarg;
57 #endif
58
59 #ifndef HAVE_INADDR_NONE
60 #define INADDR_NONE ((in_addr_t) 0xffffffff)
61 #endif
62
63 /* jm: turned off for now, it should not be necessary. */
64 #undef USE_TCP_NODELAY
65
66 #ifndef HAVE_EX__MAX
67 /* jm: very conservative figure, should be well out of range on almost all NIXes */
68 #define EX__MAX 200
69 #endif
70
71 #undef DO_CONNECT_DEBUG_SYSLOGS
72 /* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */
73
74 static const int ESC_PASSTHROUGHRAW = EX__MAX+666;
75
76 /* set EXPANSION_ALLOWANCE to something more than might be
77 added to a message in X-headers and the report template */
78 static const int EXPANSION_ALLOWANCE = 16384;
79
80 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
81 of the data streams before and after processing by spamd
82 Aug 7 2002 jm: no longer seems to be used
83 static const int NUM_CHECK_BYTES = 32;
84 */
85
86 /* Set the protocol version that this spamc speaks */
87 static const char *PROTOCOL_VERSION="SPAMC/1.3";
88
89 int libspamc_timeout = 0;
90
91 static int
92 try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
93 int hent_port, int *sockptr)
94 {
95 #ifdef USE_TCP_NODELAY
96 int value;
97 #endif
98 int mysock = -1;
99 int status = -1;
100 int origerr;
101 int numloops;
102 int hostnum = 0;
103 struct sockaddr_in addrbuf, *addr;
104 struct in_addr inaddrlist[256];
105
106 #ifdef DO_CONNECT_DEBUG_SYSLOGS
107 int dbgiter; char dbgbuf[2048]; int dbgbuflen = 0;
108 #endif
109
110 /* NOTE: do not call syslog() (unless you are about to return) before
111 * we take a copy of the h_addr_list.
112 */
113
114 /* only one set of connection targets can be used. assert this */
115 if (argaddr == NULL && hent == NULL) {
116 syslog (LOG_ERR, "oops! both NULL in try_to_connect");
117 return EX_SOFTWARE;
118 } else if (argaddr != NULL && hent != NULL) {
119 syslog (LOG_ERR, "oops! both non-NULL in try_to_connect");
120 return EX_SOFTWARE;
121 }
122
123 /* take a copy of the h_addr_list part of the struct hostent */
124 if (hent != NULL) {
125 memset (inaddrlist, 0, sizeof(inaddrlist));
126
127 for (hostnum=0; hent->h_addr_list[hostnum] != 0; hostnum++) {
128
129 #ifdef DO_CONNECT_DEBUG_SYSLOGS
130 dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen,
131 "[%d %lx: %d.%d.%d.%d]",
132 hostnum, hent->h_addr_list[hostnum],
133 hent->h_addr_list[hostnum][0],
134 hent->h_addr_list[hostnum][1],
135 hent->h_addr_list[hostnum][2],
136 hent->h_addr_list[hostnum][3]);
137 #endif
138
139 if (hostnum > 255) {
140 syslog (LOG_ERR, "too many address in hostent (%d), ignoring others",
141 hostnum);
142 break;
143 }
144
145 if (hent->h_addr_list[hostnum] == NULL) {
146 /* shouldn't happen */
147 syslog (LOG_ERR, "hent->h_addr_list[hostnum] == NULL! foo!");
148 return EX_SOFTWARE;
149 }
150
151 #ifdef DO_CONNECT_DEBUG_SYSLOGS
152 dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen,
153 "[%d: %d.%d.%d.%d] ", sizeof (struct in_addr),
154 hent->h_addr_list[hostnum][0],
155 hent->h_addr_list[hostnum][1],
156 hent->h_addr_list[hostnum][2],
157 hent->h_addr_list[hostnum][3]);
158 #endif
159
160 memcpy ((void *) &(inaddrlist[hostnum]),
161 (void *) hent->h_addr_list[hostnum],
162 sizeof (struct in_addr));
163 }
164
165 #ifdef DO_CONNECT_DEBUG_SYSLOGS
166 syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0;
167 #endif
168 }
169
170
171 #ifdef DO_CONNECT_DEBUG_SYSLOGS
172 for (dbgiter = 0; dbgiter < hostnum; dbgiter++) {
173 syslog (LOG_DEBUG, "dbg: host addr %d/%d = %lx at %lx", dbgiter, hostnum,
174 inaddrlist[dbgiter].s_addr, &(inaddrlist[dbgiter]));
175 }
176 #endif
177
178 hent = NULL; /* cannot use hent after this point, syslog() may overwrite it */
179
180 #ifdef DO_CONNECT_DEBUG_SYSLOGS
181 syslog (LOG_DEBUG, "dbg: socket");
182 #endif
183
184 if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
185 {
186 origerr = errno; /* take a copy before syslog() */
187 syslog (LOG_ERR, "socket() to spamd failed: %m");
188 switch(origerr)
189 {
190 case EPROTONOSUPPORT:
191 case EINVAL:
192 return EX_SOFTWARE;
193 case EACCES:
194 return EX_NOPERM;
195 case ENFILE:
196 case EMFILE:
197 case ENOBUFS:
198 case ENOMEM:
199 return EX_OSERR;
200 default:
201 return EX_SOFTWARE;
202 }
203 }
204
205 #ifdef USE_TCP_NODELAY
206 /* TODO: should this be up above the connect()? */
207 value = 1; /* make this explicit! */
208 if(-1 == setsockopt(mysock,0,TCP_NODELAY,&value,sizeof(value)))
209 {
210 switch(errno)
211 {
212 case EBADF:
213 case ENOTSOCK:
214 case ENOPROTOOPT:
215 case EFAULT:
216 syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
217 close (mysock);
218 return EX_SOFTWARE;
219
220 default:
221 break; /* ignored */
222 }
223 }
224 #endif
225
226 for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
227
228 #ifdef DO_CONNECT_DEBUG_SYSLOGS
229 syslog (LOG_DEBUG, "dbg: connect() to spamd %d", numloops);
230 #endif
231
232 if (argaddr != NULL) {
233 addr = (struct sockaddr_in *) argaddr; /* use the one provided */
234
235 #ifdef DO_CONNECT_DEBUG_SYSLOGS
236 syslog (LOG_DEBUG, "dbg: using argaddr");
237 #endif
238
239 } else {
240 /* cycle through the addrs in hent */
241 memset(&addrbuf, 0, sizeof(addrbuf));
242 addrbuf.sin_family=AF_INET;
243 addrbuf.sin_port=htons(hent_port);
244
245 if (sizeof(addrbuf.sin_addr) != sizeof(struct in_addr)) { /* shouldn't happen */
246 syslog (LOG_ERR,
247 "foo! sizeof(sockaddr.sin_addr) != sizeof(struct in_addr)");
248 return EX_SOFTWARE;
249 }
250
251 #ifdef DO_CONNECT_DEBUG_SYSLOGS
252 syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx",
253 numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum]));
254 #endif
255
256 memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]),
257 sizeof(addrbuf.sin_addr));
258 addr = &addrbuf;
259
260 #ifdef DO_CONNECT_DEBUG_SYSLOGS
261 syslog (LOG_DEBUG, "dbg: conn addr %d/%d = %lx",
262 numloops%hostnum, hostnum, addrbuf.sin_addr.s_addr);
263 #endif
264
265 }
266
267 #ifdef DO_CONNECT_DEBUG_SYSLOGS
268 syslog (LOG_DEBUG, "dbg: connect() to spamd at %s",
269 inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
270 #endif
271 status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
272
273 #ifdef DO_CONNECT_DEBUG_SYSLOGS
274 syslog (LOG_DEBUG, "dbg: connect() to spamd at %s done",
275 inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
276 #endif
277
278 if (status < 0)
279 {
280 origerr = errno; /* take a copy before syslog() */
281 syslog (LOG_ERR, "connect() to spamd at %s failed, retrying (%d/%d): %m",
282 inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
283 numloops+1, MAX_CONNECT_RETRIES);
284 sleep(CONNECT_RETRY_SLEEP);
285
286 } else {
287 *sockptr = mysock;
288 return EX_OK;
289 }
290 }
291
292 /* failed, even with a few retries */
293 close (mysock);
294 syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
295 MAX_CONNECT_RETRIES);
296
297 switch(origerr)
298 {
299 case EBADF:
300 case EFAULT:
301 case ENOTSOCK:
302 case EISCONN:
303 case EADDRINUSE:
304 case EINPROGRESS:
305 case EALREADY:
306 case EAFNOSUPPORT:
307 return EX_SOFTWARE;
308 case ECONNREFUSED:
309 case ETIMEDOUT:
310 case ENETUNREACH:
311 return EX_UNAVAILABLE;
312 case EACCES:
313 return EX_NOPERM;
314 default:
315 return EX_SOFTWARE;
316 }
317 }
318
319 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
320 * message_dump, lookup_host, message_filter, and message_process, and a bunch
321 * of helper functions.
322 */
323
324 static void clear_message(struct message *m){
325 m->type=MESSAGE_NONE;
326 m->raw=NULL; m->raw_len=0;
327 m->pre=NULL; m->pre_len=0;
328 m->msg=NULL; m->msg_len=0;
329 m->post=NULL; m->post_len=0;
330 m->is_spam=EX_TOOBIG;
331 m->score=0.0; m->threshold=0.0;
332 m->out=NULL; m->out_len=0;
333 m->content_length=-1;
334 }
335
336 static int
337 message_read_raw(int fd, struct message *m){
338 clear_message(m);
339 if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
340 m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1);
341 if(m->raw_len<=0){
342 free(m->raw); m->raw=NULL; m->raw_len=0;
343 return EX_IOERR;
344 }
345 m->type=MESSAGE_ERROR;
346 if(m->raw_len>m->max_len) return EX_TOOBIG;
347 m->type=MESSAGE_RAW;
348 m->msg=m->raw;
349 m->msg_len=m->raw_len;
350 m->out=m->msg;
351 m->out_len=m->msg_len;
352 return EX_OK;
353 }
354
355 static int message_read_bsmtp(int fd, struct message *m){
356 off_t i, j;
357 char prev;
358
359 clear_message(m);
360 if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
361
362 /* Find the DATA line */
363 m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1);
364 if(m->raw_len<=0){
365 free(m->raw); m->raw=NULL; m->raw_len=0;
366 return EX_IOERR;
367 }
368 m->type=MESSAGE_ERROR;
369 if(m->raw_len>m->max_len) return EX_TOOBIG;
370 m->pre=m->raw;
371 for(i=0; i<m->raw_len-6; i++){
372 if((m->raw[i]=='\n') &&
373 (m->raw[i+1]=='D' || m->raw[i+1]=='d') &&
374 (m->raw[i+2]=='A' || m->raw[i+2]=='a') &&
375 (m->raw[i+3]=='T' || m->raw[i+3]=='t') &&
376 (m->raw[i+4]=='A' || m->raw[i+4]=='a') &&
377 ((m->raw[i+5]=='\r' && m->raw[i+6]=='\n') || m->raw[i+5]=='\n')){
378 /* Found it! */
379 i+=6;
380 if(m->raw[i-1]=='\r') i++;
381 m->pre_len=i;
382 m->msg=m->raw+i;
383 m->msg_len=m->raw_len-i;
384 break;
385 }
386 }
387 if(m->msg==NULL) return EX_DATAERR;
388
389 /* Find the end-of-DATA line */
390 prev='\n';
391 for(i=j=0; i<m->msg_len; i++){
392 if(prev=='\n' && m->msg[i]=='.'){
393 /* Dot at the beginning of a line */
394 if((m->msg[i+1]=='\r' && m->msg[i+2]=='\n') || m->msg[i+1]=='\n'){
395 /* Lone dot! That's all, folks */
396 m->post=m->msg+i;
397 m->post_len=m->msg_len-i;
398 m->msg_len=j;
399 break;
400 } else if(m->msg[i+1]=='.'){
401 /* Escaping dot, eliminate. */
402 prev='.';
403 continue;
404 } /* Else an ordinary dot, drop down to ordinary char handler */
405 }
406 prev=m->msg[i];
407 m->msg[j++]=m->msg[i];
408 }
409
410 m->type=MESSAGE_BSMTP;
411 m->out=m->msg;
412 m->out_len=m->msg_len;
413 return EX_OK;
414 }
415
416 int message_read(int fd, int flags, struct message *m){
417 libspamc_timeout = 0;
418
419 switch(flags&SPAMC_MODE_MASK){
420 case SPAMC_RAW_MODE:
421 return message_read_raw(fd, m);
422
423 case SPAMC_BSMTP_MODE:
424 return message_read_bsmtp(fd, m);
425
426 default:
427 syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK);
428 return EX_USAGE;
429 }
430 }
431
432 long message_write(int fd, struct message *m){
433 long total=0;
434 off_t i, j;
435 off_t jlimit;
436 char buffer[1024];
437
438 /* if we're to output a message, m->is_spam will be EX_OUTPUTMESSAGE */
439 if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
440 return full_write(fd, (unsigned char *) m->out, m->out_len);
441 }
442
443 if (m->is_spam != EX_OUTPUTMESSAGE && m->is_spam != EX_TOOBIG) {
444 syslog(LOG_ERR,
445 "Cannot write this message, is_spam = %d!\n", m->is_spam);
446 return -1;
447 }
448
449 switch(m->type){
450 case MESSAGE_NONE:
451 syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n");
452 return -1;
453
454 case MESSAGE_ERROR:
455 return full_write(fd, (unsigned char *) m->raw, m->raw_len);
456
457 case MESSAGE_RAW:
458 return full_write(fd, (unsigned char *) m->out, m->out_len);
459
460 case MESSAGE_BSMTP:
461 total=full_write(fd, (unsigned char *) m->pre, m->pre_len);
462 for(i=0; i<m->out_len; ){
463 jlimit = (off_t) (sizeof(buffer)/sizeof(*buffer)-4);
464 for(j=0; i < (off_t) m->out_len &&
465 j < jlimit;)
466 {
467 if(i+1<m->out_len && m->out[i]=='\n' && m->out[i+1]=='.'){
468 if (j > jlimit - 4) {
469 break; /* avoid overflow */
470 }
471 buffer[j++]=m->out[i++];
472 buffer[j++]=m->out[i++];
473 buffer[j++]='.';
474 } else {
475 buffer[j++]=m->out[i++];
476 }
477 }
478 total+=full_write(fd, (unsigned char *) buffer, j);
479 }
480 return total+full_write(fd, (unsigned char *) m->post, m->post_len);
481
482 default:
483 syslog(LOG_ERR, "Unknown message type %d\n", m->type);
484 return -1;
485 }
486 }
487
488 void message_dump(int in_fd, int out_fd, struct message *m){
489 char buf[8196];
490 int bytes;
491
492 if(m!=NULL && m->type!=MESSAGE_NONE) {
493 message_write(out_fd, m);
494 }
495 while((bytes=full_read(in_fd, (unsigned char *) buf, 8192, 8192))>0){
496 if (bytes!=full_write(out_fd, (unsigned char *) buf, bytes)) {
497 syslog(LOG_ERR, "oops! message_dump of %d returned different", bytes);
498 }
499 }
500 }
501
502 static int
503 _spamc_read_full_line (struct message *m, int flags, SSL *ssl, int sock,
504 char *buf, int *lenp, int bufsiz)
505 {
506 int failureval;
507 int bytesread = 0;
508 int len;
509
510 /* Now, read from spamd */
511 for(len=0; len<bufsiz-1; len++) {
512 if(flags&SPAMC_USE_SSL) {
513 bytesread = ssl_timeout_read (ssl, buf+len, 1);
514 } else {
515 bytesread = fd_timeout_read (sock, buf+len, 1);
516 }
517
518 if(buf[len]=='\n') {
519 buf[len]='\0';
520 if (len > 0 && buf[len-1] == '\r') {
521 len--;
522 buf[len]='\0';
523 }
524 *lenp = len;
525 return EX_OK;
526 }
527
528 if(bytesread<=0){
529 failureval = EX_IOERR; goto failure;
530 }
531 }
532
533 syslog(LOG_ERR, "spamd responded with line of %d bytes, dying", len);
534 failureval = EX_TOOBIG;
535
536 failure:
537 return failureval;
538 }
539
540 /*
541 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
542 * work around using our own locale-independent float-parser code.
543 */
544 static float
545 _locale_safe_string_to_float (char *buf, int siz)
546 {
547 int is_neg;
548 char *cp, *dot;
549 int divider;
550 float ret, postdot;
551
552 buf[siz-1] = '\0'; /* ensure termination */
553
554 /* ok, let's illustrate using "100.033" as an example... */
555
556 is_neg = 0;
557 if (*buf == '-') { is_neg = 1; }
558
559 ret = (float) (strtol (buf, &dot, 10));
560 if (dot == NULL) { return 0.0; }
561 if (dot != NULL && *dot != '.') { return ret; }
562
563 /* ex: ret == 100.0 */
564
565 cp = (dot + 1);
566 postdot = (float) (strtol (cp, NULL, 10));
567 if (postdot == 0.0) { return ret; }
568
569 /* ex: postdot == 33.0, cp="033" */
570
571 /* now count the number of decimal places and figure out what power of 10 to use */
572 divider = 1;
573 while (*cp != '\0') {
574 divider *= 10; cp++;
575 }
576
577 /* ex:
578 * cp="033", divider=1
579 * cp="33", divider=10
580 * cp="3", divider=100
581 * cp="", divider=1000
582 */
583
584 if (is_neg) {
585 ret -= (postdot / ((float) divider));
586 } else {
587 ret += (postdot / ((float) divider));
588 }
589 /* ex: ret == 100.033, tada! ... hopefully */
590
591 return ret;
592 }
593
594 static int
595 _handle_spamd_header (struct message *m, int flags, char *buf, int len)
596 {
597 char is_spam[6];
598 char s_str[20], t_str[20];
599
600 /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;)
601 * let's stick with it for this parser.
602 * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale.
603 * work around using our own locale-independent float-parser code.
604 */
605 if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3)
606 {
607 m->score = _locale_safe_string_to_float (s_str, 20);
608 m->threshold = _locale_safe_string_to_float (t_str, 20);
609
610 /* Format is "Spam: x; y / x" */
611 m->is_spam=strcasecmp("true", is_spam) == 0 ? EX_ISSPAM: EX_NOTSPAM;
612
613 if(flags&SPAMC_CHECK_ONLY) {
614 m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE,
615 "%.1f/%.1f\n", m->score, m->threshold);
616 }
617 return EX_OK;
618
619 } else if(sscanf(buf, "Content-length: %d", &m->content_length) == 1) {
620 if (m->content_length < 0) {
621 syslog(LOG_ERR, "spamd responded with bad Content-length '%s'", buf);
622 return EX_PROTOCOL;
623 }
624 return EX_OK;
625 }
626
627 syslog(LOG_ERR, "spamd responded with bad header '%s'", buf);
628 return EX_PROTOCOL;
629 }
630
631 static int _message_filter(const struct sockaddr *addr,
632 const struct hostent *hent, int hent_port, char *username,
633 int flags, struct message *m)
634 {
635 char buf[8192];
636 int bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
637 int len, i;
638 int sock = -1;
639 char versbuf[20];
640 float version;
641 int response;
642 int failureval;
643 SSL_CTX* ctx;
644 SSL* ssl;
645 SSL_METHOD *meth;
646
647 if (flags&SPAMC_USE_SSL) {
648 #ifdef SPAMC_SSL
649 SSLeay_add_ssl_algorithms();
650 meth = SSLv2_client_method();
651 SSL_load_error_strings();
652 ctx = SSL_CTX_new(meth);
653 #else
654 (void) ssl; (void) meth; (void) ctx; /* avoid "unused" warnings */
655 syslog(LOG_ERR, "spamc not built with SSL support");
656 return EX_SOFTWARE;
657 #endif
658 }
659
660 m->is_spam=EX_TOOBIG;
661 if((m->out=malloc(m->max_len+EXPANSION_ALLOWANCE+1))==NULL){
662 failureval = EX_OSERR; goto failure;
663 }
664 m->out_len=0;
665
666
667 /* Build spamd protocol header */
668 if(flags & SPAMC_CHECK_ONLY)
669 len=snprintf(buf, bufsiz, "CHECK %s\r\n", PROTOCOL_VERSION);
670 else if(flags & SPAMC_REPORT_IFSPAM)
671 len=snprintf(buf, bufsiz, "REPORT_IFSPAM %s\r\n", PROTOCOL_VERSION);
672 else if(flags & SPAMC_REPORT)
673 len=snprintf(buf, bufsiz, "REPORT %s\r\n", PROTOCOL_VERSION);
674 else if(flags & SPAMC_SYMBOLS)
675 len=snprintf(buf, bufsiz, "SYMBOLS %s\r\n", PROTOCOL_VERSION);
676 else
677 len=snprintf(buf, bufsiz, "PROCESS %s\r\n", PROTOCOL_VERSION);
678
679 if(len<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
680 if(username!=NULL){
681 len+=i=snprintf(buf+len, bufsiz-len, "User: %s\r\n", username);
682 if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
683 }
684 len+=i=snprintf(buf+len, bufsiz-len, "Content-length: %d\r\n", m->msg_len);
685 if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
686 len+=i=snprintf(buf+len, bufsiz-len, "\r\n");
687 if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
688
689 libspamc_timeout = m->timeout;
690
691 if((i=try_to_connect(addr, (struct hostent *) hent,
692 hent_port, &sock)) != EX_OK)
693 {
694 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
695 return i;
696 }
697
698 if(flags&SPAMC_USE_SSL) {
699 #ifdef SPAMC_SSL
700 ssl = SSL_new(ctx);
701 SSL_set_fd(ssl, sock);
702 SSL_connect(ssl);
703 #endif
704 }
705
706 /* Send to spamd */
707 if(flags&SPAMC_USE_SSL) {
708 #ifdef SPAMC_SSL
709 SSL_write(ssl, buf, len);
710 SSL_write(ssl, m->msg, m->msg_len);
711 #endif
712 } else {
713 full_write(sock, (unsigned char *) buf, len);
714 full_write(sock, (unsigned char *) m->msg, m->msg_len);
715 shutdown(sock, SHUT_WR);
716 }
717
718 /* ok, now read and parse it. SPAMD/1.2 line first... */
719 failureval = _spamc_read_full_line (m, flags, ssl, sock, buf, &len, bufsiz);
720 if (failureval != EX_OK) { goto failure; }
721
722 if(sscanf(buf, "SPAMD/%s %d %*s", versbuf, &response)!=2) {
723 syslog(LOG_ERR, "spamd responded with bad string '%s'", buf);
724 failureval = EX_PROTOCOL; goto failure;
725 }
726
727 version = _locale_safe_string_to_float (versbuf, 20);
728 if (version < 1.0) {
729 syslog(LOG_ERR, "spamd responded with bad version string '%s'", versbuf);
730 failureval = EX_PROTOCOL; goto failure;
731 }
732
733 m->score = 0;
734 m->threshold = 0;
735 m->is_spam = EX_TOOBIG;
736 while (1) {
737 failureval = _spamc_read_full_line (m, flags, ssl, sock, buf, &len, bufsiz);
738 if (failureval != EX_OK) { goto failure; }
739
740 if (len == 0 && buf[0] == '\0') {
741 break; /* end of headers */
742 }
743
744 if (_handle_spamd_header(m, flags, buf, len) < 0) {
745 failureval = EX_PROTOCOL; goto failure;
746 }
747 }
748
749 len = 0; /* overwrite those headers */
750
751 if (flags&SPAMC_CHECK_ONLY) {
752 close(sock); sock = -1;
753 if (m->is_spam == EX_TOOBIG) {
754 /* We should have gotten headers back... Damnit. */
755 failureval = EX_PROTOCOL; goto failure;
756 }
757 return EX_OK;
758 }
759 else {
760 m->is_spam=EX_OUTPUTMESSAGE;
761 if (m->content_length < 0) {
762 /* should have got a length too. */
763 failureval = EX_PROTOCOL; goto failure;
764 }
765
766 if (flags&SPAMC_USE_SSL) {
767 len = ssl_timeout_read (ssl, m->out+m->out_len,
768 m->max_len+EXPANSION_ALLOWANCE+1-m->out_len);
769 } else{
770 len = full_read (sock, (unsigned char *) m->out+m->out_len,
771 m->max_len+EXPANSION_ALLOWANCE+1-m->out_len,
772 m->max_len+EXPANSION_ALLOWANCE+1-m->out_len);
773 }
774
775
776 if(len+m->out_len>m->max_len+EXPANSION_ALLOWANCE){
777 failureval = EX_TOOBIG; goto failure;
778 }
779 m->out_len+=len;
780
781 shutdown(sock, SHUT_RD);
782 close(sock); sock = -1;
783 }
784 libspamc_timeout = 0;
785
786 if(m->out_len!=m->content_length) {
787 syslog(LOG_ERR, "failed sanity check, %d bytes claimed, %d bytes seen",
788 m->content_length, m->out_len);
789 failureval = EX_PROTOCOL; goto failure;
790 }
791
792 return EX_OK;
793
794 failure:
795 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
796 if (sock != -1) {
797 close(sock);
798 }
799 libspamc_timeout = 0;
800
801 if(flags&SPAMC_USE_SSL) {
802 #ifdef SPAMC_SSL
803 SSL_free(ssl);
804 SSL_CTX_free(ctx);
805 #endif
806 }
807 return failureval;
808 }
809
810 static int _lookup_host(const char *hostname, struct hostent *out_hent)
811 {
812 struct hostent *hent = NULL;
813 int origherr;
814
815 /* no need to try using inet_addr(), gethostbyname() will do that */
816
817 if (NULL == (hent = gethostbyname(hostname))) {
818 origherr = h_errno; /* take a copy before syslog() */
819 syslog (LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
820 hostname, origherr);
821 switch(origherr)
822 {
823 case HOST_NOT_FOUND:
824 case NO_ADDRESS:
825 case NO_RECOVERY:
826 return EX_NOHOST;
827 case TRY_AGAIN:
828 return EX_TEMPFAIL;
829 default:
830 return EX_OSERR;
831 }
832 }
833
834 memcpy (out_hent, hent, sizeof(struct hostent));
835
836 return EX_OK;
837 }
838
839 int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags){
840 struct hostent hent;
841 int ret;
842 struct message m;
843
844 m.type=MESSAGE_NONE;
845
846 ret=lookup_host_for_failover(hostname, &hent);
847 if(ret!=EX_OK) goto FAIL;
848
849 m.max_len=max_size;
850 ret=message_read(in_fd, flags, &m);
851 if(ret!=EX_OK) goto FAIL;
852 ret=message_filter_with_failover(&hent, port, username, flags, &m);
853 if(ret!=EX_OK) goto FAIL;
854 if(message_write(out_fd, &m)<0) goto FAIL;
855 if(m.is_spam!=EX_TOOBIG) {
856 message_cleanup(&m);
857 return m.is_spam;
858 }
859 message_cleanup(&m);
860 return ret;
861
862 FAIL:
863 if(flags&SPAMC_CHECK_ONLY){
864 full_write(out_fd, (unsigned char *) "0/0\n", 4);
865 message_cleanup(&m);
866 return EX_NOTSPAM;
867 } else {
868 message_dump(in_fd, out_fd, &m);
869 message_cleanup(&m);
870 return ret;
871 }
872 }
873
874 void message_cleanup(struct message *m) {
875 if (m->out != NULL && m->out != m->raw) free(m->out);
876 if (m->raw != NULL) free(m->raw);
877 clear_message(m);
878 }
879
880 /* Aug 14, 2002 bj: Obsolete! */
881 int process_message(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int my_check_only, const int my_safe_fallback){
882 int flags;
883
884 flags=SPAMC_RAW_MODE;
885 if(my_check_only) flags|=SPAMC_CHECK_ONLY;
886 if(my_safe_fallback) flags|=SPAMC_SAFE_FALLBACK;
887
888 return message_process(hostname, port, username, max_size, in_fd, out_fd, flags);
889 }
890
891 /* public APIs, which call into the static code and enforce sockaddr-OR-hostent
892 * conventions */
893
894 int lookup_host(const char *hostname, int port, struct sockaddr *out_addr)
895 {
896 struct sockaddr_in *addr = (struct sockaddr_in *)out_addr;
897 struct hostent hent;
898 int ret;
899
900 memset(&out_addr, 0, sizeof(out_addr));
901 addr->sin_family=AF_INET;
902 addr->sin_port=htons(port);
903 ret = _lookup_host(hostname, &hent);
904 memcpy (&(addr->sin_addr), hent.h_addr, sizeof(addr->sin_addr));
905 return ret;
906 }
907
908 int lookup_host_for_failover(const char *hostname, struct hostent *hent) {
909 return _lookup_host(hostname, hent);
910 }
911
912 int message_filter(const struct sockaddr *addr, char *username, int flags,
913 struct message *m)
914 { return _message_filter (addr, NULL, 0, username, flags, m); }
915
916 int message_filter_with_failover (const struct hostent *hent, int port,
917 char *username, int flags, struct message *m)
918 { return _message_filter (NULL, hent, port, username, flags, m); }
919