1 // This module allows Citadel to use an external SpamAssassin service to filter incoming messages arriving via SMTP.
3 // Copyright (c) 1998-2023 by the citadel.org team
5 // This program is open source software. Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
8 #define SPAMASSASSIN_PORT "783"
10 #include "../../sysdep.h"
18 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <libcitadel.h>
25 #include "../../citadel_defs.h"
26 #include "../../server.h"
27 #include "../../citserver.h"
28 #include "../../support.h"
29 #include "../../config.h"
30 #include "../../control.h"
31 #include "../../user_ops.h"
32 #include "../../database.h"
33 #include "../../msgbase.h"
34 #include "../../internet_addressing.h"
35 #include "../../domain.h"
36 #include "../../clientsocket.h"
37 #include "../../ctdl_module.h"
40 // Connect to the SpamAssassin server and scan a message.
41 int spam_assassin(struct CtdlMessage *msg, struct recptypes *recp) {
50 // For users who have authenticated to this server we never want to
51 // apply spam filtering, because presumably they're trustworthy.
52 if (CC->logged_in) return(0);
54 // See if we have any SpamAssassin hosts configured
55 num_sahosts = get_hosts(sahosts, "spamassassin");
56 if (num_sahosts < 1) return(0);
58 // Try them one by one until we get a working one
59 for (sa=0; sa<num_sahosts; ++sa) {
60 extract_token(buf, sahosts, sa, '|', sizeof buf);
61 syslog(LOG_INFO, "Connecting to SpamAssassin at <%s>\n", buf);
62 sock = sock_connect(buf, SPAMASSASSIN_PORT);
63 if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n");
66 // If the service isn't running, just pass the mail through. Potentially throwing away mails isn't good.
71 CC->SBuf.Buf = NewStrBuf();
72 CC->sMigrateBuf = NewStrBuf();
73 CC->SBuf.ReadWritePointer = NULL;
76 syslog(LOG_DEBUG, "Transmitting command\n");
77 sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n");
78 sock_write(&sock, buf, strlen(buf));
81 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
82 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
83 msgtext = CC->redirect_buffer;
84 CC->redirect_buffer = NULL;
86 sock_write(&sock, SKEY(msgtext));
89 // Close one end of the socket connection; this tells SpamAssassin that we're done.
91 shutdown(sock, SHUT_WR);
95 syslog(LOG_DEBUG, "Awaiting response\n");
96 if (sock_getln(&sock, buf, sizeof buf) < 0) {
99 syslog(LOG_DEBUG, "<%s\n", buf);
100 if (strncasecmp(buf, "SPAMD", 5)) {
103 if (sock_getln(&sock, buf, sizeof buf) < 0) {
106 syslog(LOG_DEBUG, "<%s\n", buf);
107 syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", CtdlGetConfigInt("c_spam_flag_only"));
108 if (CtdlGetConfigInt("c_spam_flag_only")) {
116 syslog(LOG_DEBUG, "flag spam code used");
118 extract_token(sastatus, buf, 1, ' ', sizeof sastatus);
119 extract_token(sascore, buf, 3, ' ', sizeof sascore);
120 extract_token(saoutof, buf, 5, ' ', sizeof saoutof);
122 memcpy(buf, HKEY("X-Spam-Level: "));
124 for (numscore = atoi(sascore); numscore>0; numscore--)
128 headerlen = cur - buf;
129 headerlen += snprintf(cur, (sizeof(buf) - headerlen),
130 "\r\nX-Spam-Status: %s, score=%s required=%s\r\n",
131 sastatus, sascore, saoutof);
133 CM_PrependToField(msg, eMesageText, buf, headerlen);
137 syslog(LOG_DEBUG, "reject spam code used");
138 if (!strncasecmp(buf, "Spam: True", 10)) {
143 CM_SetField(msg, eErrorMsg, "message rejected by spam filter");
148 FreeStrBuf(&CC->SBuf.Buf);
149 FreeStrBuf(&CC->sMigrateBuf);
154 // Initialization function, called from modules_init.c
155 char *ctdl_module_init_spam(void) {
157 CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN);
160 // return our module name for the log