git-svn: trunk@3145
Nigel Horne authored on 2007/07/15 07:04:12... | ... |
@@ -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) |