1 This is an unofficial patch for Postfix stable release 2.0 that
2 back-ports a new feature from the after-2.0 snapshot releases.
4 This feature can be used to block mail from so-called spammer
5 havens, from sender addresses that resolve to Verisign's wild-card
6 mail responder, or from domains that claim to have mail servers in
7 reserved networks such as 127.0.0.1.
9 This patch is a revised version; the first implementation of the
10 check_{helo,sender,recipient}_ns_access feature did not correctly
11 look up name server records and caused mail to be deferred with a
12 450 status code; the second revision no longer defers mail when
13 some NS or MX host lookup fails.
17 The check_{helo,sender,recipient}_{ns,mx}_access maptype:mapname
18 restriction applies the specified access table to the NS or MX
19 hosts of the host/domain given in HELO, EHLO, MAIL FROM or RCPT TO
23 smtpd_mumble_restrictions =
25 reject_unknown_sender_domain
26 check_sender_mx_access hash:/etc/postfix/mx_access
29 /etc/postfix/mx_access:
30 spammer.haven.tld reject spammer mx host
31 64.94.110.11 reject mail server in verisign wild-card domain
32 127 reject mail server in loopback network
33 0 reject mail server in broadcast network
35 The following entries block mail from domains that claim to have
36 have mail servers or domain servers in reserved networks.
38 10 reject mail server in RFC 1918 private network
39 169.254 reject mail server in link local network
40 172.16 reject mail server in RFC 1918 private network
41 ...similar entries for 172.17...172.31
42 192.0.2 reject mail server in TEST-NET network
43 192.168 reject mail server in RFC 1918 private network
44 224 reject mail server in class D multicast network
45 ...similar entries for 225...239
46 240 reject mail server in class E reserved network
47 ...similar entries for 241...247
48 248 reject mail server in reserved network
49 ...similar entries for 249...255
51 diff -cr /tmp/postfix-2.0.16/conf/sample-smtpd.cf ./conf/sample-smtpd.cf
52 *** /tmp/postfix-2.0.16/conf/sample-smtpd.cf Tue Aug 12 12:28:46 2003
53 --- ./conf/sample-smtpd.cf Fri Sep 19 11:17:34 2003
57 # check_helo_access maptype:mapname
58 # look up HELO hostname or parent domains.
59 # see access(5) for possible lookup results.
60 + # check_helo_mx_access maptype:mapname
61 + # check_helo_ns_access maptype:mapname
62 + # look up the helo hostname MX hosts (or name servers) and apply the
63 + # specified access table to those hosts.
64 + # Note: the OK result is not allowed here for security reasons.
65 # reject: reject the request. Place this at the end of a restriction.
66 # permit: permit the request. Place this at the end of a restriction.
67 # warn_if_reject: next restriction logs a warning instead of rejecting.
71 # check_sender_access maptype:mapname
72 # look up sender address, parent domain, or localpart@.
73 # see access(5) for possible lookup results.
74 + # check_sender_mx_access maptype:mapname
75 + # check_sender_ns_access maptype:mapname
76 + # look up sender address MX hosts (or name servers) and apply the
77 + # specified access table to those hosts.
78 + # Note: the OK result is not allowed here for security reasons.
79 # reject_sender_login_mismatch: reject if $smtpd_sender_login_maps specifies
80 # a MAIL FROM address owner, but the client is not (SASL) logged in as
81 # that MAIL FROM address owner; or if the client is (SASL) logged in, but
85 # check_recipient_access maptype:mapname
86 # look up recipient address, parent domain, or localpart@.
87 # see access(5) for possible lookup results.
88 + # check_recipient_mx_access maptype:mapname
89 + # check_recipient_ns_access maptype:mapname
90 + # look up the recipient address MX hosts (or name servers) and apply the
91 + # specified access table to those hosts.
92 + # Note: the OK result is not allowed here for security reasons.
93 # reject_non_fqdn_recipient: reject recipient address that is not in FQDN form
94 # reject: reject the request. Place this at the end of a restriction.
95 # permit: permit the request. Place this at the end of a restriction.
96 diff -cr /tmp/postfix-2.0.16/html/uce.html ./html/uce.html
97 *** /tmp/postfix-2.0.16/html/uce.html Mon Aug 25 09:54:11 2003
98 --- ./html/uce.html Fri Sep 19 11:17:34 2003
105 + <a name="check_helo_ns_access">
107 + <dt> <b>check_helo_ns_access</b> <i>maptype</i>:<i>mapname</i>
109 + <a name="check_helo_mx_access">
111 + <dt> <b>check_helo_mx_access</b> <i>maptype</i>:<i>mapname</i>
113 + <dd> Apply the specified <a href="access.5.html">access database</a>
114 + to the DNS (or MX) servers for the host or domain name given with
115 + the HELO (or EHLO) command.
117 + <dd> Note: an OK result is not allowed for safety reasons.
121 <dt> <b><a href="#permit">permit</a></b>
123 <dt> <b><a href="#defer">defer</a></b>
130 + <a name="check_sender_ns_access">
132 + <dt> <b>check_sender_ns_access</b> <i>maptype</i>:<i>mapname</i>
134 + <a name="check_sender_mx_access">
136 + <dt> <b>check_sender_mx_access</b> <i>maptype</i>:<i>mapname</i>
138 + <dd> Apply the specified <a href="access.5.html">access database</a>
139 + to the DNS (or MX) servers for the host or domain name given with
140 + the MAIL FROM command.
142 + <dd> Note: an OK result is not allowed for safety reasons.
146 <a name="reject_non_fqdn_sender">
148 <dt> <b>reject_non_fqdn_sender</b> <dd> Reject the request when
152 <dt> <i>maptype</i>:<i>mapname</i> <dd> Search the named <a
153 href="access.5.html">access database</a> for the resolved destination
154 address, recipient domain or parent domain, or <i>localpart</i>@.
158 + <a name="check_recipient_ns_access">
160 + <dt> <b>check_recipient_ns_access</b> <i>maptype</i>:<i>mapname</i>
162 + <a name="check_recipient_mx_access">
164 + <dt> <b>check_recipient_mx_access</b> <i>maptype</i>:<i>mapname</i>
166 + <dd> Apply the specified <a href="access.5.html">access database</a>
167 + to the DNS servers (or MX hosts) for the host or domain name given
168 + with the RCPT TO command.
170 + <dd> Note: an OK result is not allowed for safety reasons.
174 diff -cr /tmp/postfix-2.0.16/src/dns/dns_lookup.c ./src/dns/dns_lookup.c
175 *** /tmp/postfix-2.0.16/src/dns/dns_lookup.c Sun Dec 8 09:09:11 2002
176 --- ./src/dns/dns_lookup.c Fri Sep 19 11:17:34 2003
181 "Name service error for %s: invalid host or domain name",
183 + h_errno = HOST_NOT_FOUND;
184 return (DNS_NOTFOUND);
191 "Name service error for %s: invalid host or domain name",
193 + h_errno = HOST_NOT_FOUND;
194 return (DNS_NOTFOUND);
197 diff -cr /tmp/postfix-2.0.16/src/global/mail_params.h ./src/global/mail_params.h
198 *** /tmp/postfix-2.0.16/src/global/mail_params.h Mon Mar 3 17:07:03 2003
199 --- ./src/global/mail_params.h Fri Sep 19 11:17:35 2003
203 #define CHECK_RECIP_ACL "check_recipient_access"
204 #define CHECK_ETRN_ACL "check_etrn_access"
206 + #define CHECK_HELO_MX_ACL "check_helo_mx_access"
207 + #define CHECK_SENDER_MX_ACL "check_sender_mx_access"
208 + #define CHECK_RECIP_MX_ACL "check_recipient_mx_access"
209 + #define CHECK_HELO_NS_ACL "check_helo_ns_access"
210 + #define CHECK_SENDER_NS_ACL "check_sender_ns_access"
211 + #define CHECK_RECIP_NS_ACL "check_recipient_ns_access"
213 #define WARN_IF_REJECT "warn_if_reject"
215 #define REJECT_RBL "reject_rbl" /* LaMont compatibility */
216 diff -cr /tmp/postfix-2.0.16/src/smtpd/smtpd_check.c ./src/smtpd/smtpd_check.c
217 *** /tmp/postfix-2.0.16/src/smtpd/smtpd_check.c Tue Aug 12 10:53:25 2003
218 --- ./src/smtpd/smtpd_check.c Fri Sep 19 11:17:52 2003
222 /* .IP "check_recipient_access maptype:mapname"
223 /* Look up the resolved recipient address in the named access table,
224 /* any parent domains of the recipient domain, and the localpart@.
225 + /* .IP "check_helo_mx_access maptype:mapname"
226 + /* .IP "check_sender_mx_access maptype:mapname"
227 + /* .IP "check_recipient_mx_access maptype:mapname"
228 + /* Apply the specified access table to the MX server host name and IP
229 + /* addresses for the helo hostname, sender, or recipient, respectively.
230 + /* If no MX record is found the A record is used instead.
231 + /* .IP "check_helo_ns_access maptype:mapname"
232 + /* .IP "check_sender_ns_access maptype:mapname"
233 + /* .IP "check_recipient_ns_access maptype:mapname"
234 + /* Apply the specified access table to the DNS server host name and IP
235 + /* addresses for the helo hostname, sender, or recipient, respectively.
236 + /* If no NS record is found, the parent domain is used instead.
237 /* .IP "check_recipient_maps"
238 /* Reject recipients not listed as valid local, virtual or relay
244 (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2)); \
246 + #define DEFER_IF_PERMIT3(state, class, fmt, a1, a2, a3) do { \
247 + if ((state)->warn_if_reject == 0) \
248 + defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2), (a3)); \
250 + (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2), (a3)); \
252 + #define DEFER_IF_PERMIT4(state, class, fmt, a1, a2, a3, a4) do { \
253 + if ((state)->warn_if_reject == 0) \
254 + defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2), (a3), (a4)); \
256 + (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2), (a3), (a4)); \
260 * Cached RBL lookup state.
264 return (SMTPD_CHECK_DUNNO);
267 + /* check_server_access - access control by server host name or address */
269 + static int check_server_access(SMTPD_STATE *state, const char *table,
272 + const char *reply_name,
273 + const char *reply_class,
274 + const char *def_acl)
276 + const char *myname = "check_server_access";
277 + const char *domain;
279 + DNS_RR *server_list;
282 + struct in_addr addr;
283 + struct hostent *hp;
287 + static DNS_FIXED fixed;
292 + if (type != T_MX && type != T_NS)
293 + msg_panic("%s: unexpected resource type \"%s\" in request",
294 + myname, dns_strtype(type));
297 + msg_info("%s: %s %s", myname, dns_strtype(type), name);
300 + * Skip over local-part.
302 + if ((domain = strrchr(name, '@')) != 0)
308 + * If the domain name does not exist then we apply no restriction.
310 + * If the domain name exists but no MX record exists, fabricate an MX record
311 + * that points to the domain name itself.
313 + * If the domain name exists but no NS record exists, look up parent domain
316 + dns_status = dns_lookup(domain, type, 0, &server_list,
317 + (VSTRING *) 0, (VSTRING *) 0);
318 + if (dns_status == DNS_NOTFOUND && h_errno == NO_DATA) {
319 + if (type == T_MX) {
320 + server_list = dns_rr_create(domain, &fixed, 0,
321 + domain, strlen(domain) + 1);
322 + dns_status = DNS_OK;
323 + } else if (type == T_NS) {
324 + while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
326 + dns_status = dns_lookup(domain, type, 0, &server_list,
327 + (VSTRING *) 0, (VSTRING *) 0);
328 + if (dns_status != DNS_NOTFOUND || h_errno != NO_DATA)
333 + if (dns_status != DNS_OK) {
334 + msg_warn("Unable to look up %s host for %s", dns_strtype(type),
335 + domain && domain[1] ? domain : reply_name);
336 + return (SMTPD_CHECK_DUNNO);
340 + * No bare returns after this point or we have a memory leak.
342 + #define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); }
345 + * Check the hostnames first, then the addresses.
347 + for (server = server_list; server != 0; server = server->next) {
348 + if ((hp = gethostbyname((char *) server->data)) == 0) {
349 + msg_warn("Unable to look up %s host %s for %s %s",
350 + dns_strtype(type), (char *) server->data,
351 + reply_class, reply_name);
354 + if (hp->h_addrtype != AF_INET || hp->h_length != sizeof(addr)) {
356 + msg_warn("address type %d length %d for %s",
357 + hp->h_addrtype, hp->h_length, (char *) server->data);
358 + continue; /* XXX */
361 + msg_info("%s: %s hostname check: %s",
362 + myname, dns_strtype(type), (char *) server->data);
363 + if ((status = check_domain_access(state, table, (char *) server->data,
364 + FULL, &found, reply_name, reply_class,
365 + def_acl)) != 0 || found)
366 + CHECK_SERVER_RETURN(status);
368 + msg_info("%s: %s host address check: %s",
369 + myname, dns_strtype(type), (char *) server->data);
370 + for (cpp = hp->h_addr_list; *cpp; cpp++) {
371 + memcpy((char *) &addr, *cpp, sizeof(addr));
372 + addr_string = mystrdup(inet_ntoa(addr));
373 + status = check_addr_access(state, table, addr_string, FULL,
374 + &found, reply_name, reply_class,
376 + myfree(addr_string);
377 + if (status != 0 || found)
378 + CHECK_SERVER_RETURN(status);
381 + CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
384 /* check_mail_access - OK/FAIL based on mail address lookup */
386 static int check_mail_access(SMTPD_STATE *state, const char *table,
393 + /* forbid_whitelist - disallow whitelisting */
395 + static void forbid_whitelist(SMTPD_STATE *state, const char *name,
396 + int status, const char *target)
398 + if (status == SMTPD_CHECK_OK) {
399 + msg_warn("restriction %s returns OK for %s", name, target);
400 + msg_warn("this is not allowed for security reasons");
401 + msg_warn("use DUNNO instead of OK if you want to make an exception");
402 + longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
403 + "451 Server configuration error"));
407 /* generic_checks - generic restrictions */
409 static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
413 state->helo_name, SMTPD_NAME_HELO)) == 0)
414 status = SMTPD_CHECK_OK;
416 + } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) {
417 + if (state->helo_name) {
418 + status = check_server_access(state, *cpp, state->helo_name,
419 + T_NS, state->helo_name,
420 + SMTPD_NAME_HELO, def_acl);
421 + forbid_whitelist(state, name, status, state->helo_name);
423 + } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) {
424 + if (state->helo_name) {
425 + status = check_server_access(state, *cpp, state->helo_name,
426 + T_MX, state->helo_name,
427 + SMTPD_NAME_HELO, def_acl);
428 + forbid_whitelist(state, name, status, state->helo_name);
430 } else if (strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) {
431 if (state->helo_name) {
432 if (*state->helo_name != '[')
436 } else if (strcasecmp(name, REJECT_SENDER_LOGIN_MISMATCH) == 0) {
437 if (state->sender && *state->sender)
438 status = reject_sender_login_mismatch(state, state->sender);
439 + } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) {
440 + if (state->sender && *state->sender) {
441 + status = check_server_access(state, *cpp, state->sender,
442 + T_NS, state->sender,
443 + SMTPD_NAME_SENDER, def_acl);
444 + forbid_whitelist(state, name, status, state->sender);
446 + } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) {
447 + if (state->sender && *state->sender) {
448 + status = check_server_access(state, *cpp, state->sender,
449 + T_MX, state->sender,
450 + SMTPD_NAME_SENDER, def_acl);
451 + forbid_whitelist(state, name, status, state->sender);
453 } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) {
455 msg_warn("restriction %s requires domain name argument", name);
459 if (state->recipient)
460 status = reject_non_fqdn_address(state, state->recipient,
461 state->recipient, SMTPD_NAME_RECIPIENT);
462 + } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) {
463 + if (state->recipient && *state->recipient) {
464 + status = check_server_access(state, *cpp, state->recipient,
465 + T_NS, state->recipient,
466 + SMTPD_NAME_RECIPIENT, def_acl);
467 + forbid_whitelist(state, name, status, state->recipient);
469 + } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) {
470 + if (state->recipient && *state->recipient) {
471 + status = check_server_access(state, *cpp, state->recipient,
472 + T_MX, state->recipient,
473 + SMTPD_NAME_RECIPIENT, def_acl);
474 + forbid_whitelist(state, name, status, state->recipient);
476 } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) {
478 msg_warn("restriction %s requires domain name argument", name);