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