7 /* mail address resolver
9 /* #include "trivial-rewrite.h"
11 /* void resolve_init(void)
13 /* void resolve_proto(context, stream)
14 /* RES_CONTEXT *context;
17 /* This module implements the trivial address resolving engine.
18 /* It distinguishes between local and remote mail, and optionally
19 /* consults one or more transport tables that map a destination
20 /* to a transport, nexthop pair.
22 /* resolve_init() initializes data structures that are private
23 /* to this module. It should be called once before using the
24 /* actual resolver routines.
26 /* resolve_proto() implements the client-server protocol:
27 /* read one address in FQDN form, reply with a (transport,
28 /* nexthop, internalized recipient) triple.
31 /* Problems and transactions are logged to the syslog daemon.
37 /* The Secure Mailer license must be distributed with this software.
40 /* IBM T.J. Watson Research
42 /* Yorktown Heights, NY 10598, USA
51 #ifdef STRCASECMP_IN_STRINGS_H
55 /* Utility library. */
60 #include <vstring_vstream.h>
62 #include <valid_hostname.h>
63 #include <stringops.h>
68 #include <mail_params.h>
69 #include <mail_proto.h>
70 #include <resolve_local.h>
71 #include <mail_conf.h>
72 #include <quote_822_local.h>
74 #include <domain_list.h>
75 #include <string_list.h>
76 #include <match_parent_style.h>
78 #include <mail_addr_find.h>
79 #include <valid_mailhost_addr.h>
81 /* Application-specific. */
83 #include "trivial-rewrite.h"
84 #include "transport.h"
87 * The job of the address resolver is to map one recipient address to a
88 * triple of (channel, nexthop, recipient). The channel is the name of the
89 * delivery service specified in master.cf, the nexthop is (usually) a
90 * description of the next host to deliver to, and recipient is the final
91 * recipient address. The latter may differ from the input address as the
92 * result of stripping multiple layers of sender-specified routing.
94 * Addresses are resolved by their domain name. Known domain names are
95 * categorized into classes: local, virtual alias, virtual mailbox, relay,
96 * and everything else. Finding the address domain class is a matter of
99 * Different address domain classes generally use different delivery channels,
100 * and may use class dependent ways to arrive at the corresponding nexthop
101 * information. With classes that do final delivery, the nexthop is
102 * typically the local machine hostname.
104 * The transport lookup table provides a means to override the domain class
105 * channel and/or nexhop information for specific recipients or for entire
106 * domain hierarchies.
108 * This works well in the general case. The only bug in this approach is that
109 * the structure of the nexthop information is transport dependent.
110 * Typically, the nexthop specifies a hostname, hostname + TCP Port, or the
111 * pathname of a UNIX-domain socket. However, with the error transport the
112 * nexthop field contains free text with the reason for non-delivery.
114 * Therefore, a transport map entry that overrides the channel but not the
115 * nexthop information (or vice versa) may produce surprising results. In
116 * particular, the free text nexthop information for the error transport is
117 * likely to confuse regular delivery agents; and conversely, a hostname or
118 * socket pathname is not an adequate text as reason for non-delivery.
120 * In the code below, rcpt_domain specifies the domain name that we will use
121 * when the transport table specifies a non-default channel but no nexthop
122 * information (we use a generic text when that non-default channel is the
126 #define STR vstring_str
129 * Some of the lists that define the address domain classes.
131 static DOMAIN_LIST
*relay_domains
;
132 static STRING_LIST
*virt_alias_doms
;
133 static STRING_LIST
*virt_mailbox_doms
;
135 static MAPS
*relocated_maps
;
137 /* resolve_addr - resolve address according to rule set */
139 static void resolve_addr(RES_CONTEXT
*rp
, char *sender
, char *addr
,
140 VSTRING
*channel
, VSTRING
*nexthop
,
141 VSTRING
*nextrcpt
, int *flags
)
143 const char *myname
= "resolve_addr";
144 VSTRING
*addr_buf
= vstring_alloc(100);
146 TOK822
*saved_domain
= 0;
149 const char *blame
= 0;
150 const char *rcpt_domain
;
160 vstring_strcpy(channel
, "CHANNEL NOT UPDATED");
161 vstring_strcpy(nexthop
, "NEXTHOP NOT UPDATED");
162 vstring_strcpy(nextrcpt
, "NEXTRCPT NOT UPDATED");
165 * The address is in internalized (unquoted) form.
167 * In an ideal world we would parse the externalized address form as given
168 * to us by the sender.
170 * However, in the real world we have to look for routing characters like
171 * %@! in the address local-part, even when that information is quoted
172 * due to the presence of special characters or whitespace. Although
173 * technically incorrect, this is needed to stop user@domain@domain relay
174 * attempts when forwarding mail to a Sendmail MX host.
176 * This suggests that we parse the address in internalized (unquoted) form.
177 * Unfortunately, if we do that, the unparser generates incorrect white
178 * space between adjacent non-operator tokens. Example: ``first last''
179 * needs white space, but ``stuff[stuff]'' does not. This is is not a
180 * problem when unparsing the result from parsing externalized forms,
181 * because the parser/unparser were designed for valid externalized forms
182 * where ``stuff[stuff]'' does not happen.
184 * As a workaround we start with the quoted form and then dequote the
185 * local-part only where needed. This will do the right thing in most
186 * (but not all) cases.
188 addr_len
= strlen(addr
);
189 quote_822_local(addr_buf
, addr
);
190 tree
= tok822_scan_addr(vstring_str(addr_buf
));
193 * The optimizer will eliminate tests that always fail, and will replace
194 * multiple expansions of this macro by a GOTO to a single instance.
196 #define FREE_MEMORY_AND_RETURN { \
198 tok822_free_tree(saved_domain); \
200 tok822_free_tree(tree); \
202 vstring_free(addr_buf); \
207 * Preliminary resolver: strip off all instances of the local domain.
208 * Terminate when no destination domain is left over, or when the
209 * destination domain is remote.
211 * XXX To whom it may concern. If you change the resolver loop below, or
212 * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests
213 * under "make resolve_clnt_test" in the global directory.
215 #define RESOLVE_LOCAL(domain) \
216 resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL)))
220 for (loop_count
= 0, loop_max
= addr_len
+ 100; /* void */ ; loop_count
++) {
223 * Grr. resolve_local() table lookups may fail. It may be OK for
224 * local file lookup code to abort upon failure, but with
225 * network-based tables it is preferable to return an error
226 * indication to the requestor.
229 *flags
|= RESOLVE_FLAG_FAIL
;
230 FREE_MEMORY_AND_RETURN
;
234 * XXX Should never happen, but if this happens with some
235 * pathological address, then that is not sufficient reason to
236 * disrupt the operation of an MTA.
238 if (loop_count
> loop_max
) {
239 msg_warn("resolve_addr: <%s>: giving up after %ld iterations",
240 addr
, (long) loop_count
);
245 * Strip trailing dot at end of domain, but not dot-dot or at-dot.
246 * This merely makes diagnostics more accurate by leaving bogus
250 && tree
->tail
->type
== '.'
251 && tok822_rfind_type(tree
->tail
, '@') != 0
252 && tree
->tail
->prev
->type
!= '.'
253 && tree
->tail
->prev
->type
!= '@')
254 tok822_free_tree(tok822_sub_keep_before(tree
, tree
->tail
));
259 if (var_resolve_nulldom
261 && tree
->tail
->type
== '@')
262 tok822_free_tree(tok822_sub_keep_before(tree
, tree
->tail
));
265 * Strip (and save) @domain if local.
267 if ((domain
= tok822_rfind_type(tree
->tail
, '@')) != 0) {
268 if (domain
->next
&& RESOLVE_LOCAL(domain
->next
) == 0)
270 tok822_sub_keep_before(tree
, domain
);
272 tok822_free_tree(saved_domain
);
273 saved_domain
= domain
;
274 domain
= 0; /* safety for future change */
278 * After stripping the local domain, if any, replace foo%bar by
279 * foo@bar, site!user by user@site, rewrite to canonical form, and
282 if (tok822_rfind_type(tree
->tail
, '@')
283 || (var_swap_bangpath
&& tok822_rfind_type(tree
->tail
, '!'))
284 || (var_percent_hack
&& tok822_rfind_type(tree
->tail
, '%'))) {
285 rewrite_tree(&local_context
, tree
);
290 * If the local-part is a quoted string, crack it open when we're
291 * permitted to do so and look for routing operators. This is
292 * technically incorrect, but is needed to stop relaying problems.
294 * XXX Do another feeble attempt to keep local-part info quoted.
296 if (var_resolve_dequoted
297 && tree
->head
&& tree
->head
== tree
->tail
298 && tree
->head
->type
== TOK822_QSTRING
299 && ((oper
= strrchr(local
= STR(tree
->head
->vstr
), '@')) != 0
300 || (var_percent_hack
&& (oper
= strrchr(local
, '%')) != 0)
301 || (var_swap_bangpath
&& (oper
= strrchr(local
, '!')) != 0))) {
304 tok822_internalize(addr_buf
, tree
->head
, TOK822_STR_DEFL
);
306 junk
= mystrdup(STR(addr_buf
));
307 quote_822_local(addr_buf
, junk
);
310 tok822_free(tree
->head
);
311 tree
->head
= tok822_scan(STR(addr_buf
), &tree
->tail
);
312 rewrite_tree(&local_context
, tree
);
317 * An empty local-part or an empty quoted string local-part becomes
318 * the local MAILER-DAEMON, for consistency with our own From:
321 if (tree
->head
&& tree
->head
== tree
->tail
322 && tree
->head
->type
== TOK822_QSTRING
323 && VSTRING_LEN(tree
->head
->vstr
) == 0) {
324 tok822_free(tree
->head
);
327 /* XXX must be localpart only, not user@domain form. */
329 tree
->head
= tok822_scan(var_empty_addr
, &tree
->tail
);
332 * We're done. There are no domains left to strip off the address,
333 * and all null local-part information is sanitized.
339 vstring_free(addr_buf
);
343 * Make sure the resolved envelope recipient has the user@domain form. If
344 * no domain was specified in the address, assume the local machine. See
345 * above for what happens with an empty address.
349 tok822_sub_append(tree
, saved_domain
);
352 tok822_sub_append(tree
, tok822_alloc('@', (char *) 0));
353 tok822_sub_append(tree
, tok822_scan(var_myhostname
, (TOK822
**) 0));
358 * Transform the recipient address back to internal form.
360 * XXX This may produce incorrect results if we cracked open a quoted
361 * local-part with routing operators; see discussion above at the top of
364 * XXX We explicitly disallow domain names in bare network address form. A
365 * network address destination should be formatted according to RFC 2821:
366 * it should be enclosed in [], and an IPv6 address should have an IPv6:
369 tok822_internalize(nextrcpt
, tree
, TOK822_STR_DEFL
);
370 rcpt_domain
= strrchr(STR(nextrcpt
), '@') + 1;
371 if (rcpt_domain
== 0)
372 msg_panic("no @ in address: \"%s\"", STR(nextrcpt
));
373 if (*rcpt_domain
== '[') {
374 if (!valid_mailhost_literal(rcpt_domain
, DONT_GRIPE
))
375 *flags
|= RESOLVE_FLAG_ERROR
;
376 } else if (!valid_hostname(rcpt_domain
, DONT_GRIPE
)) {
377 if (var_resolve_num_dom
&& valid_hostaddr(rcpt_domain
, DONT_GRIPE
)) {
378 vstring_insert(nextrcpt
, rcpt_domain
- STR(nextrcpt
), "[", 1);
379 vstring_strcat(nextrcpt
, "]");
380 rcpt_domain
= strrchr(STR(nextrcpt
), '@') + 1;
381 if (resolve_local(rcpt_domain
)) /* XXX */
384 *flags
|= RESOLVE_FLAG_ERROR
;
387 tok822_free_tree(tree
);
391 * XXX Short-cut invalid address forms.
393 if (*flags
& RESOLVE_FLAG_ERROR
) {
394 *flags
|= RESOLVE_CLASS_DEFAULT
;
395 FREE_MEMORY_AND_RETURN
;
399 * Recognize routing operators in the local-part, even when we do not
400 * recognize ! or % as valid routing operators locally. This is needed to
401 * prevent backup MX hosts from relaying third-party destinations through
402 * primary MX hosts, otherwise the backup host could end up on black
403 * lists. Ignore local swap_bangpath and percent_hack settings because we
404 * can't know how the next MX host is set up.
406 if (strcmp(STR(nextrcpt
) + strcspn(STR(nextrcpt
), "@!%") + 1, rcpt_domain
))
407 *flags
|= RESOLVE_FLAG_ROUTED
;
410 * With local, virtual, relay, or other non-local destinations, give the
411 * highest precedence to transport associated nexthop information.
413 * Otherwise, with relay or other non-local destinations, the relayhost
414 * setting overrides the recipient domain name, and the sender-dependent
415 * relayhost overrides both.
417 * XXX Nag if the recipient domain is listed in multiple domain lists. The
418 * result is implementation defined, and may break when internals change.
420 * For now, we distinguish only a fixed number of address classes.
421 * Eventually this may become extensible, so that new classes can be
422 * configured with their own domain list, delivery transport, and
425 #define STREQ(x,y) (strcmp((x), (y)) == 0)
431 * Virtual alias domain.
434 && string_list_match(virt_alias_doms
, rcpt_domain
)) {
435 if (var_helpful_warnings
) {
436 if (virt_mailbox_doms
437 && string_list_match(virt_mailbox_doms
, rcpt_domain
))
438 msg_warn("do not list domain %s in BOTH %s and %s",
439 rcpt_domain
, VAR_VIRT_ALIAS_DOMS
,
440 VAR_VIRT_MAILBOX_DOMS
);
442 && domain_list_match(relay_domains
, rcpt_domain
))
443 msg_warn("do not list domain %s in BOTH %s and %s",
444 rcpt_domain
, VAR_VIRT_ALIAS_DOMS
,
447 if (strcasecmp(rcpt_domain
, var_myorigin
) == 0)
448 msg_warn("do not list $%s (%s) in %s",
449 VAR_MYORIGIN
, var_myorigin
, VAR_VIRT_ALIAS_DOMS
);
452 vstring_strcpy(channel
, MAIL_SERVICE_ERROR
);
453 vstring_sprintf(nexthop
, "User unknown%s",
454 var_show_unk_rcpt_table
?
455 " in virtual alias table" : "");
456 *flags
|= RESOLVE_CLASS_ALIAS
;
457 } else if (dict_errno
!= 0) {
458 msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS
);
459 *flags
|= RESOLVE_FLAG_FAIL
;
460 FREE_MEMORY_AND_RETURN
;
464 * Virtual mailbox domain.
466 else if (virt_mailbox_doms
467 && string_list_match(virt_mailbox_doms
, rcpt_domain
)) {
468 if (var_helpful_warnings
) {
470 && domain_list_match(relay_domains
, rcpt_domain
))
471 msg_warn("do not list domain %s in BOTH %s and %s",
472 rcpt_domain
, VAR_VIRT_MAILBOX_DOMS
,
475 vstring_strcpy(channel
, RES_PARAM_VALUE(rp
->virt_transport
));
476 vstring_strcpy(nexthop
, rcpt_domain
);
477 blame
= rp
->virt_transport_name
;
478 *flags
|= RESOLVE_CLASS_VIRTUAL
;
479 } else if (dict_errno
!= 0) {
480 msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS
);
481 *flags
|= RESOLVE_FLAG_FAIL
;
482 FREE_MEMORY_AND_RETURN
;
486 * Off-host relay destination.
489 && domain_list_match(relay_domains
, rcpt_domain
)) {
490 vstring_strcpy(channel
, RES_PARAM_VALUE(rp
->relay_transport
));
491 blame
= rp
->relay_transport_name
;
492 *flags
|= RESOLVE_CLASS_RELAY
;
493 } else if (dict_errno
!= 0) {
494 msg_warn("%s lookup failure", VAR_RELAY_DOMAINS
);
495 *flags
|= RESOLVE_FLAG_FAIL
;
496 FREE_MEMORY_AND_RETURN
;
500 * Other off-host destination.
503 vstring_strcpy(channel
, RES_PARAM_VALUE(rp
->def_transport
));
504 blame
= rp
->def_transport_name
;
505 *flags
|= RESOLVE_CLASS_DEFAULT
;
509 * With off-host delivery, sender-dependent or global relayhost
510 * override the recipient domain.
512 if (rp
->snd_relay_info
513 && (relay
= mail_addr_find(rp
->snd_relay_info
, *sender
?
514 sender
: var_null_relay_maps_key
,
516 vstring_strcpy(nexthop
, strcasecmp(relay
, "DUNNO") == 0 ?
517 rcpt_domain
: relay
);
518 else if (*RES_PARAM_VALUE(rp
->relayhost
))
519 vstring_strcpy(nexthop
, RES_PARAM_VALUE(rp
->relayhost
));
521 vstring_strcpy(nexthop
, rcpt_domain
);
528 * XXX Nag if the domain is listed in multiple domain lists. The effect is
529 * implementation defined, and may break when internals change.
532 if (var_helpful_warnings
) {
534 && string_list_match(virt_alias_doms
, rcpt_domain
))
535 msg_warn("do not list domain %s in BOTH %s and %s",
536 rcpt_domain
, VAR_MYDEST
, VAR_VIRT_ALIAS_DOMS
);
537 if (virt_mailbox_doms
538 && string_list_match(virt_mailbox_doms
, rcpt_domain
))
539 msg_warn("do not list domain %s in BOTH %s and %s",
540 rcpt_domain
, VAR_MYDEST
, VAR_VIRT_MAILBOX_DOMS
);
542 vstring_strcpy(channel
, RES_PARAM_VALUE(rp
->local_transport
));
543 vstring_strcpy(nexthop
, rcpt_domain
);
544 blame
= rp
->local_transport_name
;
545 *flags
|= RESOLVE_CLASS_LOCAL
;
549 * An explicit main.cf transport:nexthop setting overrides the nexthop.
551 * XXX We depend on this mechanism to enforce per-recipient concurrencies
552 * for local recipients. With "local_transport = local:$myhostname" we
553 * force mail for any domain in $mydestination/${proxy,inet}_interfaces
554 * to share the same queue.
556 if ((destination
= split_at(STR(channel
), ':')) != 0 && *destination
)
557 vstring_strcpy(nexthop
, destination
);
562 if (*STR(channel
) == 0) {
564 msg_panic("%s: null blame", myname
);
565 msg_warn("file %s/%s: parameter %s: null transport is not allowed",
566 var_config_dir
, MAIN_CONF_FILE
, blame
);
567 *flags
|= RESOLVE_FLAG_FAIL
;
568 FREE_MEMORY_AND_RETURN
;
570 if (*STR(nexthop
) == 0)
571 msg_panic("%s: null nexthop", myname
);
574 * The transport map can selectively override any transport and/or
575 * nexthop host info that is set up above. Unfortunately, the syntax for
576 * nexthop information is transport specific. We therefore need sane and
577 * intuitive semantics for transport map entries that specify a channel
580 * With non-error transports, the initial nexthop information is the
581 * recipient domain. However, specific main.cf transport definitions may
582 * specify a transport-specific destination, such as a host + TCP socket,
583 * or the pathname of a UNIX-domain socket. With less precedence than
584 * main.cf transport definitions, a main.cf relayhost definition may also
585 * override nexthop information for off-host deliveries.
587 * With the error transport, the nexthop information is free text that
588 * specifies the reason for non-delivery.
590 * Because nexthop syntax is transport specific we reset the nexthop
591 * information to the recipient domain when the transport table specifies
592 * a transport without also specifying the nexthop information.
594 * Subtle note: reset nexthop even when the transport table does not change
595 * the transport. Otherwise it is hard to get rid of main.cf specified
596 * nexthop information.
598 * XXX Don't override the virtual alias class (error:User unknown) result.
600 if (rp
->transport_info
&& !(*flags
& RESOLVE_CLASS_ALIAS
)) {
601 if (transport_lookup(rp
->transport_info
, STR(nextrcpt
),
602 rcpt_domain
, channel
, nexthop
) == 0
603 && dict_errno
!= 0) {
604 msg_warn("%s lookup failure", rp
->transport_maps_name
);
605 *flags
|= RESOLVE_FLAG_FAIL
;
606 FREE_MEMORY_AND_RETURN
;
611 * Bounce recipients that have moved, regardless of domain address class.
612 * We do this last, in anticipation of transport maps that can override
613 * the recipient address.
615 * The downside of not doing this in delivery agents is that this table has
616 * no effect on local alias expansion results. Such mail will have to
617 * make almost an entire iteration through the mail system.
619 #define IGNORE_ADDR_EXTENSION ((char **) 0)
621 if (relocated_maps
!= 0) {
624 if ((newloc
= mail_addr_find(relocated_maps
, STR(nextrcpt
),
625 IGNORE_ADDR_EXTENSION
)) != 0) {
626 vstring_strcpy(channel
, MAIL_SERVICE_ERROR
);
627 /* 5.1.6 is the closest match, but not perfect. */
628 vstring_sprintf(nexthop
, "5.1.6 User has moved to %s", newloc
);
629 } else if (dict_errno
!= 0) {
630 msg_warn("%s lookup failure", VAR_RELOCATED_MAPS
);
631 *flags
|= RESOLVE_FLAG_FAIL
;
632 FREE_MEMORY_AND_RETURN
;
637 * Bounce recipient addresses that start with `-'. External commands may
638 * misinterpret such addresses as command-line options.
640 * In theory I could say people should always carefully set up their
641 * master.cf pipe mailer entries with `--' before the first non-option
642 * argument, but mistakes will happen regardless.
644 * Therefore the protection is put in place here, where it cannot be
647 if (var_allow_min_user
== 0 && STR(nextrcpt
)[0] == '-') {
648 *flags
|= RESOLVE_FLAG_ERROR
;
649 FREE_MEMORY_AND_RETURN
;
655 FREE_MEMORY_AND_RETURN
;
658 /* Static, so they can be used by the network protocol interface only. */
660 static VSTRING
*channel
;
661 static VSTRING
*nexthop
;
662 static VSTRING
*nextrcpt
;
663 static VSTRING
*query
;
664 static VSTRING
*sender
;
666 /* resolve_proto - read request and send reply */
668 int resolve_proto(RES_CONTEXT
*context
, VSTREAM
*stream
)
672 if (attr_scan(stream
, ATTR_FLAG_STRICT
,
673 ATTR_TYPE_STR
, MAIL_ATTR_SENDER
, sender
,
674 ATTR_TYPE_STR
, MAIL_ATTR_ADDR
, query
,
678 resolve_addr(context
, STR(sender
), STR(query
),
679 channel
, nexthop
, nextrcpt
, &flags
);
682 msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')",
683 STR(sender
), STR(query
), STR(channel
),
684 STR(nexthop
), STR(nextrcpt
), flags
);
686 attr_print(stream
, ATTR_FLAG_NONE
,
687 ATTR_TYPE_INT
, MAIL_ATTR_FLAGS
, server_flags
,
688 ATTR_TYPE_STR
, MAIL_ATTR_TRANSPORT
, STR(channel
),
689 ATTR_TYPE_STR
, MAIL_ATTR_NEXTHOP
, STR(nexthop
),
690 ATTR_TYPE_STR
, MAIL_ATTR_RECIP
, STR(nextrcpt
),
691 ATTR_TYPE_INT
, MAIL_ATTR_FLAGS
, flags
,
694 if (vstream_fflush(stream
) != 0) {
695 msg_warn("write resolver reply: %m");
701 /* resolve_init - module initializations */
703 void resolve_init(void)
705 sender
= vstring_alloc(100);
706 query
= vstring_alloc(100);
707 channel
= vstring_alloc(100);
708 nexthop
= vstring_alloc(100);
709 nextrcpt
= vstring_alloc(100);
711 if (*var_virt_alias_doms
)
713 string_list_init(MATCH_FLAG_NONE
, var_virt_alias_doms
);
715 if (*var_virt_mailbox_doms
)
717 string_list_init(MATCH_FLAG_NONE
, var_virt_mailbox_doms
);
719 if (*var_relay_domains
)
721 domain_list_init(match_parent_style(VAR_RELAY_DOMAINS
),
724 if (*var_relocated_maps
)
726 maps_create(VAR_RELOCATED_MAPS
, var_relocated_maps
,
727 DICT_FLAG_LOCK
| DICT_FLAG_FOLD_FIX
);