--- /dev/null
+/*
+ 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://cebka.pp.ru/trac/
+*/
+
+/* 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 writen (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 (writen (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 succesful 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;
+
+ 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
+ if (connect (sock, (struct sockaddr *) &ssun, sizeof (struct sockaddr_un)) < 0)
+#else
+ if (connect (sock, (struct sockaddr *) &ssin, sizeof (struct sockaddr_in)) < 0)
+#endif
+ {
+ 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, 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[0] = tok[1]= tok[2]= tok[3]= tok[4]= tok[5]= tok[6]= ' ';
+ symbols = string_sprintf ("%s\n%s", symbols, tok+5);
+ } 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 == '\n') || (*tmp == '\r')) {
+ tmp++;
+ continue;
+ }
+ if ((*tmp == ' ') && (*(tmp+1)==' ')) {
+ tok[i++] = ',';
+ tmp += 2;
+ continue;
+ }
+ tok[i] = *tmp;
+ i++;
+ tmp++;
+ }
+ tok[i] = '\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 (1);
+ }
+ 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 (1);
+ }
+ 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 (1);
+ }
+ 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 */
--- /dev/null
+<html><head><title>Фильтрация спама при помощи системы rspamd.</title>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" >
+</head>
+<body class='pod'>
+<!--
+ generated by Pod::Simple::HTML v3.13,
+ using Pod::Simple::PullParser v3.13,
+ under Perl v5.010000 at Thu Feb 4 17:55:58 2010 GMT.
+
+ If you want to change this HTML document, you probably shouldn't do that
+ by changing it directly. Instead, see about changing the calling options
+ to Pod::Simple::HTML, and/or subclassing Pod::Simple::HTML,
+ then reconverting this document from the Pod source.
+ When in doubt, email the author of Pod::Simple::HTML for advice.
+ See 'perldoc Pod::Simple::HTML' for more info.
+
+-->
+
+<!-- start doc -->
+<a name='___top' class='dummyTopAnchor' ></a>
+
+<h1><a class='u' href='#___top' title='click to go to top of document'
+name="(1060)(1080)(1083)(1100)(1090)(1088)(1072)(1094)(1080)(1103)_(1089)(1087)(1072)(1084)(1072)_(1087)(1088)(1080)_(1087)(1086)(1084)(1086)(1097)(1080)_(1089)(1080)(1089)(1090)(1077)(1084)(1099)_rspamd."
+>Фильтрация спама при помощи системы rspamd.</a></h1>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="1._(1048)(1089)(1090)(1086)(1088)(1080)(1103)_(1080)_(1087)(1088)(1077)(1076)(1087)(1086)(1089)(1099)(1083)(1082)(1080)_(1089)(1086)(1079)(1076)(1072)(1085)(1080)(1103)."
+>1.
+История и предпосылки создания.</a></h2>
+
+<p>Мы долгое время использовали для фильтрации спама spamassassin,
+однако,
+при наших объемах почтового трафика (9-10 миллионов писем в сутки) spamassassin не обеспечивал достаточной производительности.
+После анализа исходного кода и принципов работы было выявлены следующие "врожденные" проблемы SA:</p>
+
+<ul>
+<li>обработка письма осуществляется набором регулярных выражений,
+однако,
+количество этих выражений слишком велико и является ключевым моментом задумчивости SA (например,
+для извлечения received заголовков письма,
+SA проверяет оные на соответствие всем ему известным MTA,
+очевидно,
+что такое действие не является оптимальным);</li>
+
+<li>нет возможности явно получить ip адрес,
+с которого нам отправили письмо (в SA это делается парсом received заголовков,
+что я лично не считаю правильным,
+а тем более оптимальным способом,
+так как MTA всегда знает,
+откуда ему пришло данное письмо)</li>
+
+<li>нет возможности передачи спам фильтру данных SMTP диалога</li>
+
+<li>MIME парсинг писем очень медленный,
+так как опять же сделан на базе регулярных выражений</li>
+</ul>
+
+<p>В итоге можно сказать,
+что основная проблема SA - это излишняя увлеченность авторов регулярными выражениями и отсутствие оптимизации.
+В итоге,
+в качестве базовых принципов создания rspamd были принципы оптимизации времени обработки писем,
+в числе которых:</p>
+
+<ul>
+<li>полностью асинхронная работа с сетью (на базе libevent),
+в том числе DNS запросы и работа с http</li>
+
+<li>быстрые механизмы выделения памяти</li>
+
+<li>компиляция всех регулярных выражений и вызов только тех из них,
+которые наиболее возможны (то есть,
+имеется механизм ведения статистики срабатывания различных правил,
+и при обработке следующего письма учитывается обработка предыдущих,
+что позволяет максимально быстро провести основные тесты)</li>
+
+<li>расширяемая архитектура: возможность написания плагинов на си,
+lua,
+возможность добавления новых типов фильтров,
+расширений протокола rspamc,
+возможность добавления новых типов статистических алгоритмов,
+алгоритмов нормализации и парсинга текстов</li>
+
+<li>возможность динамической загрузки различных настроек,
+списков ip адресов и прочей информации через HTTP протокол с поддержкой даты модификации таких списков</li>
+
+<li>использование везде,
+где возможно,
+специализированных конечных автоматов для разбора и анализа текстов</li>
+
+<li>использование быстрого mime парсера gmime</li>
+</ul>
+
+<p>В результате перехода с SA на rspamd последний показал в среднем в 10 раз более быструю обработку сообщений.
+В данный момент rspamd имеет статус бета версии,
+но вполне пригоден для работы.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="2._(1059)(1089)(1090)(1072)(1085)(1086)(1074)(1082)(1072)_(1080)_(1085)(1072)(1089)(1090)(1088)(1086)(1081)(1082)(1072)_rspamd"
+>2.
+Установка и настройка rspamd</a></h2>
+
+<p>Для сборки rspamd вам потребуются:</p>
+
+<ul>
+<li>libevent <a href="http://www.monkey.org/~provos/libevent/" class="podlinkurl"
+>http://www.monkey.org/~provos/libevent/</a> - библиотека для асинхронной обработки событий</li>
+
+<li>glib <a href="http://library.gnome.org/devel/glib/" class="podlinkurl"
+>http://library.gnome.org/devel/glib/</a> - библиотека общего назначения,
+содержащая многие вещи,
+о которых разработчики libc,
+увы,
+забыли (glib не является графической библиотекой,
+хотя и используется,
+например,
+gtk)</li>
+
+<li>gmime <a href="http://spruce.sourceforge.net/gmime/" class="podlinkurl"
+>http://spruce.sourceforge.net/gmime/</a> - MIME парсер на базе библиотеки glib</li>
+
+<li>lua <a href="http://www.lua.org/" class="podlinkurl"
+>http://www.lua.org/</a> - встраиваемый скриптовый язык (в принципе,
+наличие lua необязательно,
+но без lua многий функционал rspamd останется нереализованным)</li>
+
+<li>perl <a href="http://www.perl.org/" class="podlinkurl"
+>http://www.perl.org/</a> - на перле написан,
+например,
+консольный клиент rspamc,
+распознаватель редиректов в URL'ях,
+поэтому наличие перла в системе крайне желательно</li>
+
+<li>cmake <a href="http://www.cmake.org/" class="podlinkurl"
+>http://www.cmake.org/</a> - система сборки,
+которая позволяет rspamd собираться (по крайней мере,
+в теории) на всех Posix совместимых системах.
+Использовать стандартные в данной ситуации autotools я не стал,
+так как считаю их самой неудобной в использовании системой сборки исходников,
+которую можно только придумать.</li>
+
+<li>mercurial <a href="http://mercurial.selenic.com/" class="podlinkurl"
+>http://mercurial.selenic.com/</a> - система управления версиями (SCM),
+используемая для разработки rspamd.
+Так как в настоящее время rspamd находится в состоянии бета-версии,
+то релизы выходят нечасто и зачастую являются менее стабильными,
+чем текущая версия в репозитории,
+поэтому для установки rspamd лучше использовать версию непосредственно из репозитория.</li>
+</ul>
+
+<p>После установки всех требуемых программ и библиотек можно приступить непосредственно к установке rspamd.</p>
+
+<dl>
+<dt><a name="1_(1089)(1082)(1072)(1095)(1080)(1074)(1072)(1077)(1084)_(1090)(1077)(1082)(1091)(1097)(1077)(1077)_(1089)(1086)(1089)(1090)(1086)(1103)(1085)(1080)(1077)_(1088)(1077)(1087)(1086)(1079)(1080)(1090)(1086)(1088)(1080)(1103):_$_hg_clone_http://rspamd.hg.sourceforge.net:8000/hgroot/rspamd/rspamd"
+>1 скачиваем текущее состояние репозитория: $ hg clone http://rspamd.hg.sourceforge.net:8000/hgroot/rspamd/rspamd</a></dt>
+
+<dd>
+<dt><a
+>2 конфигурируем rspamd: $ cd rspamd && cmake .
+(при необходимости изменить параметры можно вначале воспользоваться командой ccmake .)</a></dt>
+
+<dd>
+<dt><a name="3_(1089)(1086)(1073)(1080)(1088)(1072)(1077)(1084):_$_make"
+>3 собираем: $ make</a></dt>
+
+<dd>
+<dt><a name="4_(1091)(1089)(1090)(1072)(1085)(1072)(1074)(1083)(1080)(1074)(1072)(1077)(1084):_#_make_install"
+>4 устанавливаем: # make install</a></dt>
+</dl>
+
+<h3><a class='u' href='#___top' title='click to go to top of document'
+name="(1050)(1086)(1085)(1092)(1080)(1075)(1091)(1088)(1080)(1088)(1086)(1074)(1072)(1085)(1080)(1077)"
+>Конфигурирование</a></h3>
+
+<p>В процессе установки ставятся конфигурационные файлы по умолчанию в каталог {PREFIX}/etc.
+При этом,
+в каталог etc/rspamd устанавливаются правила по умолчанию (headers,
+html,
+drugs,
+loto и.т.п.),
+а в каталог etc/rspamd.conf.sample устанавливается конфигурационный файл по умолчанию.
+Его можно скопировать в файл etc/rspamd.conf и отредактировать в соответствии с собственными предпочтениями.
+Конфигурационный файл по умолчанию снабжен комментариями к каждой директиве,
+а также подробно описан в документации к rspamd,
+находящейся в каталоге исходников doc/rspamd.pdf.
+В данном же руководстве подробно изложен принцип работы rspamd и настройки всех модулей системы.
+Для базовой работы достаточно исправить пути к файлам логов,
+пидфайлу,
+а также обратить внимание на разделы factors и classifiers.
+Остановлюсь на этом несколько подробнее.</p>
+
+<p>Все директивы rspamd имеют формат param = value; где символ ';' является признаком окончания директивы.
+Также файл разбит на секции,
+каждая секция имеет имя (например worker {}) и внутренние директвы внутри фигурных скобок.
+После каждой секции также обязателен символ ';'.
+Также каждый модуль имеет особый формат секции:</p>
+
+<pre>
+.module 'name' {
+ param = value;
+};
+</pre>
+
+
+<p>Все это может показаться сложным на первый взгляд,
+но это позволяет лучше структурировать файл и облегчает расширяемость rspamd.
+Для первоначальной настройки достаточно обратить внимание на раздел factors,
+который описывает веса различных правил,
+раздел metric,
+который описывает максимальный вес,
+чтобы письмо считалось спамом,
+а также на раздел classifiers.
+Раздел classifiers обеспечивает работу статистических алгоритмов.
+В rspamd в настоящее время реализован алгоритм winnow,
+который работает следующим образом:</p>
+
+<dl>
+<dt><a name="1_(1090)(1077)(1082)(1089)(1090)_(1088)(1072)(1079)(1073)(1080)(1074)(1072)(1077)(1090)(1089)(1103)_(1085)(1072)_(1089)(1083)(1086)(1074)(1072),_(1090)(1072)(1082)(1078)(1077)_(1074)(1099)(1076)(1077)(1083)(1103)(1077)(1090)(1089)(1103)_(1086)(1082)(1085)(1086)_(1074)_(1087)(1103)(1090)(1100)_(1089)(1083)(1086)(1074)"
+>1 текст разбивается на слова,
+также выделяется окно в пять слов</a></dt>
+
+<dd>
+<dt><a name="2_(1086)(1082)(1085)(1086)_(1087)(1077)(1088)(1077)(1084)(1077)(1097)(1072)(1077)(1090)(1089)(1103)_(1087)(1086)_(1089)(1083)(1086)(1074)(1072)(1084),_(1087)(1088)(1080)_(1101)(1090)(1086)(1084)_(1074)(1099)(1076)(1077)(1083)(1103)(1102)(1090)(1089)(1103)_(1089)(1083)(1086)(1074)(1086)(1089)(1086)(1095)(1077)(1090)(1072)(1085)(1080)(1103)_(1087)(1086)_(1086)(1087)(1088)(1077)(1076)(1077)(1083)(1077)(1085)(1085)(1086)(1084)(1091)_(1072)(1083)(1075)(1086)(1088)(1080)(1090)(1084)(1091)_(1080)_(1074)(1099)(1095)(1080)(1089)(1083)(1103)(1077)(1090)(1089)(1103)_(1080)(1093)_(1093)(1077)(1096)"
+>2 окно перемещается по словам,
+при этом выделяются словосочетания по определенному алгоритму и вычисляется их хеш</a></dt>
+
+<dd>
+<dt><a
+>3 каждый полученный таким образом хеш проверяется в файле статистики,
+в котором хранятся хеши и их веса,
+если хеш найден,
+то его вес добавляется к суммарному весу данного файла</a></dt>
+
+<dd>
+<dt><a name="4_(1092)(1072)(1081)(1083),_(1085)(1072)(1073)(1088)(1072)(1074)(1096)(1080)(1081)_(1074)_(1089)(1091)(1084)(1084)(1077)_(1073)(1086)(1083)(1100)(1096)(1077)_(1074)(1077)(1089)(1072)_(1089)(1095)(1080)(1090)(1072)(1077)(1090)(1089)(1103)_(1082)(1083)(1072)(1089)(1089)(1086)(1084)_(1087)(1080)(1089)(1100)(1084)(1072)"
+>4 файл,
+набравший в сумме больше веса считается классом письма</a></dt>
+</dl>
+
+<p>Основная идея такого подхода в определении по словосочетаниям,
+к какому классу принадлежит данное письмо.
+В SA для этой цели используется вероятностный алгоритм bayes,
+который достаточно похож на вышеописанный,
+но считает не веса,
+а вероятности.
+Эффективность каждого из алгоритмов примерно одинакова и зависит в основном от эффективности обучения.
+Однако,
+в отличие от SA,
+rspamd использует не отдельные слова,
+а словосочетания,
+что повышает его эффективность.
+В секции classifiers можно установить различные классы писем,
+а далее в секции factors указать их вес (задав,
+например,
+для заведомо безвредных писем - ham - отрицательный вес).
+Размер файлов статистики должен быть разумно большим для хранения как можно большего числа хешей.
+Однако,
+необходимо учитывать,
+что эти файлы целиком загружаются в память (хотя,
+реализация отображения файла в памяти в различных ОС допускает загрузку и выгрузку страниц этого файла в своп системы,
+но эффективность этого довольно низка),
+поэтому необходимо соизмерять размер оперативной памяти и файлов статистики.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="4._(1055)(1086)(1076)(1082)(1083)(1102)(1095)(1077)(1085)(1080)(1077)_(1080)_(1087)(1088)(1086)(1074)(1077)(1088)(1082)(1072)_(1088)(1072)(1073)(1086)(1090)(1086)(1089)(1087)(1086)(1089)(1086)(1073)(1085)(1086)(1089)(1090)(1080)_rspamd"
+>4.
+Подключение и проверка работоспособности rspamd</a></h2>
+
+<p>Для проверки работы системы rspamd проще всего воспользоваться командой rspamc:</p>
+
+<pre>
+hostname:~> rspamc stat
+Do rspamc command stat
+Messages scanned: 1234040
+Messages learned: 59151
+Connections count: 1176623
+Control connections count: 59152
+Pools allocated: 2530714
+Pools freed: 2530699
+Bytes allocated: 66991793394
+Memory chunks allocated: 6453232
+Shared chunks allocated: 3
+Chunks freed: 6453090
+Oversized chunks: 468849
+Statfile: WINNOW_HAM (version 69); length: 100.0 MB; free blocks: 4563488; total blocks: 6553581; free: 69.63%
+Statfile: WINNOW_SPAM (version 36383); length: 100.0 MB; free blocks: 760504; total blocks: 6553581; free: 11.60%
+</pre>
+
+
+<p>В данном случае выводится общая статистика.
+Такого же эффекта можно достигнуть командой telnet на порт процесса controller (он описан в конфигурационном файле в секции worker,
+type у которого controller).
+По умолчанию это порт 11334 на адресе 127.0.0.1.
+Работу правил и статистики можно также проверить по команде rspamc:</p>
+
+<pre>
+~> rspamc symbols /tmp/mailman.eml
+Processing /tmp/mailman.eml
+Process file: /tmp/mailman.eml
+Sending 4950 bytes...
+RSPAMD/1.1 0 OK
+Metric: default; False; -3.35 / 10.00 / 0.00
+Symbol: WINNOW_HAM; 5.00
+Symbol: RECEIVED_RBL; pbl.spamhaus.org,insecure-bl.rambler.ru
+Urls:
+</pre>
+
+
+<p>В данном случае показываются баллы,
+набранные письмом,
+а также дополнительная информация.
+Подробнее о протоколе rspamc в документации (doc/rspamd.pdf).
+Для подключения rspamd к MTA лучше всего использовать milter,
+если ваш MTA postfix или sendmail.
+В качестве milter'а можно использовать,
+например,
+rmilter: <a href="https://www.milter.org/milter/71/" class="podlinkurl"
+>https://www.milter.org/milter/71/</a>.
+О настройке postfix + rmilter мной была написана следующая заметка: <a href="http://cebka.pp.ru/blog/2009/02/-nginxpostfixrmilter.html" class="podlinkurl"
+>http://cebka.pp.ru/blog/2009/02/-nginxpostfixrmilter.html</a> Для MTA exim можно воспользоваться файлом local_scan.c в каталоге contrib/exim.
+Инструкции по его установке описаны в самом файле,
+однако,
+это требует пересборки MTA.
+Также rspamd "понимает" протокол SA,
+поэтому для подключения rspamd можно использовать те же средства,
+что и для подключения к MTA SA.</p>
+
+<p>Подробнее о командах rspamc и протоколе rspamc написано опять же в документации.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="5._(1054)(1073)(1091)(1095)(1077)(1085)(1080)(1077)_rspamd"
+>5.
+Обучение rspamd</a></h2>
+
+<p>Процесс обучения важен для корректной работы статистики.
+Для обучения используется команда rspamc learn.
+Для указания файла (класса) письма используется ключ -s.
+Пример использования:</p>
+
+<pre>
+~> rspamc -s WINNOW_HAM learn /tmp/mailman.eml
+Processing /tmp/mailman.eml
+Process file: /tmp/mailman.eml
+Do rspamc command learn
+Sending 4950 bytes...
+Learn succeed. Sum weight: 128.48
+</pre>
+
+
+<p>В качестве параметров можно указывать несколько файлов,
+целые каталоги,
+а также imap папки:</p>
+
+<pre>
+~> rspamc -s WINNOW_SPAM learn imaps:user:cebka:password::host:somehost.rambler.ru:mbox:abuse.spam
+Processing imaps:user:cebka:password::host:mailsupport.rambler.ru:mbox:abuse.spam
+Enter IMAP password:
+Process imap: host: somehost.rambler.ru, mbox: abuse.spam
+Do rspamc command learn
+Sending 382650 bytes...
+Learn succeed. Sum weight: 1850.24
+<skipped>
+</pre>
+
+
+<p>При обучении необходимо учитывать,
+чтобы количество spam и ham писем было примерно равным.
+Вес показывает суммарный вес словосочетаний текста,
+который был до обучения данным письмом (то есть,
+то,
+насколько велика вероятность принадлежности данного письма к этому классу).
+При обнаружении неверных срабатываний статистики можно несколько раз применить learn к заданному письму,
+проверяя его суммарный вес.</p>
+
+<h2><a class='u' href='#___top' title='click to go to top of document'
+name="6._(1055)(1086)(1076)(1076)(1077)(1088)(1078)(1082)(1072)_(1080)_(1076)(1072)(1083)(1100)(1085)(1077)(1081)(1096)(1080)(1077)_(1076)(1077)(1081)(1089)(1090)(1074)(1080)(1103)"
+>6.
+Поддержка и дальнейшие действия</a></h2>
+
+<p>Если вы решили использовать rspamd для обработки вашей почты,
+то лучшим источником информации будет являться документация к rspamd,
+а также примеры lua плагинов: etc/plugins/lua.
+Целью же данной статьи является ознакомление с основными приемами по установке,
+конфигурированию и обучению rspamd,
+а также описание преимуществ rspamd по сравнению с SA.
+Если на любом этапе работы с rspamd у вас возникает проблема,
+то можно о ней сообщить мне: <b>vsevolod@highsecure.ru</b> или же в список рассылки rspamd на sourceforge (англоговорящий): <b>rspamd-devel@lists.sourceforge.net</b>.
+Сама система rspamd находится в состоянии бета-версии,
+поэтому ваша помощь в тестировании и запросы функциональности будут неоценимы в развитии rspamd.</p>
+
+<!-- end doc -->
+
+</body></html>
--- /dev/null
+=encoding utf8
+
+=head1 Фильтрация спама при помощи системы rspamd.
+
+=head2 1. История и предпосылки создания.
+
+Мы долгое время использовали для фильтрации спама spamassassin, однако, при
+наших объемах почтового трафика (9-10 миллионов писем в сутки) spamassassin
+не обеспечивал достаточной производительности. После анализа исходного кода и
+принципов работы было выявлены следующие "врожденные" проблемы SA:
+
+=over
+
+=item *
+обработка письма осуществляется набором регулярных выражений, однако,
+количество этих выражений слишком велико и является ключевым моментом
+задумчивости SA (например, для извлечения received заголовков письма, SA
+проверяет оные на соответствие всем ему известным MTA, очевидно, что такое
+действие не является оптимальным);
+
+=item *
+нет возможности явно получить ip адрес, с которого нам отправили письмо (в SA
+это делается парсом received заголовков, что я лично не считаю правильным, а тем
+более оптимальным способом, так как MTA всегда знает, откуда ему пришло данное
+письмо)
+
+=item *
+нет возможности передачи спам фильтру данных SMTP диалога
+
+=item *
+MIME парсинг писем очень медленный, так как опять же сделан на базе регулярных
+выражений
+
+=back
+
+В итоге можно сказать, что основная проблема SA - это излишняя увлеченность
+авторов регулярными выражениями и отсутствие оптимизации. В итоге, в качестве
+базовых принципов создания rspamd были принципы оптимизации времени обработки
+писем, в числе которых:
+
+=over
+
+=item *
+полностью асинхронная работа с сетью (на базе libevent), в том числе DNS запросы
+и работа с http
+
+=item *
+быстрые механизмы выделения памяти
+
+=item *
+компиляция всех регулярных выражений и вызов только тех из них, которые наиболее
+возможны (то есть, имеется механизм ведения статистики срабатывания различных
+правил, и при обработке следующего письма учитывается обработка предыдущих, что
+позволяет максимально быстро провести основные тесты)
+
+=item *
+расширяемая архитектура: возможность написания плагинов на си, lua, возможность
+добавления новых типов фильтров, расширений протокола rspamc, возможность
+добавления новых типов статистических алгоритмов, алгоритмов нормализации и
+парсинга текстов
+
+=item *
+возможность динамической загрузки различных настроек, списков ip адресов и
+прочей информации через HTTP протокол с поддержкой даты модификации таких
+списков
+
+=item *
+использование везде, где возможно, специализированных конечных автоматов для
+разбора и анализа текстов
+
+=item *
+использование быстрого mime парсера gmime
+
+=back
+
+В результате перехода с SA на rspamd последний показал в среднем в 10 раз более
+быструю обработку сообщений. В данный момент rspamd имеет статус бета версии, но
+вполне пригоден для работы.
+
+=head2 2. Установка и настройка rspamd
+
+Для сборки rspamd вам потребуются:
+
+=over
+
+=item * libevent
+L<http://www.monkey.org/~provos/libevent/> - библиотека для асинхронной
+обработки событий
+
+=item * glib
+L<http://library.gnome.org/devel/glib/> - библиотека общего назначения,
+содержащая многие вещи, о которых разработчики libc, увы, забыли (glib не
+является графической библиотекой, хотя и используется, например, gtk)
+
+=item * gmime
+L<http://spruce.sourceforge.net/gmime/> - MIME парсер на базе библиотеки glib
+
+=item * lua
+L<http://www.lua.org/> - встраиваемый скриптовый язык (в принципе, наличие lua
+необязательно, но без lua многий функционал rspamd останется нереализованным)
+
+=item * perl
+L<http://www.perl.org/> - на перле написан, например, консольный клиент rspamc,
+распознаватель редиректов в URL'ях, поэтому наличие перла в системе крайне
+желательно
+
+=item * cmake
+L<http://www.cmake.org/> - система сборки, которая позволяет rspamd собираться
+(по крайней мере, в теории) на всех Posix совместимых системах. Использовать
+стандартные в данной ситуации autotools я не стал, так как считаю их самой
+неудобной в использовании системой сборки исходников, которую можно только
+придумать.
+
+=item * mercurial
+L<http://mercurial.selenic.com/> - система управления версиями (SCM),
+используемая для разработки rspamd. Так как в настоящее время rspamd находится в
+состоянии бета-версии, то релизы выходят нечасто и зачастую являются менее
+стабильными, чем текущая версия в репозитории, поэтому для установки rspamd
+лучше использовать версию непосредственно из репозитория.
+
+=back
+
+После установки всех требуемых программ и библиотек можно приступить
+непосредственно к установке rspamd.
+
+=over
+
+=item 1
+скачиваем текущее состояние репозитория: $ hg clone http://rspamd.hg.sourceforge.net:8000/hgroot/rspamd/rspamd
+
+=item 2
+конфигурируем rspamd: $ cd rspamd && cmake .
+(при необходимости изменить параметры можно вначале воспользоваться командой
+ccmake .)
+
+=item 3
+собираем: $ make
+
+=item 4
+устанавливаем: # make install
+
+=back
+
+=head3 Конфигурирование
+
+В процессе установки ставятся конфигурационные файлы по умолчанию в каталог
+{PREFIX}/etc. При этом, в каталог etc/rspamd устанавливаются правила по
+умолчанию (headers, html, drugs, loto и.т.п.), а в каталог
+etc/rspamd.conf.sample устанавливается конфигурационный файл по умолчанию. Его
+можно скопировать в файл etc/rspamd.conf и отредактировать в соответствии с
+собственными предпочтениями. Конфигурационный файл по умолчанию снабжен
+комментариями к каждой директиве, а также подробно описан в документации к
+rspamd, находящейся в каталоге исходников doc/rspamd.pdf. В данном же
+руководстве подробно изложен принцип работы rspamd и настройки всех модулей
+системы. Для базовой работы достаточно исправить пути к файлам логов, пидфайлу,
+а также обратить внимание на разделы factors и classifiers. Остановлюсь на этом
+несколько подробнее.
+
+Все директивы rspamd имеют формат param = value; где символ ';' является
+признаком окончания директивы. Также файл разбит на секции, каждая секция имеет
+имя (например worker {}) и внутренние директвы внутри фигурных скобок. После
+каждой секции также обязателен символ ';'. Также каждый модуль имеет особый
+формат секции:
+
+=begin html
+
+<pre>
+.module 'name' {
+ param = value;
+};
+</pre>
+
+=end html
+
+Все это может показаться сложным на первый взгляд, но это позволяет лучше
+структурировать файл и облегчает расширяемость rspamd. Для первоначальной
+настройки достаточно обратить внимание на раздел factors, который описывает веса
+различных правил, раздел metric, который описывает максимальный вес, чтобы
+письмо считалось спамом, а также на раздел classifiers. Раздел classifiers
+обеспечивает работу статистических алгоритмов. В rspamd в настоящее время
+реализован алгоритм winnow, который работает следующим образом:
+
+=over
+
+=item 1
+текст разбивается на слова, также выделяется окно в пять слов
+
+=item 2
+окно перемещается по словам, при этом выделяются словосочетания по определенному
+алгоритму и вычисляется их хеш
+
+=item 3
+каждый полученный таким образом хеш проверяется в файле статистики, в котором
+хранятся хеши и их веса, если хеш найден, то его вес добавляется к суммарному
+весу данного файла
+
+=item 4
+файл, набравший в сумме больше веса считается классом письма
+
+=back
+
+Основная идея такого подхода в определении по словосочетаниям, к какому классу
+принадлежит данное письмо. В SA для этой цели используется вероятностный
+алгоритм bayes, который достаточно похож на вышеописанный, но считает не веса, а
+вероятности. Эффективность каждого из алгоритмов примерно одинакова и зависит в
+основном от эффективности обучения. Однако, в отличие от SA, rspamd использует
+не отдельные слова, а словосочетания, что повышает его эффективность. В секции
+classifiers можно установить различные классы писем, а далее в секции factors
+указать их вес (задав, например, для заведомо безвредных писем - ham -
+отрицательный вес). Размер файлов статистики должен быть разумно большим для
+хранения как можно большего числа хешей. Однако, необходимо учитывать, что эти
+файлы целиком загружаются в память (хотя, реализация отображения файла в памяти
+в различных ОС допускает загрузку и выгрузку страниц этого файла в своп системы,
+но эффективность этого довольно низка), поэтому необходимо соизмерять размер
+оперативной памяти и файлов статистики.
+
+=head2 4. Подключение и проверка работоспособности rspamd
+
+Для проверки работы системы rspamd проще всего воспользоваться командой rspamc:
+
+=begin html
+
+<pre>
+hostname:~> rspamc stat
+Do rspamc command stat
+Messages scanned: 1234040
+Messages learned: 59151
+Connections count: 1176623
+Control connections count: 59152
+Pools allocated: 2530714
+Pools freed: 2530699
+Bytes allocated: 66991793394
+Memory chunks allocated: 6453232
+Shared chunks allocated: 3
+Chunks freed: 6453090
+Oversized chunks: 468849
+Statfile: WINNOW_HAM (version 69); length: 100.0 MB; free blocks: 4563488; total blocks: 6553581; free: 69.63%
+Statfile: WINNOW_SPAM (version 36383); length: 100.0 MB; free blocks: 760504; total blocks: 6553581; free: 11.60%
+</pre>
+
+=end html
+
+В данном случае выводится общая статистика. Такого же эффекта можно достигнуть
+командой telnet на порт процесса controller (он описан в конфигурационном файле
+в секции worker, type у которого controller). По умолчанию это порт 11334 на
+адресе 127.0.0.1. Работу правил и статистики можно также проверить по команде
+rspamc:
+
+=begin html
+
+<pre>
+~> rspamc symbols /tmp/mailman.eml
+Processing /tmp/mailman.eml
+Process file: /tmp/mailman.eml
+Sending 4950 bytes...
+RSPAMD/1.1 0 OK
+Metric: default; False; -3.35 / 10.00 / 0.00
+Symbol: WINNOW_HAM; 5.00
+Symbol: RECEIVED_RBL; pbl.spamhaus.org,insecure-bl.rambler.ru
+Urls:
+</pre>
+
+=end html
+
+В данном случае показываются баллы, набранные письмом, а также дополнительная
+информация. Подробнее о протоколе rspamc в документации (doc/rspamd.pdf). Для
+подключения rspamd к MTA лучше всего использовать milter, если ваш MTA postfix
+или sendmail. В качестве milter'а можно использовать, например, rmilter:
+L<https://www.milter.org/milter/71/>. О настройке postfix + rmilter мной была
+написана следующая заметка: L<http://cebka.pp.ru/blog/2009/02/-nginxpostfixrmilter.html>
+Для MTA exim можно воспользоваться файлом local_scan.c в каталоге contrib/exim.
+Инструкции по его установке описаны в самом файле, однако, это требует
+пересборки MTA. Также rspamd "понимает" протокол SA, поэтому для подключения
+rspamd можно использовать те же средства, что и для подключения к MTA SA.
+
+Подробнее о командах rspamc и протоколе rspamc написано опять же в документации.
+
+=head2 5. Обучение rspamd
+
+Процесс обучения важен для корректной работы статистики. Для обучения
+используется команда rspamc learn. Для указания файла (класса) письма
+используется ключ -s. Пример использования:
+
+=begin html
+
+<pre>
+~> rspamc -s WINNOW_HAM learn /tmp/mailman.eml
+Processing /tmp/mailman.eml
+Process file: /tmp/mailman.eml
+Do rspamc command learn
+Sending 4950 bytes...
+Learn succeed. Sum weight: 128.48
+</pre>
+
+=end html
+
+В качестве параметров можно указывать несколько файлов, целые каталоги, а также
+imap папки:
+
+=begin html
+
+<pre>
+~> rspamc -s WINNOW_SPAM learn imaps:user:cebka:password::host:somehost.rambler.ru:mbox:abuse.spam
+Processing imaps:user:cebka:password::host:mailsupport.rambler.ru:mbox:abuse.spam
+Enter IMAP password:
+Process imap: host: somehost.rambler.ru, mbox: abuse.spam
+Do rspamc command learn
+Sending 382650 bytes...
+Learn succeed. Sum weight: 1850.24
+<skipped>
+</pre>
+
+=end html
+
+При обучении необходимо учитывать, чтобы количество spam и ham писем было
+примерно равным. Вес показывает суммарный вес словосочетаний текста, который был
+до обучения данным письмом (то есть, то, насколько велика вероятность
+принадлежности данного письма к этому классу). При обнаружении неверных
+срабатываний статистики можно несколько раз применить learn к заданному письму,
+проверяя его суммарный вес.
+
+=head2 6. Поддержка и дальнейшие действия
+
+Если вы решили использовать rspamd для обработки вашей почты, то лучшим
+источником информации будет являться документация к rspamd, а также примеры lua
+плагинов: etc/plugins/lua. Целью же данной статьи является ознакомление с
+основными приемами по установке, конфигурированию и обучению rspamd, а также
+описание преимуществ rspamd по сравнению с SA. Если на любом этапе работы с
+rspamd у вас возникает проблема, то можно о ней сообщить мне:
+B<vsevolod@highsecure.ru> или же в список рассылки rspamd на sourceforge
+(англоговорящий): B<rspamd-devel@lists.sourceforge.net>. Сама система
+rspamd находится в состоянии бета-версии, поэтому ваша помощь в тестировании и
+запросы функциональности будут неоценимы в развитии rspamd.
--- /dev/null
+Фильтрация спама при помощи системы rspamd.
+ 1. История и предпосылки создания.
+ Мы долгое время использовали для фильтрации спама spamassassin, однако, при
+ наших объемах почтового трафика (9-10 миллионов писем в сутки) spamassassin
+ не обеспечивал достаточной производительности. После анализа исходного кода
+ и принципов работы было выявлены следующие "врожденные" проблемы SA:
+
+ * обработка письма осуществляется набором регулярных выражений, однако,
+ количество этих выражений слишком велико и является ключевым моментом
+ задумчивости SA (например, для извлечения received заголовков письма, SA
+ проверяет оные на соответствие всем ему известным MTA, очевидно, что
+ такое действие не является оптимальным);
+
+ * нет возможности явно получить ip адрес, с которого нам отправили письмо
+ (в SA это делается парсом received заголовков, что я лично не считаю
+ правильным, а тем более оптимальным способом, так как MTA всегда знает,
+ откуда ему пришло данное письмо)
+
+ * нет возможности передачи спам фильтру данных SMTP диалога
+
+ * MIME парсинг писем очень медленный, так как опять же сделан на базе
+ регулярных выражений
+
+ В итоге можно сказать, что основная проблема SA - это излишняя увлеченность
+ авторов регулярными выражениями и отсутствие оптимизации. В итоге, в
+ качестве базовых принципов создания rspamd были принципы оптимизации времени
+ обработки писем, в числе которых:
+
+ * полностью асинхронная работа с сетью (на базе libevent), в том числе DNS
+ запросы и работа с http
+
+ * быстрые механизмы выделения памяти
+
+ * компиляция всех регулярных выражений и вызов только тех из них, которые
+ наиболее возможны (то есть, имеется механизм ведения статистики
+ срабатывания различных правил, и при обработке следующего письма
+ учитывается обработка предыдущих, что позволяет максимально быстро
+ провести основные тесты)
+
+ * расширяемая архитектура: возможность написания плагинов на си, lua,
+ возможность добавления новых типов фильтров, расширений протокола
+ rspamc, возможность добавления новых типов статистических алгоритмов,
+ алгоритмов нормализации и парсинга текстов
+
+ * возможность динамической загрузки различных настроек, списков ip адресов
+ и прочей информации через HTTP протокол с поддержкой даты модификации
+ таких списков
+
+ * использование везде, где возможно, специализированных конечных автоматов
+ для разбора и анализа текстов
+
+ * использование быстрого mime парсера gmime
+
+ В результате перехода с SA на rspamd последний показал в среднем в 10 раз
+ более быструю обработку сообщений. В данный момент rspamd имеет статус бета
+ версии, но вполне пригоден для работы.
+
+ 2. Установка и настройка rspamd
+ Для сборки rspamd вам потребуются:
+
+ libevent <http://www.monkey.org/~provos/libevent/> - библиотека для
+ асинхронной обработки событий
+ glib <http://library.gnome.org/devel/glib/> - библиотека общего назначения,
+ содержащая многие вещи, о которых разработчики libc, увы, забыли (glib не
+ является графической библиотекой, хотя и используется, например, gtk)
+ gmime <http://spruce.sourceforge.net/gmime/> - MIME парсер на базе
+ библиотеки glib
+ lua <http://www.lua.org/> - встраиваемый скриптовый язык (в принципе,
+ наличие lua необязательно, но без lua многий функционал rspamd останется
+ нереализованным)
+ perl <http://www.perl.org/> - на перле написан, например, консольный клиент
+ rspamc, распознаватель редиректов в URL'ях, поэтому наличие перла в системе
+ крайне желательно
+ cmake <http://www.cmake.org/> - система сборки, которая позволяет rspamd
+ собираться (по крайней мере, в теории) на всех Posix совместимых системах.
+ Использовать стандартные в данной ситуации autotools я не стал, так как
+ считаю их самой неудобной в использовании системой сборки исходников,
+ которую можно только придумать.
+ mercurial <http://mercurial.selenic.com/> - система управления версиями
+ (SCM), используемая для разработки rspamd. Так как в настоящее время rspamd
+ находится в состоянии бета-версии, то релизы выходят нечасто и зачастую
+ являются менее стабильными, чем текущая версия в репозитории, поэтому для
+ установки rspamd лучше использовать версию непосредственно из репозитория.
+
+ После установки всех требуемых программ и библиотек можно приступить
+ непосредственно к установке rspamd.
+
+ 1) скачиваем текущее состояние репозитория: $ hg clone
+ http://rspamd.hg.sourceforge.net:8000/hgroot/rspamd/rspamd
+ 2) конфигурируем rspamd: $ cd rspamd && cmake . (при необходимости изменить
+ параметры можно вначале воспользоваться командой ccmake .)
+ 3) собираем: $ make
+ 4) устанавливаем: # make install
+
+ Конфигурирование
+ В процессе установки ставятся конфигурационные файлы по умолчанию в каталог
+ {PREFIX}/etc. При этом, в каталог etc/rspamd устанавливаются правила по
+ умолчанию (headers, html, drugs, loto и.т.п.), а в каталог
+ etc/rspamd.conf.sample устанавливается конфигурационный файл по умолчанию.
+ Его можно скопировать в файл etc/rspamd.conf и отредактировать в
+ соответствии с собственными предпочтениями. Конфигурационный файл по
+ умолчанию снабжен комментариями к каждой директиве, а также подробно описан
+ в документации к rspamd, находящейся в каталоге исходников doc/rspamd.pdf. В
+ данном же руководстве подробно изложен принцип работы rspamd и настройки
+ всех модулей системы. Для базовой работы достаточно исправить пути к файлам
+ логов, пидфайлу, а также обратить внимание на разделы factors и classifiers.
+ Остановлюсь на этом несколько подробнее.
+
+ Все директивы rspamd имеют формат param = value; где символ ';' является
+ признаком окончания директивы. Также файл разбит на секции, каждая секция
+ имеет имя (например worker {}) и внутренние директвы внутри фигурных скобок.
+ После каждой секции также обязателен символ ';'. Также каждый модуль имеет
+ особый формат секции:
+
+.module 'name' {
+ param = value;
+};
+ Все это может показаться сложным на первый взгляд, но это позволяет лучше
+ структурировать файл и облегчает расширяемость rspamd. Для первоначальной
+ настройки достаточно обратить внимание на раздел factors, который описывает
+ веса различных правил, раздел metric, который описывает максимальный вес,
+ чтобы письмо считалось спамом, а также на раздел classifiers. Раздел
+ classifiers обеспечивает работу статистических алгоритмов. В rspamd в
+ настоящее время реализован алгоритм winnow, который работает следующим
+ образом:
+
+ 1) текст разбивается на слова, также выделяется окно в пять слов
+ 2) окно перемещается по словам, при этом выделяются словосочетания по
+ определенному алгоритму и вычисляется их хеш
+ 3) каждый полученный таким образом хеш проверяется в файле статистики, в
+ котором хранятся хеши и их веса, если хеш найден, то его вес добавляется к
+ суммарному весу данного файла
+ 4) файл, набравший в сумме больше веса считается классом письма
+
+ Основная идея такого подхода в определении по словосочетаниям, к какому
+ классу принадлежит данное письмо. В SA для этой цели используется
+ вероятностный алгоритм bayes, который достаточно похож на вышеописанный, но
+ считает не веса, а вероятности. Эффективность каждого из алгоритмов примерно
+ одинакова и зависит в основном от эффективности обучения. Однако, в отличие
+ от SA, rspamd использует не отдельные слова, а словосочетания, что повышает
+ его эффективность. В секции classifiers можно установить различные классы
+ писем, а далее в секции factors указать их вес (задав, например, для
+ заведомо безвредных писем - ham - отрицательный вес). Размер файлов
+ статистики должен быть разумно большим для хранения как можно большего числа
+ хешей. Однако, необходимо учитывать, что эти файлы целиком загружаются в
+ память (хотя, реализация отображения файла в памяти в различных ОС допускает
+ загрузку и выгрузку страниц этого файла в своп системы, но эффективность
+ этого довольно низка), поэтому необходимо соизмерять размер оперативной
+ памяти и файлов статистики.
+
+ 4. Подключение и проверка работоспособности rspamd
+ Для проверки работы системы rspamd проще всего воспользоваться командой
+ rspamc:
+
+hostname:~> rspamc stat
+Do rspamc command stat
+Messages scanned: 1234040
+Messages learned: 59151
+Connections count: 1176623
+Control connections count: 59152
+Pools allocated: 2530714
+Pools freed: 2530699
+Bytes allocated: 66991793394
+Memory chunks allocated: 6453232
+Shared chunks allocated: 3
+Chunks freed: 6453090
+Oversized chunks: 468849
+Statfile: WINNOW_HAM (version 69); length: 100.0 MB; free blocks: 4563488; total blocks: 6553581; free: 69.63%
+Statfile: WINNOW_SPAM (version 36383); length: 100.0 MB; free blocks: 760504; total blocks: 6553581; free: 11.60%
+ В данном случае выводится общая статистика. Такого же эффекта можно
+ достигнуть командой telnet на порт процесса controller (он описан в
+ конфигурационном файле в секции worker, type у которого controller). По
+ умолчанию это порт 11334 на адресе 127.0.0.1. Работу правил и статистики
+ можно также проверить по команде rspamc:
+
+~> rspamc symbols /tmp/mailman.eml
+Processing /tmp/mailman.eml
+Process file: /tmp/mailman.eml
+Sending 4950 bytes...
+RSPAMD/1.1 0 OK
+Metric: default; False; -3.35 / 10.00 / 0.00
+Symbol: WINNOW_HAM; 5.00
+Symbol: RECEIVED_RBL; pbl.spamhaus.org,insecure-bl.rambler.ru
+Urls:
+ В данном случае показываются баллы, набранные письмом, а также
+ дополнительная информация. Подробнее о протоколе rspamc в документации
+ (doc/rspamd.pdf). Для подключения rspamd к MTA лучше всего использовать
+ milter, если ваш MTA postfix или sendmail. В качестве milter'а можно
+ использовать, например, rmilter: <https://www.milter.org/milter/71/>. О
+ настройке postfix + rmilter мной была написана следующая заметка:
+ <http://cebka.pp.ru/blog/2009/02/-nginxpostfixrmilter.html> Для MTA exim
+ можно воспользоваться файлом local_scan.c в каталоге contrib/exim.
+ Инструкции по его установке описаны в самом файле, однако, это требует
+ пересборки MTA. Также rspamd "понимает" протокол SA, поэтому для подключения
+ rspamd можно использовать те же средства, что и для подключения к MTA SA.
+
+ Подробнее о командах rspamc и протоколе rspamc написано опять же в
+ документации.
+
+ 5. Обучение rspamd
+ Процесс обучения важен для корректной работы статистики. Для обучения
+ используется команда rspamc learn. Для указания файла (класса) письма
+ используется ключ -s. Пример использования:
+
+~> rspamc -s WINNOW_HAM learn /tmp/mailman.eml
+Processing /tmp/mailman.eml
+Process file: /tmp/mailman.eml
+Do rspamc command learn
+Sending 4950 bytes...
+Learn succeed. Sum weight: 128.48
+ В качестве параметров можно указывать несколько файлов, целые каталоги, а
+ также imap папки:
+
+~> rspamc -s WINNOW_SPAM learn imaps:user:cebka:password::host:somehost.rambler.ru:mbox:abuse.spam
+Processing imaps:user:cebka:password::host:mailsupport.rambler.ru:mbox:abuse.spam
+Enter IMAP password:
+Process imap: host: somehost.rambler.ru, mbox: abuse.spam
+Do rspamc command learn
+Sending 382650 bytes...
+Learn succeed. Sum weight: 1850.24
+<skipped>
+ При обучении необходимо учитывать, чтобы количество spam и ham писем было
+ примерно равным. Вес показывает суммарный вес словосочетаний текста, который
+ был до обучения данным письмом (то есть, то, насколько велика вероятность
+ принадлежности данного письма к этому классу). При обнаружении неверных
+ срабатываний статистики можно несколько раз применить learn к заданному
+ письму, проверяя его суммарный вес.
+
+ 6. Поддержка и дальнейшие действия
+ Если вы решили использовать rspamd для обработки вашей почты, то лучшим
+ источником информации будет являться документация к rspamd, а также примеры
+ lua плагинов: etc/plugins/lua. Целью же данной статьи является ознакомление
+ с основными приемами по установке, конфигурированию и обучению rspamd, а
+ также описание преимуществ rspamd по сравнению с SA. Если на любом этапе
+ работы с rspamd у вас возникает проблема, то можно о ней сообщить мне:
+ <mailto:vsevolod@highsecure.ru> или же в список рассылки rspamd на
+ sourceforge (англоговорящий): <mailto:rspamd-devel@lists.sourceforge.net>.
+ Сама система rspamd находится в состоянии бета-версии, поэтому ваша помощь в
+ тестировании и запросы функциональности будут неоценимы в развитии rspamd.
+
statfile {
# Alias is used for learning and is used as symbol
symbol = "WINNOW_SPAM";
- # Pattern is path to file, can include %r - recipient name and %f - mail from value
- path = "/tmp/test.spam";
+ # Pattern is path to file
+ path = "/tmp/test.spam";
# Size of this statfile class
size = 10M;
- # Tokenizer for this statfile
- # Deafault: osb-text
- #tokenizer = "osb-text";
- autolearn {
- min_mark = 10.0;
- };
+
+ # Used for normalizing results, number means maximum score for this
+ # class
+ normalizer="internal:10";
+ # Autolearn params
+ #autolearn {
+ # min_mark = 10.0;
+ #};
};
statfile {
symbol = "WINNOW_HAM";
path = "/tmp/test.ham";
size = 10M;
- autolearn {
- max_mark = 0.1;
- };
+ # Autolearn params
+ #autolearn {
+ # max_mark = 0.1;
+ #};
};
};
};
# Settings files
-settings {
+#settings {
# json data for user's settings
#user_settings = "file:///some/json/file";
# json data for domain's settings
#domain_settings = "file:///some/other/json/file";
-};
+#};
# Example of json config:
# [