0.8.8claws26
[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 for use with SpamAssassin according to the terms of the Perl Artistic License
5  * The text of this license is included in the SpamAssassin distribution in the file named "License"
6  */
7
8 #include "libspamc.h"
9 #include "utils.h"
10 #include <unistd.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <syslog.h>
15 #include <sys/types.h>
16 #include <sys/socket.h>
17 #include <netinet/in.h>
18 #include <netinet/tcp.h>
19 #include <netdb.h>
20 #include <arpa/inet.h>
21
22 #ifdef HAVE_SYSEXITS_H
23 #include <sysexits.h>
24 #endif
25 #ifdef HAVE_ERRNO_H
26 #include <errno.h>
27 #endif
28 #ifdef HAVE_SYS_ERRNO_H
29 #include <sys/errno.h>
30 #endif
31 #ifdef HAVE_TIME_H
32 #include <time.h>
33 #endif
34 #ifdef HAVE_SYS_TIME_H
35 #include <sys/time.h>
36 #endif
37
38 #define MAX_CONNECT_RETRIES 3
39 #define CONNECT_RETRY_SLEEP 1
40
41 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
42 /* KAM 12-4-01 */
43 #ifndef HAVE_SHUT_RD
44 #define SHUT_RD (0)   /* No more receptions.  */
45 #define SHUT_WR (1)   /* No more transmissions.  */
46 #define SHUT_RDWR (2) /* No more receptions or transmissions.  */
47 #endif
48
49 #ifndef HAVE_H_ERRNO
50 #define h_errno errno
51 #endif
52
53 #ifndef HAVE_OPTARG
54 extern char *optarg;
55 #endif
56
57 #ifndef HAVE_INADDR_NONE
58 #define INADDR_NONE             ((in_addr_t) 0xffffffff)
59 #endif
60
61 /* jm: turned off for now, it should not be necessary. */
62 #undef USE_TCP_NODELAY
63
64 #ifndef HAVE_EX__MAX
65 /* jm: very conservative figure, should be well out of range on almost all NIXes */
66 #define EX__MAX 200 
67 #endif
68
69 static const int ESC_PASSTHROUGHRAW = EX__MAX+666;
70
71 /* set EXPANSION_ALLOWANCE to something more than might be
72    added to a message in X-headers and the report template */
73 static const int EXPANSION_ALLOWANCE = 16384;
74
75 /* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end
76    of the data streams before and after processing by spamd 
77    Aug  7 2002 jm: no longer seems to be used
78    static const int NUM_CHECK_BYTES = 32;
79  */
80
81 /* Set the protocol version that this spamc speaks */
82 static const char *PROTOCOL_VERSION="SPAMC/1.2";
83
84 /* Aug 14, 2002 bj: No more ctx! */
85 static int
86 try_to_connect (const struct sockaddr *addr, int *sockptr)
87 {
88 #ifdef USE_TCP_NODELAY
89   int value;
90 #endif
91   int mysock = -1;
92   int status = -1;
93   int origerr;
94   int numloops;
95
96   if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
97   {
98     origerr = errno;    /* take a copy before syslog() */
99     syslog (LOG_ERR, "socket() to spamd failed: %m");
100     switch(origerr)
101     {
102     case EPROTONOSUPPORT:
103     case EINVAL:
104       return EX_SOFTWARE;
105     case EACCES:
106       return EX_NOPERM;
107     case ENFILE:
108     case EMFILE:
109     case ENOBUFS:
110     case ENOMEM:
111       return EX_OSERR;
112     default:
113       return EX_SOFTWARE;
114     }
115   }
116   
117 #ifdef USE_TCP_NODELAY
118   /* TODO: should this be up above the connect()? */
119   value = 1;            /* make this explicit! */
120   if(-1 == setsockopt(mysock,0,TCP_NODELAY,&value,sizeof(value)))
121   {
122     switch(errno)
123     {
124     case EBADF:
125     case ENOTSOCK:
126     case ENOPROTOOPT:
127     case EFAULT:
128       syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
129       return EX_SOFTWARE;
130
131     default:
132       break;            /* ignored */
133     }
134   }
135 #endif
136
137   for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
138     status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
139
140     if (status < 0)
141     {
142       origerr = errno;        /* take a copy before syslog() */
143       syslog (LOG_ERR, "connect() to spamd at %s failed, retrying (%d/%d): %m",
144                         inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
145                         numloops+1, MAX_CONNECT_RETRIES);
146       sleep(1);
147
148     } else {
149       *sockptr = mysock;
150       return EX_OK;
151     }
152   }
153  
154   /* failed, even with a few retries */
155   syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
156        MAX_CONNECT_RETRIES);
157  
158   switch(origerr)
159   {
160    case EBADF:
161    case EFAULT:
162    case ENOTSOCK:
163    case EISCONN:
164    case EADDRINUSE:
165    case EINPROGRESS:
166    case EALREADY:
167    case EAFNOSUPPORT:
168      return EX_SOFTWARE;
169    case ECONNREFUSED:
170    case ETIMEDOUT:
171    case ENETUNREACH:
172      return EX_UNAVAILABLE;
173    case EACCES:
174      return EX_NOPERM;
175    default:
176      return EX_SOFTWARE;
177   }
178 }
179
180 /* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write,
181  * message_dump, lookup_host, message_filter, and message_process, and a bunch
182  * of helper functions.
183  */
184
185 static void clear_message(struct message *m){
186     m->type=MESSAGE_NONE;
187     m->raw=NULL; m->raw_len=0;
188     m->pre=NULL; m->pre_len=0;
189     m->msg=NULL; m->msg_len=0;
190     m->post=NULL; m->post_len=0;
191     m->is_spam=EX_TOOBIG;
192     m->score=0.0; m->threshold=0.0;
193     m->out=NULL; m->out_len=0;
194 }
195
196 static int message_read_raw(int fd, struct message *m){
197     clear_message(m);
198     if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
199     m->raw_len=full_read(fd, m->raw, m->max_len+1, m->max_len+1);
200     if(m->raw_len<=0){
201         free(m->raw); m->raw=NULL; m->raw_len=0;
202         return EX_IOERR;
203     }
204     m->type=MESSAGE_ERROR;
205     if(m->raw_len>m->max_len) return EX_TOOBIG;
206     m->type=MESSAGE_RAW;
207     m->msg=m->raw;
208     m->msg_len=m->raw_len;
209     m->out=m->msg;
210     m->out_len=m->msg_len;
211     return EX_OK;
212 }
213
214 static int message_read_bsmtp(int fd, struct message *m){
215     off_t i, j;
216     char prev;
217
218     clear_message(m);
219     if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
220
221     /* Find the DATA line */
222     m->raw_len=full_read(fd, m->raw, m->max_len+1, m->max_len+1);
223     if(m->raw_len<=0){
224         free(m->raw); m->raw=NULL; m->raw_len=0;
225         return EX_IOERR;
226     }
227     m->type=MESSAGE_ERROR;
228     if(m->raw_len>m->max_len) return EX_TOOBIG;
229     m->pre=m->raw;
230     for(i=0; i<m->raw_len-6; i++){
231         if((m->raw[i]=='\n') &&
232            (m->raw[i+1]=='D' || m->raw[i+1]=='d') &&
233            (m->raw[i+2]=='A' || m->raw[i+2]=='a') &&
234            (m->raw[i+3]=='T' || m->raw[i+3]=='t') &&
235            (m->raw[i+4]=='A' || m->raw[i+4]=='a') &&
236            ((m->raw[i+5]=='\r' && m->raw[i+6]=='\n') || m->raw[i+5]=='\n')){
237             /* Found it! */
238             i+=6;
239             if(m->raw[i-1]=='\r') i++;
240             m->pre_len=i;
241             m->msg=m->raw+i;
242             m->msg_len=m->raw_len-i;
243             break;
244         }
245     }
246     if(m->msg==NULL) return EX_DATAERR;
247
248     /* Find the end-of-DATA line */
249     prev='\n';
250     for(i=j=0; i<m->msg_len; i++){
251         if(prev=='\n' && m->msg[i]=='.'){
252             /* Dot at the beginning of a line */
253             if((m->msg[i+1]=='\r' && m->msg[i+2]=='\n') || m->msg[i+1]=='\n'){
254                 /* Lone dot! That's all, folks */
255                 m->post=m->msg+i;
256                 m->post_len=m->msg_len-i;
257                 m->msg_len=j;
258                 break;
259             } else if(m->msg[i+1]=='.'){
260                 /* Escaping dot, eliminate. */
261                 prev='.';
262                 continue;
263             } /* Else an ordinary dot, drop down to ordinary char handler */
264         }
265         prev=m->msg[i];
266         m->msg[j++]=m->msg[i];
267     }
268
269     m->type=MESSAGE_BSMTP;
270     m->out=m->msg;
271     m->out_len=m->msg_len;
272     return EX_OK;
273 }
274
275 int message_read(int fd, int flags, struct message *m){
276     switch(flags&SPAMC_MODE_MASK){
277       case SPAMC_RAW_MODE:
278         return message_read_raw(fd, m);
279
280       case SPAMC_BSMTP_MODE:
281         return message_read_bsmtp(fd, m);
282
283       default:
284         syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK);
285         return EX_USAGE;
286     }
287 }
288
289 long message_write(int fd, struct message *m){
290     long total=0;
291     off_t i, j;
292     char buffer[1024];
293
294     if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
295         return full_write(fd, m->out, m->out_len);
296     }
297
298     switch(m->type){
299       case MESSAGE_NONE:
300         syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n");
301         return -1;
302
303       case MESSAGE_ERROR:
304         return full_write(fd, m->raw, m->raw_len);
305
306       case MESSAGE_RAW:
307         return full_write(fd, m->out, m->out_len);
308
309       case MESSAGE_BSMTP:
310         total=full_write(fd, m->pre, m->pre_len);
311         for(i=0; i<m->out_len; ){
312             for(j=0; i<m->out_len && j<sizeof(buffer)/sizeof(*buffer)-1; ){
313                 if(i+1<m->out_len && m->out[i]=='\n' && m->out[i+1]=='.'){
314                     buffer[j++]=m->out[i++];
315                     buffer[j++]=m->out[i++];
316                     buffer[j++]='.';
317                 } else {
318                     buffer[j++]=m->out[i++];
319                 }
320             }
321             total+=full_write(fd, buffer, j);
322         }
323         return total+full_write(fd, m->post, m->post_len);
324
325       default:
326         syslog(LOG_ERR, "Unknown message type %d\n", m->type);
327         return -1;
328     }
329 }
330
331 void message_dump(int in_fd, int out_fd, struct message *m){
332     char buf[8196];
333     int bytes;
334     
335     if(m!=NULL && m->type!=MESSAGE_NONE) {
336         message_write(out_fd, m);
337     }
338     while((bytes=full_read(in_fd, buf, 8192, 8192))>0){
339         if(bytes!=full_write(out_fd, buf, bytes));
340     }
341 }
342
343 int message_filter(const struct sockaddr *addr, char *username, int flags, struct message *m){
344     char *buf=NULL, is_spam[5];
345     int len, expected_len, i, header_read=0;
346     int sock;
347     float version;
348     int response;
349
350     m->is_spam=EX_TOOBIG;
351     if((buf=malloc(8192))==NULL) return EX_OSERR;
352     if((m->out=malloc(m->max_len+EXPANSION_ALLOWANCE+1))==NULL){
353         free(buf);
354         return EX_OSERR;
355     }
356     m->out_len=0;
357
358     /* Build spamd protocol header */
359     len=snprintf(buf, 1024, "%s %s\r\n", (flags&SPAMC_CHECK_ONLY)?"CHECK":"PROCESS", PROTOCOL_VERSION);
360     if(len<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
361     if(username!=NULL){
362         len+=i=snprintf(buf+len, 1024-len, "User: %s\r\n", username);
363         if(i<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
364     }
365     len+=i=snprintf(buf+len, 1024-len, "Content-length: %d\r\n", m->msg_len);
366     if(i<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
367     len+=i=snprintf(buf+len, 1024-len, "\r\n");
368     if(i<0 || len>1024){ free(buf); free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; }
369
370     if((i=try_to_connect(addr, &sock))!=EX_OK){
371         free(buf);
372         free(m->out); m->out=m->msg; m->out_len=m->msg_len;
373         return i;
374     }
375
376     /* Send to spamd */
377     full_write(sock, buf, len);
378     full_write(sock, m->msg, m->msg_len);
379     shutdown(sock, SHUT_WR);
380
381     /* Now, read from spamd */
382     for(len=0; len<8192; len++){
383         i=read(sock, buf+len, 1);
384         if(i<0){
385             free(buf);
386             free(m->out); m->out=m->msg; m->out_len=m->msg_len;
387             close(sock);
388             return EX_IOERR;
389         }
390         if(i==0){
391             /* Read to end of message! Must be a version <1.0 server */
392             if(len<100){
393                 /* Nope, communication error */
394                 free(buf);
395                 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
396                 close(sock);
397                 return EX_IOERR;
398             }
399             break;
400         }
401         if(buf[len]=='\n'){
402             buf[len]='\0';
403             if(sscanf(buf, "SPAMD/%f %d %*s", &version, &response)!=2){
404                 syslog(LOG_ERR, "spamd responded with bad string '%s'", buf);
405                 free(buf);
406                 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
407                 close(sock);
408                 return EX_PROTOCOL;
409             }
410             header_read=-1;
411             break;
412         }
413     }
414     if(!header_read){
415         /* No header, so it must be a version <1.0 server */
416         memcpy(m->out, buf, len);
417         m->out_len=len;
418     } else {
419         /* Handle different versioned headers */
420         if(version-1.0>0.01){
421             for(len=0; len<8192; len++){
422                 i=read(sock, buf+len, 1);
423                 if(i<=0){
424                     free(buf);
425                     free(m->out); m->out=m->msg; m->out_len=m->msg_len;
426                     close(sock);
427                     return (i<0)?EX_IOERR:EX_PROTOCOL;
428                 }
429                 if(buf[len]=='\n'){
430                     buf[len]='\0';
431                     if(flags&SPAMC_CHECK_ONLY){
432                         /* Check only mode, better be "Spam: x; y / x" */
433                         i=sscanf(buf, "Spam: %5s ; %f / %f", is_spam, &m->score, &m->threshold);
434                         free(buf);
435                         if(i!=3){
436                             free(m->out); m->out=m->msg; m->out_len=m->msg_len;
437                             return EX_PROTOCOL;
438                         }
439                         m->out_len=snprintf(m->out, m->max_len+EXPANSION_ALLOWANCE, "%.1f/%.1f\n", m->score, m->threshold);
440                         m->is_spam=strcasecmp("true", is_spam)?EX_NOTSPAM:EX_ISSPAM;
441                         close(sock);
442                         return EX_OK;
443                     } else {
444                         /* Not check-only, better be Content-length */
445                         if(sscanf(buf, "Content-length: %d", &expected_len)!=1){
446                             free(buf);
447                             free(m->out); m->out=m->msg; m->out_len=m->msg_len;
448                             close(sock);
449                             return EX_PROTOCOL;
450                         }
451                     }
452
453                     /* Should be end of headers now */
454                     if(full_read(sock, buf, 2, 2)!=2 || buf[0]!='\r' || buf[1]!='\n'){
455                         /* Nope, bail. */
456                         free(buf);
457                         free(m->out); m->out=m->msg; m->out_len=m->msg_len;
458                         close(sock);
459                         return EX_PROTOCOL;
460                     }
461
462                     break;
463                 }
464             }
465         }
466     }
467     free(buf);
468
469     if(flags&SPAMC_CHECK_ONLY){
470         /* We should have gotten headers back... Damnit. */
471         free(m->out); m->out=m->msg; m->out_len=m->msg_len;
472         close(sock);
473         return EX_PROTOCOL;
474     }
475
476     len=full_read(sock, m->out+m->out_len, m->max_len+EXPANSION_ALLOWANCE+1-m->out_len, m->max_len+EXPANSION_ALLOWANCE+1-m->out_len);
477     if(len+m->out_len>m->max_len+EXPANSION_ALLOWANCE){
478         free(m->out); m->out=m->msg; m->out_len=m->msg_len;
479         close(sock);
480         return EX_TOOBIG;
481     }
482     m->out_len+=len;
483
484     shutdown(sock, SHUT_RD);
485     close(sock);
486
487     if(m->out_len!=expected_len){
488         syslog(LOG_ERR, "failed sanity check, %d bytes claimed, %d bytes seen", expected_len, m->out_len);
489         free(m->out); m->out=m->msg; m->out_len=m->msg_len;
490         close(sock);
491         return EX_PROTOCOL;
492     }
493
494     return EX_OK;
495 }
496
497 int lookup_host(const char *hostname, int port, struct sockaddr *a){
498     struct sockaddr_in *addr=(struct sockaddr_in *)a;
499   struct hostent *hent;
500   int origherr;
501
502     memset(&a, 0, sizeof(a));
503
504     addr->sin_family=AF_INET;
505     addr->sin_port=htons(port);
506
507     /* first, try to mangle it directly into an addr->  This will work
508    * for numeric IP addresses, but not for hostnames...
509    */
510     addr->sin_addr.s_addr = inet_addr (hostname);
511     if (addr->sin_addr.s_addr == INADDR_NONE) {
512     /* If that failed, we can use gethostbyname() to resolve it.
513      */
514     if (NULL == (hent = gethostbyname(hostname))) {
515       origherr = h_errno;       /* take a copy before syslog() */
516       syslog (LOG_ERR, "gethostbyname(%s) failed: h_errno=%d",
517               hostname, origherr);
518       switch(origherr)
519       {
520       case HOST_NOT_FOUND:
521       case NO_ADDRESS:
522       case NO_RECOVERY:
523                 return EX_NOHOST;
524       case TRY_AGAIN:
525                 return EX_TEMPFAIL;
526               default:
527                 return EX_OSERR;
528       }
529     }
530
531         memcpy (&addr->sin_addr, hent->h_addr, sizeof(addr->sin_addr));
532   }
533
534     return EX_OK;
535 }
536
537 int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags){
538     struct sockaddr addr;
539     int ret;
540     struct message m;
541
542     m.type=MESSAGE_NONE;
543
544     ret=lookup_host(hostname, port, &addr);
545     if(ret!=EX_OK) goto FAIL;
546     
547     m.max_len=max_size;
548     ret=message_read(in_fd, flags, &m);
549     if(ret!=EX_OK) goto FAIL;
550     ret=message_filter(&addr, username, flags, &m);
551     if(ret!=EX_OK) goto FAIL;
552     if(message_write(out_fd, &m)<0) goto FAIL;
553     if(m.is_spam!=EX_TOOBIG) {
554        message_cleanup(&m);
555        return m.is_spam;
556     }
557     message_cleanup(&m);
558     return ret;
559
560 FAIL:
561    if(flags&SPAMC_CHECK_ONLY){
562        full_write(out_fd, "0/0\n", 4);
563        message_cleanup(&m);
564        return EX_NOTSPAM;
565    } else {
566        message_dump(in_fd, out_fd, &m);
567        message_cleanup(&m);
568        return ret;
569     }
570 }
571
572 void message_cleanup(struct message *m) {
573    if (m->out != NULL && m->out != m->raw) free(m->out);
574    if (m->raw != NULL) free(m->raw);
575    clear_message(m);
576 }
577
578 /* Aug 14, 2002 bj: Obsolete! */
579 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){
580     int flags;
581
582     flags=SPAMC_RAW_MODE;
583     if(my_check_only) flags|=SPAMC_CHECK_ONLY;
584     if(my_safe_fallback) flags|=SPAMC_SAFE_FALLBACK;
585
586     return message_process(hostname, port, username, max_size, in_fd, out_fd, flags);
587 }