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