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