/* This file is part of Mailfromd. -*- c -*-
Copyright (C) 2006, 2007, 2008, 2009 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include
#include
#include
#include
#include
#include
#include
MF_VAR(sa_score, NUMBER);
MF_VAR(sa_threshold, NUMBER);
MF_VAR(sa_keywords, STRING);
MF_VAR(clamav_virus_name, STRING);
static int
spamd_connect(eval_environ_t env, mu_stream_t *stream, char *host, int port)
{
int rc;
char *fname;
if (port) {
fname = "mu_tcp_stream_create";
rc = mu_tcp_stream_create(stream, host, port,
MU_STREAM_NO_CHECK);
} else {
fname = "mu_socket_stream_create";
rc = mu_socket_stream_create(stream, host, MU_STREAM_NO_CHECK);
}
MF_ASSERT(rc == 0, mfe_failure,
"%s: %s",
fname, mu_strerror(rc));
rc = mu_stream_open(*stream);
MF_ASSERT(rc == 0, mfe_failure,
"mu_stream_open: %s",
mu_strerror(rc));
return rc;
}
static void
spamd_destroy(mu_stream_t *stream)
{
mu_stream_close(*stream);
mu_stream_destroy(stream, NULL);
}
static void
spamd_send_command(mu_stream_t stream, const char *fmt, ...)
{
char buf[512];
size_t n;
va_list ap;
va_start (ap, fmt);
n = vsnprintf (buf, sizeof buf, fmt, ap);
va_end (ap);
mu_stream_sequential_write(stream, buf, n);
mu_stream_sequential_write(stream, "\r\n", 2);
}
/* Write SIZE bytes from BUF to STREAM, converting every \n to \r\n.
Note that it assumes that BUF itself does not contain any \r\n sequences.
This is always true for captured message streams (see env_capture_write
in prog.c).
FIXME (mailutils): We cannot use MU's RFC822 filter for that purpose,
because (as of v. 2.0.90) there is no way to destroy it without
closing and destroying the transport stream.
*/
static int
write_822(mu_stream_t stream, char *buf, size_t size)
{
int rc = 0;
while (size) {
size_t len = mem_search(buf, '\n', size);
rc = mu_stream_sequential_write(stream, buf, len);
if (rc)
break;
if (buf[len] == '\n') {
rc = mu_stream_sequential_write(stream, "\r\n", 2);
if (rc)
break;
len++;
}
buf += len;
size -= len;
}
return rc;
}
static int
spamd_send_stream(mu_stream_t ostr, mu_stream_t istr)
{
size_t size;
char buf[512];
int rc;
mu_stream_seek(istr, 0, SEEK_SET);
while ((rc = mu_stream_sequential_readline(istr, buf, sizeof(buf),
&size)) == 0
&& size > 0) {
debug3(80,"<< %*.*s", size, size, buf);
rc = write_822(ostr, buf, size);
if (rc)
break;
}
return rc;
}
static int
spamd_read_line0(mu_stream_t stream, char *buffer, size_t size, size_t *pn)
{
size_t n = 0;
int rc = mu_stream_sequential_readline(stream, buffer, size, &n);
if (rc == 0) {
debug3(80,">> %*.*s", n, n, buffer);
if (pn)
*pn = n;
}
return rc;
}
static int
spamd_read_line(mu_stream_t stream, char *buffer, size_t size)
{
size_t n;
int rc = spamd_read_line0(stream, buffer, size, &n);
if (rc == 0) {
while (n > 0 && (buffer[n-1] == '\r' || buffer[n-1] == '\n'))
n--;
buffer[n] = 0;
}
return rc;
}
#define char_to_num(c) (c-'0')
static void
decode_float(long *vn, char *str, int digits)
{
long v;
size_t frac = 0;
size_t base = 1;
int i;
int negative = 0;
for (i = 0; i < digits; i++)
base *= 10;
v = strtol(str, &str, 10);
if (v < 0) {
negative = 1;
v = - v;
}
v *= base;
if (*str == '.') {
for (str++, i = 0; *str && i < digits; i++, str++)
frac = frac * 10 + char_to_num (*str);
if (*str) {
if (char_to_num(*str) >= 5)
frac++;
}
else
for (; i < digits; i++)
frac *= 10;
}
*vn = v + frac;
if (negative)
*vn = - *vn;
}
static int
decode_boolean (char *str)
{
if (strcasecmp (str, "true") == 0)
return 1;
else if (strcasecmp (str, "false") == 0)
return 0;
/*else?*/
return 0;
}
typedef RETSIGTYPE (*signal_handler_fn)(int);
static signal_handler_fn
set_signal_handler (int sig, signal_handler_fn h)
{
#ifdef HAVE_SIGACTION
struct sigaction act, oldact;
act.sa_handler = h;
sigemptyset (&act.sa_mask);
act.sa_flags = 0;
sigaction (sig, &act, &oldact);
return oldact.sa_handler;
#else
return signal (sig, h);
#endif
}
static int got_sigpipe;
static RETSIGTYPE
sigpipe_handler (int sig)
{
got_sigpipe = 1;
}
mu_stream_t
open_connection(eval_environ_t env, char *urlstr, char **phost)
{
char *path = NULL;
short port = 0;
mu_stream_t str = NULL;
int rc;
if (urlstr[0] == '/') {
path = strdup(urlstr);
if (!path)
runtime_error(env, _("Not enough memory"));
} else {
char buffer[10];
mu_url_t url = NULL;
rc = mu_url_create(&url, urlstr);
if (rc)
MF_THROW(mfe_failure,
_("Cannot create URL from `%s': %s"),
urlstr, mu_strerror(rc));
if (rc = mu_url_parse(url)) {
mu_url_destroy(&url);
MF_THROW(mfe_url,
_("%s: error parsing URL: %s"),
urlstr, mu_strerror(rc));
}
if (rc = mu_url_get_scheme(url, buffer, sizeof buffer, NULL)) {
mu_url_destroy(&url);
MF_THROW(mfe_url,
_("%s: cannot get scheme: %s"),
urlstr, mu_strerror(rc));
}
if (strcmp(buffer, "file") == 0
|| strcmp(buffer, "socket") == 0) {
size_t size;
if (rc = mu_url_get_path(url, NULL, 0, &size)) {
MF_THROW(mfe_url,
_("%s: cannot get path: %s"),
urlstr, mu_strerror(rc));
}
path = malloc(size + 1);
if (!path) {
mu_url_destroy(&url);
runtime_error(env, _("Not enough memory"));
}
mu_url_get_path(url, path, size + 1, NULL);
} else if (strcmp(buffer, "tcp") == 0) {
size_t size;
long n;
if (rc = mu_url_get_port(url, &n)) {
mu_url_destroy(&url);
MF_THROW(mfe_url,
_("%s: cannot get port: %s"),
urlstr, mu_strerror(rc));
}
if (n == 0 || (port = n) != n) {
mu_url_destroy(&url);
MF_THROW(mfe_range,
_("Port out of range: %ld"),
n);
}
if (rc = mu_url_get_host(url, NULL, 0, &size)) {
mu_url_destroy(&url);
MF_THROW(mfe_url,
_("%s: cannot get host: %s"),
urlstr, mu_strerror(rc));
}
path = malloc(size + 1);
if (!path) {
mu_url_destroy(&url);
runtime_error(env, _("Not enough memory"));
}
mu_url_get_host(url, path, size + 1, NULL);
} else
MF_THROW(mfe_url,
_("Invalid URL: %s"), buffer);
mu_url_destroy(&url);
}
rc = spamd_connect(env, &str, path, port);
if (rc == 0 && phost) {
if (port) {
*phost = path;
path = NULL;
} else
*phost = NULL;
}
free(path);
return str;
}
MF_STATE(eom)
MF_CAPTURE
MF_DEFUN(sa, NUMBER, STRING urlstr, NUMBER prec, OPTIONAL, NUMBER report)
{
mu_off_t msize;
size_t size;
mu_stream_t mstr = env_get_stream(env);
mu_stream_t ostr;
signal_handler_fn handler;
char buffer[512];
char version_str[19];
char spam_str[6], score_str[21], threshold_str[21];
long version;
int result;
long score, threshold;
int report_mode = MF_OPTVAL(report);
int rc;
rc = mu_stream_size(mstr, &msize);
MF_ASSERT(rc == 0,
mfe_failure,
"mu_stream_size: %s",
mu_strerror (rc));
ostr = open_connection(env, urlstr, NULL);
msize += env_get_line_count(env);
spamd_send_command(ostr,
report_mode ? "REPORT SPAMC/1.2"
: "SYMBOLS SPAMC/1.2");
spamd_send_command(ostr, "Content-length: %lu",
(unsigned long) msize);
/*FIXME: spamd_send_command(ostr, "User: %s", ??) */
got_sigpipe = 0;
handler = set_signal_handler(SIGPIPE, sigpipe_handler);
spamd_send_command(ostr, "");
if (rc = spamd_send_stream(ostr, mstr)) {
spamd_destroy(&ostr);
MF_THROW(mfe_failure,
_("Send stream failed: %s"),
mu_strerror (rc));
}
mu_stream_shutdown(ostr, MU_STREAM_WRITE);
set_signal_handler(SIGPIPE, handler);
spamd_read_line(ostr, buffer, sizeof buffer);
if (got_sigpipe) {
spamd_destroy(&ostr);
MF_THROW(mfe_failure, _("Remote side has closed connection"));
}
if (sscanf(buffer, "SPAMD/%18s %d %*s", version_str, &result) != 2) {
spamd_destroy(&ostr);
MF_THROW(mfe_failure,
_("spamd responded with bad string '%s'"),
buffer);
}
decode_float(&version, version_str, 1);
if (version < 10) {
spamd_destroy(&ostr);
MF_THROW(mfe_failure,
_("Unsupported SPAMD version: %s"),
version_str);
}
spamd_read_line(ostr, buffer, sizeof buffer);
if (sscanf (buffer, "Spam: %5s ; %20s / %20s",
spam_str, score_str, threshold_str) != 3) {
spamd_destroy(&ostr);
MF_THROW(mfe_failure,
_("spamd responded with bad Spam header '%s'"),
buffer);
}
result = decode_boolean(spam_str);
decode_float(&score, score_str, prec);
decode_float(&threshold, threshold_str, prec);
MF_VAR_REF(sa_score, score);
MF_VAR_REF(sa_threshold, threshold);
/* Skip newline */
spamd_read_line(ostr, buffer, sizeof buffer);
if (report_mode) {
MF_OBSTACK_BEGIN();
while (spamd_read_line0(ostr, buffer, sizeof buffer, &size)
== 0
&& size > 0)
MF_OBSTACK_GROW(buffer, size);
MF_OBSTACK_1GROW(0);
MF_VAR_REF(sa_keywords, MF_OBSTACK_FINISH);
} else {
/* Read symbol list */
spamd_read_line(ostr, buffer, sizeof buffer);
MF_VAR_SET_STRING(sa_keywords, buffer);
while (spamd_read_line0(ostr, buffer, sizeof buffer, &size)
== 0
&& size > 0)
/* Drain input */;
}
spamd_destroy(&ostr);
MF_RETURN(result);
}
END
MF_STATE(eom)
MF_CAPTURE
MF_DEFUN(clamav, NUMBER, STRING urlstr)
{
mu_stream_t mstr = env_get_stream(env);
mu_stream_t cstr, dstr;
char buffer[512];
char *host;
unsigned short port;
int rc;
signal_handler_fn handler;
char *p;
cstr = open_connection(env, urlstr, &host);
spamd_send_command(cstr, "STREAM");
spamd_read_line(cstr, buffer, sizeof buffer);
if (sscanf(buffer, "PORT %hu\n", &port) != 1) {
spamd_destroy(&cstr);
MF_THROW(mfe_failure,
_("Bad response from clamav: expected `PORT' but found `%s'"),
buffer);
}
if (!host)
host = strdup("127.0.0.1"); /* FIXME */
rc = mu_tcp_stream_create(&dstr, host, port, 0);
free(host);
if (rc) {
spamd_destroy(&cstr);
MF_THROW(mfe_failure,
"mu_tcp_stream_create: %s",
mu_strerror(rc));
}
rc = mu_stream_open(dstr);
if (rc) {
spamd_destroy(&cstr);
mu_stream_destroy(&dstr, mu_stream_get_owner(dstr));
MF_THROW(mfe_failure,
"mu_stream_open: %s",
mu_strerror(rc));
}
handler = set_signal_handler(SIGPIPE, sigpipe_handler);
rc = spamd_send_stream(dstr, mstr);
mu_stream_shutdown(dstr, MU_STREAM_WRITE);
spamd_destroy(&dstr);
set_signal_handler(SIGPIPE, handler);
if (rc) {
spamd_destroy(&cstr);
MF_THROW(mfe_failure,
_("Send stream failed: %s"),
mu_strerror (rc));
}
rc = spamd_read_line(cstr, buffer, sizeof buffer);
spamd_destroy(&cstr);
MF_ASSERT(rc == 0, mfe_failure, _("Error reading clamav response: %s"),
mu_strerror(rc));
p = strrchr(buffer, ' ');
MF_ASSERT(p, mfe_failure,
_("Unknown clamav response: %s"), buffer);
++p;
if (strncmp(p, "OK", 2) == 0)
rc = 0;
else if (strncmp(p, "FOUND", 5) == 0) {
char *s;
*--p = '\0';
s = strrchr(buffer, ' ');
if (!s)
s = buffer;
else
s++;
MF_VAR_SET_STRING(clamav_virus_name, s);
debug2(2,
"%sclamav found %s",
mailfromd_msgid(env_get_context(env)),
s);
rc = 1;
} else if (strncmp(p, "ERROR", 5) == 0) {
/* FIXME: mf code */
MF_THROW(mfe_failure,
_("Clamav error: %s"),
buffer);
} else {
MF_THROW(mfe_failure,
_("Unknown clamav response: %s"),
buffer);
}
MF_RETURN(rc);
}
END
MF_INIT