1 /*
2 * Copyright (c) 2007-2012, Vsevolod Stakhov
3 * All rights reserved.
4
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer. Redistributions in binary form
9 * must reproduce the above copyright notice, this list of conditions and the
10 * following disclaimer in the documentation and/or other materials provided with
11 * the distribution. Neither the name of the author nor the names of its
12 * contributors may be used to endorse or promote products derived from this
13 * software without specific prior written permission.
14
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 /******************************************************************************
28
29 Clamav clamd client library
30
31 Scanning file with tcp-based clamd servers. Library for general use.
32 Thread-safe if compiled with _THREAD_SAFE defined.
33
34 Warnings logged via syslog (must be opended).
35
36 Random generator used (random() must be initialized before use, e.g.
37 by srandomdev() call).
38
39 Written by Maxim Dounin, mdounin@rambler-co.ru
40
41 $Id$
42
43 ******************************************************************************/
44
45 #include "config.h"
46
47 #include "cfg_file.h"
48 #include "rmilter.h"
49 #include "libclamc.h"
50 #include "sds.h"
51
52 /* Maximum time in seconds during which clamav server is marked inactive after scan error */
53 #define INACTIVE_INTERVAL 60.0
54 /* Maximum number of failed attempts before marking server as inactive */
55 #define MAX_FAILED 5
56 /* Maximum inactive timeout (20 min) */
57 #define MAX_TIMEOUT 1200.0
58
59 /* Global mutexes */
60
61 #ifdef _THREAD_SAFE
62 pthread_mutex_t mx_clamav_write = PTHREAD_MUTEX_INITIALIZER;
63 #endif
64
65 /*****************************************************************************/
66
67 /*
68 * clamscan_socket() - send file to specified host. See clamscan() for
69 * load-balanced wrapper.
70 *
71 * returns 0 when checked, -1 on some error during scan (try another server), -2
72 * on unexpected error (probably clamd died on our file, fallback to another
73 * host not recommended)
74 */
75
clamscan_socket(const char * file,const struct clamav_server * srv,char * strres,size_t strres_len,struct config_file * cfg,struct mlfi_priv * priv)76 static int clamscan_socket(const char *file, const struct clamav_server *srv,
77 char *strres, size_t strres_len, struct config_file *cfg,
78 struct mlfi_priv *priv)
79 {
80 char *c;
81 sds readbuf;
82 char buf[2048];
83 struct sockaddr_un server_un;
84 struct sockaddr_in server_in, server_w;
85 int s, r, fd, port = 0, ofl;
86 uint32_t sz;
87 size_t size;
88 struct stat sb;
89
90 *strres = '\0';
91
92 /* somebody doesn't need reply... */
93 if (!srv)
94 return 0;
95
96 s = rmilter_connect_addr (srv->name, srv->port, cfg->clamav_connect_timeout, priv);
97
98 if (s == -1) {
99 return -1;
100 }
101
102 fd = open (file, O_RDONLY);
103
104 if (fstat (fd, &sb) == -1) {
105 msg_warn("<%s>; clamav: stat failed for %s: %s", priv->mlfi_id,
106 file, strerror (errno));
107 close (s);
108 return -1;
109 }
110
111 sz = sb.st_size;
112 sz = htonl (sz);
113 r = rmilter_strlcpy (buf, "nINSTREAM\n", sizeof (buf) - sizeof (sz));
114 memcpy (&buf[r], &sz, sizeof (sz));
115 r += sizeof (sz);
116
117 if (write (s, buf, r) != r) {
118 msg_warn("<%s>; clamav: write %s: %s", priv->mlfi_id,
119 srv->name, strerror (errno));
120 close (s);
121 return -1;
122 }
123
124 /*
125 * send data stream
126 */
127 /* Set blocking again */
128 ofl = fcntl (s, F_GETFL, 0);
129 fcntl (s, F_SETFL, ofl & (~O_NONBLOCK));
130
131 #if defined(FREEBSD) && defined(HAVE_SENDFILE)
132 if (sendfile(fd, s, 0, 0, 0, 0, 0) != 0) {
133 msg_warn("<%s>; clamav: sendfile %s (%s): %s", priv->mlfi_id,
134 file, srv->name, strerror (errno));
135 close(fd);
136 close(s);
137 return -1;
138 }
139 #elif defined(LINUX) && defined(HAVE_SENDFILE)
140 off_t off = 0;
141 if (sendfile(s, fd, &off, sb.st_size) == -1) {
142 msg_warn("<%s>; clamav: sendfile %s (%s): %s", priv->mlfi_id,
143 file, srv->name, strerror (errno));
144 close(fd);
145 close(s);
146 return -1;
147 }
148 #else
149 void *map;
150
151 map = mmap (NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
152
153 if (map == MAP_FAILED) {
154 msg_warn ("<%s>; rspamd: mmap (%s), %s", priv->mlfi_id, srv->name,
155 strerror (errno));
156 close(fd);
157 close(s);
158 return -1;
159 }
160
161 if (rmilter_atomic_write (s, map, sb.st_size) == -1) {
162 msg_warn ("<%s>; rspamd: write (%s), %s", priv->mlfi_id, srv->name,
163 strerror (errno));
164 close(fd);
165 close(s);
166 munmap (map, sb.st_size);
167 return -1;
168 }
169
170 munmap (map, sb.st_size);
171 #endif
172
173 close (fd);
174
175 /* Send zero chunk */
176 sz = 0;
177 if (write (s, &sz, sizeof (sz)) <= 0) {
178 msg_warn("<%s>; clamav: write (%s): %s", priv->mlfi_id,
179 srv->name, strerror (errno));
180 close(fd);
181 close(s);
182 return -1;
183 }
184
185 fcntl (s, F_SETFL, ofl | O_NONBLOCK);
186
187 /* wait for reply */
188 if (rmilter_poll_fd (s, cfg->clamav_results_timeout, POLLIN) < 1) {
189 msg_warn("<%s>; clamav: timeout waiting results %s", priv->mlfi_id,
190 srv->name);
191 close (s);
192 return -1;
193 }
194
195 /*
196 * read results
197 */
198 readbuf = sdsempty();
199
200 for (;;) {
201 if (rmilter_poll_fd (s, cfg->spamd_results_timeout, POLLIN) < 1) {
202 msg_warn("<%s>; clamav: timeout waiting results %s", priv->mlfi_id,
203 srv->name);
204 close (s);
205 return -1;
206 }
207
208 r = read (s, buf, sizeof (buf));
209
210 if (r == -1) {
211 if (errno == EAGAIN || errno == EINTR) {
212 continue;
213 }
214 else {
215 msg_warn("<%s>; clamav: read, %s, %s", priv->mlfi_id,
216 srv->name, strerror (errno));
217 close (s);
218 return -1;
219 }
220 }
221 else if (r == 0) {
222 break;
223 }
224 else {
225 readbuf = sdscatlen (readbuf, buf, r);
226 }
227 }
228
229 size = sdslen (readbuf);
230
231 close (s);
232
233 /*
234 * ok, we got result; test what we got
235 */
236
237 /* msg_warn("clamav: %s", buf); */
238 if ((c = strstr (readbuf, "OK\n")) != NULL) {
239 /* <file> ": OK\n" */
240 sdsfree (readbuf);
241 return 0;
242
243 }
244 else if ((c = strstr (readbuf, "FOUND\n")) != NULL) {
245 /* <file> ": " <virusname> " FOUND\n" */
246
247 if (strncmp (readbuf, "stream", sizeof ("stream") - 1) != 0) {
248 msg_warn ("<%s>; clamav: paths differ: '%s' instead of 'stream'",
249 priv->mlfi_id, readbuf);
250 snprintf (strres, strres_len, "%.*s", (int)(c - readbuf), readbuf);
251 }
252 else {
253 *(--c) = 0;
254 c = readbuf + sizeof ("stream") + 1;
255 snprintf (strres, strres_len, "%s", c);
256 }
257
258 sdsfree (readbuf);
259
260 return 0;
261
262 }
263 else if ((c = strstr (readbuf, "ERROR\n")) != NULL) {
264 *(--c) = 0;
265 msg_warn("<%s>; clamav: error (%s) %s", priv->mlfi_id, srv->name, readbuf);
266 sdsfree (readbuf);
267 return -1;
268 }
269
270 /*
271 * Most common reason is clamd died while processing our request. Try to
272 * save file for further investigation and fail.
273 */
274 sdsfree (readbuf);
275 msg_warn("<%s>; clamav: unexpected result on file (%s) %s, %s",
276 priv->mlfi_id, srv->name, file,
277 buf);
278
279 return -2;
280 }
281
282 /*
283 * clamscan() - send file to one of remote clamd, with pseudo load-balancing
284 * (select one random server, fallback to others in case of errors).
285 *
286 * returns 0 if file scanned (or not scanned due to filesize limit), -1 when
287 * retry limit exceeded, -2 on unexpected error, e.g. unexpected reply from
288 * server (suppose scanned message killed clamd...)
289 */
290
clamscan(void * ctx,struct mlfi_priv * priv,struct config_file * cfg,char * strres,size_t strres_len)291 int clamscan (void *ctx, struct mlfi_priv *priv, struct config_file *cfg,
292 char *strres, size_t strres_len)
293 {
294 int retry = 5, r = -2;
295 /* struct stat sb; */
296 struct timeval t;
297 double ts, tf;
298 struct clamav_server *selected = NULL;
299 struct timespec sleep_ts;
300 const char *file = priv->file;
301
302 *strres = '\0';
303 /*
304 * Parse sockets to use in balancing.
305 */
306 /* msg_warn("(clamscan) defined %d server sockets...", sockets_n); */
307
308 /*
309 * save scanning start time
310 */
311 gettimeofday (&t, NULL);
312 ts = t.tv_sec + t.tv_usec / 1000000.0;
313 retry = cfg->spamd_retry_count;
314 sleep_ts.tv_sec = cfg->spamd_retry_timeout / 1000;
315 sleep_ts.tv_nsec = (cfg->spamd_retry_timeout % 1000) * 1000000ULL;
316
317 /* try to scan with available servers */
318 while (1) {
319 if (cfg->weighted_clamav) {
320 selected = (struct clamav_server *) get_upstream_master_slave (
321 (void *) cfg->clamav_servers, cfg->clamav_servers_num,
322 sizeof(struct clamav_server), t.tv_sec,
323 cfg->clamav_error_time, cfg->clamav_dead_time,
324 cfg->clamav_maxerrors, priv);
325 }
326 else {
327 selected = (struct clamav_server *) get_random_upstream (
328 (void *) cfg->clamav_servers, cfg->clamav_servers_num,
329 sizeof(struct clamav_server), t.tv_sec,
330 cfg->clamav_error_time, cfg->clamav_dead_time,
331 cfg->clamav_maxerrors, priv);
332 }
333 if (selected == NULL) {
334 msg_err("<%s>; clamscan: upstream get error, %s", priv->mlfi_id,
335 file);
336 return -1;
337 }
338
339 msg_info ("<%s>; clamscan: start scanning message on %s", priv->mlfi_id,
340 selected->name);
341 r = clamscan_socket (file, selected, strres, strres_len, cfg, priv);
342 msg_info ("<%s>; clamscan: finish scanning message on %s", priv->mlfi_id,
343 selected->name);
344
345 if (r == 0) {
346 upstream_ok (&selected->up, t.tv_sec);
347 break;
348 }
349 upstream_fail (&selected->up, t.tv_sec);
350 if (r == -2) {
351 msg_warn("<%s>; clamscan: unexpected problem, %s, %s", priv->mlfi_id,
352 selected->name,
353 file);
354 break;
355 }
356 if (--retry < 1) {
357 msg_warn("<%s>; clamscan: retry limit exceeded, %s, %s",
358 priv->mlfi_id, selected->name,
359 file);
360 break;
361 }
362 msg_warn("<%s>; clamscan: failed to scan, retry, %s, %s", priv->mlfi_id,
363 selected->name,
364 file);
365 nanosleep (&sleep_ts, NULL);
366 }
367
368 /*
369 * print scanning time, server and result
370 */
371 gettimeofday (&t, NULL);
372 tf = t.tv_sec + t.tv_usec / 1000000.0;
373
374 if (*strres) {
375 msg_info("<%s>; clamscan: scan %f, %s, found %s, %s", priv->mlfi_id,
376 tf - ts, selected->name,
377 strres, file);
378 }
379 else {
380 msg_info("<%s>; clamscan: scan %f, %s, %s", priv->mlfi_id,
381 tf - ts, selected->name, file);
382 }
383
384 return r;
385 }
386