rspamd/contrib/exim/local_scan.c

701 lines
18 KiB
C

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