Browse code

Started SPF tester

git-svn: trunk@3145

Nigel Horne authored on 2007/07/15 07:04:12
Showing 2 changed files
... ...
@@ -1,3 +1,10 @@
1
+Sat Jul 14 22:07:16 BST 2007 (njh)
2
+----------------------------------
3
+  * clamav-milter:	Experimental mode: basic SPF parser to reduce
4
+  				phish false-positives
5
+			Possible fix for 487
6
+			Some small tidies
7
+
1 8
 Sat Jul 14 14:17:01 CEST 2007 (acab)
2 9
 ------------------------------------
3 10
   * libclamav/nsis: fix macro collision on AIX - bb#570
... ...
@@ -33,7 +33,7 @@
33 33
  */
34 34
 static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.312 2007/02/12 22:24:21 njh Exp $";
35 35
 
36
-#define	CM_VERSION	"devel-070630"
36
+#define	CM_VERSION	"devel-140707"
37 37
 
38 38
 #if HAVE_CONFIG_H
39 39
 #include "clamav-config.h"
... ...
@@ -275,10 +275,13 @@ struct	privdata {
275 275
 	long	numBytes;	/* Number of bytes sent so far */
276 276
 	char	*received;	/* keep track of received from */
277 277
 	const	char	*rejectCode;	/* 550 or 554? */
278
-	int	discard;	/*
278
+	unsigned	int	discard:1;	/*
279 279
 				 * looks like the remote end is playing ping
280 280
 				 * pong with us
281 281
 				 */
282
+#ifdef	CL_EXPERIMENTAL
283
+	unsigned	int	spf_ok:1;
284
+#endif
282 285
 	int	statusCount;	/* number of X-Virus-Status headers */
283 286
 	int	serverNumber;	/* Index into serverIPs */
284 287
 	struct	cl_node	*root;	/* database of viruses used to scan this one */
... ...
@@ -511,7 +514,7 @@ static	pthread_cond_t	watchdog_cond = PTHREAD_COND_INITIALIZER;
511 511
 static	const	char	*postmaster = "postmaster";
512 512
 static	const	char	*from = "MAILER-DAEMON";
513 513
 static	int	quitting;
514
-static	const	char	*report;
514
+static	const	char	*report;	/* Report Phishing to this address */
515 515
 
516 516
 static	const	char	*whitelistFile;	/*
517 517
 					 * file containing destination email
... ...
@@ -562,6 +565,9 @@ static	int	isBlacklisted(const char *ip_address);
562 562
 static	void	mx(void);
563 563
 #ifdef	HAVE_RESOLV_H
564 564
 static	void	resolve(const char *host);
565
+#ifdef	CL_EXPERIMENTAL
566
+static	void	spf(struct privdata *privdata);
567
+#endif
565 568
 #endif
566 569
 static	sfsistat	black_hole(const struct privdata *privdata);
567 570
 static	int	useful_header(const char *cmd);
... ...
@@ -1347,11 +1353,11 @@ main(int argc, char **argv)
1347 1347
 			fprintf(stderr, _("%s: --freshclam_monitor must be at least one second\n"), argv[0]);
1348 1348
 			return EX_CONFIG;
1349 1349
 		}
1350
-#if	C_LINUX
1350
+#ifdef	C_LINUX
1351 1351
 		lang = getenv("LANG");
1352 1352
 
1353 1353
 		if(lang && (strstr(lang, "UTF-8") != NULL)) {
1354
-			fprintf(stderr, "Your LANG environment variable is set to '%s\n", lang);
1354
+			fprintf(stderr, "Your LANG environment variable is set to '%s'\n", lang);
1355 1355
 			fprintf(stderr, "This is known to cause problems for some %s installations.\n", argv[0]);
1356 1356
 			fputs("If you get failures with temporary files, please try again with LANG unset.\n", stderr);
1357 1357
 		}
... ...
@@ -2832,6 +2838,8 @@ clamfi_envfrom(SMFICTX *ctx, char **argv)
2832 2832
 
2833 2833
 		strcpy(ip, privdata->ip);
2834 2834
 		if(isBlacklisted(ip)) {
2835
+			char mess[80 + INET6_ADDRSTRLEN];
2836
+
2835 2837
 			logg("Rejected email from blacklisted IP %s\n", ip);
2836 2838
 
2837 2839
 			/*
... ...
@@ -2839,7 +2847,8 @@ clamfi_envfrom(SMFICTX *ctx, char **argv)
2839 2839
 			 *	sending	a try again code
2840 2840
 			 * TODO: state *which* virus
2841 2841
 			 */
2842
-			smfi_setreply(ctx, "550", "5.7.1", _("Your IP is blacklisted because your machine is infected with a virus"));
2842
+			sprintf(mess, "Your IP (%s) is blacklisted because your machine is infected with a virus", ip);
2843
+			smfi_setreply(ctx, "550", "5.7.1", mess);
2843 2844
 			broadcast(_("Blacklisted IP detected"));
2844 2845
 
2845 2846
 			/*
... ...
@@ -2873,6 +2882,14 @@ clamfi_envfrom(SMFICTX *ctx, char **argv)
2873 2873
 	if(hflag)
2874 2874
 		privdata->headers = header_list_new();
2875 2875
 
2876
+#ifdef	CL_EXPERIMENTAL
2877
+	/*
2878
+	 * FIXME: This should only be done when a phish is found. It's done
2879
+	 *	on every email for now to test the SPF code
2880
+	 */
2881
+	spf(privdata);
2882
+#endif	/*CL_EXPERIMENTAL*/
2883
+
2876 2884
 	return SMFIS_CONTINUE;
2877 2885
 }
2878 2886
 
... ...
@@ -3182,33 +3199,33 @@ clamfi_eom(SMFICTX *ctx)
3182 3182
 
3183 3183
 	logg("*clamfi_eom\n");
3184 3184
 
3185
-	if(!nflag) {
3186
-		/*
3187
-		 * remove any existing claims that it's virus free so that
3188
-		 * downstream checkers aren't fooled by a carefully crafted
3189
-		 * virus.
3190
-		 */
3191
-		int i;
3192
-
3193
-		for(i = privdata->statusCount; i > 0; --i)
3194
-			if(smfi_chgheader(ctx, "X-Virus-Status", i, NULL) == MI_FAILURE)
3195
-				logg(_("^Failed to delete X-Virus-Status header %d"), i);
3196
-	}
3197
-
3198 3185
 #ifdef	CL_DEBUG
3199 3186
 	assert(privdata != NULL);
3200 3187
 #ifndef	SESSION
3201 3188
 	assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
3202 3189
 	assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL)));
3203 3190
 #endif
3204
-	assert(privdata->dataSocket >= 0);
3205 3191
 #endif
3206 3192
 
3207 3193
 	if(external) {
3194
+		shutdown(privdata->dataSocket, SHUT_WR);	/* bug 487 */
3208 3195
 		close(privdata->dataSocket);
3209 3196
 		privdata->dataSocket = -1;
3210 3197
 	}
3211 3198
 
3199
+	if(!nflag) {
3200
+		/*
3201
+		 * remove any existing claims that it's virus free so that
3202
+		 * downstream checkers aren't fooled by a carefully crafted
3203
+		 * virus.
3204
+		 */
3205
+		int i;
3206
+
3207
+		for(i = privdata->statusCount; i > 0; --i)
3208
+			if(smfi_chgheader(ctx, "X-Virus-Status", i, NULL) == MI_FAILURE)
3209
+				logg(_("^Failed to delete X-Virus-Status header %d"), i);
3210
+	}
3211
+
3212 3212
 	if(!external) {
3213 3213
 		const char *virname;
3214 3214
 
... ...
@@ -3430,6 +3447,16 @@ clamfi_eom(SMFICTX *ctx)
3430 3430
 	 * TODO: it would be useful to add a header if mbox.c/FOLLOWURLS was
3431 3431
 	 * exceeded
3432 3432
 	 */
3433
+#ifdef	CL_EXPERIMENTAL
3434
+	/*
3435
+	 * FIXME: SPF lookup should only be done when a phish is found, see
3436
+	 *	above
3437
+	 */
3438
+	if(privdata->spf_ok && (strstr(mess, "FOUND") != NULL) && (strstr(mess, "Phishing") != NULL)) {
3439
+		logg(_("%s: Ignoring phish false positive\n"), sendmailId);
3440
+		strcpy(mess, "OK");
3441
+	}
3442
+#endif
3433 3443
 	if(strstr(mess, "ERROR") != NULL) {
3434 3444
 		if(strstr(mess, "Size limit reached") != NULL) {
3435 3445
 			/*
... ...
@@ -3682,6 +3709,9 @@ clamfi_eom(SMFICTX *ctx)
3682 3682
 
3683 3683
 		if(report && (quarantine == NULL) && (!advisory) &&
3684 3684
 		   (strstr(virusname, "Phishing") != NULL)) {
3685
+			/*
3686
+			 * Report phishing to an agency
3687
+			 */
3685 3688
 			for(to = privdata->to; *to; to++) {
3686 3689
 				smfi_delrcpt(ctx, *to);
3687 3690
 				smfi_addheader(ctx, "X-Original-To", *to);
... ...
@@ -3699,14 +3729,14 @@ clamfi_eom(SMFICTX *ctx)
3699 3699
 						logg(_("#Reported phishing to %s"), report);
3700 3700
 					else
3701 3701
 						logg(_("^Couldn't report to %s\n"), report);
3702
+					if((!rejectmail) || privdata->discard)
3703
+						rc = SMFIS_DISCARD;
3704
+					else
3705
+						rc = SMFIS_REJECT;
3702 3706
 				} else {
3703 3707
 					logg(_("^Can't set anti-phish header\n"));
3704 3708
 					rc = (privdata->discard) ? SMFIS_DISCARD : SMFIS_REJECT;
3705 3709
 				}
3706
-				if((!rejectmail) || privdata->discard)
3707
-					rc = SMFIS_DISCARD;
3708
-				else
3709
-					rc = SMFIS_REJECT;
3710 3710
 			} else {
3711 3711
 				setsubject(ctx, "Phishing attempt trapped by ClamAV and redirected");
3712 3712
 
... ...
@@ -5042,12 +5072,12 @@ add_local_ip(char *address)
5042 5042
 		for(net = (struct cidr_net *)localNets; net->base; net++)
5043 5043
 			;
5044 5044
 		if(pref && *(pref+1))
5045
-			preflen = atoi (pref+1);
5045
+			preflen = atoi(pref+1);
5046 5046
 		else
5047 5047
 			preflen = 32;
5048 5048
 
5049 5049
 		net->base = ntohl(ignoreIP.s_addr);
5050
-		net->mask = MAKEMASK (preflen);
5050
+		net->mask = MAKEMASK(preflen);
5051 5051
 
5052 5052
 		retval = 1;
5053 5053
 	}
... ...
@@ -5072,7 +5102,7 @@ add_local_ip(char *address)
5072 5072
 	else
5073 5073
 		retval = 0;
5074 5074
 
5075
-	free (opt);
5075
+	free(opt);
5076 5076
 	return retval;
5077 5077
 }
5078 5078
 
... ...
@@ -5935,14 +5965,14 @@ static void
5935 5935
 mx(void)
5936 5936
 {
5937 5937
 	u_char *p, *end;
5938
-	char name[MAXHOSTNAMELEN + 1];
5939
-	char buf[BUFSIZ];
5938
+	const HEADER *hp;
5939
+	int len, i, was_initialised;
5940 5940
 	union {
5941 5941
 		HEADER h;
5942 5942
 		u_char u[PACKETSZ];
5943 5943
 	} q;
5944
-	const HEADER *hp;
5945
-	int len, i, was_initialised;
5944
+	char name[MAXHOSTNAMELEN + 1];
5945
+	char buf[BUFSIZ];
5946 5946
 
5947 5947
 	if(gethostname(name, sizeof(name)) < 0) {
5948 5948
 		perror("gethostname");
... ...
@@ -6030,13 +6060,13 @@ static void
6030 6030
 resolve(const char *host)
6031 6031
 {
6032 6032
 	u_char *p, *end;
6033
-	char buf[BUFSIZ];
6033
+	const HEADER *hp;
6034
+	int len, i;
6034 6035
 	union {
6035 6036
 		HEADER h;
6036 6037
 		u_char u[PACKETSZ];
6037 6038
 	} q;
6038
-	const HEADER *hp;
6039
-	int len, i;
6039
+	char buf[BUFSIZ];
6040 6040
 
6041 6041
 	if((host == NULL) || (*host == '\0'))
6042 6042
 		return;
... ...
@@ -6076,7 +6106,7 @@ resolve(const char *host)
6076 6076
 			continue;
6077 6077
 		}
6078 6078
 		memcpy(&addr, p, sizeof(struct in_addr));
6079
-		p += 4;
6079
+		p += 4;	/* Should check len == 4 */
6080 6080
 		ip = inet_ntoa(addr);
6081 6081
 		if(ip) {
6082 6082
 			logg(_("Won't blacklist %s\n"), ip);
... ...
@@ -6084,6 +6114,175 @@ resolve(const char *host)
6084 6084
 		}
6085 6085
 	}
6086 6086
 }
6087
+
6088
+#ifdef	CL_EXPERIMENTAL
6089
+/*
6090
+ * Validate SPF records to help to stop Phish false positives
6091
+ * Currently only handles ip4 fields in the DNS record
6092
+ * Having said that, we don't need a full SPF parser, only something to stop
6093
+ *	Phish FPs
6094
+ * TODO: a: include: mx: hostnames
6095
+ * TODO: IPv6
6096
+ */
6097
+static void
6098
+spf(struct privdata *privdata)
6099
+{
6100
+	char *mailaddr, *ptr;
6101
+	u_char *p, *end;
6102
+	const HEADER *hp;
6103
+	int len, i;
6104
+	union {
6105
+		HEADER h;
6106
+		u_char u[PACKETSZ];
6107
+	} q;
6108
+	char buf[BUFSIZ];
6109
+
6110
+	if(privdata->ip[0] == '\0')
6111
+		return;
6112
+	if(strcmp(privdata->ip, "127.0.0.1") == 0) {
6113
+		/* Loopback always pass SPF */
6114
+		privdata->spf_ok = 1;
6115
+		return;
6116
+	}
6117
+	if(isLocal(privdata->ip)) {
6118
+		/* Local addresses always pass SPF */
6119
+		privdata->spf_ok = 1;
6120
+		return;
6121
+	}
6122
+
6123
+	if(privdata->from == NULL)
6124
+		return;
6125
+	if((mailaddr = strchr(privdata->from, '@')) == NULL)
6126
+		return;
6127
+
6128
+	mailaddr = cli_strdup(++mailaddr);
6129
+
6130
+	if(mailaddr == NULL)
6131
+		return;
6132
+
6133
+	ptr = strchr(mailaddr, '>');
6134
+
6135
+	if(ptr)
6136
+		*ptr = '\0';
6137
+
6138
+	len = res_query(mailaddr, C_IN, T_TXT, (u_char *)&q, sizeof(q));
6139
+	if(len < 0) {
6140
+		free(mailaddr);
6141
+		return;	/* Host has no TXT records */
6142
+	}
6143
+
6144
+	if((unsigned int)len > sizeof(q)) {
6145
+		free(mailaddr);
6146
+		return;
6147
+	}
6148
+
6149
+	hp = &(q.h);
6150
+	p = q.u + HFIXEDSZ;
6151
+	end = q.u + len;
6152
+
6153
+	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
6154
+		if((len = dn_skipname(p, end)) < 0) {
6155
+			free(mailaddr);
6156
+			return;
6157
+		}
6158
+
6159
+	i = ntohs(hp->ancount);
6160
+
6161
+	while((--i >= 0) && (p < end) && !privdata->spf_ok) {
6162
+		u_short type;
6163
+		u_long ttl;
6164
+		char txt[BUFSIZ];
6165
+
6166
+		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0) {
6167
+			free(mailaddr);
6168
+			return;
6169
+		}
6170
+		p += len;
6171
+		GETSHORT(type, p);
6172
+		p += INT16SZ;
6173
+		GETLONG(ttl, p);	/* unused */
6174
+		GETSHORT(len, p);
6175
+		if(type != T_TXT) {
6176
+			p += len;
6177
+			continue;
6178
+		}
6179
+		strncpy(txt, &p[1], sizeof(txt) - 1);
6180
+		txt[len - 1] = '\0';
6181
+		if((strncmp(txt, "v=spf1 ", 7) == 0) || (strncmp(txt, "spf2.0/pra ", 11) == 0)) {
6182
+			int j;
6183
+			char *record;
6184
+			struct in_addr remote_ip;	/* IP connecting to us */
6185
+
6186
+			logg("%s(%s): SPF record %s\n",
6187
+				mailaddr, privdata->ip, txt);
6188
+			/*
6189
+			 * This is were the beef of the check will go. This
6190
+			 * trivial check is of little real benefit, but it
6191
+			 * won't create false positives.
6192
+			 */
6193
+			if(strstr(txt, privdata->ip) != NULL) {
6194
+				logg("SPF simple pass\n");
6195
+				privdata->spf_ok = 1;
6196
+				break;
6197
+			}
6198
+
6199
+#ifdef HAVE_INET_NTOP
6200
+			/* IPv4 address ? */
6201
+			if(inet_pton(AF_INET, privdata->ip, &remote_ip) <= 0) {
6202
+				p += len;
6203
+				continue;
6204
+			}
6205
+#else
6206
+			if(inet_aton(privdata->ip, &remote_ip) == 0) {
6207
+				p += len;
6208
+				continue;
6209
+			}
6210
+#endif
6211
+
6212
+			j = 1;	/* strtok 0 would give the v= part */
6213
+			while((record = cli_strtok(txt, j++, " ")) != NULL) {
6214
+				if(strncmp(record, "ip4:", 4) == 0) {
6215
+					int preflen;
6216
+					char *ip, *pref;
6217
+					uint32_t mask;
6218
+					struct in_addr spf_range;	/* acceptable range of IPs */
6219
+
6220
+					ip = &record[4];
6221
+
6222
+					pref = strchr(ip, '/');
6223
+					preflen = 32;
6224
+					if(pref) {
6225
+						*pref++ = '\0';
6226
+						if(*pref)
6227
+							preflen = atoi(pref);
6228
+					}
6229
+
6230
+#ifdef HAVE_INET_NTOP
6231
+					/* IPv4 address ? */
6232
+					if(inet_pton(AF_INET, ip, &spf_range) <= 0)
6233
+						continue;
6234
+#else
6235
+					if(inet_aton(ip, &spf_range) == 0)
6236
+						continue;
6237
+#endif
6238
+					mask = MAKEMASK(preflen);
6239
+					if((ntohl(remote_ip.s_addr) & mask) == (ntohl(spf_range.s_addr) & mask)) {
6240
+						free(record);
6241
+						logg("SPF ip4 pass\n");
6242
+						privdata->spf_ok = 1;
6243
+						break;
6244
+					}
6245
+				}
6246
+				free(record);
6247
+			}
6248
+		}
6249
+		p += len;
6250
+	}
6251
+	free(mailaddr);
6252
+}
6253
+
6254
+#endif	/*CL_EXPERIMENTAL*/
6255
+
6087 6256
 #else	/*!HAVE_RESOLV_H */
6088 6257
 static void
6089 6258
 mx(void)