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