5d5f988727d9aac70ee76014b2d0644530d595bc
[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 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