forked from smfreegard/Haraka
-
Notifications
You must be signed in to change notification settings - Fork 6
/
mail_from.is_resolvable.js
140 lines (133 loc) · 5.7 KB
/
mail_from.is_resolvable.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Check MAIL FROM domain is resolvable to an MX
var dns = require('dns');
var re_bogus_ip = /^(?:0\.0\.0\.0|255\.255\.255\.255|127\.)/;
exports.hook_mail = function(next, connection, params) {
var mail_from = params[0];
// Check for MAIL FROM without an @ first - ignore those here
if (!mail_from.host) {
return next();
}
var called_next = 0;
var plugin = this;
var domain = mail_from.host;
var config = this.config.get('mail_from.is_resolvable.ini');
// Just in case DNS never comes back (UDP), we should DENYSOFT.
var timeout_id = setTimeout(function () {
connection.loginfo(plugin, 'timed out when looking up MX for ' + domain);
called_next++;
return next(DENYSOFT, 'Temporary resolver error (timeout)');
}, ((config.main.timeout) ? config.main.timeout : 30) * 1000);
var cb = function (code, reply) {
if (!called_next) {
clearTimeout(timeout_id);
called_next++;
next(code, reply);
}
}
dns.resolveMx(domain, function(err, addresses) {
if (err) {
connection.logdebug(plugin, domain + ': MX => ' + err.message);
switch (err.code) {
case dns.NXDOMAIN:
case 'ENOTFOUND':
case 'ENODATA':
// Ignore these as they are not 'temporary'
// In this case we need to look up the implicit MX
break;
default:
return cb(DENYSOFT, 'Temporary resolver error ('
+ err.code + ')');
break;
}
}
if (addresses && addresses.length) {
// Verify that the MX records resolve to valid addresses
var a_records = {};
var pending_queries = 0;
var check_results = function () {
a_records = Object.keys(a_records);
if (a_records && a_records.length) {
connection.logdebug(plugin, domain + ': ' + a_records);
return cb();
}
return cb(((config.main.reject_no_mx) ? DENY : DENYSOFT),
'No MX for your FROM address');
}
addresses.forEach(function (addr) {
// Handle MX records that are IP addresses
// This is invalid - but a lot of MTAs allow it.
if (/^\d+\.\d+\.\d+\.\d+$/.test(addr.exchange)) {
connection.logwarn(plugin, domain + ': invalid MX ' + addr.exchange);
if (config.main.allow_mx_ip) {
a_records[addr.exchange] = 1;
}
return;
}
pending_queries++;
dns.resolve(addr.exchange, function(err, addresses) {
pending_queries--;
if (err) {
connection.logdebug(plugin, domain + ': MX ' + addr.priority + ' '
+ addr.exchange + ' => ' + err.message);
}
else {
connection.logdebug(plugin, domain + ': MX ' + addr.priority + ' '
+ addr.exchange + ' => ' + addresses);
for (var i=0; i < addresses.length; i++) {
// Ignore anything obviously bogus
if (re_bogus_ip.test(addresses[i])) {
connection.logdebug(plugin, addr.exchange + ': discarding ' + addresses[i]);
continue;
}
a_records[addresses[i]] = 1;
}
}
if (pending_queries === 0) {
check_results();
}
});
});
// In case we don't run any queries
if (pending_queries === 0) {
check_results();
}
}
else {
// Check for implicit MX 0 record
dns.resolve(domain, function(err, addresses) {
if (err) {
connection.logdebug(plugin, domain + ': A => ' + err.message);
switch (err.code) {
case dns.NXDOMAIN:
case 'ENOTFOUND':
case 'ENODATA':
// Ignore
break;
default:
return cb(DENYSOFT, 'Temporary resolver error ('
+ err.code + ')');
break;
}
}
if (addresses && addresses.length) {
connection.logdebug(plugin, domain + ': A => ' + addresses);
var a_records = {};
for (var i=0; i < addresses.length; i++) {
// Ignore anything obviously bogus
if (re_bogus_ip.test(addresses[i])) {
connection.logdebug(plugin, domain + ': discarding ' + addresses[i]);
continue;
}
a_records[addresses[i]] = 1;
}
a_records = Object.keys(a_records);
if (a_records && a_records.length) {
return cb();
}
}
return cb(((config.main.reject_no_mx) ? DENY : DENYSOFT),
'No MX for your FROM address');
});
}
});
}