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