/* 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); 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 (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: %s"), mu_strerror(rc)); } 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