/* This file is part of mailfromd. -*- c -*-
Copyright (C) 2006, 2007 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);
/* FIXME: Use socket_stream from the Mailutils when 1.3 (2.0?) stable is
out.
*/
static int
spamd_connect_tcp(eval_environ_t env, mu_stream_t *stream,
char *host, int port)
{
int rc = mu_tcp_stream_create(stream, host, port, 0);
MF_ASSERT(rc == 0, mf_failure,
"mu_tcp_stream_create: %s",
mu_strerror(rc));
rc = mu_stream_open(*stream);
MF_ASSERT(rc == 0, mf_failure,
"mu_stream_open: %s",
mu_strerror(rc));
return rc;
}
static int
spamd_connect_socket(eval_environ_t env, mu_stream_t *stream, char *path)
{
int fd, rc;
FILE *fp;
struct sockaddr_un addr;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
MF_ASSERT(fd >= 0, mf_failure,
"socket: %s", mu_strerror(errno));
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof addr.sun_path - 1);
addr.sun_path[sizeof addr.sun_path - 1] = 0;
if (connect(fd, (struct sockaddr *) &addr, sizeof(addr))) {
close(fd);
MF_THROW(mf_failure, "connect: %s", mu_strerror(errno));
}
fp = fdopen(fd, "w+");
rc = mu_stdio_stream_create(stream, fp, MU_STREAM_RDWR);
if (rc) {
fclose(fp);
MF_THROW(mf_failure,
"mu_stdio_stream_create: %s",
mu_strerror(rc));
}
rc = mu_stream_open(*stream);
if (rc) {
mu_stream_destroy (stream, mu_stream_get_owner (*stream));
fclose(fp);
MF_THROW(mf_failure,
"mu_stream_open: %s",
mu_strerror(rc));
}
return rc;
}
static void
spamd_shutdown(mu_stream_t stream, int isfile, int flag)
{
int fd;
mu_transport_t trans;
mu_stream_flush(stream);
mu_stream_get_transport(stream, &trans);
if (isfile)
fd = fileno((FILE*)trans);
else
fd = (int) trans;
shutdown(fd, flag);
}
static void
spamd_destroy(mu_stream_t *stream)
{
mu_stream_close(*stream);
mu_stream_destroy(stream, mu_stream_get_owner(*stream));
}
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);
}
static void
spamd_send_stream(mu_stream_t stream, mu_stream_t instr)
{
size_t size;
char buf[512];
mu_stream_seek(instr, 0, SEEK_SET);
while (mu_stream_sequential_readline(instr, buf, sizeof(buf),
&size) == 0
&& size > 0) {
debug3(80,"<< %*.*s", size, size, buf);
mu_stream_sequential_write (stream, buf, size);
}
}
static int
spamd_read_line(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) {
if (pn)
*pn = n;
while (n > 0 && (buffer[n-1] == '\r' || buffer[n-1] == '\n'))
n--;
buffer[n] = 0;
debug1(80,">> %s", buffer);
}
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, int *isfile, 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(mf_failure,
_("Cannot create URL from `%s': %s"),
urlstr, mu_strerror(rc));
if (rc = mu_url_parse(url)) {
mu_url_destroy(&url);
MF_THROW(mf_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(mf_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(mf_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(mf_url,
_("%s: cannot get port: %s"),
urlstr, mu_strerror(rc));
}
if (n == 0 || (port = n) != n) {
mu_url_destroy(&url);
MF_THROW(mf_range,
_("Port out of range: %ld"),
n);
}
if (rc = mu_url_get_host(url, NULL, 0, &size)) {
mu_url_destroy(&url);
MF_THROW(mf_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(mf_url,
_("Invalid URL: %s"), buffer);
mu_url_destroy(&url);
}
if (port == 0) {
rc = spamd_connect_socket(env, &str, path);
*isfile = 1;
} else {
rc = spamd_connect_tcp(env, &str, path, port);
*isfile = 0;
}
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)
{
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 isfile;
ostr = open_connection(env, urlstr, &isfile, NULL);
mu_stream_size(mstr, &msize);
spamd_send_command(ostr, "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, "");
spamd_send_stream(ostr, mstr);
spamd_shutdown(ostr, isfile, SHUT_WR);
set_signal_handler(SIGPIPE, handler);
spamd_read_line(ostr, buffer, sizeof buffer, NULL);
if (got_sigpipe) {
spamd_destroy(&ostr);
MF_THROW(mf_failure, _("Remote side has closed connection"));
}
if (sscanf(buffer, "SPAMD/%18s %d %*s", version_str, &result) != 2) {
spamd_destroy(&ostr);
MF_THROW(mf_failure,
_("spamd responded with bad string '%s'"),
buffer);
}
decode_float(&version, version_str, 1);
if (version < 10) {
spamd_destroy(&ostr);
MF_THROW(mf_failure,
_("Unsupported SPAMD version: %s"),
version_str);
}
spamd_read_line(ostr, buffer, sizeof buffer, NULL);
if (sscanf (buffer, "Spam: %5s ; %20s / %20s",
spam_str, score_str, threshold_str) != 3) {
spamd_destroy(&ostr);
MF_THROW(mf_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);
/* Skip newline */
spamd_read_line(ostr, buffer, sizeof buffer, NULL);
/* Read symbol list */
spamd_read_line(ostr, buffer, sizeof buffer, &size);
MF_VAR_REF(sa_score, score);
MF_VAR_REF(sa_threshold, threshold);
MF_VAR_SET_STRING(sa_keywords, buffer);
while (spamd_read_line(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;
int isfile;
cstr = open_connection(env, urlstr, &isfile, &host);
spamd_send_command(cstr, "STREAM");
spamd_read_line(cstr, buffer, sizeof buffer, NULL);
if (sscanf(buffer, "PORT %hu\n", &port) != 1) {
spamd_destroy(&cstr);
MF_THROW(mf_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(mf_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(mf_failure,
"mu_stream_open: %s",
mu_strerror(rc));
}
handler = set_signal_handler(SIGPIPE, sigpipe_handler);
spamd_send_stream(dstr, mstr);
spamd_shutdown(dstr, 0, SHUT_WR);
set_signal_handler(SIGPIPE, handler);
rc = spamd_read_line(cstr, buffer, sizeof buffer, NULL);
spamd_destroy(&cstr);
MF_ASSERT(rc == 0, mf_failure, _("Error reading clamav response: %s"),
mu_strerror(rc));
p = strrchr(buffer, ' ');
MF_ASSERT(p, mf_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(mf_failure,
_("Clamav error: %s"),
buffer);
} else {
MF_THROW(mf_failure,
_("Unknown clamav response: %s"),
buffer);
}
MF_RETURN(rc);
}
END
MF_INIT