2 * This module allows Citadel to use SpamAssassin to filter incoming messages
3 * arriving via SMTP. For more information on SpamAssassin, visit
4 * http://www.spamassassin.org (the SpamAssassin project is not in any way
5 * affiliated with the Citadel project).
7 * Copyright (c) 1998-2015 by the citadel.org team
9 * This program is open source software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 3.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
18 #define SPAMASSASSIN_PORT "783"
28 #include <sys/types.h>
30 #if TIME_WITH_SYS_TIME
31 # include <sys/time.h>
35 # include <sys/time.h>
44 #include <sys/socket.h>
45 #include <libcitadel.h>
48 #include "citserver.h"
55 #include "internet_addressing.h"
57 #include "clientsocket.h"
60 #include "ctdl_module.h"
65 * Connect to the SpamAssassin server and scan a message.
67 int spam_assassin(struct CtdlMessage *msg, recptypes *recp) {
77 /* For users who have authenticated to this server we never want to
78 * apply spam filtering, because presumably they're trustworthy.
80 if (CC->logged_in) return(0);
82 /* See if we have any SpamAssassin hosts configured */
83 num_sahosts = get_hosts(sahosts, "spamassassin");
84 if (num_sahosts < 1) return(0);
86 /* Try them one by one until we get a working one */
87 for (sa=0; sa<num_sahosts; ++sa) {
88 extract_token(buf, sahosts, sa, '|', sizeof buf);
89 syslog(LOG_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
90 sock = sock_connect(buf, SPAMASSASSIN_PORT);
91 if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
95 /* If the service isn't running, just pass the mail
96 * through. Potentially throwing away mails isn't good.
101 CCC->SBuf.Buf = NewStrBuf();
102 CCC->sMigrateBuf = NewStrBuf();
103 CCC->SBuf.ReadWritePointer = NULL;
106 syslog(LOG_DEBUG, "Transmitting command\n");
107 sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
108 sock_write(&sock, buf, strlen(buf));
111 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
112 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
113 msgtext = CC->redirect_buffer;
114 CC->redirect_buffer = NULL;
116 sock_write(&sock, SKEY(msgtext));
117 FreeStrBuf(&msgtext);
119 /* Close one end of the socket connection; this tells SpamAssassin
123 sock_shutdown(sock, SHUT_WR);
126 syslog(LOG_DEBUG, "Awaiting response\n");
127 if (sock_getln(&sock, buf, sizeof buf) < 0) {
130 syslog(LOG_DEBUG, "<%s\n", buf);
131 if (strncasecmp(buf, "SPAMD", 5)) {
134 if (sock_getln(&sock, buf, sizeof buf) < 0) {
137 syslog(LOG_DEBUG, "<%s\n", buf);
138 syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", CtdlGetConfigInt("c_spam_flag_only"));
139 if (CtdlGetConfigInt("c_spam_flag_only")) {
147 syslog(LOG_DEBUG, "flag spam code used");
149 extract_token(sastatus, buf, 1, ' ', sizeof sastatus);
150 extract_token(sascore, buf, 3, ' ', sizeof sascore);
151 extract_token(saoutof, buf, 5, ' ', sizeof saoutof);
153 memcpy(buf, HKEY("X-Spam-Level: "));
155 for (numscore = atoi(sascore); numscore>0; numscore--)
159 headerlen = cur - buf;
160 headerlen += snprintf(cur, (sizeof(buf) - headerlen),
161 "\r\nX-Spam-Status: %s, score=%s required=%s\r\n",
162 sastatus, sascore, saoutof);
164 CM_PrependToField(msg, eMesageText, buf, headerlen);
167 syslog(LOG_DEBUG, "reject spam code used");
168 if (!strncasecmp(buf, "Spam: True", 10)) {
173 CM_SetField(msg, eErrorMsg, HKEY("message rejected by spam filter"));
178 FreeStrBuf(&CCC->SBuf.Buf);
179 FreeStrBuf(&CCC->sMigrateBuf);
185 CTDL_MODULE_INIT(spam)
189 CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
192 /* return our module name for the log */