f5352ff0 |
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 | |