1 // This module allows Citadel to use clamd to filter incoming messages
2 // arriving via SMTP. For more information on clamd, visit
3 // http://clamav.net (the ClamAV project is not in any way
4 // affiliated with the Citadel project).
6 // Copyright (c) 1987-2024 by the citadel.org team
8 // This program is open source software. Use, duplication, or disclosure
9 // are subject to the terms of the GNU General Public License version 3.
11 #define CLAMD_PORT "3310"
13 #include "../../sysdep.h"
21 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <libcitadel.h>
28 #include "../../citadel_defs.h"
29 #include "../../server.h"
30 #include "../../citserver.h"
31 #include "../../support.h"
32 #include "../../config.h"
33 #include "../../control.h"
34 #include "../../user_ops.h"
35 #include "../../database.h"
36 #include "../../msgbase.h"
37 #include "../../internet_addressing.h"
38 #include "../../domain.h"
39 #include "../../clientsocket.h"
40 #include "../../ctdl_module.h"
43 // Connect to the clamd server and scan a message.
44 int clamd(struct CtdlMessage *msg, struct recptypes *recp) {
46 int streamsock = (-1);
56 // See if we have any clamd hosts configured
57 num_clamhosts = get_hosts(clamhosts, "clamav");
58 if (num_clamhosts < 1) {
62 // Try them one by one until we get a working one
63 for (clamhost=0; clamhost<num_clamhosts; ++clamhost) {
64 extract_token(buf, clamhosts, clamhost, '|', sizeof buf);
65 syslog(LOG_INFO, "Connecting to clamd at <%s>\n", buf);
67 // Assuming a host:port entry
68 extract_token(hostbuf, buf, 0, ':', sizeof hostbuf);
69 if (extract_token(portbuf, buf, 1, ':', sizeof portbuf)==-1) {
70 // Didn't specify a port so we'll try the psuedo-standard 3310
71 sock = sock_connect(hostbuf, CLAMD_PORT);
74 // Port specified lets try connecting to it!
75 sock = sock_connect(hostbuf, portbuf);
79 syslog(LOG_DEBUG, "Connected!\n");
83 // If the service isn't running, just pass the mail through. Potentially throwing away mails isn't good.
88 CC->SBuf.Buf = NewStrBuf();
89 CC->sMigrateBuf = NewStrBuf();
90 CC->SBuf.ReadWritePointer = NULL;
93 syslog(LOG_DEBUG, "Transmitting STREAM command\n");
94 sprintf(buf, "STREAM\r\n");
95 sock_write(&sock, buf, strlen(buf));
97 syslog(LOG_DEBUG, "Waiting for PORT number\n");
98 if (sock_getln(&sock, buf, sizeof buf) < 0) {
102 syslog(LOG_DEBUG, "<%s\n", buf);
103 if (strncasecmp(buf, "PORT", 4)!=0) {
107 // Should have received a port number to connect to
108 extract_token(portbuf, buf, 1, ' ', sizeof portbuf);
110 // Attempt to establish connection to STREAM socket
111 streamsock = sock_connect(hostbuf, portbuf);
113 // If the service isn't running, just pass the mail through. Potentially throwing away mails isn't good.
114 if (streamsock < 0) {
115 FreeStrBuf(&CC->SBuf.Buf);
116 FreeStrBuf(&CC->sMigrateBuf);
120 syslog(LOG_DEBUG, "STREAM socket connected!\n");
124 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
125 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
126 msgtext = CC->redirect_buffer;
127 CC->redirect_buffer = NULL;
129 sock_write(&streamsock, SKEY(msgtext));
130 FreeStrBuf(&msgtext);
132 // Close the streamsocket connection; this tells clamd that we're done.
133 if (streamsock != -1) {
138 syslog(LOG_DEBUG, "Awaiting response\n");
139 if (sock_getln(&sock, buf, sizeof buf) < 0) {
142 syslog(LOG_DEBUG, "<%s\n", buf);
143 if (strncasecmp(buf, "stream: OK", 10)!=0) {
148 CM_SetField(msg, eErrorMsg, "message rejected by virus filter");
152 FreeStrBuf(&CC->SBuf.Buf);
153 FreeStrBuf(&CC->sMigrateBuf);
158 // Initialization function, called from modules_init.c
159 char *ctdl_module_init_virus(void) {
161 CtdlRegisterMessageHook(clamd, EVT_SMTPSCAN);
164 // return our module name for the log