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