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"
15 #include <sys/types.h>
16 #include <sys/socket.h>
17 #include <netinet/in.h>
18 #include <netinet/tcp.h>
20 #include <arpa/inet.h>
22 #ifdef HAVE_SYSEXITS_H
28 #ifdef HAVE_SYS_ERRNO_H
29 #include <sys/errno.h>
34 #ifdef HAVE_SYS_TIME_H
38 #define MAX_CONNECT_RETRIES 3
39 #define CONNECT_RETRY_SLEEP 1
41 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
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. */
57 #ifndef HAVE_INADDR_NONE
58 #define INADDR_NONE ((in_addr_t) 0xffffffff)
61 /* jm: turned off for now, it should not be necessary. */
62 #undef USE_TCP_NODELAY
65 /* jm: very conservative figure, should be well out of range on almost all NIXes */
69 static const int ESC_PASSTHROUGHRAW = EX__MAX+666;
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;
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;
81 /* Set the protocol version that this spamc speaks */
82 static const char *PROTOCOL_VERSION="SPAMC/1.2";
84 /* Aug 14, 2002 bj: No more ctx! */
86 try_to_connect (const struct sockaddr *addr, int *sockptr)
88 #ifdef USE_TCP_NODELAY
96 if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
98 origerr = errno; /* take a copy before syslog() */
99 syslog (LOG_ERR, "socket() to spamd failed: %m");
102 case EPROTONOSUPPORT:
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)))
128 syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
137 for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) {
138 status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr));
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);
154 /* failed, even with a few retries */
155 syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
156 MAX_CONNECT_RETRIES);
172 return EX_UNAVAILABLE;
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.
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;
196 static int message_read_raw(int fd, struct 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);
201 free(m->raw); m->raw=NULL; m->raw_len=0;
204 m->type=MESSAGE_ERROR;
205 if(m->raw_len>m->max_len) return EX_TOOBIG;
208 m->msg_len=m->raw_len;
210 m->out_len=m->msg_len;
214 static int message_read_bsmtp(int fd, struct message *m){
219 if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
221 /* Find the DATA line */
222 m->raw_len=full_read(fd, m->raw, m->max_len+1, m->max_len+1);
224 free(m->raw); m->raw=NULL; m->raw_len=0;
227 m->type=MESSAGE_ERROR;
228 if(m->raw_len>m->max_len) return EX_TOOBIG;
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')){
239 if(m->raw[i-1]=='\r') i++;
242 m->msg_len=m->raw_len-i;
246 if(m->msg==NULL) return EX_DATAERR;
248 /* Find the end-of-DATA line */
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 */
256 m->post_len=m->msg_len-i;
259 } else if(m->msg[i+1]=='.'){
260 /* Escaping dot, eliminate. */
263 } /* Else an ordinary dot, drop down to ordinary char handler */
266 m->msg[j++]=m->msg[i];
269 m->type=MESSAGE_BSMTP;
271 m->out_len=m->msg_len;
275 int message_read(int fd, int flags, struct message *m){
276 switch(flags&SPAMC_MODE_MASK){
278 return message_read_raw(fd, m);
280 case SPAMC_BSMTP_MODE:
281 return message_read_bsmtp(fd, m);
284 syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK);
289 long message_write(int fd, struct message *m){
294 if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
295 return full_write(fd, m->out, m->out_len);
300 syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n");
304 return full_write(fd, m->raw, m->raw_len);
307 return full_write(fd, m->out, m->out_len);
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++];
318 buffer[j++]=m->out[i++];
321 total+=full_write(fd, buffer, j);
323 return total+full_write(fd, m->post, m->post_len);
326 syslog(LOG_ERR, "Unknown message type %d\n", m->type);
331 void message_dump(int in_fd, int out_fd, struct message *m){
335 if(m!=NULL && m->type!=MESSAGE_NONE) {
336 message_write(out_fd, m);
338 while((bytes=full_read(in_fd, buf, 8192, 8192))>0){
339 if(bytes!=full_write(out_fd, buf, bytes));
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;
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){
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; }
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; }
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; }
370 if((i=try_to_connect(addr, &sock))!=EX_OK){
372 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
377 full_write(sock, buf, len);
378 full_write(sock, m->msg, m->msg_len);
379 shutdown(sock, SHUT_WR);
381 /* Now, read from spamd */
382 for(len=0; len<8192; len++){
383 i=read(sock, buf+len, 1);
386 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
391 /* Read to end of message! Must be a version <1.0 server */
393 /* Nope, communication error */
395 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
403 if(sscanf(buf, "SPAMD/%f %d %*s", &version, &response)!=2){
404 syslog(LOG_ERR, "spamd responded with bad string '%s'", buf);
406 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
415 /* No header, so it must be a version <1.0 server */
416 memcpy(m->out, buf, len);
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);
425 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
427 return (i<0)?EX_IOERR:EX_PROTOCOL;
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);
436 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
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;
444 /* Not check-only, better be Content-length */
445 if(sscanf(buf, "Content-length: %d", &expected_len)!=1){
447 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
453 /* Should be end of headers now */
454 if(full_read(sock, buf, 2, 2)!=2 || buf[0]!='\r' || buf[1]!='\n'){
457 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
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;
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;
484 shutdown(sock, SHUT_RD);
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;
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;
502 memset(&a, 0, sizeof(a));
504 addr->sin_family=AF_INET;
505 addr->sin_port=htons(port);
507 /* first, try to mangle it directly into an addr-> This will work
508 * for numeric IP addresses, but not for hostnames...
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.
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",
531 memcpy (&addr->sin_addr, hent->h_addr, sizeof(addr->sin_addr));
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;
544 ret=lookup_host(hostname, port, &addr);
545 if(ret!=EX_OK) goto FAIL;
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) {
561 if(flags&SPAMC_CHECK_ONLY){
562 full_write(out_fd, "0/0\n", 4);
566 message_dump(in_fd, out_fd, &m);
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);
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){
582 flags=SPAMC_RAW_MODE;
583 if(my_check_only) flags|=SPAMC_CHECK_ONLY;
584 if(my_safe_fallback) flags|=SPAMC_SAFE_FALLBACK;
586 return message_process(hostname, port, username, max_size, in_fd, out_fd, flags);