Browse code

Added blacklist and blackhole mode

git-svn: trunk@2080

Nigel Horne authored on 2006/07/14 21:16:23
Showing 4 changed files
... ...
@@ -1,3 +1,8 @@
1
+Fri Jul 14 13:14:13 BST 2006 (njh)
2
+----------------------------------
3
+  * libclamav/table.c:	Added tableIterate
4
+  * clamav-milter:	Added black hole mode and IP blacklist support
5
+
1 6
 Fri Jul 14 08:45:04 BST 2006 (njh)
2 7
 ----------------------------------
3 8
   * libclamav/table:	Added helper routines to delete items
... ...
@@ -109,7 +109,7 @@ See http://www.nmt.edu/~wcolburn/sendmail-8.12.5/libmilter/docs/sample.html
109 109
 Installations for RedHat Linux and it's derivatives such as YellowDog:
110 110
 	Ensure that you have the sendmail-devel RPM installed
111 111
 	Add to /etc/mail/sendmail.mc before the MAILER statement:
112
-	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m')dnl
112
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m;C:30s;E:10m')dnl
113 113
 	define(`confINPUT_MAIL_FILTERS', `clamav')
114 114
 
115 115
 	Note that the INPUT_MAIL_FILTER line must come before the
... ...
@@ -23,28 +23,9 @@
23 23
  *
24 24
  * For installation instructions see the file INSTALL that came with this file
25 25
  */
26
-static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.253 2006/07/13 07:59:22 njh Exp $";
26
+static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.254 2006/07/14 12:15:26 njh Exp $";
27 27
 
28
-#define	CM_VERSION	"devel-120706"
29
-
30
-/*#define	DONT_SCAN_BLACK_HOLES	/*
31
-				 * Don't scan emails to addresses set to
32
-				 * /dev/null in /etc/aliases
33
-				 *
34
-				 * Since sendmail calls its milters before it
35
-				 * looks in /etc/aliases we can spend time
36
-				 * looking for malware that's going to be
37
-				 * thrown away even if the message is clean.
38
-				 * Enable this #define to not scan these
39
-				 * messages.
40
-				 * Note that this needs -ldb to be added to
41
-				 * the link line, which isn't usually done.
42
-				 * You will also need the db4 SDK
43
-				 *
44
-				 * TODO: Handle virtusertable, using
45
-				 * sendmail -bv should help that and remove the
46
-				 * db4 dependancy.
47
-				 */
28
+#define	CM_VERSION	"devel-130706"
48 29
 
49 30
 #if HAVE_CONFIG_H
50 31
 #include "clamav-config.h"
... ...
@@ -104,15 +85,6 @@ static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.253 2006/07/13 07:59:22 nj
104 104
 #include <sys/param.h>
105 105
 #endif
106 106
 
107
-#ifdef	DONT_SCAN_BLACK_HOLES
108
-#include <db.h>
109
-
110
-#define	ALIASES	"/etc/aliases.db"	/* some use /etc/mail/aliases.db */
111
-
112
-typedef struct  __db    DB;
113
-static	DB *db;
114
-#endif
115
-
116 107
 #if HAVE_MMAP
117 108
 #if HAVE_SYS_MMAN_H
118 109
 #include <sys/mman.h>
... ...
@@ -317,6 +289,8 @@ static	void	setsubject(SMFICTX *ctx, const char *virusname);
317 317
 static	int	isLocalAddr(in_addr_t addr);
318 318
 static	void	clamdIsDown(void);
319 319
 static	void	*watchdog(void *a);
320
+static	int	check_and_reload_database(void);
321
+static	void	timeoutBlacklist(char *ip_address, int time_of_blacklist);
320 322
 static	int	logg_facility(const char *name);
321 323
 static	void	quit(void);
322 324
 static	void	broadcast(const char *mess);
... ...
@@ -492,6 +466,20 @@ static	const	char	*whitelistFile;	/*
492 492
 					 */
493 493
 static	const	char	*sendmailCF;	/* location of sendmail.cf to verify */
494 494
 static	const	char	*pidfile;
495
+static	int	black_hole_mode; /*
496
+				 * Since sendmail calls its milters before it
497
+				 * looks in /etc/aliases we can spend time
498
+				 * looking for malware that's going to be
499
+				 * thrown away even if the message is clean.
500
+				 * Enable this to not scan these messages.
501
+				 * Sadly, because these days sendmail -bv
502
+				 * only works as root, you can't use this with
503
+				 * the User directive, which some won't like
504
+				 *
505
+				 * TODO: Investigate
506
+				 *	smfi_getsymval(ctx, "{rcpt_addr}")
507
+				 * which also may contain the real target name
508
+				 */
495 509
 
496 510
 static	table_t	*blacklist;	/* never freed */
497 511
 static	int	blacklist_time;	/* How long to blacklist an IP */
... ...
@@ -520,6 +508,7 @@ static	int	isBlacklisted(const char *ip_address);
520 520
 static	void	logger(const char *mess);
521 521
 static	void	mx(void);
522 522
 static	void	resolve(const char *host);
523
+static	sfsistat	black_hole(const struct privdata *privdata);
523 524
 
524 525
 short	logg_time, logg_lock, logok;
525 526
 int	logg_size;
... ...
@@ -532,6 +521,7 @@ help(void)
532 532
 
533 533
 	puts(_("\t--advisory\t\t-A\tFlag viruses rather than deleting them."));
534 534
 	puts(_("\t--blacklist=time\t-k\tTime (in seconds) to blacklist an IP."));
535
+	puts(_("\t--black-hole-mode\t\tDon't scan messages aliased to /dev/null."));
535 536
 	puts(_("\t--bounce\t\t-b\tSend a failure message to the sender."));
536 537
 	puts(_("\t--broadcast\t\t-B [IFACE]\tBroadcast to a network manager when a virus is found."));
537 538
 	puts(_("\t--config-file=FILE\t-c FILE\tRead configuration from FILE."));
... ...
@@ -639,9 +629,9 @@ main(int argc, char **argv)
639 639
 		struct cidr_net *net;
640 640
 		struct in_addr ignoreIP;
641 641
 #ifdef	CL_DEBUG
642
-		const char *args = "a:AbB:c:CdDefF:I:k:lLm:M:nNop:PqQ:hHs:St:T:U:VwW:x:0:";
642
+		const char *args = "a:AbB:c:CdDefF:I:k:lLm:M:nNop:PqQ:hHs:St:T:U:VwW:x:0:1:2";
643 643
 #else
644
-		const char *args = "a:AbB:c:CdDefF:I:k:lLm:M:nNop:PqQ:hHs:St:T:U:VwW:0:";
644
+		const char *args = "a:AbB:c:CdDefF:I:k:lLm:M:nNop:PqQ:hHs:St:T:U:VwW:0:1:2";
645 645
 #endif
646 646
 
647 647
 		static struct option long_options[] = {
... ...
@@ -756,6 +746,9 @@ main(int argc, char **argv)
756 756
 			{
757 757
 				"version", 0, NULL, 'V'
758 758
 			},
759
+			{
760
+				"black-hole-mode", 0, NULL, '2'
761
+			},
759 762
 #ifdef	CL_DEBUG
760 763
 			{
761 764
 				"debug-level", 1, NULL, 'x'
... ...
@@ -904,6 +897,9 @@ main(int argc, char **argv)
904 904
 			case '1':	/* headers for the template file */
905 905
 				templateHeaders = optarg;
906 906
 				break;
907
+			case '2':
908
+				black_hole_mode++;
909
+				break;
907 910
 			case 'T':	/* time to wait for child to die */
908 911
 				child_timeout = atoi(optarg);
909 912
 				break;
... ...
@@ -1021,19 +1017,6 @@ main(int argc, char **argv)
1021 1021
 	consolefd = open(console, O_WRONLY);
1022 1022
 #endif
1023 1023
 
1024
-#ifdef	DONT_SCAN_BLACK_HOLES
1025
-	if(db_create(&db, NULL, 0) == 0) {
1026
-		int ret = db->open(db, NULL, ALIASES, NULL, DB_HASH,
1027
-			DB_RDONLY, 0644);
1028
-
1029
-		if(ret != 0) {
1030
-			perror(ALIASES);
1031
-			return EX_OSFILE;
1032
-		}
1033
-	} else
1034
-		db = NULL;
1035
-#endif
1036
-
1037 1024
 	if(getuid() == 0) {
1038 1025
 		if(iface) {
1039 1026
 #ifdef	SO_BINDTODEVICE
... ...
@@ -1079,14 +1062,22 @@ main(int argc, char **argv)
1079 1079
 #endif
1080 1080
 			}
1081 1081
 
1082
+			if(black_hole_mode && (user->pw_uid != 0)) {
1083
+				fprintf(stderr, _("%s: You cannot use black hole mode unless you are root\n"),
1084
+					argv[0]);
1085
+				return EX_CONFIG;
1086
+			}
1087
+
1082 1088
 			setgid(user->pw_gid);
1089
+
1083 1090
 			if(setuid(user->pw_uid) < 0)
1084 1091
 				perror(cpt->strarg);
1085 1092
 			else
1086 1093
 				cli_dbgmsg(_("Running as user %s (UID %d, GID %d)\n"),
1087 1094
 					cpt->strarg, user->pw_uid, user->pw_gid);
1088
-		} else
1095
+		} else if(!black_hole_mode)
1089 1096
 			fprintf(stderr, _("%s: running as root is not recommended (check \"User\" in %s)\n"), argv[0], cfgfile);
1097
+
1090 1098
 	} else if(iface) {
1091 1099
 		fprintf(stderr, _("%s: Only root can set an interface for --broadcast\n"), argv[0]);
1092 1100
 		return EX_USAGE;
... ...
@@ -1638,11 +1629,7 @@ main(int argc, char **argv)
1638 1638
 		}
1639 1639
 	}
1640 1640
 
1641
-#ifdef	SESSION
1642
-	/* FIXME: add localSocket support to watchdog */
1643
-	if((localSocket == NULL) || external)
1644
-#endif
1645
-		pthread_create(&tid, NULL, watchdog, NULL);
1641
+	pthread_create(&tid, NULL, watchdog, NULL);
1646 1642
 
1647 1643
 	if(((cpt = cfgopt(copt, "PidFile")) != NULL) && cpt->enabled)
1648 1644
 		pidFile = cpt->strarg;
... ...
@@ -2362,10 +2349,18 @@ clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
2362 2362
 		 */
2363 2363
 		smfi_setreply(ctx, "550", "5.7.1", _("Your IP is blacklisted"));
2364 2364
 		broadcast(_("Blacklisted IP detected"));
2365
+
2366
+		/*
2367
+		 * Keep them blacklisted
2368
+		 */
2369
+		pthread_mutex_lock(&blacklist_mutex);
2370
+		(void)tableUpdate(blacklist, remoteIP, (int)time((time_t *)0));
2371
+		pthread_mutex_unlock(&blacklist_mutex);
2372
+
2365 2373
 		return SMFIS_REJECT;
2366 2374
 	}
2367 2375
 	if(smfi_getpriv(ctx) != NULL) {
2368
-		/* More than one MAIL FROM command, "can't happen" */
2376
+		/* More than one connection command, "can't happen" */
2369 2377
 		cli_warnmsg("clamfi_connect: called more than once\n");
2370 2378
 		return SMFIS_TEMPFAIL;
2371 2379
 	}
... ...
@@ -2380,7 +2375,7 @@ clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
2380 2380
 		return SMFIS_CONTINUE;
2381 2381
 	}
2382 2382
 
2383
-	clamfi_free(privdata);
2383
+	free(privdata);
2384 2384
 
2385 2385
 	return cl_error;
2386 2386
 }
... ...
@@ -2695,28 +2690,14 @@ clamfi_eoh(SMFICTX *ctx)
2695 2695
 		return cl_error;
2696 2696
 	}
2697 2697
 
2698
-#ifdef	DONT_SCAN_BLACK_HOLES
2699
-	for(to = privdata->to; *to; to++) {
2700
-		DBT key, data;
2701
-
2702
-		memset(&key, '\0', sizeof(DBT));
2703
-		memset(&data, '\0', sizeof(DBT));
2698
+	if(black_hole_mode) {
2699
+		sfsistat rc = black_hole(privdata);
2704 2700
 
2705
-		key.data = (char *)*to;
2706
-		key.size = strlen(key.data) + 1;
2707
-
2708
-		if(db->get(db, NULL, &key, &data, 0) == 0)
2709
-			/* FIXME: The result may be aliased as well */
2710
-			if(strcmp(data.data, "/dev/null") == 0)
2711
-				continue;
2712
-		break;
2713
-	}
2714
-	if(*to == NULL) {
2715
-		/* All recipients map to /dev/null */
2716
-		syslog(LOG_NOTICE, "discarded, since all recipients are /dev/null");
2717
-		return SMFIS_DISCARD;
2701
+		if(rc != SMFIS_CONTINUE) {
2702
+			clamfi_cleanup(ctx);
2703
+			return rc;
2704
+		}
2718 2705
 	}
2719
-#endif
2720 2706
 
2721 2707
 	/*
2722 2708
 	 * See if the e-mail is only going to members of the list
... ...
@@ -4853,8 +4834,6 @@ watchdog(void *a)
4853 4853
 {
4854 4854
 	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
4855 4855
 
4856
-	assert((!external) || (sessions != NULL));
4857
-
4858 4856
 	while(!quitting) {
4859 4857
 		unsigned int i;
4860 4858
 		struct timespec ts;
... ...
@@ -4882,32 +4861,11 @@ watchdog(void *a)
4882 4882
 		cli_dbgmsg("watchdog wakes\n");
4883 4883
 		pthread_mutex_unlock(&watchdog_mutex);
4884 4884
 
4885
-		if(!external) {
4886
-			/*
4887
-			 * Re-load the database if needed
4888
-			 */
4889
-			switch(cl_statchkdir(&dbstat)) {
4890
-				case 1:
4891
-					cli_dbgmsg("Database has changed\n");
4892
-					cl_statfree(&dbstat);
4893
-					if(use_syslog)
4894
-						syslog(LOG_WARNING, _("Loading new database"));
4895
-					if(loadDatabase() != 0) {
4896
-						smfi_stop();
4897
-						cli_errmsg("Failed to load updated database\n");
4898
-						return NULL;
4899
-					}
4900
-					break;
4901
-				case 0:
4902
-					cli_dbgmsg("Database has not changed\n");
4903
-					break;
4904
-				default:
4905
-					smfi_stop();
4906
-					cli_errmsg("Database error - %s is stopping\n", progname);
4907
-					return NULL;
4908
-			}
4909
-			continue;
4885
+		if(check_and_reload_database() != 0) {
4886
+			smfi_stop();
4887
+			return NULL;
4910 4888
 		}
4889
+
4911 4890
 		i = 0;
4912 4891
 		session = sessions;
4913 4892
 		pthread_mutex_lock(&sstatus_mutex);
... ...
@@ -4990,6 +4948,13 @@ watchdog(void *a)
4990 4990
 		if(i == max_children)
4991 4991
 			clamdIsDown();
4992 4992
 		pthread_mutex_unlock(&sstatus_mutex);
4993
+
4994
+		/* Garbage collect IP addresses no longer blacklisted */
4995
+		if(blacklist) {
4996
+			pthread_mutex_lock(&blacklist_mutex);
4997
+			tableIterate(blacklist, timeoutBlacklist);
4998
+			pthread_mutex_unlock(&blacklist_mutex);
4999
+		}
4993 5000
 	}
4994 5001
 	cli_dbgmsg("watchdog quits\n");
4995 5002
 	return NULL;
... ...
@@ -5006,9 +4971,6 @@ watchdog(void *a)
5006 5006
 {
5007 5007
 	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
5008 5008
 
5009
-	if(external)
5010
-		return NULL;
5011
-
5012 5009
 	while(!quitting) {
5013 5010
 		struct timespec ts;
5014 5011
 		struct timeval tp;
... ...
@@ -5040,29 +5002,15 @@ watchdog(void *a)
5040 5040
 		 * thread-safe, since it's governed by a mutex that we can't
5041 5041
 		 * see, and there's no access to it via an approved method
5042 5042
 		 */
5043
-
5044
-		/*
5045
-		 * Re-load the database.
5046
-		 */
5047
-		switch(cl_statchkdir(&dbstat)) {
5048
-			case 1:
5049
-				cli_dbgmsg("Database has changed\n");
5050
-				cl_statfree(&dbstat);
5051
-				if(use_syslog)
5052
-					syslog(LOG_WARNING, _("Loading new database"));
5053
-				if(loadDatabase() != 0) {
5054
-					smfi_stop();
5055
-					cli_errmsg("Failed to load updated database\n");
5056
-					return NULL;
5057
-				}
5058
-				break;
5059
-			case 0:
5060
-				cli_dbgmsg("Database has not changed\n");
5061
-				break;
5062
-			default:
5063
-				smfi_stop();
5064
-				cli_errmsg("Database error - %s is stopping\n", progname);
5065
-				return NULL;
5043
+		if(check_and_reload_database() != 0) {
5044
+			smfi_stop();
5045
+			return NULL;
5046
+		}
5047
+		/* Garbage collect IP addresses no longer blacklisted */
5048
+		if(blacklist) {
5049
+			pthread_mutex_lock(&blacklist_mutex);
5050
+			tableIterate(blacklist, timeoutBlacklist);
5051
+			pthread_mutex_unlock(&blacklist_mutex);
5066 5052
 		}
5067 5053
 	}
5068 5054
 	cli_dbgmsg("watchdog quits\n");
... ...
@@ -5070,6 +5018,49 @@ watchdog(void *a)
5070 5070
 }
5071 5071
 #endif
5072 5072
 
5073
+/*
5074
+ * Check to see if the database needs to be reloaded
5075
+ *	Return 0 for success
5076
+ */
5077
+static int
5078
+check_and_reload_database(void)
5079
+{
5080
+	int rc;
5081
+
5082
+	if(external)
5083
+		return 0;
5084
+
5085
+	switch(cl_statchkdir(&dbstat)) {
5086
+		case 1:
5087
+			cli_dbgmsg("Database has changed\n");
5088
+			cl_statfree(&dbstat);
5089
+			if(use_syslog)
5090
+				syslog(LOG_WARNING, _("Loading new database"));
5091
+			rc = loadDatabase();
5092
+			if(rc != 0) {
5093
+				cli_errmsg("Failed to load updated database\n");
5094
+				return rc;
5095
+			}
5096
+			break;
5097
+		case 0:
5098
+			cli_dbgmsg("Database has not changed\n");
5099
+			break;
5100
+		default:
5101
+			cli_errmsg("Database error - %s is stopping\n", progname);
5102
+			return 1;
5103
+	}
5104
+	return 0;	/* all OK */
5105
+}
5106
+
5107
+static void
5108
+timeoutBlacklist(char *ip_address, int time_of_blacklist)
5109
+{
5110
+	if(time_of_blacklist == 0)	/* Must not blacklist this IP address */
5111
+		return;
5112
+	if((time((time_t *)0) - time_of_blacklist) > blacklist_time)
5113
+		tableRemove(blacklist, ip_address);
5114
+}
5115
+
5073 5116
 static const struct {
5074 5117
 	const char *name;
5075 5118
 	int code;
... ...
@@ -5565,13 +5556,11 @@ isBlacklisted(const char *ip_address)
5565 5565
 	if(blacklist == NULL) {
5566 5566
 		blacklist = tableCreate();
5567 5567
 
5568
-		if(blacklist == NULL) {
5568
+		pthread_mutex_unlock(&blacklist_mutex);
5569
+
5570
+		if(blacklist == NULL)
5569 5571
 			if(use_syslog)
5570 5572
 				syslog(LOG_ERR, _("Can't create blacklist table"));
5571
-			pthread_mutex_unlock(&blacklist_mutex);
5572
-			return 0;
5573
-		}
5574
-		pthread_mutex_unlock(&blacklist_mutex);
5575 5573
 		return 0;
5576 5574
 	}
5577 5575
 	t = tableFind(blacklist, ip_address);
... ...
@@ -5585,8 +5574,7 @@ isBlacklisted(const char *ip_address)
5585 5585
 		/* IP cannot be blacklisted */
5586 5586
 		return 0;
5587 5587
 
5588
-	if((now - t) < blacklist_time)
5589
-		/* FIXME: should be able to renew the certificate */
5588
+	if((now - t) <= blacklist_time)
5590 5589
 		return 1;
5591 5590
 
5592 5591
 	/* FIXME: should be able to remove the certificate */
... ...
@@ -5770,3 +5758,63 @@ resolve(const char *host)
5770 5770
 		}
5771 5771
 	}
5772 5772
 }
5773
+
5774
+static sfsistat
5775
+black_hole(const struct privdata *privdata)
5776
+{
5777
+	int must_scan;
5778
+	char **to;
5779
+
5780
+	to = privdata->to;
5781
+	must_scan = (*to) ? 0 : 1;
5782
+
5783
+	for(; *to; to++) {
5784
+		char cmd[128];
5785
+		FILE *sendmail;
5786
+
5787
+		snprintf(cmd, sizeof(cmd) - 1, "%s -bv \"%s\" < /dev/null 2>&1",
5788
+			SENDMAIL_BIN, *to);
5789
+
5790
+		cli_dbgmsg("Calling %s\n", cmd);
5791
+		sendmail = popen(cmd, "r");
5792
+
5793
+		if(sendmail) {
5794
+			char buf[BUFSIZ];
5795
+
5796
+			while(fgets(buf, sizeof(buf), sendmail) != NULL) {
5797
+				if(cli_chomp(buf) == 0)
5798
+					continue;
5799
+
5800
+				cli_dbgmsg("sendmail output: %s\n", buf);
5801
+
5802
+				if(strstr(buf, "... deliverable: mailer ")) {
5803
+					const char *p = strstr(buf, ", user ");
5804
+
5805
+					if(strcmp(&p[7], "/dev/null") != 0) {
5806
+						must_scan = 1;
5807
+						break;
5808
+					}
5809
+				}
5810
+			}
5811
+			pclose(sendmail);
5812
+		} else if(use_syslog) {
5813
+			syslog(LOG_WARNING, _("Can't execute '%s' to expand '%s'"),
5814
+				cmd, *to);
5815
+			must_scan = 1;
5816
+		}
5817
+		if(must_scan)
5818
+			break;
5819
+	}
5820
+	if(!must_scan) {
5821
+		/* All recipients map to /dev/null */
5822
+		if(use_syslog) {
5823
+			to = privdata->to;
5824
+			if(*to)
5825
+				syslog(LOG_NOTICE, "discarded, since all recipients (e.g. \"%s\") are /dev/null", *to);
5826
+			else
5827
+				syslog(LOG_NOTICE, "discarded, since all recipients are /dev/null");
5828
+		}
5829
+		return SMFIS_DISCARD;
5830
+	}
5831
+	return SMFIS_CONTINUE;
5832
+}
... ...
@@ -115,6 +115,7 @@ To avoid this, blacklisting does not last for ever. The recommended value is
115 115
 .TP
116 116
 Machines on the LAN, the local host, and machines that are our MX peers are
117 117
 never blacklisted.
118
+.TP
118 119
 \fB-l, \-\-local\fR
119 120
 Also scan messages sent from LAN. You probably want this especially if
120 121
 your LAN is populated by machines running Windows or DOS.
... ...
@@ -256,16 +257,30 @@ When neither \-\-force, \-\-local nor \-\-outgoing is given,
256 256
 this option intercepts incoming mails that incorrectly claim to be from the
257 257
 local domain.
258 258
 .TP
259
-\fB\-\-whitelist-file=FILE, \-W file
259
+\fB\-\-whitelist-file=FILE, \-W file\fR
260 260
 This option specifies a file which contains a list of e-mail addresses.
261 261
 E-mails sent to these addresses will NOT be checked.
262 262
 While this is not an Anti-Virus function, it is quite useful for some systems.
263 263
 The address given to the \-\-quarantine directive is always white-listed.
264 264
 .TP
265
-\fB\-\-sendmail-cf=FILE
265
+\fB\-\-sendmail-cf=FILE\fR
266 266
 When starting, clamav\-milter runs some sanity checks against the sendmail.cf
267 267
 file, usually in /etc/sendmail.cf or /etc/mail/sendmail.cf. This directive
268 268
 tells clamav\-milter where to find the sendmail.cf file.
269
+.TP
270
+\fB\-\-black-hole-mode\fR
271
+Since \fIsendmail\fR calls its milters before it looks in its alias and virtuser
272
+tables, clamav-milter can spend time looking for malware that's going to be
273
+thrown away even if the message is clean.
274
+.TP.
275
+Enable this to not scan these messages (in practice clamav\-milter will discard
276
+these messages so the message doesn't go further down the milter call chain).
277
+.TP
278
+Sadly, because these days sendmail \-bv only works as root,
279
+so this option is not compatible with the User directive in clamd.conf,
280
+which some may view as a security risk.
281
+Only enable this if your site has many addresses aliased to /dev/null.
282
+.TP
269 283
 .SH "BUGS"
270 284
 There is no support for IPv6.
271 285
 .SH "EXAMPLES"