Browse code

(bb #913, #916) * fix scan of partial messages * allow for tempfiles to be cleaned up based on age * new clamd.conf option ScanPartialMessages * sample cleanup script * clamd/thrmgr.c: fix item_count

git-svn: trunk@4031

Török Edvin authored on 2008/07/30 22:54:34
Showing 12 changed files
... ...
@@ -1,3 +1,12 @@
1
+Wed Jul 30 16:38:26 EEST 2008 (edwin)
2
+-------------------------------------
3
+  * clamd, libclamav, shared: (bb #913, #916)
4
+	* fix scan of partial messages
5
+	* allow for tempfiles to be cleaned up based on age
6
+	* new clamd.conf option ScanPartialMessages
7
+  * contrib/cleanup-partial.pl: sample cleanup script
8
+  * clamd/thrmgr.c: fix item_count
9
+
1 10
 Tue Jul 29 23:18:23 CEST 2008 (tk)
2 11
 ----------------------------------
3 12
   * clamd: revert patch from bb#1028 (bb#1113)
... ...
@@ -402,6 +402,11 @@ int acceptloop_th(int *socketds, int nsockets, struct cl_engine *engine, unsigne
402 402
 	    options |= CL_SCAN_MAILURL;
403 403
 	}
404 404
 
405
+	if(cfgopt(copt, "ScanPartialMessages")->enabled) {
406
+	    logg("Mail: RFC1341 handling enabled.\n");
407
+	    options |= CL_SCAN_PARTIAL_MESSAGE;
408
+	}
409
+
405 410
     } else {
406 411
 	logg("Mail files support disabled.\n");
407 412
     }
... ...
@@ -95,6 +95,7 @@ static void *work_queue_pop(work_queue_t *work_q)
95 95
 		work_q->tail = NULL;
96 96
 	}
97 97
 	free(work_item);
98
+	work_q->item_count--;
98 99
 	return data;
99 100
 }
100 101
 
101 102
new file mode 100755
... ...
@@ -0,0 +1,23 @@
0
+#!/usr/bin/perl
1
+
2
+# ---- Settings ----
3
+# TemporaryDirectory in clamd.conf
4
+my($TMPDIR)='/tmp';
5
+# How long to wait for next part of RFC1341 message (seconds)
6
+my($cleanup_interval)=3600;
7
+
8
+# ---- End of Settings ----
9
+
10
+my ($partial_dir) = "$TMPDIR/clamav-partial";
11
+#  if there is no partial directory, nothing to clean up
12
+opendir(DIR, $partial_dir) || exit 0;
13
+
14
+my ($cleanup_threshold) = time - $cleanup_interval;
15
+while(my $file = readdir(DIR)) {
16
+	next unless $file =~ m/^clamav-partial-([0-9]+)_[0-9a-f]{32}-[0-9]+$/;
17
+	my $filetime = $1;
18
+	if ($filetime <= $cleanup_threshold) {
19
+		unlink "$partial_dir/$file";
20
+	}
21
+}
22
+closedir DIR;
... ...
@@ -230,6 +230,14 @@ LocalSocket /tmp/clamd.socket
230 230
 # Default: no
231 231
 #MailFollowURLs no
232 232
 
233
+# Scan RFC1341 messages split over many emails.
234
+# You will need to periodically clean up $TemporaryDirectory/clamav-partial directory.
235
+# WARNING: This option may open your system to a DoS attack.
236
+#	   Never use it on loaded servers.
237
+# Default: no
238
+#ScanPartialMessages yes
239
+
240
+
233 241
 # With this option enabled ClamAV will try to detect phishing attempts by using
234 242
 # signatures.
235 243
 # Default: yes
... ...
@@ -492,6 +492,39 @@ fileblobDestroy(fileblob *fb)
492 492
 }
493 493
 
494 494
 void
495
+fileblobPartialSet(fileblob *fb, const char *fullname, const char *arg)
496
+{
497
+	if(fb->b.name)
498
+		return;
499
+
500
+	assert(fullname != NULL);
501
+
502
+	cli_dbgmsg("fileblobPartialSet: saving to %s\n", fullname);
503
+
504
+	fb->fd = open(fullname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY|O_EXCL, 0600);
505
+	if(fb->fd < 0) {
506
+		cli_errmsg("fileblobPartialSet: unable to create file: %s\n",fullname);
507
+		return;
508
+	}
509
+	fb->fp = fdopen(fb->fd, "wb");
510
+
511
+	if(fb->fp == NULL) {
512
+		cli_errmsg("fileblobSetFilename: fdopen failed (%s)\n", strerror(errno));
513
+		close(fb->fd);
514
+		return;
515
+	}
516
+	blobSetFilename(&fb->b, NULL, fullname);
517
+	if(fb->b.data)
518
+		if(fileblobAddData(fb, fb->b.data, fb->b.len) == 0) {
519
+			free(fb->b.data);
520
+			fb->b.data = NULL;
521
+			fb->b.len = fb->b.size = 0;
522
+			fb->isNotEmpty = 1;
523
+		}
524
+	fb->fullname = cli_strdup(fullname);
525
+}
526
+
527
+void
495 528
 fileblobSetFilename(fileblob *fb, const char *dir, const char *filename)
496 529
 {
497 530
 	char *fullname;
... ...
@@ -69,6 +69,7 @@ int	fileblobScanAndDestroy(fileblob *fb);
69 69
 void	fileblobDestructiveDestroy(fileblob *fb);
70 70
 void	fileblobDestroy(fileblob *fb);
71 71
 void	fileblobSetFilename(fileblob *fb, const char *dir, const char *filename);
72
+void    fileblobPartialSet(fileblob *fb, const char *fullname, const char *arg);
72 73
 const	char	*fileblobGetFilename(const fileblob *fb);
73 74
 void	fileblobSetCTX(fileblob *fb, cli_ctx *ctx);
74 75
 int	fileblobAddData(fileblob *fb, const unsigned char *data, size_t len);
... ...
@@ -95,6 +95,7 @@ extern "C"
95 95
 #define CL_SCAN_STRUCTURED		0x8000
96 96
 #define CL_SCAN_STRUCTURED_SSN_NORMAL	0x10000
97 97
 #define CL_SCAN_STRUCTURED_SSN_STRIPPED	0x20000
98
+#define CL_SCAN_PARTIAL_MESSAGE         0x40000
98 99
 
99 100
 /* recommended scan settings */
100 101
 #define CL_SCAN_STDOPT		(CL_SCAN_ARCHIVE | CL_SCAN_MAIL | CL_SCAN_OLE2 | CL_SCAN_HTML | CL_SCAN_PE | CL_SCAN_ALGORITHMIC | CL_SCAN_ELF)
... ...
@@ -81,6 +81,7 @@ static	char	const	rcsid[] = "$Id: mbox.c,v 1.381 2007/02/15 12:26:44 njh Exp $";
81 81
 #include "filetypes.h"
82 82
 #include "mbox.h"
83 83
 #include "dconf.h"
84
+#include "md5.h"
84 85
 
85 86
 #define DCONF_PHISHING mctx->ctx->dconf->phishing
86 87
 
... ...
@@ -193,7 +194,7 @@ typedef	unsigned	int	in_addr_t;
193 193
 #endif
194 194
 
195 195
 /*
196
- * Define this to handle messages covered by section 7.3.2 of RFC1341.
196
+ * Use CL_SCAN_PARTIAL_MESSAGE to handle messages covered by section 7.3.2 of RFC1341.
197 197
  *	This is experimental code so it is up to YOU to (1) ensure it's secure
198 198
  * (2) periodically trim the directory of old files
199 199
  *
... ...
@@ -201,13 +202,6 @@ typedef	unsigned	int	in_addr_t;
201 201
  * more than one machine you must make sure that .../partial is on a shared
202 202
  * network filesystem
203 203
  */
204
-#ifdef CL_EXPERIMENTAL
205
-
206
-#ifndef	C_WINDOWS	/* TODO: when opendir() is done */
207
-#define	PARTIAL_DIR
208
-#endif
209
-
210
-#endif
211 204
 /*#define	NEW_WORLD*/
212 205
 
213 206
 /*#define	SCAN_UNENCODED_BOUNCES	*//*
... ...
@@ -250,9 +244,7 @@ static	int	parseMimeHeader(message *m, const char *cmd, const table_t *rfc821Tab
250 250
 static	int	saveTextPart(mbox_ctx *mctx, message *m, int destroy_text);
251 251
 static	char	*rfc2047(const char *in);
252 252
 static	char	*rfc822comments(const char *in, char *out);
253
-#ifdef	PARTIAL_DIR
254 253
 static	int	rfc1341(message *m, const char *dir);
255
-#endif
256 254
 static	bool	usefulHeader(int commandNumber, const char *cmd);
257 255
 static	char	*getline_from_mbox(char *buffer, size_t len, FILE *fin);
258 256
 static	bool	isBounceStart(mbox_ctx *mctx, const char *line);
... ...
@@ -2771,13 +2763,13 @@ parseEmailBody(message *messageIn, text *textIn, mbox_ctx *mctx, unsigned int re
2771 2771
 				rc = OK;
2772 2772
 				break;
2773 2773
 			} else if(strcasecmp(mimeSubtype, "partial") == 0) {
2774
-#ifdef	PARTIAL_DIR
2775
-				/* RFC1341 message split over many emails */
2776
-				if(rfc1341(mainMessage, mctx->dir) >= 0)
2777
-					rc = OK;
2778
-#else
2779
-				cli_warnmsg("Partial message received from MUA/MTA - message cannot be scanned\n");
2780
-#endif
2774
+				if(mctx->ctx->options&CL_SCAN_PARTIAL_MESSAGE) {
2775
+					/* RFC1341 message split over many emails */
2776
+					if(rfc1341(mainMessage, mctx->dir) >= 0)
2777
+						rc = OK;
2778
+				} else {
2779
+					cli_warnmsg("Partial message received from MUA/MTA - message cannot be scanned\n");
2780
+				}
2781 2781
 			} else if(strcasecmp(mimeSubtype, "external-body") == 0)
2782 2782
 				/* TODO */
2783 2783
 				cli_warnmsg("Attempt to send Content-type message/external-body trapped");
... ...
@@ -3719,7 +3711,6 @@ rfc2047(const char *in)
3719 3719
 	return out;
3720 3720
 }
3721 3721
 
3722
-#ifdef	PARTIAL_DIR
3723 3722
 /*
3724 3723
  * Handle partial messages
3725 3724
  */
... ...
@@ -3729,7 +3720,11 @@ rfc1341(message *m, const char *dir)
3729 3729
 	fileblob *fb;
3730 3730
 	char *arg, *id, *number, *total, *oldfilename;
3731 3731
 	const char *tmpdir;
3732
+	int n;
3732 3733
 	char pdir[NAME_MAX + 1];
3734
+	unsigned char md5_val[16];
3735
+	cli_md5_ctx md5;
3736
+	char *md5_hex;
3733 3737
 
3734 3738
 	id = (char *)messageFindArgument(m, "id");
3735 3739
 	if(id == NULL)
... ...
@@ -3797,18 +3792,28 @@ rfc1341(message *m, const char *dir)
3797 3797
 		free(oldfilename);
3798 3798
 	}
3799 3799
 
3800
-	if((fb = messageToFileblob(m, pdir, 0)) == NULL) {
3800
+	n = atoi(number);
3801
+	cli_md5_init(&md5);
3802
+	cli_md5_update(&md5, id, strlen(id));
3803
+	cli_md5_final(md5_val, &md5);
3804
+	md5_hex = cli_str2hex((const char*)md5_val, 16);
3805
+
3806
+	if(!md5_hex) {
3801 3807
 		free(id);
3802 3808
 		free(number);
3803
-		return -1;
3809
+		return CL_EMEM;
3804 3810
 	}
3805 3811
 
3806
-	fileblobDestroy(fb);
3812
+	if(messageSavePartial(m, pdir, md5_hex, n) < 0) {
3813
+		free(md5_hex);
3814
+		free(id);
3815
+		free(number);
3816
+		return -1;
3817
+	}
3807 3818
 
3808 3819
 	total = (char *)messageFindArgument(m, "total");
3809 3820
 	cli_dbgmsg("rfc1341: %s, %s of %s\n", id, number, (total) ? total : "?");
3810 3821
 	if(total) {
3811
-		int n = atoi(number);
3812 3822
 		int t = atoi(total);
3813 3823
 		DIR *dd = NULL;
3814 3824
 
... ...
@@ -3833,6 +3838,7 @@ rfc1341(message *m, const char *dir)
3833 3833
 				cli_errmsg("Can't open '%s' for writing", outname);
3834 3834
 				free(id);
3835 3835
 				free(number);
3836
+				free(md5_hex);
3836 3837
 				closedir(dd);
3837 3838
 				return -1;
3838 3839
 			}
... ...
@@ -3848,7 +3854,7 @@ rfc1341(message *m, const char *dir)
3848 3848
 				} result;
3849 3849
 #endif
3850 3850
 
3851
-				snprintf(filename, sizeof(filename), "%s%d", id, n);
3851
+				snprintf(filename, sizeof(filename), "_%s-%u", md5_hex, n);
3852 3852
 
3853 3853
 #ifdef HAVE_READDIR_R_3
3854 3854
 				while((readdir_r(dd, &result.d, &dent) == 0) && dent) {
... ...
@@ -3861,6 +3867,7 @@ rfc1341(message *m, const char *dir)
3861 3861
 					char buffer[BUFSIZ], fullname[NAME_MAX + 1];
3862 3862
 					int nblanks;
3863 3863
 					struct stat statb;
3864
+					const char *dentry_idpart;
3864 3865
 
3865 3866
 #ifndef  C_CYGWIN
3866 3867
 					if(dent->d_ino == 0)
... ...
@@ -3869,8 +3876,10 @@ rfc1341(message *m, const char *dir)
3869 3869
 
3870 3870
 					snprintf(fullname, sizeof(fullname) - 1,
3871 3871
 						"%s/%s", pdir, dent->d_name);
3872
+					dentry_idpart = strchr(dent->d_name, '_');
3872 3873
 
3873
-					if(strncmp(filename, dent->d_name, strlen(filename)) != 0) {
3874
+					if(!dentry_idpart ||
3875
+							strcmp(filename, dentry_idpart) != 0) {
3874 3876
 						if(!cli_leavetemps_flag)
3875 3877
 							continue;
3876 3878
 						if(stat(fullname, &statb) < 0)
... ...
@@ -3879,6 +3888,7 @@ rfc1341(message *m, const char *dir)
3879 3879
 							if (cli_unlink(fullname)) {
3880 3880
 								cli_unlink(outname);
3881 3881
 								fclose(fout);
3882
+								free(md5_hex);
3882 3883
 								free(id);
3883 3884
 								free(number);
3884 3885
 								closedir(dd);
... ...
@@ -3915,6 +3925,7 @@ rfc1341(message *m, const char *dir)
3915 3915
 								fclose(fin);
3916 3916
 								fclose(fout);
3917 3917
 								cli_unlink(outname);
3918
+								free(md5_hex);
3918 3919
 								free(id);
3919 3920
 								free(number);
3920 3921
 								closedir(dd);
... ...
@@ -3928,6 +3939,7 @@ rfc1341(message *m, const char *dir)
3928 3928
 						if(cli_unlink(fullname)) {
3929 3929
 							fclose(fout);
3930 3930
 							cli_unlink(outname);
3931
+							free(md5_hex);
3931 3932
 							free(id);
3932 3933
 							free(number);
3933 3934
 							closedir(dd);
... ...
@@ -3944,10 +3956,10 @@ rfc1341(message *m, const char *dir)
3944 3944
 	}
3945 3945
 	free(number);
3946 3946
 	free(id);
3947
+	free(md5_hex);
3947 3948
 
3948 3949
 	return 0;
3949 3950
 }
3950
-#endif
3951 3951
 
3952 3952
 static void
3953 3953
 hrefs_done(blob *b, tag_arguments_t *hrefs)
... ...
@@ -1707,6 +1707,30 @@ base64Flush(message *m, unsigned char *buf)
1707 1707
 	return NULL;
1708 1708
 }
1709 1709
 
1710
+int messageSavePartial(message *m, const char *dir, const char *md5id, unsigned part)
1711
+{
1712
+	char fullname[1024];
1713
+	fileblob *fb;
1714
+	unsigned long time_val;
1715
+
1716
+	cli_dbgmsg("messageSavePartial\n");
1717
+	time_val  = time(NULL);
1718
+	snprintf(fullname, 1024, "%s/clamav-partial-%lu_%s-%u", dir, time_val, md5id, part);
1719
+
1720
+	fb = messageExport(m, fullname,
1721
+		(void *(*)(void))fileblobCreate,
1722
+		(void(*)(void *))fileblobDestroy,
1723
+		(void(*)(void *, const char *, const char *))fileblobPartialSet,
1724
+		(void(*)(void *, const unsigned char *, size_t))fileblobAddData,
1725
+		(void *(*)(text *, void *, int))textToFileblob,
1726
+		(void(*)(void *, cli_ctx *))fileblobSetCTX,
1727
+		0);
1728
+	if(!fb)
1729
+		return CL_EFORMAT;
1730
+	fileblobDestroy(fb);
1731
+	return CL_SUCCESS;
1732
+}
1733
+
1710 1734
 /*
1711 1735
  * Decode and transfer the contents of the message into a fileblob
1712 1736
  * The caller must free the returned fileblob
... ...
@@ -82,5 +82,6 @@ unsigned char	*decodeLine(message *m, encoding_type enctype, const char *line, u
82 82
 int	isuuencodebegin(const char *line);
83 83
 void	messageSetCTX(message *m, cli_ctx *ctx);
84 84
 int	messageContainsVirus(const message *m);
85
+int messageSavePartial(message *m, const char *dir, const char *id, unsigned part);
85 86
 
86 87
 #endif	/*_MESSAGE_H*/
... ...
@@ -47,6 +47,7 @@ struct cfgoption cfg_options[] = {
47 47
     {"DetectBrokenExecutables", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
48 48
     {"ScanMail", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
49 49
     {"MailFollowURLs", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
50
+    {"ScanPartialMessages", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
50 51
     {"PhishingSignatures", OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
51 52
     {"PhishingScanURLs",OPT_BOOL, 1, NULL, 0, OPT_CLAMD},
52 53
     /* these are FP prone options, if default isn't used */