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
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>
25 #include <openssl/crypto.h>
26 #include <openssl/pem.h>
27 #include <openssl/ssl.h>
28 #include <openssl/err.h>
31 #ifdef HAVE_SYSEXITS_H
37 #ifdef HAVE_SYS_ERRNO_H
38 #include <sys/errno.h>
43 #ifdef HAVE_SYS_TIME_H
47 #define MAX_CONNECT_RETRIES 3
48 #define CONNECT_RETRY_SLEEP 1
50 /* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */
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. */
66 #ifndef HAVE_INADDR_NONE
67 #define INADDR_NONE ((in_addr_t) 0xffffffff)
70 /* jm: turned off for now, it should not be necessary. */
71 #undef USE_TCP_NODELAY
74 /* jm: very conservative figure, should be well out of range on almost all NIXes */
78 static const int DO_CONNECT_DEBUG_SYSLOGS = 0;
80 static const int ESC_PASSTHROUGHRAW = EX__MAX+666;
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;
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;
92 /* Set the protocol version that this spamc speaks */
93 static const char *PROTOCOL_VERSION="SPAMC/1.2";
95 int libspamc_timeout = 0;
98 try_to_connect (const struct sockaddr *argaddr, struct hostent *hent,
99 int hent_port, int *sockptr)
101 #ifdef USE_TCP_NODELAY
109 struct sockaddr_in addrbuf, *addr;
110 struct in_addr inaddrlist[256];
112 int i; char dbgbuf[2048]; int dbgbuflen = 0; // DBG
114 /* NOTE: do not call syslog() (unless you are about to return) before
115 * we take a copy of the h_addr_list.
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");
122 } else if (argaddr != NULL && hent != NULL) {
123 syslog (LOG_ERR, "oops! both non-NULL in try_to_connect");
127 /* take a copy of the h_addr_list part of the struct hostent */
129 memset (inaddrlist, 0, sizeof(inaddrlist));
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]);
141 syslog (LOG_ERR, "too many address in hostent (%d), ignoring others",
146 if (hent->h_addr_list[hostnum] == NULL) {
147 /* shouldn't happen */
148 syslog (LOG_ERR, "hent->h_addr_list[hostnum] == NULL! foo!");
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]);
159 memcpy ((void *) &(inaddrlist[hostnum]),
160 (void *) hent->h_addr_list[hostnum],
161 sizeof (struct in_addr));
164 if (DO_CONNECT_DEBUG_SYSLOGS) {
165 syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0;
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]));
177 hent = NULL; /* cannot use hent after this point, syslog() may overwrite it */
179 if (DO_CONNECT_DEBUG_SYSLOGS) { syslog (LOG_DEBUG, "dbg: socket"); }
181 if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0)))
183 origerr = errno; /* take a copy before syslog() */
184 syslog (LOG_ERR, "socket() to spamd failed: %m");
187 case EPROTONOSUPPORT:
202 if (DO_CONNECT_DEBUG_SYSLOGS) { syslog (LOG_DEBUG, "dbg: setsockopt"); }
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)))
215 syslog (LOG_ERR, "setsockopt() to spamd failed: %m");
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);
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");
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);
241 if (sizeof(addrbuf.sin_addr) != sizeof(struct in_addr)) { /* shouldn't happen */
243 "foo! sizeof(sockaddr.sin_addr) != sizeof(struct in_addr)");
247 if (DO_CONNECT_DEBUG_SYSLOGS) {
248 syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx",
249 numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum]));
252 memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]),
253 sizeof(addrbuf.sin_addr));
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);
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));
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);
285 /* failed, even with a few retries */
287 syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries",
288 MAX_CONNECT_RETRIES);
304 return EX_UNAVAILABLE;
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.
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;
328 static int message_read_raw(int fd, struct 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);
333 free(m->raw); m->raw=NULL; m->raw_len=0;
336 m->type=MESSAGE_ERROR;
337 if(m->raw_len>m->max_len) return EX_TOOBIG;
340 m->msg_len=m->raw_len;
342 m->out_len=m->msg_len;
346 static int message_read_bsmtp(int fd, struct message *m){
351 if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR;
353 /* Find the DATA line */
354 m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1);
356 free(m->raw); m->raw=NULL; m->raw_len=0;
359 m->type=MESSAGE_ERROR;
360 if(m->raw_len>m->max_len) return EX_TOOBIG;
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')){
371 if(m->raw[i-1]=='\r') i++;
374 m->msg_len=m->raw_len-i;
378 if(m->msg==NULL) return EX_DATAERR;
380 /* Find the end-of-DATA line */
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 */
388 m->post_len=m->msg_len-i;
391 } else if(m->msg[i+1]=='.'){
392 /* Escaping dot, eliminate. */
395 } /* Else an ordinary dot, drop down to ordinary char handler */
398 m->msg[j++]=m->msg[i];
401 m->type=MESSAGE_BSMTP;
403 m->out_len=m->msg_len;
407 int message_read(int fd, int flags, struct message *m){
408 libspamc_timeout = 0;
410 switch(flags&SPAMC_MODE_MASK){
412 return message_read_raw(fd, m);
414 case SPAMC_BSMTP_MODE:
415 return message_read_bsmtp(fd, m);
418 syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK);
423 long message_write(int fd, struct message *m){
429 if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){
430 return full_write(fd, (unsigned char *) m->out, m->out_len);
435 syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n");
439 return full_write(fd, (unsigned char *) m->raw, m->raw_len);
442 return full_write(fd, (unsigned char *) m->out, m->out_len);
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 &&
451 if(i+1<m->out_len && m->out[i]=='\n' && m->out[i+1]=='.'){
452 if (j > jlimit - 4) {
453 break; /* avoid overflow */
455 buffer[j++]=m->out[i++];
456 buffer[j++]=m->out[i++];
459 buffer[j++]=m->out[i++];
462 total+=full_write(fd, (unsigned char *) buffer, j);
464 return total+full_write(fd, (unsigned char *) m->post, m->post_len);
467 syslog(LOG_ERR, "Unknown message type %d\n", m->type);
472 void message_dump(int in_fd, int out_fd, struct message *m){
476 if(m!=NULL && m->type!=MESSAGE_NONE) {
477 message_write(out_fd, m);
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);
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)
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;
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);
510 m->is_spam=EX_TOOBIG;
511 if((m->out=malloc(m->max_len+EXPANSION_ALLOWANCE+1))==NULL){
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; }
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; }
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; }
528 libspamc_timeout = m->timeout;
530 if((i=try_to_connect(addr, (struct hostent *) hent,
531 hent_port, &sock)) != EX_OK)
533 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
537 if(flags&SPAMC_USE_SSL) {
540 SSL_set_fd(ssl, sock);
546 if(flags&SPAMC_USE_SSL) {
548 SSL_write(ssl, buf, len);
549 SSL_write(ssl, m->msg, m->msg_len);
552 full_write(sock, (unsigned char *) buf, len);
553 full_write(sock, (unsigned char *) m->msg, m->msg_len);
554 shutdown(sock, SHUT_WR);
557 /* Now, read from spamd */
558 for(len=0; len<bufsiz; len++) {
559 if(flags&SPAMC_USE_SSL) {
561 i=timeout_read(SSL_read, ssl, buf+len, 1);
564 i=timeout_read(read, sock, buf+len, 1);
568 failureval = EX_IOERR; goto failure;
571 /* Read to end of message! Must be a version <1.0 server */
573 /* Nope, communication error */
574 failureval = EX_IOERR; goto failure;
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;
589 /* No header, so it must be a version <1.0 server */
590 memcpy(m->out, buf, len);
593 /* Handle different versioned headers */
594 if(version-1.0>0.01){
595 for(len=0; len<bufsiz; len++){
597 if(flags&SPAMC_USE_SSL){
598 i=timeout_read(SSL_read, ssl, buf+len, 1);
601 i=timeout_read(read, sock, buf+len, 1);
606 failureval = (i<0)?EX_IOERR:EX_PROTOCOL; goto failure;
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);
615 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
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;
623 /* Not check-only, better be Content-length */
624 if(sscanf(buf, "Content-length: %d", &expected_len)!=1){
625 failureval = EX_PROTOCOL;
630 /* Should be end of headers now */
631 if(flags&SPAMC_USE_SSL){
633 i=timeout_read(SSL_read,ssl, buf, 2);
636 i=full_read (sock, (unsigned char *) buf, 2, 2);
639 if(i!=2 || buf[0]!='\r' || buf[1]!='\n'){
641 failureval = EX_PROTOCOL; goto failure;
650 if(flags&SPAMC_CHECK_ONLY){
651 /* We should have gotten headers back... Damnit. */
652 failureval = EX_PROTOCOL; goto failure;
655 if(flags&SPAMC_USE_SSL){
657 len=timeout_read(SSL_read,ssl, m->out+m->out_len,
658 m->max_len+EXPANSION_ALLOWANCE+1-m->out_len);
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);
666 if(len+m->out_len>m->max_len+EXPANSION_ALLOWANCE){
667 failureval = EX_TOOBIG; goto failure;
671 shutdown(sock, SHUT_RD);
673 libspamc_timeout = 0;
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;
683 free(m->out); m->out=m->msg; m->out_len=m->msg_len;
685 libspamc_timeout = 0;
688 if(flags&SPAMC_USE_SSL){
696 static int _lookup_host(const char *hostname, struct hostent *out_hent)
698 struct hostent *hent = NULL;
701 /* no need to try using inet_addr(), gethostbyname() will do that */
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",
720 memcpy (out_hent, hent, sizeof(struct hostent));
725 int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags){
732 ret=lookup_host_for_failover(hostname, &hent);
733 if(ret!=EX_OK) goto FAIL;
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) {
749 if(flags&SPAMC_CHECK_ONLY){
750 full_write(out_fd, (unsigned char *) "0/0\n", 4);
754 message_dump(in_fd, out_fd, &m);
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);
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){
770 flags=SPAMC_RAW_MODE;
771 if(my_check_only) flags|=SPAMC_CHECK_ONLY;
772 if(my_safe_fallback) flags|=SPAMC_SAFE_FALLBACK;
774 return message_process(hostname, port, username, max_size, in_fd, out_fd, flags);
777 /* public APIs, which call into the static code and enforce sockaddr-OR-hostent
780 int lookup_host(const char *hostname, int port, struct sockaddr *out_addr)
782 struct sockaddr_in *addr = (struct sockaddr_in *)out_addr;
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));
794 int lookup_host_for_failover(const char *hostname, struct hostent *hent) {
795 return _lookup_host(hostname, hent);
798 int message_filter(const struct sockaddr *addr, char *username, int flags,
800 { return _message_filter (addr, NULL, 0, username, flags, m); }
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); }