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