diff options
Diffstat (limited to 'contrib/exim')
-rw-r--r-- | contrib/exim/dlfunc-json/README | 10 | ||||
-rw-r--r-- | contrib/exim/dlfunc-json/exim-example.txt | 75 | ||||
-rw-r--r-- | contrib/exim/dlfunc-json/rspamd.c | 576 | ||||
-rw-r--r-- | contrib/exim/local_scan.c | 700 | ||||
-rw-r--r-- | contrib/exim/local_scan.c.in | 377 | ||||
-rw-r--r-- | contrib/exim/patch-exim-src_spam.c.diff | 338 | ||||
-rw-r--r-- | contrib/exim/patch-exim-src_spam.c.diff.exim-4.85.diff | 332 | ||||
-rw-r--r-- | contrib/exim/shutdown.patch | 14 |
8 files changed, 2422 insertions, 0 deletions
diff --git a/contrib/exim/dlfunc-json/README b/contrib/exim/dlfunc-json/README new file mode 100644 index 0000000..0e52270 --- /dev/null +++ b/contrib/exim/dlfunc-json/README @@ -0,0 +1,10 @@ +FEATURES +1) Support new http-protocol (/checkv2) +2) Return action, symbols, symbol options, messages, scan time + +INSTALL + +1) Get cJSON.c, cJSON.h, put to dlfunc src dir (https://github.com/DaveGamble/cJSON) +2) Compile dlfunc library: + cc rspamd.c -fPIC -fpic -shared -I/root/rpmbuild/BUILD/exim-4.89/build-Linux-x86_64/ -o exim-rspamd-http-dlfunc.so +3) See exim-example.txt for exim configure diff --git a/contrib/exim/dlfunc-json/exim-example.txt b/contrib/exim/dlfunc-json/exim-example.txt new file mode 100644 index 0000000..e6da334 --- /dev/null +++ b/contrib/exim/dlfunc-json/exim-example.txt @@ -0,0 +1,75 @@ +acl_smtp_data = acl_check_data + +..... + +acl_check_data: + +..... + +# RSPAMD: START + warn + !authenticated = * + add_header = X-Spam-Checker-Version: Rspamd + add_header = :at_start:Authentication-Results: ip=$sender_host_address:$sender_host_port, host=$sender_host_name, helo=$sender_helo_name, mailfrom=$sender_address + warn + #spam = nobody:true + #set acl_m0_rspamd = $spam_report + set acl_m0_rspamd = ${dlfunc{/usr/local/libexec/exim/exim-rspamd-http-dlfunc.so}{rspamd}{/var/run/rspamd/rspamd.sock}{defer_ok}} + accept + authenticated = * + warn + condition = ${if eq{$acl_m0_rspamd}{}} + logwrite = RSPAMD check failed + add_header = X-Spam-Info: Check failed + warn + condition = ${if match{$acl_m0_rspamd}{\N^rspamd dlfunc:\s*\N}{yes}{no}} + logwrite = RSPAMD check defer: ${sg{$acl_m0_rspamd}{\N^rspamd dlfunc:\s*\N}{}} + add_header = X-Spam-Info: Check deffered + + warn + remove_header = X-Spam-Checker-Version:X-Spam-Status:X-Spam-Info:X-Spam-Result + set acl_m1 = No + warn + condition = ${if !eq{$acl_m0_rspamd}{}} + set acl_m1_yesno = ${if match{$acl_m0_rspamd}{\NAction: (.+?)\n\N}{$1}{}} + set acl_m2_status = ${if eq{$acl_m1_yesno}{reject}{REJECT}{\ + ${if eq{$acl_m1_yesno}{add header}{PROBABLY}{\ + ${if eq{$acl_m1_yesno}{rewrite subject}{PROBABLY}{\ + ${if eq{$acl_m1_yesno}{soft reject}{SOFT}{\ + ${if eq{$acl_m1_yesno}{greylist}{GREYLIST}{NO}}\ + }}\ + }}\ + }}\ + }} + set acl_m1_yesno = ${if eq{$acl_m1_yesno}{}{unknown}{\ + ${if eq{$acl_m1_yesno}{reject}{Yes}{\ + ${if eq{$acl_m1_yesno}{add header}{Yes}{\ + ${if eq{$acl_m1_yesno}{rewrite subject}{Yes}{\ + ${if eq{$acl_m1_yesno}{soft reject}{Probably}{\ + ${if eq{$acl_m1_yesno}{greylist}{Probably}{No}}\ + }}\ + }}\ + }}\ + }}\ + }} + #logwrite = RSPAMD: status: $acl_m2_status + #logwrite = RSPAMD DEBUG: $acl_m0_rspamd + set acl_m0_rspamd = ${sg{$acl_m0_rspamd}{ Action:.+\n}{}} + warn + condition = ${if !eq{$acl_m0_rspamd}{}} + logwrite = RSPAMD: $acl_m2_status, $acl_m0_rspamd + add_header = X-Spam-Result: $acl_m0_rspamd + add_header = X-Spam-Status: $acl_m1_yesno + defer + condition = ${if eq{$acl_m2_status}{GREYLIST}} + log_message = Rspamd $acl_m2_status + message = Try again later. Message greylisted + defer + condition = ${if eq{$acl_m2_status}{SOFT}} + log_message = Rspamd $acl_m2_status + message = Try again later. Message previously greylisted + deny + condition = ${if eq{$acl_m2_status}{REJECT}} + log_message = Rspamd $acl_m2_status + message = This message detected as SPAM and rejected +# RSPAMD: END diff --git a/contrib/exim/dlfunc-json/rspamd.c b/contrib/exim/dlfunc-json/rspamd.c new file mode 100644 index 0000000..2623c7d --- /dev/null +++ b/contrib/exim/dlfunc-json/rspamd.c @@ -0,0 +1,576 @@ +/*- + * Copyright (c) 2013-2015, Alexey Savelyev <info@homeweb.ru> + * Copyright 2017 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + * Based on source code from http://www.ols.es/exim/dlext/ by David Saez <david@ols.es>, + * source code of exim by Philip Hazel <ph10@cam.ac.uk> + * and source code of exiscan by Tom Kistner <tom@duncanthrax.net> + * and source code of Victor Ustugov http://mta.org.ua/ +*/ + +#include "exim.h" + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdarg.h> +#include <errno.h> +#include "cJSON.h" +#include "cJSON.c" + +#define RSPAMD_TIMEOUT 120 + +extern uschar *tod_stamp (int); +//extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */ +//extern uschar *spool_directory; /* Name of spool directory */ +//extern uschar message_subdir[]; /* Subdirectory for messages */ + +//------------------------------------------------------------------------- + +int +rspamd (uschar **yield, int argc, uschar *argv[]) { + char *arg_socket_addr; + char *arg_defer_ok; + int defer_ok; + int rspamd_command; + char tcp_addr[15]; + int tcp_port; + FILE *mbox_file = NULL; +// unsigned long mbox_size; + off_t mbox_size; + uschar *s, *p; + header_line *my_header, *header_new, *header_last, *tmp_headerlist; + header_line *last_received = NULL; + uschar *address; + uschar *helo; + uschar *sender_host_name; + uschar *authenticated_id; + char mbox_path[8192]; + int max_len, len; + int rspamd_sock = 0; + struct hostent *he; + struct in_addr in; + struct sockaddr_un server; +#ifndef NO_POLL_H + int result; + struct pollfd pollfd; +#endif + int offset; + uschar spamd_buffer[32600]; + uschar spamd_buffer2[32600]; + time_t start; + size_t read, wrote; + int i, j, c; + + arg_socket_addr = argv[0]; + arg_defer_ok = argv[1]; + + if (argc < 2) { + defer_ok = 0; + } else if (strcmpic (arg_defer_ok, US"1") == 0) + || (strcmpic (arg_defer_ok, US + "yes") == 0) + || (strcmpic (arg_defer_ok, US + "true") == 0) + || (strcmpic (arg_defer_ok, US + "defer_ok") == 0) { + defer_ok = 1; + } else { + defer_ok = 0; + } + + if ((arg_socket_addr == NULL) || (arg_socket_addr[0] == 0)) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: Socket address expected"); + *yield = string_sprintf ("rspamd dlfunc: Socket address expected"); + goto RETURN_DEFER; + } + + + if (split_spool_directory == 0) { + snprintf (mbox_path, sizeof (mbox_path), "%s/input/%s-D", + spool_directory, + message_id); + } else { + snprintf (mbox_path, sizeof (mbox_path), "%s/input/%s/%s-D", + spool_directory, message_subdir, message_id); + } + + mbox_file = fopen (mbox_path, "rb"); + + if (!mbox_file) { + *yield = string_sprintf ("rspamd dlfunc: Unable to spool message '%s'", + mbox_path); + return (defer_ok ? OK : ERROR); + } + + (void) fseek (mbox_file, 0, SEEK_END); + mbox_size = ftell (mbox_file); +//debug_printf(" Total spool file size: %d\n", mbox_size); + mbox_size -= SPOOL_DATA_START_OFFSET; +//debug_printf(" Spool file size: %d\n", mbox_size); +//debug_printf(" fseek %d, %d\n", SPOOL_DATA_START_OFFSET, SEEK_SET); + (void) fseek (mbox_file, SPOOL_DATA_START_OFFSET, SEEK_SET); + + start = time (NULL); + /* socket does not start with '/' -> network socket */ + if (arg_socket_addr[0] != '/') { + if (sscanf (CS arg_socket_addr, "%s %u", tcp_addr, &tcp_port) != 2 ) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: Invalid rspamd address: '%s'", + arg_socket_addr); + *yield = string_sprintf ( + "rspamd dlfunc: Invalid rspamd address: '%s'", + arg_socket_addr); + goto RETURN_DEFER; + } + + /* Lookup the host */ + if ((he = gethostbyname (CS tcp_addr)) == 0) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: failed to lookup host '%s'", tcp_addr); + *yield = string_sprintf ( + "rspamd dlfunc: failed to lookup host '%s'", tcp_addr); + goto RETURN_DEFER; + } + + in = *(struct in_addr *) he->h_addr_list[0]; + +/* contact a rspamd */ + + if ((rspamd_sock = ip_socket (SOCK_STREAM, AF_INET)) < 0) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: TCP socket creation failed: %s", + strerror (errno)); + *yield = string_sprintf ( + "rspamd dlfunc: TCP socket creation failed: %s", + strerror (errno)); + goto RETURN_DEFER; + }; + + if (ip_connect (rspamd_sock, AF_INET, (uschar *) inet_ntoa (in), + tcp_port, 5, FALSE) < 0) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: connection to %s, port %u failed: %s", + tcp_addr, tcp_port, strerror (errno)); + *yield = string_sprintf ( + "rspamd dlfunc: connection to %s, port %u failed: %s", + tcp_addr, tcp_port, strerror (errno)); + goto RETURN_DEFER; + } + +//debug_printf(" Use TCP socket %s:%d\n", tcp_addr, tcp_port); + } else { + if ((rspamd_sock = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: Unable to acquire socket (%s)", + strerror (errno)); + *yield = string_sprintf ( + "rspamd dlfunc: Unable to acquire socket (%s)", + strerror (errno)); + goto RETURN_DEFER; + } + + server.sun_family = AF_UNIX; + Ustrcpy (server.sun_path, arg_socket_addr); + + if (connect (rspamd_sock, (struct sockaddr *) &server, + sizeof (struct sockaddr_un)) < 0) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: Unable to connect to UNIX socket %s (%s)", + socket, strerror (errno)); + *yield = string_sprintf ( + "rspamd dlfunc: Unable to connect to UNIX socket %s (%s)", + socket, strerror (errno)); + goto RETURN_DEFER; + } + +//debug_printf(" Use UNIX Domain socket %s\n", arg_socket_addr); + } + +// now we are connected to rspamd on rspamd_sock + + memset (spamd_buffer2, 0, sizeof (spamd_buffer2)); + offset = 0; + + // headers list + tmp_headerlist = NULL; + header_last = NULL; + for (my_header = header_list; my_header; my_header = my_header->next) { + if ((my_header->type != '*') && (my_header->type != htype_old)) { + header_new = store_get (sizeof (header_line)); + header_new->text = my_header->text; + + header_new->slen = my_header->slen; + header_new->type = my_header->type; + header_new->next = NULL; + //debug_printf(" copy header item: '%s'\n", my_header->text); + + max_len = sizeof (spamd_buffer2) - offset - 1; + len = my_header->slen; + if (len > max_len) len = max_len; + Ustrncpy (spamd_buffer2 + offset, my_header->text, len); + offset += len; + + if (header_last != NULL) header_last->next = header_new; + header_last = header_new; + } + } + + s = string_sprintf ("\n"); + max_len = sizeof (spamd_buffer2) - offset - 1; + len = Ustrlen (s); + if (len > max_len) len = max_len; + Ustrncpy (spamd_buffer2 + offset, s, len); + offset += len; + +//debug_printf(" Headers size: %d\n", offset); + mbox_size += offset; +//debug_printf(" Total message size: %d\n", mbox_size); + +// copy request to buffer + memset (spamd_buffer, 0, sizeof (spamd_buffer)); + string_format (spamd_buffer, + sizeof (spamd_buffer), + "POST /checkv2 HTTP/1.0\r\nContent-length: "OFF_T_FMT + "\r\nQueue-Id: %s\r\nFrom: %s\r\n", + mbox_size, message_id, sender_address); + + for (i = 0; i < recipients_count; i++) { + string_format (spamd_buffer + Ustrlen (spamd_buffer), + sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "Rcpt: %s\r\n", + recipients_list[i].address); + } + + if ((helo = expand_string (US"$sender_helo_name")) != NULL && *helo != '\0') { + string_format (spamd_buffer + Ustrlen (spamd_buffer), + sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "Helo: %s\r\n", + helo); + } + + if ((sender_host_name = expand_string (US"$sender_host_name")) != NULL && + *sender_host_name != '\0') { + string_format (spamd_buffer + Ustrlen (spamd_buffer), + sizeof (spamd_buffer) - Ustrlen (spamd_buffer), + "Hostname: %s\r\n", + sender_host_name); + } + //else + //string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "Hostname: unknown\r\n"); + + if (sender_host_address != NULL) { + string_format (spamd_buffer + Ustrlen (spamd_buffer), + sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "IP: %s\r\n", + sender_host_address); + } + + //authenticated_id + if ((authenticated_id = expand_string (US"$authenticated_id")) != NULL && + *authenticated_id != '\0') { + string_format (spamd_buffer + Ustrlen (spamd_buffer), + sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "User: %s\r\n", + authenticated_id); + } + + string_format (spamd_buffer + Ustrlen (spamd_buffer), + sizeof (spamd_buffer) - Ustrlen (spamd_buffer), "\r\n"); + + if (send (rspamd_sock, spamd_buffer, Ustrlen (spamd_buffer), 0) < 0) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: rspamd send failed: %s", strerror (errno)); + goto RETURN_DEFER; + } + + /* + * now send the data buffer and spool file + */ + Ustrcpy (big_buffer, "sending data block"); + + wrote = send (rspamd_sock, spamd_buffer2, strlen (spamd_buffer2), 0); + if (wrote == -1) { + goto WRITE_FAILED; + } + + /* + * Note: poll() is not supported in OSX 10.2. + */ + +#ifndef NO_POLL_H + pollfd.fd = rspamd_sock; + pollfd.events = POLLOUT; +#endif +// (void)fcntl(rspamd_sock, F_SETFL, O_NONBLOCK); + do { + read = fread (spamd_buffer, 1, sizeof (spamd_buffer) - 1, mbox_file); + + if (read < sizeof (spamd_buffer)) { + spamd_buffer[read] = 0; + } +//debug_printf(" Read from spool file: %s", spamd_buffer); + if (read > 0) { + offset = 0; + again: + +#ifndef NO_POLL_H + result = poll (&pollfd, 1, 1000); + if (result == -1 && errno == EINTR) { + continue; + } + else if (result < 1) { + if (result == -1) + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: %s on rspamd socket", + strerror (errno)); + else { + if (time (NULL) - start < RSPAMD_TIMEOUT) + goto again; + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: timed out writing rspamd socket"); + *yield = string_sprintf ( + "rspamd dlfunc: timed out writing rspamd socket"); + } + goto RETURN_DEFER; + } +#endif + wrote = send (rspamd_sock, spamd_buffer + offset, read - offset, 0); + + if (wrote == -1) { + goto WRITE_FAILED; + } + + if (offset + wrote != read) { + offset += wrote; + goto again; + } + } + } while (!feof (mbox_file) && !ferror (mbox_file)); + + if (ferror (mbox_file)) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: error reading spool file: %s", + strerror (errno)); + *yield = string_sprintf ("rspamd dlfunc: error reading spool file: %s", + strerror (errno)); + goto RETURN_DEFER; + } + + /* + read rspamd response using what's left of the timeout. + */ + + memset (spamd_buffer, 0, sizeof (spamd_buffer)); + offset = 0; + while ((i = ip_recv (rspamd_sock, + spamd_buffer + offset, + sizeof (spamd_buffer) - offset - 1, + RSPAMD_TIMEOUT - time (NULL) + start)) > 0 + ) { + //debug_printf(" read %d bytes from socket\n", i); + offset += i; + } +//debug_printf(" total read %d bytes from socket\n", offset); + +/* error handling */ + if ((i <= 0) && (errno != 0)) { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: error reading from rspamd socket: %s", + strerror (errno)); + *yield = string_sprintf ( + "rspamd dlfunc: error reading from rspamd socket: %s", + strerror (errno)); + goto RETURN_DEFER; + } + +//debug_printf("read from socket: %s\n", spamd_buffer); + + if (rspamd_sock > 0) { + (void) close (rspamd_sock); + rspamd_sock = 0; + } + if (mbox_file != NULL) { + (void) fclose (mbox_file); + mbox_file = NULL; + } + + //Parse http response code + if (strstr (spamd_buffer, "HTTP/1.1 200 OK") == NULL && + strstr (spamd_buffer, "HTTP/1.0 200 OK") == NULL) { + *yield = string_sprintf ("rspamd dlfunc: HTTP return code != 200: %s", + spamd_buffer); + goto RETURN_DEFER; + } + + //Parse http response + const char *crlf_pos = strstr (spamd_buffer, "\r\n\r\n"); + if (crlf_pos == NULL) { + *yield = string_sprintf ("rspamd dlfunc: HTTP response error: %s", + spamd_buffer); + goto RETURN_DEFER; + } + + char *json_answer = string_sprintf ("%s", crlf_pos + 4); + + //Parse json + cJSON *json = NULL; + json = cJSON_Parse (json_answer); + + if (!json) { + *yield = string_sprintf ("rspamd dlfunc: Json parse error, json: %s", + spamd_buffer); + goto RETURN_DEFER; + } + + //Score + cJSON *score = cJSON_GetObjectItem (json, "score"); + if (!cJSON_IsNumber (score)) { + *yield = string_sprintf ( + "rspamd dlfunc: Json parse error, no found 'score'"); + goto RETURN_DEFER; + } + //required_score + cJSON *required_score = cJSON_GetObjectItem (json, "required_score"); + if (!cJSON_IsNumber (required_score)) { + *yield = string_sprintf ( + "rspamd dlfunc: Json parse error, no found 'required_score'"); + goto RETURN_DEFER; + } + //Action + cJSON *action = cJSON_GetObjectItem (json, "action"); + if (!cJSON_IsString (action)) { + *yield = string_sprintf ( + "rspamd dlfunc: Json parse error, no found 'action'"); + goto RETURN_DEFER; + } + *yield = string_sprintf ("[%.2f / %.2f]", score->valuedouble, + required_score->valuedouble); + + //Parse scan time + cJSON *time_real = cJSON_GetObjectItem (json, "time_real"); + cJSON *time_virtual = cJSON_GetObjectItem (json, "time_virtual"); + if (cJSON_IsNumber (time_real) && cJSON_IsNumber (time_virtual)) + *yield = string_sprintf ("%s [time: %.6f, %.6f]", *yield, + time_real->valuedouble, time_virtual->valuedouble); + + *yield = string_sprintf ("%s\n Action: %s\n", *yield, action->valuestring); + + cJSON *symbol = NULL; + cJSON *symbol_name = NULL; + cJSON *symbol_score = NULL; + cJSON *symbol_options = NULL; + cJSON *option = NULL; + + //parse symbols + cJSON *symbols = cJSON_GetObjectItem (json, "symbols"); + for (i = 0; i < cJSON_GetArraySize (symbols); i++) { + symbol = cJSON_GetArrayItem (symbols, i); + symbol_name = cJSON_GetObjectItem (symbol, "name"); + symbol_score = cJSON_GetObjectItem (symbol, "score"); + symbol_options = cJSON_GetObjectItem (symbol, "options"); + + if (cJSON_IsString (symbol_name)) { + *yield = string_sprintf ("%s %s", *yield, symbol_name->valuestring); + } + if (cJSON_IsNumber (symbol_score)) { + *yield = string_sprintf ("%s(%.2f)", *yield, + symbol_score->valuedouble); + } + + //parse options + c = cJSON_GetArraySize (symbol_options); + if (c > 0) { + *yield = string_sprintf ("%s[", *yield); + } + + for (j = 0; j < c; j++) { + option = cJSON_GetArrayItem (symbol_options, j); + + if (cJSON_IsString (option)) { + *yield = string_sprintf ("%s%s", *yield, option->valuestring); + if (j < c - 1) *yield = string_sprintf ("%s, ", *yield); + } + } + if (c > 0) { + *yield = string_sprintf ("%s]", *yield); + } + + *yield = string_sprintf ("%s\n", *yield); + } + + //Parse messages + cJSON *mess = NULL; + cJSON *messages = cJSON_GetObjectItem (json, "messages"); + c = cJSON_GetArraySize (messages); + + for (i = 0; i < c; i++) { + mess = cJSON_GetArrayItem (messages, i); + if (cJSON_IsString (mess)) { + *yield = string_sprintf ("%s %s", *yield, mess->valuestring); + } + + if (i < c - 1) { + *yield = string_sprintf ("%s\n", *yield); + } + } + + return OK; + +/* Come here if any call to read_response, other than a response after the data +phase, failed. Analyse the error, and if isn't too bad, send a QUIT +command. Wait for the response with a short timeout, so we don't wind up this +process before the far end has had time to read the QUIT. */ + + WRITE_FAILED: + { + log_write (0, LOG_MAIN | LOG_PANIC, + "rspamd dlfunc: %s on rspamd socket", strerror (errno)); + *yield = string_sprintf ("rspamd dlfunc: %s on rspamd socket", + strerror (errno)); + goto RETURN_DEFER; + } + + RESPONSE_FAILED: + { + int code; + int save_errno; + int more_errno; + uschar message_buffer[256]; + uschar *message; + + save_errno = errno; + + message = &message_buffer[0]; + + log_write (0, LOG_MAIN | LOG_PANIC, "rspamd dlfunc: %s", message); + *yield = string_sprintf ("rspamd dlfunc: %s", message); + + goto RETURN_DEFER; + } + + RETURN_DEFER: + { + if (rspamd_sock > 0) { + (void) close (rspamd_sock); + rspamd_sock = 0; + } + if (mbox_file != NULL) { + (void) fclose (mbox_file); + mbox_file = NULL; + } + + return (defer_ok ? OK : ERROR); + } + + return OK; +} diff --git a/contrib/exim/local_scan.c b/contrib/exim/local_scan.c new file mode 100644 index 0000000..42cd5ad --- /dev/null +++ b/contrib/exim/local_scan.c @@ -0,0 +1,700 @@ +/* + This program is RSPAMD agent for use with + exim (http://www.exim.org) MTA by its local_scan feature. + + To enable exim local scan please copy this file to exim source tree + Local/local_scan.c, edit Local/Makefile to add + + LOCAL_SCAN_SOURCE=Local/local_scan.c + LOCAL_SCAN_HAS_OPTIONS=yes + + and compile exim. + + Comment out RSPAM_UNIXSOCKET definition below if you have remote RSPAMD + daemon + + AND + + use Exim parameters daemonIP and daemonPort to configure remote + RSPAMD daemon. + + For exim compilation with local scan feature details please visit + http://www.exim.org/exim-html-4.50/doc/html/spec_toc.html#TOC333 + + For RSPAMD details please visit + http://rspamd.sourceforge.net +*/ + +/* Comment out the row below to use socket type AF_INET + to connect RSPAMD daemon */ +//#define RSPAM_UNIXSOCKET + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <stdlib.h> + +#include "local_scan.h" + +extern uschar *sender_helo_name; +extern int message_size; + +#define READ_FAIL(x) ((x) < 0) +#define RSPAMD_FAILURE_HDR "X-Spam-Flag" +#define RSPAMD_SCORE_HDR "X-Spam-Status" +#define REJECT_ON_ERROR 0 + +static int _OK = 0; +static int ERR_WRITE = 53; +static int ERR_READ = 54; +static int MAX_FAILS_C = 256; +static int MAX_PATH = 256; +static int MAX_SIZE_FILE = 64*1024; + +static uschar *daemonIP = US"127.0.0.1"; +static int daemonPort = 11333; +static uschar *temp_dir = US"/var/tmp"; +static uschar *socket_name = US"/var/run/rspamd.sock"; +static int strange = 0; + +optionlist local_scan_options[] = +{ + {"rspam_ip", opt_stringptr, &daemonIP}, + {"rspam_port", opt_int, &daemonPort}, + {"rspam_tmp", opt_stringptr, &temp_dir}, + {"rspam_sock", opt_stringptr, &socket_name}, + +}; + +int local_scan_options_count = sizeof (local_scan_options) / sizeof (optionlist); + +typedef int socket_t; +static socket_t sock = -1; + +int iFdInp; +struct sockaddr_un ssun; +struct sockaddr_in ssin; + +static int mOpenTmp (char *pszDir, char *pszPrefix, char *pszPath) +{ + int iLen; + int iFd = -1; + char *pszSep = ""; + + iLen = (int)strlen(pszDir); + if (iLen > MAX_PATH) + return -1; + + if (pszDir[iLen - 1] != '/') + pszSep = "/"; + + sprintf (pszPath, "%s%s%sXXXXXX", pszDir, pszSep, pszPrefix); + iFd = mkstemp (pszPath); + + if (iFd < 0) + log_write (0, LOG_MAIN, "rspam-exim: Temp file create error %d", errno); + + return iFd; +} + +static int ReadFd (int iFdMsg, int fd) +{ + char psMsg [MAX_SIZE_FILE]; /* max size SO can swallow */ + int iLen, result = _OK; + + if ((iLen = read (fd, psMsg, sizeof (psMsg))) > 0) + { + if (write (iFdMsg, psMsg, (unsigned int) iLen) != iLen) + result = ERR_WRITE; + } + else + result = ERR_READ; + + close (iFdMsg); + + return result; +} + + +void CleanupInp (char *sName) +{ + if (sName) unlink (sName); + + close (iFdInp); + return; +} + + +int FakeSMTPCommand (socket_t sock, + char *command, + char *value, + char *sName, + int Cleanup, + int wa) +{ + char sCommand[1024]; + char answ [3]; + int Len; + + sprintf (sCommand, "%s %s\r\n", command, value); + + if (send (sock, sCommand, strlen (sCommand), 0) != (int) strlen (sCommand)) + { + log_write (0, LOG_MAIN, "rspam-exim: socket sending '%s' error %d", sCommand, errno); + if (Cleanup) + CleanupInp (sName); + return ERR_WRITE; + } + + if(wa) { + memset (answ, '\0', sizeof (answ)); + Len = read (sock, answ, sizeof (answ)); + if (READ_FAIL (Len)) + { + log_write (0, LOG_MAIN, "rspam-exim: read() error %d, len=%d", errno, Len); + if (Cleanup) + CleanupInp (sName); + return ERR_WRITE; + } + + if (strncmp (answ, "OK", 2) != 0) + { + log_write (0, LOG_MAIN, "rspam-exim: server did not confirm, answ=%s", answ); + if (Cleanup) + CleanupInp (sName); + return ERR_WRITE; /* Cannot read message error code */ + } + } + + return OK; +} + + +static int written (socket_t fd, const char *vptr, int n) +{ + size_t nleft; + int nwritten; + const char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) + { + if ((nwritten = send (fd, ptr, nleft, 0)) <= 0) + { + if (errno == EINTR) + nwritten = 0; + else + return (-1); + } + + nleft -= nwritten; + ptr += nwritten; + } + + return (n); +} + + +static int SendEnvelope (char *sFile) +{ + int i; + char str [256], *rh; + void *psBuf; + int fd, bytesRead; + + if(message_size > MAX_SIZE_FILE) { + log_write (0, LOG_MAIN, "rspam-exim: file %s is great %d bytes", sFile, MAX_SIZE_FILE); + return ERR_WRITE; + } + + /* send greeting */ +// if(FakeSMTPCommand(sock, "PROCESS", "RSPAMC/1.0", sFile, 1, 0) != _OK) +// return ERR_WRITE; + if(FakeSMTPCommand(sock, "SYMBOLS", "RSPAMC/1.1", sFile, 1, 0) != _OK) +// if(FakeSMTPCommand(sock, "CHECK", "RSPAMC/1.0", sFile, 1, 0) != _OK) + return ERR_WRITE; + + + + /* sender IP */ + if (FakeSMTPCommand (sock, "IP:", sender_host_address, sFile, 1, 0) != _OK) + return ERR_WRITE; + + /* mail from */ + if (FakeSMTPCommand (sock, "From:", + strlen (sender_address) == 0 ? "MAILER-DAEMON" : (char*) sender_address, sFile, 1, 0) != _OK) + return ERR_WRITE; + + /* send helo */ + if (FakeSMTPCommand (sock, "Helo:", sender_helo_name, sFile, 1, 0) != _OK) + return ERR_WRITE; + + /* send helo */ + sprintf(str, "%d", message_size); + if (FakeSMTPCommand (sock, "Content-Length:", str, sFile, 1, 0) != _OK) + return ERR_WRITE; + + /* number of recipient */ + sprintf(str, "%d", recipients_count); + if (FakeSMTPCommand (sock, "Recipient-Number:", str, sFile, 1, 0) != _OK) + return ERR_WRITE; + + /* envelope rcpto */ + for (i = 0; i < recipients_count; i ++) + { + if (FakeSMTPCommand (sock, "Rcpt:", recipients_list[i].address, sFile, 1, 0) != _OK) + return ERR_WRITE; + } + + psBuf = store_get (MAX_SIZE_FILE); + + fd = open (sFile, O_RDONLY); + if (fd > 0) + { + bytesRead = read (fd, psBuf, MAX_SIZE_FILE); + close (fd); + + if (FakeSMTPCommand (sock, "\r\n", "", sFile, 1, 0) != _OK) + return ERR_WRITE; + + if (written (sock, psBuf, bytesRead) != bytesRead) + return ERR_WRITE; + } + else + { + log_write (0, LOG_MAIN, "rspam-exim: file %s open error %d", sFile, errno); + return ERR_WRITE; + } + + return _OK; +} + + +int GetFiles (char *pInpFile, int local_scan_fd) +{ + /* + Returns OK if no errors, else error code. + On successful return, pEnvFile points to Envelope file name and + pInpFile points to Message filename + */ + int iStatus; + struct header_line *h_line; + + iFdInp = mOpenTmp ((char *)temp_dir, "sp-inp", pInpFile); + if (iFdInp == -1) + { + return ERR_WRITE; + } + + /* Emit headers */ + h_line = header_list; + while (h_line != NULL) + { + if (h_line->type == '*') /* internal header */ + { + h_line = h_line->next; + continue; + } + + if (write (iFdInp, h_line->text, strlen (h_line->text)) != strlen (h_line->text)) + { + CleanupInp (""); + return ERR_WRITE; + } + h_line = h_line->next; + } + if (write (iFdInp, "\n", 1) != 1) + { + CleanupInp (""); + return ERR_WRITE; + } + + /* Read msg */ + if ((iStatus = ReadFd (iFdInp, local_scan_fd))) + { + return iStatus; + } + + /* Return success */ + return _OK; +} + + +int GetAndTransferMessage (int fd, char *sFile) +{ + char answ [4]; + int iStatus; + int Len, ccnt; + int test; + + iStatus = GetFiles ((char *)sFile, fd); + + if (iStatus != _OK) + { + log_write (0, LOG_MAIN, "rspam-exim: Error %d getting message", iStatus); + close (sock); + return iStatus; + } + + for (ccnt = 0; ccnt <= MAX_FAILS_C; ccnt ++) + { +#ifdef RSPAM_UNIXSOCKET + test = connect (sock, (struct sockaddr *) &ssun, sizeof (struct sockaddr_un)) < 0; +#else + test = connect (sock, (struct sockaddr *) &ssin, sizeof (struct sockaddr_in)) < 0; +#endif + if (test) + { + if (ccnt < MAX_FAILS_C) + usleep (1000); + else + { + close (sock); +#ifdef RSPAM_UNIXSOCKET + log_write (0, LOG_MAIN, "rspam-exim: socket connect to %s failed", (char *)socket_name); +#else + log_write (0, LOG_MAIN, "rspam-exim: socket connect to %s:%u failed", daemonIP, daemonPort); +#endif + return REJECT_ON_ERROR ? LOCAL_SCAN_TEMPREJECT:LOCAL_SCAN_ACCEPT; + } + } + else + break; + } + + iStatus = SendEnvelope (sFile); + if (iStatus != _OK) + { + log_write (0, LOG_MAIN, "rspam-exim: error %d sending envelope data", iStatus); + close (sock); + return iStatus; + } + + /* fprintf (stderr, "Transmit OK\n"); */ + return _OK; +} + +void header_del (uschar *hdr) +{ + struct header_line *h_line; + + h_line = header_list; + while (h_line != NULL) + { + if (h_line->type == '*') /* internal header */ + { + h_line = h_line->next; + continue; + } + + if (strncasecmp (h_line->text, hdr, strlen(hdr)) == 0) + { + h_line->type = '*'; + while (h_line->next && + (*h_line->next->text == ' ' || *h_line->next->text == '\t')) + { + h_line = h_line->next; + h_line->type = '*'; + } + } + h_line = h_line->next; + } +} + +void AlterSubject (char *label) +{ + struct header_line *h_line; + char *subject, *strP; + + h_line = header_list; + + while (h_line != NULL) + { + if (h_line->type == '*') /* internal header */ + { + h_line = h_line->next; + continue; + } + + if (strncasecmp (h_line->text, "Subject", strlen("Subject")) == 0) + { + strP = strchr (h_line->text, ':'); + subject = string_copy (++strP); + while (h_line->next && + (*h_line->next->text == ' ' || *h_line->next->text == '\t')) + { + h_line = h_line->next; + subject = string_sprintf ("%s\n%s", subject, h_line->text); + } + header_del (US "Subject"); + break; + } + + h_line = h_line->next; + } + header_add (' ', "Subject: %s%s", label, subject ? subject : ""); +} + +int +io_read(int fd, char *buf, size_t size) +{ + int nfd, next = 0, rcount = 15; + size_t len = 0; + fd_set fds; + struct timeval tv; + + if((sock < 0) || (buf == NULL)) + return -1; + + FD_ZERO(&fds); + +repeat_read: + + tv.tv_sec = 5; + tv.tv_usec = 0; + FD_SET(fd, &fds); + +// log_write(0, LOG_MAIN, "rspam-exim: before select"); + + if((nfd=select(fd+1, &fds, NULL, NULL, &tv)) == -1) { +// log_write(0, LOG_MAIN, "rspam-exim: select error: %s", strerror(errno)); + return -1; + } + +// log_write(0, LOG_MAIN, "rspam-exim: select return %d fds, rcount %d, next %d", nfd, rcount, next); + + if((nfd>0) && (FD_ISSET(fd, &fds))) { + next += len = read(fd, buf + next, size - next); +// log_write(0, LOG_MAIN, "rspam-exim: read %d bytes", len); +// if(next<size) +// goto repeat_read; + } + rcount--; + if(rcount>0) + goto repeat_read; + + return next; +} + +int WaitForScanResult (uschar **resStr) +{ + int Len, i; + int rej = 0, result = LOCAL_SCAN_ACCEPT, answer_size, spm = 0, code = 0, ns = 0, smb = 0, urf = 0; + char *strP, *tok, *tmp; + char *hdr = NULL, *hdrv = NULL, *spmStr = NULL, *symbols=NULL, *urls=NULL; + char answ [4096], state[6], metric[128], back; + float sm=0, smd=0, smr=0; + + memset (answ, '\0', sizeof (answ)); +// log_write(0, LOG_MAIN, "rspam-exim: before read from %d", sock); +// Len = read (sock, answ, sizeof (answ) - 1); + Len = io_read(sock, answ, sizeof (answ) - 1); + log_write(0, LOG_MAIN, "rspam-exim: read %d bytes", Len); + + if (strncmp (answ, "RSPAMD/1.1 ", 11) == 0) + { + strP = (char *)answ; + for (tok = strtok (strP, "\n"); tok; tok = strtok (NULL, "\n")) + { +// log_write(0, LOG_MAIN, "rspam-exim: process line '%s'", tok); + + if (strncmp (tok, "RSPAMD/1.1 ", 11) == 0) + { + if (sscanf (tok, "%*s %d %s", &code, state) == 2) + { +// log_write(0, LOG_MAIN, "rspam-exim: daemon reports code %d %s", code, state); + if ((code == 0) && (strcmp(state,"OK")==0)) { + header_del ((uschar *) RSPAMD_FAILURE_HDR); + header_add (' ', "%s: SKIP\n", RSPAMD_FAILURE_HDR); + strange = 1; + continue; + } else { + header_del ((uschar *) RSPAMD_FAILURE_HDR); + header_add (' ', "%s: SKIP\n", RSPAMD_FAILURE_HDR); + log_write(0, LOG_MAIN, "rspam-exim: daemon reports code %d %s", code, state); + return LOCAL_SCAN_ACCEPT; + } + } + continue; + } + + /* Metric: default; False; 6.00 / 10.00 */ + /* Process metric */ + if (strncmp (tok, "Metric:", 7) == 0) + { + tmp = tok; + while( (*tmp++) && + ((*tmp!='\r') || (*tmp!='\n')) + ); + back = *tmp; + *tmp = '\0'; + if (sscanf (tok, "Metric: %[^';']; %[^';']; %f / %f / %f", metric, state, &sm, &smd, &smr) == 5) { + log_write(0, LOG_MAIN, "rspam-exim: metric: %s; %s; %f / %f / %f", metric, state, sm, smd, smr ); + if(strcasecmp(state,"true")==0) { + header_del ((uschar *) RSPAMD_FAILURE_HDR); + header_add (' ', "%s: %s\n", RSPAMD_FAILURE_HDR, "Yes"); + } else if(strcasecmp(state,"skip")==0) { + header_del ((uschar *) RSPAMD_FAILURE_HDR); + header_add (' ', "%s: %s\n", RSPAMD_FAILURE_HDR, "Skip"); + } else { + header_del ((uschar *) RSPAMD_FAILURE_HDR); + header_add (' ', "%s: %s\n", RSPAMD_FAILURE_HDR, "No"); + } + header_del ((uschar *) RSPAMD_SCORE_HDR); + header_add (' ', "%s: %.2f / %.2f / %.2f\n", RSPAMD_SCORE_HDR, sm, smd, smr); + strange = 0; + } + *tmp = back; + continue; + } + + if (strncmp (tok, "Symbol:", 7) == 0) + { + tmp = tok; + while( (*tmp++) && + ((*tmp!='\r') || (*tmp!='\n')) + ); + back = *tmp; + *tmp = '\0'; + if(smb>0) { + tok += 7; + while(*tok && isspace(*tok)) tok++; + if(strlen(tok)>0) { + symbols = string_sprintf ("%s\n %s", symbols, tok); + } + } else { + tok += 7; + while(*tok && isspace(*tok)) tok++; + symbols = string_copy (tok); + } + smb = 1; + *tmp = back; + continue; + } + + if (strncmp (tok, "Urls:", 5) == 0) + { + tmp = tok; + while( (*tmp++) && + ((*tmp!='\r') || (*tmp!='\n')) + ); + back = *tmp; + *tmp = '\0'; + if(urf>0) { + tok[0] = tok[1]= tok[2]= tok[3]= tok[4] = ' '; + urls = string_sprintf ("%s\n%s", urls, tok+3); + } else { + tok += 5; + while(*tok && isspace(*tok)) tok++; + urls = string_copy (tok); + } + urf = 1; + *tmp = back; + continue; + } + } + + + /* do not forget the symbols */ + if (symbols != NULL && strlen(symbols)) + { + i = 0; + tmp = tok = string_copy(symbols); + header_del ((uschar *) "X-Spam-Sybmols"); + header_add (' ', "%s: %s\n", "X-Spam-Sybmols", symbols); + while(*tmp!='\0') { + if(*tmp == '\r') + *tmp = ' '; + if(*tmp == '\n') + *tmp = ','; + tmp++; + } + *tmp = '\0'; + log_write(0, LOG_MAIN, "rspam-exim: symbols: %s", tok); + } + + /* do not forget the urls */ + if (urls != NULL && strlen(urls)) + { + log_write(0, LOG_MAIN, "rspam-exim: urls: %s", urls); + header_del ((uschar *) "X-Spam-Urls"); + header_add (' ', "%s: %s\n", "X-Spam-Urls", urls); + } + + log_write (0, LOG_MAIN, "rspam-exim: For message from %s will return %s, mailfrom: <%s>, rcpto: <%s>", sender_host_address, rej == 2 ? "DISCARD" : rej == 1 ? "REJECT" : "ACCEPT", sender_address, recipients_list[0].address); + + } + else + { + result = LOCAL_SCAN_ACCEPT; + log_write(0, LOG_MAIN, "rspam-exim: wrong signature in answer: %s", answ); + } + + if((sm>0) && (smr>0) && (sm>=smr)) { + result = LOCAL_SCAN_REJECT; + } + return result; +} + + +int +local_scan(int fd, uschar **return_text) +{ + int retval = _OK; + char sFileInp [MAX_PATH + 81]; + + /* Socket stuff */ + + strange = 0; +#ifdef RSPAM_UNIXSOCKET + if ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) + { + log_write(0, LOG_MAIN, "rspam-exim: socket() failed"); + exit (EXIT_FAILURE); + } + memset (&ssun, '\0', sizeof (struct sockaddr_un)); + ssun.sun_family = AF_UNIX; + if (sizeof (socket_name) > sizeof (ssun.sun_path)) + { + close (sock); + log_write(0, LOG_MAIN, "rspam-exim: UNIX socket name %s too long", socket_name); + exit (EXIT_FAILURE); + } + strcpy (ssun.sun_path, socket_name); +#else + if ((sock = socket (AF_INET, SOCK_STREAM, 0)) < 0) + { + log_write(0, LOG_MAIN, "rspam-exim: socket() failed"); + exit (EXIT_FAILURE); + } + memset (&ssin, '\0', sizeof (struct sockaddr_in)); + ssin.sin_family = AF_INET; + ssin.sin_addr.s_addr = inet_addr (daemonIP); + ssin.sin_port = htons (daemonPort); +#endif + + if (GetAndTransferMessage (fd, (char *)sFileInp) != _OK) + { + close (sock); + unlink (sFileInp); + SPOOL_DATA_START_OFFSET; + return REJECT_ON_ERROR ? LOCAL_SCAN_TEMPREJECT:LOCAL_SCAN_ACCEPT; + } + + retval = WaitForScanResult (return_text); + + if(!strange) + unlink (sFileInp); + close (sock); + SPOOL_DATA_START_OFFSET; + + return retval; +} + +/* End of local_scan.c */ diff --git a/contrib/exim/local_scan.c.in b/contrib/exim/local_scan.c.in new file mode 100644 index 0000000..72d2f79 --- /dev/null +++ b/contrib/exim/local_scan.c.in @@ -0,0 +1,377 @@ +/* + * This program is RSPAMD agent for use with + * exim (http://www.exim.org) MTA by its local_scan feature. + * + * To enable exim local scan please copy this file to exim source tree + * Local/local_scan.c, edit Local/Makefile to add + * + * LOCAL_SCAN_SOURCE=Local/local_scan.c + * LOCAL_SCAN_HAS_OPTIONS=yes + * + * and compile exim. + * + * For exim compilation with local scan feature details please visit + * http://www.exim.org/exim-html-current/doc/html/spec_html/ch42.html + * + * For RSPAMD details please visit + * https://bitbucket.org/vstakhov/rspamd/ + * + * Example configuration: + * ********************** + * + * local_scan_timeout = 50s + * + * begin local_scan + * rspam_ip = 127.0.0.1 + * rspam_port = 11333 + * rspam_skip_sasl_authenticated = true + * # don't reject message if on of recipients from this list + * rspam_skip_rcpt = postmaster@example.com : some_user@example.com + * rspam_message = "Spam rejected; If this is not spam, please contact <postmaster@example.com>" + * + * + * $Id: local_scan.c 646 2010-08-11 11:49:36Z ayuzhaninov $ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <math.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "local_scan.h" + +#define REQUEST_LINES 64 +#define REPLY_BUF_SIZE 16384 +#define HEADER_STATUS "X-Rspam-Status" +#define HEADER_METRIC "X-Rspam-Metric" +#define HEADER_SCORE "X-Rspam-Score" + +/* configuration options */ +static uschar *daemon_ip = US"127.0.0.1"; +static int max_scan_size = 4 * 1024 * 1024; +static uschar *reject_message = US"Spam message rejected"; +static int daemon_port = 11333; +static BOOL skip_authenticated = TRUE; +static uschar *want_spam_rcpt_list = US""; + +/* the entries must appear in alphabetical order */ +optionlist local_scan_options[] = { + { "rspam_ip", opt_stringptr, &daemon_ip }, + { "rspam_max_scan_size", opt_mkint, &max_scan_size }, + { "rspam_message", opt_stringptr, &reject_message }, + { "rspam_port", opt_int, &daemon_port }, + { "rspam_skip_rcpt", opt_stringptr, &want_spam_rcpt_list }, + { "rspam_skip_sasl_authenticated", opt_bool, &skip_authenticated }, +}; + +int local_scan_options_count = sizeof(local_scan_options) / sizeof(optionlist); + +/* push formatted line into vector */ +int push_line(struct iovec *iov, int i, const char *fmt, ...); + +int +local_scan(int fd, uschar **return_text) +{ + struct stat sb; + struct sockaddr_in server_in; + int s, i, r, request_p = 0, headers_count = 0, is_spam = 0, is_reject = 0; + off_t message_size; + struct iovec request_v[REQUEST_LINES], *headers_v; +#if "@CMAKE_SYSTEM_NAME@" == "FreeBSD" + struct sf_hdtr headers_sf; +#endif + uschar *helo, *log_buf; + header_line *header_p; + char reply_buf[REPLY_BUF_SIZE], io_buf[BUFSIZ]; + ssize_t size; + char *tok_ptr, *str; + char mteric[128], result[8]; + float score, required_score; + + *return_text = reject_message; + + /* + * one msaage can be send via exim+rspamd twice + * remove header from previous pass + */ + header_remove(0, US HEADER_STATUS); + header_remove(0, US HEADER_METRIC); + + /* check message size */ + fstat(fd,&sb); /* XXX shuld check error */ + message_size = sb.st_size - SPOOL_DATA_START_OFFSET; + if (message_size > max_scan_size) { + header_add(' ', HEADER_STATUS ": skip_big\n"); + log_write (0, LOG_MAIN, "rspam: message larger than rspam_max_scan_size, accept"); + return LOCAL_SCAN_ACCEPT; + } + + /* don't scan mail from authenticated hosts */ + if (skip_authenticated && sender_host_authenticated != NULL) { + header_add(' ', HEADER_STATUS ": skip_authenticated\n"); + log_write(0, LOG_MAIN, "rspam: from=<%s> ip=%s authenticated (%s), skip check\n", + sender_address, + sender_host_address == NULL ? US"localhost" : sender_host_address, + sender_host_authenticated); + return LOCAL_SCAN_ACCEPT; + } + + /* + * add status header, which mean, that message was not scanned + * if message will be scanned, this header will be replaced + */ + header_add(' ', HEADER_STATUS ": check_error\n"); + + /* create socket */ + memset(&server_in, 0, sizeof(server_in)); + server_in.sin_family = AF_INET; + server_in.sin_port = htons(daemon_port); + server_in.sin_addr.s_addr = inet_addr(daemon_ip); + if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + log_write(0, LOG_MAIN, "rspam: socket (%d: %s)", errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } + if (connect(s, (struct sockaddr *) &server_in, sizeof(server_in)) < 0) { + close(s); + log_write(0, LOG_MAIN, "rspam: can't connect to %s:%d (%d: %s)", daemon_ip, daemon_port, errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } + + /* count message headers */ + for (header_p = header_list; header_p != NULL; header_p = header_p->next) { + /* header type '*' is used for replaced or deleted header */ + if (header_p->type == '*') + continue; + headers_count++; + } + + /* write message headers to vector */ +#if "@CMAKE_SYSTEM_NAME@" == "FreeBSD" + memset(&headers_sf, 0, sizeof(headers_sf)); + if (headers_count > 0) { + headers_v = store_get((headers_count + 1)* sizeof(*headers_v)); + i = 0; + for (header_p = header_list; header_p != NULL; header_p = header_p->next) { + if (header_p->type == '*') + continue; + headers_v[i].iov_base = header_p->text; + headers_v[i].iov_len = header_p->slen; + i++; + message_size += header_p->slen; + } + headers_v[i].iov_base = "\n"; + headers_v[i].iov_len = strlen("\n"); + message_size += strlen("\n"); + + headers_sf.headers = headers_v; + headers_sf.hdr_cnt = headers_count + 1; + } +#else + if (headers_count > 0) { + headers_v = store_get((headers_count + 1)* sizeof(*headers_v)); + i = 0; + for (header_p = header_list; header_p != NULL; header_p = header_p->next) { + if (header_p->type == '*') + continue; + headers_v[i].iov_base = header_p->text; + headers_v[i].iov_len = header_p->slen; + i++; + message_size += header_p->slen; + } + headers_v[i].iov_base = "\n"; + headers_v[i].iov_len = strlen("\n"); + message_size += strlen("\n"); +#endif + + /* write request to vector */ + r = 0; + r += push_line(request_v, request_p++, "SYMBOLS RSPAMC/1.1\r\n"); + r += push_line(request_v, request_p++, "Content-length: " OFF_T_FMT "\r\n", message_size); + r += push_line(request_v, request_p++, "Queue-Id: %s\r\n", message_id); + r += push_line(request_v, request_p++, "From: %s\r\n", sender_address); + r += push_line(request_v, request_p++, "Recipient-Number: %d\r\n", recipients_count); + for (i = 0; i < recipients_count; i ++) + r += push_line(request_v, request_p++, "Rcpt: %s\r\n", recipients_list[i].address); + if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0') + r += push_line(request_v, request_p++, "Helo: %s\r\n", helo); + if (sender_host_address != NULL) + r += push_line(request_v, request_p++, "IP: %s\r\n", sender_host_address); + r += push_line(request_v, request_p++, "\r\n"); + + if (r < 0) { + close(s); + return LOCAL_SCAN_ACCEPT; + } + + /* send request */ + if (writev(s, request_v, request_p) < 0) { + close(s); + log_write(0, LOG_MAIN, "rspam: can't send request to %s:%d (%d: %s)", daemon_ip, daemon_port, errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } + +#if "@CMAKE_SYSTEM_NAME@" == "FreeBSD" + /* send headers (from iovec) and message body (from file) */ + if (sendfile(fd, s, SPOOL_DATA_START_OFFSET, 0, &headers_sf, NULL, 0) < 0) { + close(s); + log_write(0, LOG_MAIN, "rspam: can't send message to %s:%d (%d: %s)", daemon_ip, daemon_port, errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } +#else + /* send headers */ + if (writev(s, headers_v, headers_count) < 0) { + close(s); + log_write(0, LOG_MAIN, "rspam: can't send headers to %s:%d (%d: %s)", daemon_ip, daemon_port, errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } + + /* Send message */ + while ((r = read (fd, io_buf, sizeof (io_buf))) > 0) { + if (write (s, io_buf, r) < 0) { + close(s); + log_write(0, LOG_MAIN, "rspam: can't send message to %s:%d (%d: %s)", daemon_ip, daemon_port, errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } + } +#endif + + /* read reply from rspamd */ + reply_buf[0] = '\0'; + size = 0; + while ((r = read(s, reply_buf + size, sizeof(reply_buf) - size - 1)) > 0 && size < sizeof(reply_buf) - 1) { + size += r; + } + + if (r < 0) { + close(s); + log_write(0, LOG_MAIN, "rspam: can't read from %s:%d (%d: %s)", daemon_ip, daemon_port, errno, strerror(errno)); + return LOCAL_SCAN_ACCEPT; + } + reply_buf[size] = '\0'; + close(s); + + if (size >= REPLY_BUF_SIZE - 1) { + log_write(0, LOG_MAIN, "rspam: buffer is full, reply may be truncated"); + } + + /* parse reply */ + tok_ptr = reply_buf; + + /* + * rspamd can use several metrics, logic implemented here: + * if any metric more than reject_score - will reject + * if any metric true - message will be marked as spam + */ + + /* First line is: <PROTOCOL>/<VERSION> <ERROR_CODE> <ERROR_REPLY> */ + str = strsep(&tok_ptr, "\r\n"); + if (str != NULL && sscanf(str, "%*s %d %*s", &i) == 1) { + if (i != 0) { + log_write(0, LOG_MAIN, "rspam: server error: %s", str); + return LOCAL_SCAN_ACCEPT; + } + } else { + log_write(0, LOG_MAIN, "rspam: bad reply from server: %s", str); + return LOCAL_SCAN_ACCEPT; + } + + while ((str = strsep(&tok_ptr, "\r\n")) != NULL) { + /* skip empty tockens */ + if (*str == '\0') + continue; + if (strncmp(str, "Metric:", strlen("Metric:")) == 0) { + /* + * parse line like + * Metric: default; False; 27.00 / 30.00 + */ + if (sscanf(str, "Metric: %s %s %f / %f", + mteric, result, &score, &required_score) == 4) { + log_write(0, LOG_MAIN, "rspam: metric %s %s %.2f / %.2f", + mteric, result, score, required_score); + header_add(' ', HEADER_METRIC ": %s %s %.2f / %.2f\n", + mteric, result, score, required_score); + /* integers score for use in sieve ascii-numeric comparator */ + if (strcmp(mteric, "default;") == 0) + header_add(' ', HEADER_SCORE ": %d\n", + (int)round(score)); + } else { + log_write(0, LOG_MAIN, "rspam: can't parse: %s", str); + return LOCAL_SCAN_ACCEPT; + } + } else if (strncmp(str, "Action:", strlen("Action:")) == 0) { + /* line like Action: add header */ + str += strlen("Action: "); + if (strncmp(str, "reject", strlen("reject")) == 0) { + is_reject = 1; + is_spam = 1; + } else if (strncmp(str, "add header", strlen("add header")) == 0) { + is_spam = 1; + } + } + } + + /* XXX many allocs by string_sprintf() + * better to sprintf() to single buffer allocated by store_get() + */ + log_buf = string_sprintf("message to"); + for (i = 0; i < recipients_count; i ++) { + log_buf = string_sprintf("%s %s", log_buf, recipients_list[i].address); + if (is_reject && lss_match_address(recipients_list[i].address, want_spam_rcpt_list, TRUE) == OK) { + is_reject = 0; + log_write(0, LOG_MAIN, "rspam: %s want spam, don't reject this message", recipients_list[i].address); + } + } + + if (is_reject) { + log_write(0, LOG_MAIN, "rspam: reject %s", log_buf); + return LOCAL_SCAN_REJECT; + } + + header_remove(0, US HEADER_STATUS); + if (is_spam) { + header_add(' ', HEADER_STATUS ": spam\n"); + log_write(0, LOG_MAIN, "rspam: message marked as spam"); + } else { + header_add(' ', HEADER_STATUS ": ham\n"); + log_write(0, LOG_MAIN, "rspam: message marked as ham"); + } + + return LOCAL_SCAN_ACCEPT; +} + +int +push_line(struct iovec *iov, const int i, const char *fmt, ...) +{ + va_list ap; + size_t len; + char buf[512]; + + if (i >= REQUEST_LINES) { + log_write(0, LOG_MAIN, "rspam: %s: index out of bounds", __FUNCTION__); + return (-1); + } + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + iov[i].iov_base = string_copy(US buf); + iov[i].iov_len = len; + + if (len >= sizeof(buf)) { + log_write(0, LOG_MAIN, "rspam: %s: error, string was longer than %d", __FUNCTION__, sizeof(buf)); + return (-1); + } + + return 0; +} + diff --git a/contrib/exim/patch-exim-src_spam.c.diff b/contrib/exim/patch-exim-src_spam.c.diff new file mode 100644 index 0000000..f3ac788 --- /dev/null +++ b/contrib/exim/patch-exim-src_spam.c.diff @@ -0,0 +1,338 @@ +diff -ru exim-4.70.orig/src/expand.c exim-4.70/src/expand.c +--- exim-4.70.orig/src/expand.c 2016-04-09 13:42:00.227625074 +0200 ++++ exim-4.70/src/expand.c 2016-04-09 13:42:37.183633096 +0200 +@@ -578,6 +578,7 @@ + { "sn8", vtype_filter_int, &filter_sn[8] }, + { "sn9", vtype_filter_int, &filter_sn[9] }, + #ifdef WITH_CONTENT_SCAN ++ { "spam_action", vtype_stringptr, &spam_action }, + { "spam_bar", vtype_stringptr, &spam_bar }, + { "spam_report", vtype_stringptr, &spam_report }, + { "spam_score", vtype_stringptr, &spam_score }, +diff -ru exim-4.70.orig/src/globals.c exim-4.70/src/globals.c +--- exim-4.70.orig/src/globals.c 2016-04-09 13:42:00.219625073 +0200 ++++ exim-4.70/src/globals.c 2016-04-09 13:42:37.187633096 +0200 +@@ -1136,6 +1136,7 @@ + uschar *spamd_address = US"127.0.0.1 783"; + uschar *spam_bar = NULL; + uschar *spam_report = NULL; ++uschar *spam_action = NULL; + uschar *spam_score = NULL; + uschar *spam_score_int = NULL; + #endif +diff -ru exim-4.70.orig/src/globals.h exim-4.70/src/globals.h +--- exim-4.70.orig/src/globals.h 2016-04-09 13:42:00.219625073 +0200 ++++ exim-4.70/src/globals.h 2016-04-09 13:42:37.187633096 +0200 +@@ -703,6 +703,7 @@ + extern uschar *spamd_address; /* address for the spamassassin daemon */ + extern uschar *spam_bar; /* the spam "bar" (textual representation of spam_score) */ + extern uschar *spam_report; /* the spamd report (multiline) */ ++extern uschar *spam_action; /* the spamd action */ + extern uschar *spam_score; /* the spam score (float) */ + extern uschar *spam_score_int; /* spam_score * 10 (int) */ + #endif +diff -ru exim-4.70.orig/src/spam.c exim-4.70/src/spam.c +--- exim-4.70.orig/src/spam.c 2016-04-09 13:42:00.231625075 +0200 ++++ exim-4.70/src/spam.c 2016-04-09 13:43:43.927647677 +0200 +@@ -16,6 +16,7 @@ + uschar spam_score_buffer[16]; + uschar spam_score_int_buffer[16]; + uschar spam_bar_buffer[128]; ++uschar spam_action_buffer[32]; + uschar spam_report_buffer[32600]; + uschar prev_user_name[128] = ""; + int spam_ok = 0; +@@ -31,9 +32,11 @@ + int spamd_sock; + uschar spamd_buffer[32600]; + int i, j, offset, result; ++ BOOL is_rspamd; + uschar spamd_version[8]; ++ uschar spamd_short_result[8]; + uschar spamd_score_char; +- double spamd_threshold, spamd_score; ++ double spamd_threshold, spamd_score, spamd_reject_score; + int spamd_report_offset; + uschar *p,*q; + int override = 0; +@@ -122,8 +125,15 @@ + spamd_address_container *this_spamd = + (spamd_address_container *)store_get(sizeof(spamd_address_container)); + ++ /* Check for spamd variant */ ++ if( Ustrstr(address, "variant=rspamd") != NULL ) { ++ this_spamd->is_rspamd = TRUE; ++ } ++ else { ++ this_spamd->is_rspamd = FALSE; ++ } + /* grok spamd address and port */ +- if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) { ++ if( sscanf(CS address, "%23s %hu", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) { + log_write(0, LOG_MAIN, + "spam acl condition: warning - invalid spamd address: '%s'", address); + continue; +@@ -165,6 +175,7 @@ + spamd_address_vector[current_server]->tcp_port, + 5 ) > -1) { + /* connection OK */ ++ is_rspamd = spamd_address_vector[current_server]->is_rspamd; + break; + }; + +@@ -197,12 +208,28 @@ + } + + server.sun_family = AF_UNIX; +- Ustrcpy(server.sun_path, spamd_address_work); ++ p = Ustrstr(spamd_address_work, "variant=rspamd"); ++ if( p != NULL ) { ++ is_rspamd = TRUE; ++ /* strip spaces */ ++ p --; ++ while (p > spamd_address_work && isspace (*p)) { ++ p --; ++ } ++ Ustrncpy(server.sun_path, spamd_address_work, p - spamd_address_work + 1); ++ /* zero terminate */ ++ server.sun_path[p - spamd_address_work + 1] = 0; ++ } ++ else { ++ is_rspamd = FALSE; ++ Ustrcpy(server.sun_path, spamd_address_work); ++ } ++ + + if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)", +- spamd_address_work, strerror(errno) ); ++ server.sun_path, strerror(errno) ); + (void)fclose(mbox_file); + (void)close(spamd_sock); + return DEFER; +@@ -210,22 +237,50 @@ + + } + ++ (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); + /* now we are connected to spamd on spamd_sock */ +- (void)string_format(spamd_buffer, +- sizeof(spamd_buffer), +- "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", +- user_name, +- mbox_size); +- +- /* send our request */ +- if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { ++ if (is_rspamd) { ++ /* rspamd variant */ ++ const char *helo; ++ const char *fcrdns; ++ const char *authid; ++ uschar *req_str; ++ ++ req_str = string_sprintf("CHECK RSPAMC/1.3\r\nContent-length: %lu\r\n" ++ "Queue-Id: %s\r\nFrom: <%s>\r\nRecipient-Number: %d\r\n", mbox_size, ++ message_id, sender_address, recipients_count); ++ for (i = 0; i < recipients_count; i ++) ++ req_str = string_sprintf("%sRcpt: <%s>\r\n", req_str, recipients_list[i].address); ++ if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0') ++ req_str = string_sprintf("%sHelo: %s\r\n", req_str, helo); ++ if ((fcrdns = expand_string(US"$sender_host_name")) != NULL && *fcrdns != '\0') ++ req_str = string_sprintf("%sHostname: %s\r\n", req_str, fcrdns); ++ if (sender_host_address != NULL) ++ req_str = string_sprintf("%sIP: %s\r\n", req_str, sender_host_address); ++ if ((authid = expand_string(US"$authenticated_id")) != NULL && *authid != '\0') ++ req_str = string_sprintf("%sUser: %s\r\n", req_str, authid); ++ req_str = string_sprintf("%s\r\n", req_str); ++ wrote = send(spamd_sock, req_str, Ustrlen(req_str), 0); ++ } ++ else { ++ /* spamassassin variant */ ++ (void)string_format(spamd_buffer, ++ sizeof(spamd_buffer), ++ "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", ++ user_name, ++ mbox_size); ++ /* send our request */ ++ wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0); ++ } ++ if(wrote == -1) ++ { + (void)close(spamd_sock); + log_write(0, LOG_MAIN|LOG_PANIC, +- "spam acl condition: spamd send failed: %s", strerror(errno)); ++ "spam acl condition: spamd send failed: %s", strerror(errno)); + (void)fclose(mbox_file); + (void)close(spamd_sock); + return DEFER; +- }; ++ } + + /* now send the file */ + /* spamd sometimes accepts connections but doesn't read data off +@@ -304,7 +359,9 @@ + (void)fclose(mbox_file); + + /* we're done sending, close socket for writing */ +- shutdown(spamd_sock,SHUT_WR); ++ if (!is_rspamd) { ++ shutdown(spamd_sock,SHUT_WR); ++ } + + /* read spamd response using what's left of the timeout. + */ +@@ -328,60 +385,93 @@ + /* reading done */ + (void)close(spamd_sock); + +- /* dig in the spamd output and put the report in a multiline header, if requested */ +- if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", +- spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { +- +- /* try to fall back to pre-2.50 spamd output */ +- if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", +- spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ if (!is_rspamd) { ++ /* dig in the spamd output and put the report in a multiline header, if requested */ ++ if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", ++ spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ ++ /* try to fall back to pre-2.50 spamd output */ ++ if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", ++ spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: cannot parse spamd output"); ++ return DEFER; ++ }; ++ }; ++ ++ if( spamd_score >= spamd_threshold ) { ++ Ustrcpy(spam_action_buffer, "reject"); ++ } ++ else { ++ Ustrcpy(spam_action_buffer, "no action"); ++ } ++ } ++ else { ++ /* rspamd variant of reply */ ++ int r; ++ if( (r = sscanf(CS spamd_buffer,"RSPAMD/%7s 0 EX_OK\r\nMetric: default; %7s %lf / %lf / %lf\r\n%n", ++ spamd_version,spamd_short_result,&spamd_score,&spamd_threshold,&spamd_reject_score,&spamd_report_offset)) != 5 ) { + log_write(0, LOG_MAIN|LOG_PANIC, +- "spam acl condition: cannot parse spamd output"); ++ "spam acl condition: cannot parse spamd output: %d", r); + return DEFER; + }; +- }; ++ /* now parse action */ ++ p = &spamd_buffer[spamd_report_offset]; ++ ++ if( Ustrncmp(p, "Action: ", sizeof("Action: ") - 1) == 0 ) { ++ p += sizeof("Action: ") - 1; ++ q = &spam_action_buffer[0]; ++ while (*p && *p != '\r' && (q - spam_action_buffer) < sizeof(spam_action_buffer) - 1) { ++ *q++ = *p++; ++ } ++ *q = '\0'; ++ } ++ } + + /* Create report. Since this is a multiline string, + we must hack it into shape first */ + p = &spamd_buffer[spamd_report_offset]; + q = spam_report_buffer; + while (*p != '\0') { +- /* skip \r */ +- if (*p == '\r') { +- p++; +- continue; +- }; +- *q = *p; +- q++; +- if (*p == '\n') { +- /* add an extra space after the newline to ensure +- that it is treated as a header continuation line */ +- *q = ' '; +- q++; +- }; +- p++; ++ /* skip \r */ ++ if (*p == '\r') { ++ p++; ++ continue; ++ }; ++ *q = *p; ++ q++; ++ if (*p == '\n') { ++ /* add an extra space after the newline to ensure ++ that it is treated as a header continuation line */ ++ *q = ' '; ++ q++; ++ }; ++ p++; + }; + /* NULL-terminate */ + *q = '\0'; + q--; + /* cut off trailing leftovers */ + while (*q <= ' ') { +- *q = '\0'; +- q--; ++ *q = '\0'; ++ q--; + }; ++ ++ /* common spamd actions */ + spam_report = spam_report_buffer; ++ spam_action = spam_action_buffer; + + /* create spam bar */ + spamd_score_char = spamd_score > 0 ? '+' : '-'; + j = abs((int)(spamd_score)); + i = 0; + if( j != 0 ) { +- while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) +- spam_bar_buffer[i++] = spamd_score_char; ++ while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) ++ spam_bar_buffer[i++] = spamd_score_char; + } + else{ +- spam_bar_buffer[0] = '/'; +- i = 1; ++ spam_bar_buffer[0] = '/'; ++ i = 1; + } + spam_bar_buffer[i] = '\0'; + spam_bar = spam_bar_buffer; +@@ -397,12 +487,12 @@ + + /* compare threshold against score */ + if (spamd_score >= spamd_threshold) { +- /* spam as determined by user's threshold */ +- spam_rc = OK; ++ /* spam as determined by user's threshold */ ++ spam_rc = OK; + } + else { +- /* not spam */ +- spam_rc = FAIL; ++ /* not spam */ ++ spam_rc = FAIL; + }; + + /* remember user name and "been here" for it unless spamd_socket was expanded */ +diff -ru exim-4.70.orig/src/spam.h exim-4.70/src/spam.h +--- exim-4.70.orig/src/spam.h 2016-04-09 13:42:00.235625076 +0200 ++++ exim-4.70/src/spam.h 2016-04-09 13:42:37.187633096 +0200 +@@ -24,7 +24,8 @@ + + typedef struct spamd_address_container { + uschar tcp_addr[24]; +- unsigned int tcp_port; ++ unsigned short int tcp_port; ++ int is_rspamd:1; + } spamd_address_container; + + #endif diff --git a/contrib/exim/patch-exim-src_spam.c.diff.exim-4.85.diff b/contrib/exim/patch-exim-src_spam.c.diff.exim-4.85.diff new file mode 100644 index 0000000..0389aea --- /dev/null +++ b/contrib/exim/patch-exim-src_spam.c.diff.exim-4.85.diff @@ -0,0 +1,332 @@ +diff -ru exim-4.85.orig/src/expand.c exim-4.85/src/expand.c +--- exim-4.85.orig/src/expand.c 2016-04-09 13:47:01.707691638 +0200 ++++ exim-4.85/src/expand.c 2016-04-09 13:47:29.771697969 +0200 +@@ -652,6 +652,7 @@ + { "sn8", vtype_filter_int, &filter_sn[8] }, + { "sn9", vtype_filter_int, &filter_sn[9] }, + #ifdef WITH_CONTENT_SCAN ++ { "spam_action", vtype_stringptr, &spam_action }, + { "spam_bar", vtype_stringptr, &spam_bar }, + { "spam_report", vtype_stringptr, &spam_report }, + { "spam_score", vtype_stringptr, &spam_score }, +Only in exim-4.85/src: expand.c.orig +diff -ru exim-4.85.orig/src/globals.c exim-4.85/src/globals.c +--- exim-4.85.orig/src/globals.c 2016-04-09 13:47:01.695691635 +0200 ++++ exim-4.85/src/globals.c 2016-04-09 13:47:29.771697969 +0200 +@@ -1276,6 +1276,7 @@ + uschar *spamd_address = US"127.0.0.1 783"; + uschar *spam_bar = NULL; + uschar *spam_report = NULL; ++uschar *spam_action = NULL; + uschar *spam_score = NULL; + uschar *spam_score_int = NULL; + #endif +Only in exim-4.85/src: globals.c.orig +diff -ru exim-4.85.orig/src/globals.h exim-4.85/src/globals.h +--- exim-4.85.orig/src/globals.h 2016-04-09 13:47:01.695691635 +0200 ++++ exim-4.85/src/globals.h 2016-04-09 13:47:29.771697969 +0200 +@@ -819,6 +819,7 @@ + extern uschar *spamd_address; /* address for the spamassassin daemon */ + extern uschar *spam_bar; /* the spam "bar" (textual representation of spam_score) */ + extern uschar *spam_report; /* the spamd report (multiline) */ ++extern uschar *spam_action; /* the spamd action */ + extern uschar *spam_score; /* the spam score (float) */ + extern uschar *spam_score_int; /* spam_score * 10 (int) */ + #endif +Only in exim-4.85/src: globals.h.orig +diff -ru exim-4.85.orig/src/spam.c exim-4.85/src/spam.c +--- exim-4.85.orig/src/spam.c 2016-04-09 13:47:01.711691638 +0200 ++++ exim-4.85/src/spam.c 2016-04-09 13:52:12.611762892 +0200 +@@ -14,6 +14,7 @@ + uschar spam_score_buffer[16]; + uschar spam_score_int_buffer[16]; + uschar spam_bar_buffer[128]; ++uschar spam_action_buffer[32]; + uschar spam_report_buffer[32600]; + uschar prev_user_name[128] = ""; + int spam_ok = 0; +@@ -32,9 +33,11 @@ + int spamd_sock = -1; + uschar spamd_buffer[32600]; + int i, j, offset, result; ++ BOOL is_rspamd; + uschar spamd_version[8]; ++ uschar spamd_short_result[8]; + uschar spamd_score_char; +- double spamd_threshold, spamd_score; ++ double spamd_threshold, spamd_score, spamd_reject_score; + int spamd_report_offset; + uschar *p,*q; + int override = 0; +@@ -128,8 +131,15 @@ + spamd_address_container *this_spamd = + (spamd_address_container *)store_get(sizeof(spamd_address_container)); + ++ /* Check for spamd variant */ ++ if( Ustrstr(address, "variant=rspamd") != NULL ) { ++ this_spamd->is_rspamd = TRUE; ++ } ++ else { ++ this_spamd->is_rspamd = FALSE; ++ } + /* grok spamd address and port */ +- if (sscanf(CS address, "%23s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2) ++ if (sscanf(CS address, "%23s %hu", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2) + { + log_write(0, LOG_MAIN, + "spam acl condition: warning - invalid spamd address: '%s'", address); +@@ -174,6 +184,7 @@ + spamd_address_vector[current_server]->tcp_port, + 5 ) > -1) { + /* connection OK */ ++ is_rspamd = spamd_address_vector[current_server]->is_rspamd; + break; + }; + +@@ -210,12 +221,28 @@ + } + + server.sun_family = AF_UNIX; +- Ustrcpy(server.sun_path, spamd_address_work); ++ p = Ustrstr(spamd_address_work, "variant=rspamd"); ++ if( p != NULL ) { ++ is_rspamd = TRUE; ++ /* strip spaces */ ++ p --; ++ while (p > spamd_address_work && isspace (*p)) { ++ p --; ++ } ++ Ustrncpy(server.sun_path, spamd_address_work, p - spamd_address_work + 1); ++ /* zero terminate */ ++ server.sun_path[p - spamd_address_work + 1] = 0; ++ } ++ else { ++ is_rspamd = FALSE; ++ Ustrcpy(server.sun_path, spamd_address_work); ++ } ++ + + if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)", +- spamd_address_work, strerror(errno) ); ++ server.sun_path, strerror(errno) ); + (void)fclose(mbox_file); + (void)close(spamd_sock); + return DEFER; +@@ -231,22 +258,50 @@ + return DEFER; + } + ++ (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK); + /* now we are connected to spamd on spamd_sock */ +- (void)string_format(spamd_buffer, +- sizeof(spamd_buffer), +- "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", +- user_name, +- mbox_size); +- +- /* send our request */ +- if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { ++ if (is_rspamd) { ++ /* rspamd variant */ ++ const char *helo; ++ const char *fcrdns; ++ const char *authid; ++ uschar *req_str; ++ ++ req_str = string_sprintf("CHECK RSPAMC/1.3\r\nContent-length: %lu\r\n" ++ "Queue-Id: %s\r\nFrom: <%s>\r\nRecipient-Number: %d\r\n", mbox_size, ++ message_id, sender_address, recipients_count); ++ for (i = 0; i < recipients_count; i ++) ++ req_str = string_sprintf("%sRcpt: <%s>\r\n", req_str, recipients_list[i].address); ++ if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0') ++ req_str = string_sprintf("%sHelo: %s\r\n", req_str, helo); ++ if ((fcrdns = expand_string(US"$sender_host_name")) != NULL && *fcrdns != '\0') ++ req_str = string_sprintf("%sHostname: %s\r\n", req_str, fcrdns); ++ if (sender_host_address != NULL) ++ req_str = string_sprintf("%sIP: %s\r\n", req_str, sender_host_address); ++ if ((authid = expand_string(US"$authenticated_id")) != NULL && *authid != '\0') ++ req_str = string_sprintf("%sUser: %s\r\n", req_str, authid); ++ req_str = string_sprintf("%s\r\n", req_str); ++ wrote = send(spamd_sock, req_str, Ustrlen(req_str), 0); ++ } ++ else { ++ /* spamassassin variant */ ++ (void)string_format(spamd_buffer, ++ sizeof(spamd_buffer), ++ "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n", ++ user_name, ++ mbox_size); ++ /* send our request */ ++ wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0); ++ } ++ if(wrote == -1) ++ { + (void)close(spamd_sock); + log_write(0, LOG_MAIN|LOG_PANIC, +- "spam acl condition: spamd send failed: %s", strerror(errno)); ++ "spam acl condition: spamd send failed: %s", strerror(errno)); + (void)fclose(mbox_file); + (void)close(spamd_sock); + return DEFER; +- }; ++ } + + /* now send the file */ + /* spamd sometimes accepts connections but doesn't read data off +@@ -349,60 +404,93 @@ + /* reading done */ + (void)close(spamd_sock); + +- /* dig in the spamd output and put the report in a multiline header, if requested */ +- if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", +- spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { +- +- /* try to fall back to pre-2.50 spamd output */ +- if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", +- spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ if (!is_rspamd) { ++ /* dig in the spamd output and put the report in a multiline header, if requested */ ++ if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", ++ spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ ++ /* try to fall back to pre-2.50 spamd output */ ++ if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", ++ spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: cannot parse spamd output"); ++ return DEFER; ++ }; ++ }; ++ ++ if( spamd_score >= spamd_threshold ) { ++ Ustrcpy(spam_action_buffer, "reject"); ++ } ++ else { ++ Ustrcpy(spam_action_buffer, "no action"); ++ } ++ } ++ else { ++ /* rspamd variant of reply */ ++ int r; ++ if( (r = sscanf(CS spamd_buffer,"RSPAMD/%7s 0 EX_OK\r\nMetric: default; %7s %lf / %lf / %lf\r\n%n", ++ spamd_version,spamd_short_result,&spamd_score,&spamd_threshold,&spamd_reject_score,&spamd_report_offset)) != 5 ) { + log_write(0, LOG_MAIN|LOG_PANIC, +- "spam acl condition: cannot parse spamd output"); ++ "spam acl condition: cannot parse spamd output: %d", r); + return DEFER; + }; +- }; ++ /* now parse action */ ++ p = &spamd_buffer[spamd_report_offset]; ++ ++ if( Ustrncmp(p, "Action: ", sizeof("Action: ") - 1) == 0 ) { ++ p += sizeof("Action: ") - 1; ++ q = &spam_action_buffer[0]; ++ while (*p && *p != '\r' && (q - spam_action_buffer) < sizeof(spam_action_buffer) - 1) { ++ *q++ = *p++; ++ } ++ *q = '\0'; ++ } ++ } + + /* Create report. Since this is a multiline string, + we must hack it into shape first */ + p = &spamd_buffer[spamd_report_offset]; + q = spam_report_buffer; + while (*p != '\0') { +- /* skip \r */ +- if (*p == '\r') { +- p++; +- continue; +- }; +- *q = *p; +- q++; +- if (*p == '\n') { +- /* add an extra space after the newline to ensure +- that it is treated as a header continuation line */ +- *q = ' '; +- q++; +- }; +- p++; ++ /* skip \r */ ++ if (*p == '\r') { ++ p++; ++ continue; ++ }; ++ *q = *p; ++ q++; ++ if (*p == '\n') { ++ /* add an extra space after the newline to ensure ++ that it is treated as a header continuation line */ ++ *q = ' '; ++ q++; ++ }; ++ p++; + }; + /* NULL-terminate */ + *q = '\0'; + q--; + /* cut off trailing leftovers */ + while (*q <= ' ') { +- *q = '\0'; +- q--; ++ *q = '\0'; ++ q--; + }; ++ ++ /* common spamd actions */ + spam_report = spam_report_buffer; ++ spam_action = spam_action_buffer; + + /* create spam bar */ + spamd_score_char = spamd_score > 0 ? '+' : '-'; + j = abs((int)(spamd_score)); + i = 0; + if( j != 0 ) { +- while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) +- spam_bar_buffer[i++] = spamd_score_char; ++ while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) ++ spam_bar_buffer[i++] = spamd_score_char; + } + else{ +- spam_bar_buffer[0] = '/'; +- i = 1; ++ spam_bar_buffer[0] = '/'; ++ i = 1; + } + spam_bar_buffer[i] = '\0'; + spam_bar = spam_bar_buffer; +@@ -418,12 +506,12 @@ + + /* compare threshold against score */ + if (spamd_score >= spamd_threshold) { +- /* spam as determined by user's threshold */ +- spam_rc = OK; ++ /* spam as determined by user's threshold */ ++ spam_rc = OK; + } + else { +- /* not spam */ +- spam_rc = FAIL; ++ /* not spam */ ++ spam_rc = FAIL; + }; + + /* remember expanded spamd_address if needed */ +Only in exim-4.85/src: spam.c.orig +Only in exim-4.85/src: .spam.c.rej.swp +diff -ru exim-4.85.orig/src/spam.h exim-4.85/src/spam.h +--- exim-4.85.orig/src/spam.h 2016-04-09 13:47:01.715691640 +0200 ++++ exim-4.85/src/spam.h 2016-04-09 13:47:29.775697969 +0200 +@@ -22,7 +22,8 @@ + + typedef struct spamd_address_container { + uschar tcp_addr[24]; +- unsigned int tcp_port; ++ unsigned short int tcp_port; ++ int is_rspamd:1; + } spamd_address_container; + + #endif diff --git a/contrib/exim/shutdown.patch b/contrib/exim/shutdown.patch new file mode 100644 index 0000000..e8bf8a0 --- /dev/null +++ b/contrib/exim/shutdown.patch @@ -0,0 +1,14 @@ +--- exim4-4.86.2.orig/src/spam.c ++++ exim4-4.86.2/src/spam.c +@@ -499,7 +499,10 @@ if (ferror(mbox_file)) + (void)fclose(mbox_file); + + /* we're done sending, close socket for writing */ +-shutdown(spamd_sock,SHUT_WR); ++if (!sd->is_rspamd) ++ { ++ shutdown(spamd_sock,SHUT_WR); ++ } + + /* read spamd response using what's left of the timeout. */ + memset(spamd_buffer, 0, sizeof(spamd_buffer)); |