/* This file is part of mailfrom filter. -*- c -*- Copyright (C) 2006 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 2, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include static size_t sa_score_loc; static size_t sa_threshold_loc; static size_t sa_keywords_loc; static size_t clamav_virus_name_loc; 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); if (rc) { if (env_catch(env, mf_failure)) runtime_error(env, "mu_tcp_stream_create: %s", mu_strerror(rc)); return rc; } rc = mu_stream_open(*stream); if (rc) { if (env_catch(env, mf_failure)) runtime_error(env, "opening tcp stream: %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; if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { if (env_catch(env, mf_failure)) runtime_error(env, "socket: %s", mu_strerror(errno)); return 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); if (env_catch(env, mf_failure)) runtime_error(env, "connect: %s", mu_strerror(errno)); return errno; } fp = fdopen(fd, "w+"); rc = mu_stdio_stream_create(stream, fp, MU_STREAM_RDWR); if (rc) { fclose(fp); if (env_catch(env, mf_failure)) runtime_error(env, "mu_stdio_stream_create: %s", mu_strerror(errno)); return rc; } rc = mu_stream_open(*stream); if (rc) { mu_stream_destroy (stream, mu_stream_get_owner (*stream)); fclose(fp); if (env_catch(env, mf_failure)) runtime_error(env, "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 = 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) { char *nl = NULL; if (buf[size-1] == '\n') { size--; nl = "\r\n"; } debug3(80,"<< %*.*s", size, size, buf); mu_stream_sequential_write (stream, buf, size); if (nl) mu_stream_sequential_write (stream, nl, 2); } } 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; } static void url_error(eval_environ_t env, const char *ustr, const char *msg, int rc) { if (env_catch(env, mf_url)) runtime_error(env, "%s `%s': %s", msg, ustr, mu_strerror(rc)); } 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; if (rc = mu_url_create(&url, urlstr)) { if (env_catch(env, mf_failure) == 0) return NULL; else runtime_error(env, "cannot create url from `%s': %s", urlstr, mu_strerror(rc)); } if (rc = mu_url_parse(url)) { mu_url_destroy(&url); url_error(env, urlstr, "error parsing url", rc); return NULL; } if (rc = mu_url_get_scheme(url, buffer, sizeof buffer, NULL)) { mu_url_destroy(&url); url_error(env, urlstr, "cannot get scheme", rc); return NULL; } if (strcmp(buffer, "file") == 0 || strcmp(buffer, "socket") == 0) { size_t size; if (rc = mu_url_get_path(url, NULL, 0, &size)) { url_error(env, urlstr, "cannot get path", rc); return NULL; } 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); url_error(env, urlstr, "cannot get port", rc); return NULL; } if (n == 0 || (port = n) != n) { mu_url_destroy(&url); if (env_catch(env, mf_range)) runtime_error(env, "port out of range: %s", mu_strerror(rc)); return NULL; } if (rc = mu_url_get_host(url, NULL, 0, &size)) { mu_url_destroy(&url); url_error(env, urlstr, "cannot get host", rc); return NULL; } 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); } 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; int rc; 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, limit; int isfile; ostr = open_connection(env, urlstr, &isfile, NULL); if (!ostr) return; 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); if (env_catch(env, mf_failure)) runtime_error(env, "remote side has closed connection"); return; } if (sscanf(buffer, "SPAMD/%18s %d %*s", version_str, &result) != 2) { spamd_destroy(&ostr); if (env_catch(env, mf_failure)) runtime_error(env, "spamd responded with bad string '%s'", buffer); return; } decode_float(&version, version_str, 1); if (version < 10) { spamd_destroy(&ostr); if (env_catch(env, mf_failure)) runtime_error(env, "unsupported SPAMD version: %s", version_str); return; } 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); if (env_catch(env, mf_failure)) runtime_error(env, "spamd responded with bad Spam header " "'%s'", buffer); return; } 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); *env_var_ref(env, sa_score_loc) = (STKVAL) score; *env_var_ref(env, sa_threshold_loc) = (STKVAL) threshold; *env_var_ref(env, sa_keywords_loc) = strcpy(heap_reserve(env, strlen(buffer) + 1), buffer); while (spamd_read_line(ostr, buffer, sizeof buffer, &size) == 0 && size > 0) /* Drain input */; spamd_destroy(&ostr); push(env, (STKVAL) 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); if (!cstr) return; spamd_send_command(cstr, "STREAM"); spamd_read_line(cstr, buffer, sizeof buffer, NULL); if (sscanf(buffer, "PORT %hu\n", &port) != 1) { spamd_destroy(&cstr); if (env_catch(env, mf_failure)) runtime_error(env, "bad response from clamav: expected `PORT'" " but found `%s'", buffer); return; } 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); if (env_catch(env, mf_failure)) runtime_error(env, "mu_tcp_stream_create: %s", mu_strerror(rc)); return; } rc = mu_stream_open(dstr); if (rc) { spamd_destroy(&cstr); mu_stream_destroy(&dstr, mu_stream_get_owner(dstr)); if (env_catch(env, mf_failure)) runtime_error(env, "opening tcp stream: %s", mu_strerror(rc)); } handler = set_signal_handler(SIGPIPE, sigpipe_handler); spamd_send_stream(dstr, mstr); spamd_shutdown(dstr, isfile, SHUT_WR); set_signal_handler(SIGPIPE, handler); rc = spamd_read_line(cstr, buffer, sizeof buffer, NULL); spamd_destroy(&cstr); if (rc) { if (env_catch(env, mf_failure)) runtime_error(env, "error reading clamav response", mu_strerror(rc)); return; } if ((p = strrchr(buffer, ' ')) == NULL) { if (env_catch(env, mf_failure)) runtime_error(env, "unknown clamav response: %s", buffer); return; } else { ++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++; *env_var_ref(env, clamav_virus_name_loc) = strcpy(heap_reserve(env, strlen(s) + 1), 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 */ if (env_catch(env, mf_failure)) runtime_error(env, "clamav error: %s", buffer); return; } else { if (env_catch(env, mf_failure)) runtime_error(env, "unknown clamav response: %s", buffer); return; } } push(env, (STKVAL) rc); } END MF_INIT(sa, sa_score_loc = builtin_variable_install("sa_score", rettype_number)->off; sa_threshold_loc = builtin_variable_install("sa_threshold", rettype_number)->off; sa_keywords_loc = builtin_variable_install("sa_keywords", rettype_string)->off; clamav_virus_name_loc = builtin_variable_install("clamav_virus_name", rettype_string)->off; )