source: clamav/trunk/libclamav/blob.c@ 319

Last change on this file since 319 was 319, checked in by Yuri Dario, 14 years ago

clamav: update trunk to 0.97.

File size: 13.6 KB
Line 
1/*
2 * Copyright (C) 2007-2008 Sourcefire, Inc.
3 *
4 * Authors: Nigel Horne
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 * MA 02110-1301, USA.
19 */
20
21static char const rcsid[] = "$Id: blob.c,v 1.64 2007/02/12 22:25:14 njh Exp $";
22
23#if HAVE_CONFIG_H
24#include "clamav-config.h"
25#endif
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <errno.h>
31#include <fcntl.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34
35#ifdef HAVE_SYS_PARAM_H
36#include <sys/param.h> /* for NAME_MAX */
37#endif
38
39#ifdef C_DARWIN
40#include <sys/types.h>
41#endif
42
43#ifdef HAVE_UNISTD_H
44#include <unistd.h>
45#endif
46
47#include "others.h"
48#include "mbox.h"
49#include "matcher.h"
50#include "scanners.h"
51#include "filetypes.h"
52
53#include <assert.h>
54
55/* Scehduled for rewite in 0.94 (bb#804). Disabling for now */
56/* #define MAX_SCAN_SIZE 20*1024 /\* */
57/* * The performance benefit of scanning */
58/* * early disappears on medium and */
59/* * large sized files */
60/* *\/ */
61
62static const char *blobGetFilename(const blob *b);
63
64blob *
65blobCreate(void)
66{
67#ifdef CL_DEBUG
68 blob *b = (blob *)cli_calloc(1, sizeof(blob));
69 if(b)
70 b->magic = BLOBCLASS;
71 cli_dbgmsg("blobCreate\n");
72 return b;
73#else
74 return (blob *)cli_calloc(1, sizeof(blob));
75#endif
76}
77
78void
79blobDestroy(blob *b)
80{
81#ifdef CL_DEBUG
82 cli_dbgmsg("blobDestroy %d\n", b->magic);
83#else
84 cli_dbgmsg("blobDestroy\n");
85#endif
86
87 assert(b != NULL);
88 assert(b->magic == BLOBCLASS);
89
90 if(b->name)
91 free(b->name);
92 if(b->data)
93 free(b->data);
94#ifdef CL_DEBUG
95 b->magic = INVALIDCLASS;
96#endif
97 free(b);
98}
99
100void
101blobArrayDestroy(blob *blobList[], int n)
102{
103 assert(blobList != NULL);
104
105 while(--n >= 0) {
106 cli_dbgmsg("blobArrayDestroy: %d\n", n);
107 if(blobList[n]) {
108 blobDestroy(blobList[n]);
109 blobList[n] = NULL;
110 }
111 }
112}
113
114/*
115 * No longer needed to be growable, so turn into a normal memory area which
116 * the caller must free. The passed blob is destroyed
117 */
118void *
119blobToMem(blob *b)
120{
121 void *ret;
122
123 assert(b != NULL);
124 assert(b->magic == BLOBCLASS);
125
126 if(!b->isClosed)
127 blobClose(b);
128 if(b->name)
129 free(b->name);
130#ifdef CL_DEBUG
131 b->magic = INVALIDCLASS;
132#endif
133 ret = (void *)b->data;
134 free(b);
135
136 return ret;
137}
138
139/*ARGSUSED*/
140void
141blobSetFilename(blob *b, const char *dir, const char *filename)
142{
143 assert(b != NULL);
144 assert(b->magic == BLOBCLASS);
145 assert(filename != NULL);
146
147 cli_dbgmsg("blobSetFilename: %s\n", filename);
148
149 if(b->name)
150 free(b->name);
151
152 b->name = cli_strdup(filename);
153
154 if(b->name)
155 sanitiseName(b->name);
156}
157
158static const char *
159blobGetFilename(const blob *b)
160{
161 assert(b != NULL);
162 assert(b->magic == BLOBCLASS);
163
164 return b->name;
165}
166
167/*
168 * Returns <0 for failure
169 */
170int
171blobAddData(blob *b, const unsigned char *data, size_t len)
172{
173#if HAVE_CLI_GETPAGESIZE
174 static int pagesize;
175 int growth;
176#endif
177
178 assert(b != NULL);
179 assert(b->magic == BLOBCLASS);
180 assert(data != NULL);
181
182 if(len == 0)
183 return 0;
184
185 if(b->isClosed) {
186 /*
187 * Should be cli_dbgmsg, but I want to see them for now,
188 * and cli_dbgmsg doesn't support debug levels
189 */
190 cli_warnmsg("Reopening closed blob\n");
191 b->isClosed = 0;
192 }
193 /*
194 * The payoff here is between reducing the number of calls to
195 * malloc/realloc and not overallocating memory. A lot of machines
196 * are more tight with memory than one may imagine which is why
197 * we don't just allocate a *huge* amount and be done with it. Closing
198 * the blob helps because that reclaims memory. If you know the maximum
199 * size of a blob before you start adding data, use blobGrow() that's
200 * the most optimum
201 */
202#if HAVE_CLI_GETPAGESIZE
203 if(pagesize == 0) {
204 pagesize = cli_getpagesize();
205 if(pagesize == 0)
206 pagesize = 4096;
207 }
208 growth = pagesize;
209 if(len >= (size_t)pagesize)
210 growth = ((len / pagesize) + 1) * pagesize;
211
212 /*cli_dbgmsg("blobGrow: b->size %lu, b->len %lu, len %lu, growth = %u\n",
213 b->size, b->len, len, growth);*/
214
215 if(b->data == NULL) {
216 assert(b->len == 0);
217 assert(b->size == 0);
218
219 b->size = growth;
220 b->data = cli_malloc(growth);
221 } else if(b->size < b->len + (off_t)len) {
222 unsigned char *p = cli_realloc(b->data, b->size + growth);
223
224 if(p == NULL)
225 return -1;
226
227 b->size += growth;
228 b->data = p;
229 }
230#else
231 if(b->data == NULL) {
232 assert(b->len == 0);
233 assert(b->size == 0);
234
235 b->size = (off_t)len * 4;
236 b->data = cli_malloc(b->size);
237 } else if(b->size < b->len + (off_t)len) {
238 unsigned char *p = cli_realloc(b->data, b->size + (len * 4));
239
240 if(p == NULL)
241 return -1;
242
243 b->size += (off_t)len * 4;
244 b->data = p;
245 }
246#endif
247
248 if(b->data) {
249 memcpy(&b->data[b->len], data, len);
250 b->len += (off_t)len;
251 }
252 return 0;
253}
254
255unsigned char *
256blobGetData(const blob *b)
257{
258 assert(b != NULL);
259 assert(b->magic == BLOBCLASS);
260
261 if(b->len == 0)
262 return NULL;
263 return b->data;
264}
265
266size_t
267blobGetDataSize(const blob *b)
268{
269 assert(b != NULL);
270 assert(b->magic == BLOBCLASS);
271
272 return b->len;
273}
274
275void
276blobClose(blob *b)
277{
278 assert(b != NULL);
279 assert(b->magic == BLOBCLASS);
280
281 if(b->isClosed) {
282 cli_warnmsg("Attempt to close a previously closed blob\n");
283 return;
284 }
285
286 /*
287 * Nothing more is going to be added to this blob. If it'll save more
288 * than a trivial amount (say 64 bytes) of memory, shrink the allocation
289 */
290 if((b->size - b->len) >= 64) {
291 if(b->len == 0) { /* Not likely */
292 free(b->data);
293 b->data = NULL;
294 cli_dbgmsg("blobClose: recovered all %lu bytes\n",
295 (unsigned long)b->size);
296 b->size = 0;
297 } else {
298 unsigned char *ptr = cli_realloc(b->data, b->len);
299
300 if(ptr == NULL)
301 return;
302
303 cli_dbgmsg("blobClose: recovered %lu bytes from %lu\n",
304 (unsigned long)(b->size - b->len),
305 (unsigned long)b->size);
306 b->size = b->len;
307 b->data = ptr;
308 }
309 }
310 b->isClosed = 1;
311}
312
313/*
314 * Returns 0 if the blobs are the same
315 */
316int
317blobcmp(const blob *b1, const blob *b2)
318{
319 size_t s1, s2;
320
321 assert(b1 != NULL);
322 assert(b2 != NULL);
323
324 if(b1 == b2)
325 return 0;
326
327 s1 = blobGetDataSize(b1);
328 s2 = blobGetDataSize(b2);
329
330 if(s1 != s2)
331 return 1;
332
333 if((s1 == 0) && (s2 == 0))
334 return 0;
335
336 return memcmp(blobGetData(b1), blobGetData(b2), s1);
337}
338
339/*
340 * Return clamav return code
341 */
342int
343blobGrow(blob *b, size_t len)
344{
345 assert(b != NULL);
346 assert(b->magic == BLOBCLASS);
347
348 if(len == 0)
349 return CL_SUCCESS;
350
351 if(b->isClosed) {
352 /*
353 * Should be cli_dbgmsg, but I want to see them for now,
354 * and cli_dbgmsg doesn't support debug levels
355 */
356 cli_warnmsg("Growing closed blob\n");
357 b->isClosed = 0;
358 }
359 if(b->data == NULL) {
360 assert(b->len == 0);
361 assert(b->size == 0);
362
363 b->data = cli_malloc(len);
364 if(b->data)
365 b->size = (off_t)len;
366 } else {
367 unsigned char *ptr = cli_realloc(b->data, b->size + len);
368
369 if(ptr) {
370 b->size += (off_t)len;
371 b->data = ptr;
372 }
373 }
374
375 return (b->data) ? CL_SUCCESS : CL_EMEM;
376}
377
378fileblob *
379fileblobCreate(void)
380{
381#ifdef CL_DEBUG
382 fileblob *fb = (fileblob *)cli_calloc(1, sizeof(fileblob));
383 if(fb)
384 fb->b.magic = BLOBCLASS;
385 cli_dbgmsg("blobCreate\n");
386 return fb;
387#else
388 return (fileblob *)cli_calloc(1, sizeof(fileblob));
389#endif
390}
391
392/*
393 * Returns CL_CLEAN or CL_VIRUS. Destroys the fileblob and removes the file
394 * if possible
395 */
396int
397fileblobScanAndDestroy(fileblob *fb)
398{
399 switch(fileblobScan(fb)) {
400 case CL_VIRUS:
401 fileblobDestructiveDestroy(fb);
402 return CL_VIRUS;
403 case CL_BREAK:
404 fileblobDestructiveDestroy(fb);
405 return CL_CLEAN;
406 default:
407 fileblobDestroy(fb);
408 return CL_CLEAN;
409 }
410}
411
412/*
413 * Destroy the fileblob, and remove the file associated with it
414 */
415void
416fileblobDestructiveDestroy(fileblob *fb)
417{
418 if(fb->fp && fb->fullname) {
419 fclose(fb->fp);
420 cli_dbgmsg("fileblobDestructiveDestroy: %s\n", fb->fullname);
421 if(!fb->ctx || !fb->ctx->engine->keeptmp)
422 cli_unlink(fb->fullname);
423 free(fb->fullname);
424 fb->fp = NULL;
425 fb->fullname = NULL;
426 }
427 if(fb->b.name) {
428 free(fb->b.name);
429 fb->b.name = NULL;
430 }
431 fileblobDestroy(fb);
432}
433
434/*
435 * Destroy the fileblob, and remove the file associated with it if that file is
436 * empty
437 */
438void
439fileblobDestroy(fileblob *fb)
440{
441 assert(fb != NULL);
442 assert(fb->b.magic == BLOBCLASS);
443
444 if(fb->b.name && fb->fp) {
445 fclose(fb->fp);
446 if(fb->fullname) {
447 cli_dbgmsg("fileblobDestroy: %s\n", fb->fullname);
448 if(!fb->isNotEmpty) {
449 cli_dbgmsg("fileblobDestroy: not saving empty file\n");
450 cli_unlink(fb->fullname);
451 }
452 }
453 free(fb->b.name);
454
455 assert(fb->b.data == NULL);
456 } else if(fb->b.data) {
457 free(fb->b.data);
458 if(fb->b.name) {
459 cli_errmsg("fileblobDestroy: %s not saved: report to http://bugs.clamav.net\n",
460 (fb->fullname) ? fb->fullname : fb->b.name);
461 free(fb->b.name);
462 } else
463 cli_errmsg("fileblobDestroy: file not saved (%lu bytes): report to http://bugs.clamav.net\n",
464 (unsigned long)fb->b.len);
465 }
466 if(fb->fullname)
467 free(fb->fullname);
468#ifdef CL_DEBUG
469 fb->b.magic = INVALIDCLASS;
470#endif
471 free(fb);
472}
473
474void
475fileblobPartialSet(fileblob *fb, const char *fullname, const char *arg)
476{
477 if(fb->b.name)
478 return;
479
480 assert(fullname != NULL);
481
482 cli_dbgmsg("fileblobPartialSet: saving to %s\n", fullname);
483
484 fb->fd = open(fullname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY|O_EXCL, 0600);
485 if(fb->fd < 0) {
486 cli_errmsg("fileblobPartialSet: unable to create file: %s\n",fullname);
487 return;
488 }
489 fb->fp = fdopen(fb->fd, "wb");
490
491 if(fb->fp == NULL) {
492 cli_errmsg("fileblobSetFilename: fdopen failed\n");
493 close(fb->fd);
494 return;
495 }
496 blobSetFilename(&fb->b, fb->ctx ? fb->ctx->engine->tmpdir : NULL, fullname);
497 if(fb->b.data)
498 if(fileblobAddData(fb, fb->b.data, fb->b.len) == 0) {
499 free(fb->b.data);
500 fb->b.data = NULL;
501 fb->b.len = fb->b.size = 0;
502 fb->isNotEmpty = 1;
503 }
504 fb->fullname = cli_strdup(fullname);
505}
506
507void
508fileblobSetFilename(fileblob *fb, const char *dir, const char *filename)
509{
510 char *fullname;
511
512 if(fb->b.name)
513 return;
514
515 assert(filename != NULL);
516 assert(dir != NULL);
517
518 blobSetFilename(&fb->b, dir, filename);
519
520 /*
521 * Reload the filename, it may be different from the one we've
522 * asked for, e.g. '/'s taken out
523 */
524 filename = blobGetFilename(&fb->b);
525
526 assert(filename != NULL);
527
528 if (cli_gentempfd(dir, &fullname, &fb->fd)!=CL_SUCCESS) return;
529
530 cli_dbgmsg("fileblobSetFilename: file %s saved to %s\n", filename, fullname);
531
532 fb->fp = fdopen(fb->fd, "wb");
533
534 if(fb->fp == NULL) {
535 cli_errmsg("fileblobSetFilename: fdopen failed\n");
536 close(fb->fd);
537 free(fullname);
538 return;
539 }
540 if(fb->b.data)
541 if(fileblobAddData(fb, fb->b.data, fb->b.len) == 0) {
542 free(fb->b.data);
543 fb->b.data = NULL;
544 fb->b.len = fb->b.size = 0;
545 fb->isNotEmpty = 1;
546 }
547 fb->fullname = fullname;
548}
549
550int
551fileblobAddData(fileblob *fb, const unsigned char *data, size_t len)
552{
553 if(len == 0)
554 return 0;
555
556 assert(data != NULL);
557
558 if(fb->fp) {
559#if defined(MAX_SCAN_SIZE) && (MAX_SCAN_SIZE > 0)
560 const cli_ctx *ctx = fb->ctx;
561
562 if(fb->isInfected) /* pretend all was written */
563 return 0;
564 if(ctx) {
565 int do_scan = 1;
566
567 if(cli_checklimits("fileblobAddData", ctx, fb->bytes_scanned, 0, 0)!=CL_CLEAN)
568 do_scan = 0;
569
570 if(fb->bytes_scanned > MAX_SCAN_SIZE)
571 do_scan = 0;
572 if(do_scan) {
573 if(ctx->scanned)
574 *ctx->scanned += (unsigned long)len / CL_COUNT_PRECISION;
575 fb->bytes_scanned += (unsigned long)len;
576
577 if((len > 5) && cli_updatelimits(ctx, len)==CL_CLEAN && (cli_scanbuff(data, (unsigned int)len, 0, ctx->virname, ctx->engine, CL_TYPE_BINARY_DATA, NULL) == CL_VIRUS)) {
578 cli_dbgmsg("fileblobAddData: found %s\n", *ctx->virname);
579 fb->isInfected = 1;
580 }
581 }
582 }
583#endif
584
585 if(fwrite(data, len, 1, fb->fp) != 1) {
586 cli_errmsg("fileblobAddData: Can't write %lu bytes to temporary file %s\n",
587 (unsigned long)len, fb->b.name);
588 return -1;
589 }
590 fb->isNotEmpty = 1;
591 return 0;
592 }
593 return blobAddData(&(fb->b), data, len);
594}
595
596const char *
597fileblobGetFilename(const fileblob *fb)
598{
599 return blobGetFilename(&(fb->b));
600}
601
602void
603fileblobSetCTX(fileblob *fb, cli_ctx *ctx)
604{
605 fb->ctx = ctx;
606}
607
608/*
609 * Performs a full scan on the fileblob, returning ClamAV status:
610 * CL_BREAK means clean
611 * CL_CLEAN means unknown
612 * CL_VIRUS means infected
613 */
614int
615fileblobScan(const fileblob *fb)
616{
617 int rc;
618 struct stat sb;
619
620 if(fb->isInfected)
621 return CL_VIRUS;
622 if(fb->fp == NULL || fb->fullname == NULL) {
623 /* shouldn't happen, scan called before fileblobSetFilename */
624 cli_warnmsg("fileblobScan, fullname == NULL\n");
625 return CL_ENULLARG; /* there is no CL_UNKNOWN */
626 }
627 if(fb->ctx == NULL) {
628 /* fileblobSetCTX hasn't been called */
629 cli_dbgmsg("fileblobScan, ctx == NULL\n");
630 return CL_CLEAN; /* there is no CL_UNKNOWN */
631 }
632
633 fflush(fb->fp);
634 lseek(fb->fd, 0, SEEK_SET);
635 fstat(fb->fd, &sb);
636 if(cli_matchmeta(fb->ctx, fb->b.name, sb.st_size, sb.st_size, 0, 0, 0, NULL) == CL_VIRUS)
637 return CL_VIRUS;
638
639 rc = cli_magic_scandesc(fb->fd, fb->ctx);
640 if(rc == CL_VIRUS) {
641 cli_dbgmsg("%s is infected\n", fb->fullname);
642 return CL_VIRUS;
643 }
644 cli_dbgmsg("%s is clean\n", fb->fullname);
645 return CL_BREAK;
646}
647
648/*
649 * Doesn't perform a full scan just lets the caller know if something suspicious has
650 * been seen yet
651 */
652int
653fileblobInfected(const fileblob *fb)
654{
655 return fb->isInfected;
656}
657
658/*
659 * Different operating systems allow different characters in their filenames
660 * FIXME: What does QNX want? There is no #ifdef C_QNX, but if there were
661 * it may be best to treat it like MSDOS
662 */
663void
664sanitiseName(char *name)
665{
666 char c;
667 while((c = *name)) {
668 if(c!='.' && c!='_' && (c>'z' || c<'0' || (c>'9' && c<'A') || (c>'Z' && c<'a')))
669 *name = '_';
670 name++;
671 }
672}
Note: See TracBrowser for help on using the repository browser.