int message_filter()

in spamc/libspamc.c [1290:1664]


int message_filter(struct transport *tp, const char *username,
                   int flags, struct message *m)
{
    char buf[8192];
    size_t bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */
    size_t len;
    int sock = -1;
    int rc;
    char versbuf[20];
    float version;
    int response;
    int failureval = EX_SOFTWARE;
    unsigned int throwaway;
    SSL_CTX *ctx = NULL;
    SSL *ssl = NULL;
    const SSL_METHOD *meth;
    char zlib_on = 0;
    unsigned char *zlib_buf = NULL;
    int zlib_bufsiz = 0;
    unsigned char *towrite_buf;
    int towrite_len;
    int filter_retry_count;
    int filter_retry_sleep;
    int filter_retries;
    #ifdef SPAMC_HAS_ADDRINFO
        struct addrinfo *tmphost;
    #else
        struct in_addr tmphost;
    #endif
    int nhost_counter;

    assert(tp != NULL);
    assert(m != NULL);

    if ((flags & SPAMC_USE_ZLIB) != 0) {
      zlib_on = 1;
    }

    if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
        ctx = _try_ssl_ctx_init(flags);
        if (ctx == NULL) {
	    failureval = EX_OSERR;
	    goto failure;
        }
#else
	UNUSED_VARIABLE(ssl);
	UNUSED_VARIABLE(ctx);
	libspamc_log(flags, LOG_ERR, "spamc not built with SSL support");
	return EX_SOFTWARE;
#endif
    }

    m->is_spam = EX_TOOBIG;

    if (m->outbuf != NULL)
        free(m->outbuf);
    m->priv->alloced_size = m->max_len + EXPANSION_ALLOWANCE + 1;
    if ((m->outbuf = malloc(m->priv->alloced_size)) == NULL) {
	failureval = EX_OSERR;
	goto failure;
    }
    m->out = m->outbuf;
    m->out_len = 0;

    /* If the spamd filter takes too long and we timeout, then
     * retry again.  This gets us around a hung child thread 
     * in spamd or a problem on a spamd host in a multi-host
     * setup.  If there is more than one destination host
     * we move to the next host on each attempt.
     */

    /* default values */
    filter_retry_sleep = tp->filter_retry_sleep;
    filter_retries = tp->filter_retries;
    if (filter_retries == 0) {
        filter_retries = 1;
    }
    if (filter_retry_sleep < 0) {
        filter_retry_sleep = 1;
    }

    /* filterloop - Ensure that we run through this at least
     * once, and again if there are errors 
     */
    filter_retry_count = 0;
    while ((filter_retry_count==0) || 
                ((filter_retry_count<tp->filter_retries) && (failureval == EX_IOERR)))
    {
        if (filter_retry_count != 0){
            /* Ensure that the old socket gets closed */
            if (sock != -1) {
                closesocket(sock);
                sock=-1;
            }

            /* Move to the next host in the list, if nhosts>1 */
            if (tp->nhosts > 1) {
                tmphost = tp->hosts[0];

                /* TODO: free using freeaddrinfo() */
                for (nhost_counter = 1; nhost_counter < tp->nhosts; nhost_counter++) {
                    tp->hosts[nhost_counter - 1] = tp->hosts[nhost_counter];
                }
        
                tp->hosts[nhost_counter - 1] = tmphost;
            }

            /* Now sleep the requested amount */
            sleep(filter_retry_sleep);
        }

        filter_retry_count++;
    
        /* Build spamd protocol header */
        if (flags & SPAMC_CHECK_ONLY)
          strcpy(buf, "CHECK ");
        else if (flags & SPAMC_REPORT_IFSPAM)
          strcpy(buf, "REPORT_IFSPAM ");
        else if (flags & SPAMC_REPORT)
          strcpy(buf, "REPORT ");
        else if (flags & SPAMC_SYMBOLS)
          strcpy(buf, "SYMBOLS ");
        else if (flags & SPAMC_PING)
          strcpy(buf, "PING ");
        else if (flags & SPAMC_HEADERS)
          strcpy(buf, "HEADERS ");
        else
          strcpy(buf, "PROCESS ");
    
        len = strlen(buf);
        if (len + strlen(PROTOCOL_VERSION) + 2 >= bufsiz) {
            _use_msg_for_out(m);
            return EX_OSERR;
        }
    
        strcat(buf, PROTOCOL_VERSION);
        strcat(buf, "\r\n");
        len = strlen(buf);
    
        towrite_buf = (unsigned char *) m->msg;
        towrite_len = (int) m->msg_len;
        if (zlib_on) {
            if (_zlib_compress(m->msg, m->msg_len, &zlib_buf, &zlib_bufsiz, flags) != EX_OK)
            {
                _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
                return EX_OSERR;
            }
            towrite_buf = zlib_buf;
            towrite_len = zlib_bufsiz;
        }
    
        if (!(flags & SPAMC_PING)) {
          if (username != NULL) {
              if (strlen(username) + 8 >= (bufsiz - len)) {
                  _use_msg_for_out(m);
                  if (zlib_on) {
                      _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
                  }
                  return EX_OSERR;
              }
              strcpy(buf + len, "User: ");
              strcat(buf + len, username);
              strcat(buf + len, "\r\n");
              len += strlen(buf + len);
          }
          if (zlib_on) {
              len += snprintf(buf + len, 8192-len, "Compress: zlib\r\n");
          }
          if ((m->msg_len > SPAMC_MAX_MESSAGE_LEN) || ((len + 27) >= (bufsiz - len))) {
              _use_msg_for_out(m);
              if (zlib_on) {
                  _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
              }
              return EX_DATAERR;
          }
          len += snprintf(buf + len, 8192-len, "Content-length: %d\r\n", (int) towrite_len);
        }
        /* bug 6187, PING needs empty line too, bumps protocol version to 1.5 */
        len += snprintf(buf + len, 8192-len, "\r\n");
    
        libspamc_timeout = m->timeout;
        libspamc_connect_timeout = m->connect_timeout;	/* Sep 8, 2008 mrgus: separate connect timeout */

        if (tp->socketpath)
          rc = _try_to_connect_unix(tp, &sock);
        else
          rc = _try_to_connect_tcp(tp, &sock);
    
        if (rc != EX_OK) {
          _use_msg_for_out(m);
          if (zlib_on) {
              _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
          }
          return rc;      /* use the error code try_to_connect_*() gave us. */
        }
    
        if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
            rc = _try_ssl_connect(ctx, tp, &ssl, flags, sock);
            if (rc != EX_OK) {
                failureval = rc;
                goto failure;
            }
#endif
        }
    
        /* Send to spamd */
        if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
            rc = SSL_write(ssl, buf, len);
            if (rc <= 0) {
                libspamc_log(flags, LOG_ERR, "SSL write failed (%d)",
                             SSL_get_error(ssl, rc));
                failureval = EX_IOERR;
                goto failure;
            }
            rc = SSL_write(ssl, towrite_buf, towrite_len);
            if (rc <= 0) {
                libspamc_log(flags, LOG_ERR, "SSL write failed (%d)",
                             SSL_get_error(ssl, rc));
                failureval = EX_IOERR;
                goto failure;
            }
            SSL_shutdown(ssl);
            shutdown(sock, SHUT_WR);
#endif
        }
        else {
            full_write(sock, 0, buf, len);
            full_write(sock, 0, towrite_buf, towrite_len);
            shutdown(sock, SHUT_WR);
        }

        /* free zlib buffer
        * bug 6025: zlib buffer not freed if compression is used
        */
        if (zlib_on) {
            _free_zlib_buffer(&zlib_buf, &zlib_bufsiz);
        }
    
        /* ok, now read and parse it.  SPAMD/1.2 line first... */
        failureval =
                _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
    } /* end of filterloop */

    if (failureval != EX_OK) {
        goto failure;
    }

    if (sscanf(buf, "SPAMD/%18s %d %*s", versbuf, &response) != 2) {
	libspamc_log(flags, LOG_ERR, "spamd responded with bad string '%s'", buf);
	failureval = EX_PROTOCOL;
	goto failure;
    }

    versbuf[19] = '\0';
    version = _locale_safe_string_to_float(versbuf, 20);
    if (version < 1.0) {
	libspamc_log(flags, LOG_ERR, "spamd responded with bad version string '%s'",
	       versbuf);
	failureval = EX_PROTOCOL;
	goto failure;
    }

    if (flags & SPAMC_PING) {
	closesocket(sock);
	sock = -1;
        m->out_len = sprintf(m->out, "SPAMD/%s %d\n", versbuf, response);
        m->is_spam = EX_NOTSPAM;
        return EX_OK;
    }

    m->score = 0;
    m->threshold = 0;
    m->is_spam = EX_TOOBIG;
    while (1) {
	failureval =
	    _spamc_read_full_line(m, flags, ssl, sock, buf, &len, bufsiz);
	if (failureval != EX_OK) {
	    goto failure;
	}

	if (len == 0 && buf[0] == '\0') {
	    break;		/* end of headers */
	}

	if (_handle_spamd_header(m, flags, buf, len, &throwaway) < 0) {
	    failureval = EX_PROTOCOL;
	    goto failure;
	}
    }

    len = 0;			/* overwrite those headers */

    if (flags & SPAMC_CHECK_ONLY) {
	closesocket(sock);
	sock = -1;
	if (m->is_spam == EX_TOOBIG) {
	    /* We should have gotten headers back... Damnit. */
	    failureval = EX_PROTOCOL;
	    goto failure;
	}
	return EX_OK;
    }
    else {
	if (m->content_length < 0) {
	    /* should have got a length too. */
	    failureval = EX_PROTOCOL;
	    goto failure;
	}

	/* have we already got something in the buffer (e.g. REPORT and
	 * REPORT_IFSPAM both create a line from the "Spam:" hdr)?  If
	 * so, add the size of that so our sanity check passes.
	 */
	if (m->out_len > 0) {
	    m->content_length += m->out_len;
	}

	if (flags & SPAMC_USE_SSL) {
	    len = full_read_ssl(ssl, (unsigned char *) m->out + m->out_len,
				m->priv->alloced_size - m->out_len,
				m->priv->alloced_size - m->out_len);
	}
	else {
	    len = full_read(sock, 0, m->out + m->out_len,
			    m->priv->alloced_size - m->out_len,
			    m->priv->alloced_size - m->out_len);
	}


	if ((int) len + (int) m->out_len > (m->priv->alloced_size - 1)) {
	    failureval = EX_TOOBIG;
	    goto failure;
	}
	m->out_len += len;

	shutdown(sock, SHUT_RD);
	closesocket(sock);
	sock = -1;
    }
    libspamc_timeout = 0;

    if (m->out_len != m->content_length) {
	libspamc_log(flags, LOG_ERR,
	       "failed sanity check, %d bytes claimed, %d bytes seen",
	       m->content_length, m->out_len);
	failureval = EX_PROTOCOL;
	goto failure;
    }

    if (flags & SPAMC_HEADERS) {
        if (_append_original_body(m, flags) != EX_OK) {
            goto failure;
        }
    }

    return EX_OK;

  failure:
	_use_msg_for_out(m);
    if (sock != -1) {
	closesocket(sock);
    }
    libspamc_timeout = 0;

    if (flags & SPAMC_USE_SSL) {
#ifdef SPAMC_SSL
	SSL_free(ssl);
	SSL_CTX_free(ctx);
#endif
    }
    return failureval;
}