1 /*!\file clamav.cxx
2
3 \brief Clamd bindings
4
5 *//*
6
7 ClamFS - An user-space anti-virus protected file system
8 Copyright (C) 2007-2019 Krzysztof Burghardt
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25 #include "clamav.hxx"
26
27 #include "Poco/Net/SocketAddress.h"
28 #include "Poco/Net/StreamSocket.h"
29 #include "Poco/Net/SocketStream.h"
30 #include "Poco/StreamCopier.h"
31
32 namespace clamfs {
33
34 extern config_t config;
35 extern FastMutex scanMutex;
36
37 /*!\def CHECK_CLAMD
38 \brief Check if we are connected to clamd
39 \param clamdStream SocketStream variable representing IO stream to check
40
41 This macro is intended to easier to check if ClamFS is
42 connected to clamd socket. This code check socket condition
43 and if socket is not open returns -1.
44 */
45 #define CHECK_CLAMD(clamdStream) do {\
46 if (!clamdStream) {\
47 rLog(Warn, "error: clamd connection lost and unable to reconnect");\
48 CloseClamav();\
49 return -1;\
50 }\
51 } while(0)
52
53 /*!\brief Unix socket used to communicate with clamd */
54 StreamSocket clamdSocket;
55
56 /*!\brief Opens connection to clamd through unix socket
57 \param unixSocket name of unix socket
58 \returns 0 on success and -1 on failure
59 */
OpenClamav(const char * unixSocket)60 int OpenClamav(const char *unixSocket) {
61 SocketAddress sa(unixSocket);
62
63 DEBUG("attempt to open connection to clamd via %s", unixSocket);
64 try {
65 clamdSocket.connect(sa);
66 } catch (Exception &e) {
67 rLog(Warn, "error: unable to open connection to clamd");
68 return -1;
69 }
70 SocketStream clamd(clamdSocket);
71 CHECK_CLAMD(clamd);
72
73 DEBUG("connected to clamd");
74 return 0;
75 }
76
77 /*!\brief Check clamd availability by sending PING command and checking the reply
78 */
PingClamav()79 int PingClamav() {
80 string reply;
81
82 SocketStream clamd(clamdSocket);
83 CHECK_CLAMD(clamd);
84 clamd << "nPING" << endl;
85 clamd >> reply;
86
87 if (reply != "PONG") {
88 rLog(Warn, "invalid reply for PING received: %s", reply.c_str());
89 return -1;
90 }
91
92 DEBUG("got valid reply for PING command, clamd works");
93 return 0;
94 }
95
96 /*!\brief Close clamd connection
97 */
CloseClamav()98 void CloseClamav() {
99 DEBUG("closing clamd connection");
100 clamdSocket.close();
101 }
102
103 /*!\brief Request anti-virus scanning on file
104 \param filename name of file to scan
105 \returns -1 one error when opening clamd connection,
106 0 if no virus found and
107 1 if virus was found (or clamd error occurred)
108 */
ClamavScanFile(const char * filename)109 int ClamavScanFile(const char *filename) {
110 string reply;
111
112 DEBUG("attempt to scan file %s", filename);
113
114 /*
115 * Enqueue requests
116 */
117 FastMutex::ScopedLock lock(scanMutex);
118
119 /*
120 * Open clamd socket
121 */
122 DEBUG("started scanning file %s", filename);
123 OpenClamav(config["socket"]);
124 SocketStream clamd(clamdSocket);
125 if (!clamd)
126 return -1;
127
128 /*
129 * Scan file using SCAN method
130 */
131 clamd << "nSCAN " << filename << endl;
132 getline(clamd, reply);
133 CloseClamav();
134
135 /*
136 * Chceck for scan results, return if file is clean
137 */
138 DEBUG("clamd reply is: '%s'", reply.c_str());
139 if (strncmp(reply.c_str() + reply.size() - 2, "OK", 2) == 0 ||
140 strncmp(reply.c_str() + reply.size() - 10, "Empty file", 10) == 0 ||
141 strncmp(reply.c_str() + reply.size() - 8, "Excluded", 8) == 0 ||
142 strncmp(reply.c_str() + reply.size() - 29, "Excluded (another filesystem)", 29) == 0 ) {
143 return 0;
144 }
145
146 /*
147 * Log result through RLog (if virus is found or scan failed)
148 */
149 char* username = getusername();
150 char* callername = getcallername();
151 rLog(Warn, "(%s:%d) (%s:%d) %s", callername, fuse_get_context()->pid,
152 username, fuse_get_context()->uid, reply.empty() ? "< empty clamd reply >" : reply.c_str());
153 free(username);
154 free(callername);
155
156 /*
157 * If scan failed return without sending e-mail alert
158 */
159 if(strncmp(reply.c_str() + reply.size() - 20, "Access denied. ERROR", 20) == 0 ||
160 strncmp(reply.c_str() + reply.size() - 21, "lstat() failed. ERROR", 21) == 0) {
161 return -1;
162 }
163
164 /*
165 * Send mail notification
166 */
167 SendMailNotification(config["server"], config["to"],
168 config["from"], config["subject"], reply.c_str());
169
170 return 1;
171 }
172
173 } /* namespace clamfs */
174
175 /* EoF */
176