1*3c275423Schristos /* $NetBSD: rewrite.c,v 1.3 2020/03/18 19:05:21 christos Exp $ */
241fbaed0Stron
341fbaed0Stron /*++
441fbaed0Stron /* NAME
541fbaed0Stron /* rewrite 3
641fbaed0Stron /* SUMMARY
741fbaed0Stron /* mail address rewriter
841fbaed0Stron /* SYNOPSIS
941fbaed0Stron /* #include "trivial-rewrite.h"
1041fbaed0Stron /*
1141fbaed0Stron /* void rewrite_init(void)
1241fbaed0Stron /*
1341fbaed0Stron /* void rewrite_proto(stream)
1441fbaed0Stron /* VSTREAM *stream;
1541fbaed0Stron /*
1641fbaed0Stron /* void rewrite_addr(context, addr, result)
1741fbaed0Stron /* RWR_CONTEXT *context;
1841fbaed0Stron /* char *addr;
1941fbaed0Stron /* VSTRING *result;
2041fbaed0Stron /*
2141fbaed0Stron /* void rewrite_tree(context, tree)
2241fbaed0Stron /* RWR_CONTEXT *context;
2341fbaed0Stron /* TOK822 *tree;
2441fbaed0Stron /*
2541fbaed0Stron /* RWR_CONTEXT local_context;
2641fbaed0Stron /* RWR_CONTEXT remote_context;
2741fbaed0Stron /* DESCRIPTION
2841fbaed0Stron /* This module implements the trivial address rewriting engine.
2941fbaed0Stron /*
3041fbaed0Stron /* rewrite_init() initializes data structures that are private
3141fbaed0Stron /* to this module. It should be called once before using the
3241fbaed0Stron /* actual rewriting routines.
3341fbaed0Stron /*
3441fbaed0Stron /* rewrite_proto() implements the client-server protocol: read
3541fbaed0Stron /* one rule set name and one address in external (quoted) form,
3641fbaed0Stron /* reply with the rewritten address in external form.
3741fbaed0Stron /*
3841fbaed0Stron /* rewrite_addr() rewrites an address string to another string.
3941fbaed0Stron /* Both input and output are in external (quoted) form.
4041fbaed0Stron /*
4141fbaed0Stron /* rewrite_tree() rewrites a parse tree with a single address to
4241fbaed0Stron /* another tree. A tree is a dummy node on top of a token list.
4341fbaed0Stron /*
4441fbaed0Stron /* local_context and remote_context provide domain names for
4541fbaed0Stron /* completing incomplete address forms.
4641fbaed0Stron /* STANDARDS
4741fbaed0Stron /* DIAGNOSTICS
48*3c275423Schristos /* Problems and transactions are logged to \fBsyslogd\fR(8)
49*3c275423Schristos /* or \fBpostlogd\fR(8).
5041fbaed0Stron /* BUGS
5141fbaed0Stron /* SEE ALSO
5241fbaed0Stron /* LICENSE
5341fbaed0Stron /* .ad
5441fbaed0Stron /* .fi
5541fbaed0Stron /* The Secure Mailer license must be distributed with this software.
5641fbaed0Stron /* AUTHOR(S)
5741fbaed0Stron /* Wietse Venema
5841fbaed0Stron /* IBM T.J. Watson Research
5941fbaed0Stron /* P.O. Box 704
6041fbaed0Stron /* Yorktown Heights, NY 10598, USA
6141fbaed0Stron /*--*/
6241fbaed0Stron
6341fbaed0Stron /* System library. */
6441fbaed0Stron
6541fbaed0Stron #include <sys_defs.h>
6641fbaed0Stron #include <stdlib.h>
6741fbaed0Stron #include <string.h>
6841fbaed0Stron
69837e7c1aSchristos #ifdef STRCASECMP_IN_STRINGS_H
70837e7c1aSchristos #include <strings.h>
71837e7c1aSchristos #endif
72837e7c1aSchristos
7341fbaed0Stron /* Utility library. */
7441fbaed0Stron
7541fbaed0Stron #include <msg.h>
7641fbaed0Stron #include <vstring.h>
7741fbaed0Stron #include <vstream.h>
7841fbaed0Stron #include <vstring_vstream.h>
7941fbaed0Stron #include <split_at.h>
8041fbaed0Stron
8141fbaed0Stron /* Global library. */
8241fbaed0Stron
8341fbaed0Stron #include <mail_params.h>
8441fbaed0Stron #include <mail_proto.h>
8541fbaed0Stron #include <resolve_local.h>
8641fbaed0Stron #include <tok822.h>
8741fbaed0Stron #include <mail_conf.h>
8841fbaed0Stron
8941fbaed0Stron /* Application-specific. */
9041fbaed0Stron
9141fbaed0Stron #include "trivial-rewrite.h"
9241fbaed0Stron
9341fbaed0Stron RWR_CONTEXT local_context = {
9441fbaed0Stron VAR_MYORIGIN, &var_myorigin,
9541fbaed0Stron VAR_MYDOMAIN, &var_mydomain,
9641fbaed0Stron };
9741fbaed0Stron
9841fbaed0Stron RWR_CONTEXT remote_context = {
9941fbaed0Stron VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
10041fbaed0Stron VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
10141fbaed0Stron };
10241fbaed0Stron
10341fbaed0Stron static VSTRING *ruleset;
10441fbaed0Stron static VSTRING *address;
10541fbaed0Stron static VSTRING *result;
10641fbaed0Stron
10741fbaed0Stron /* rewrite_tree - rewrite address according to rule set */
10841fbaed0Stron
rewrite_tree(RWR_CONTEXT * context,TOK822 * tree)10941fbaed0Stron void rewrite_tree(RWR_CONTEXT *context, TOK822 *tree)
11041fbaed0Stron {
11141fbaed0Stron TOK822 *colon;
11241fbaed0Stron TOK822 *domain;
11341fbaed0Stron TOK822 *bang;
11441fbaed0Stron TOK822 *local;
115837e7c1aSchristos VSTRING *vstringval;
11641fbaed0Stron
11741fbaed0Stron /*
11841fbaed0Stron * XXX If you change this module, quote_822_local.c, or tok822_parse.c,
11941fbaed0Stron * be sure to re-run the tests under "make rewrite_clnt_test" and "make
12041fbaed0Stron * resolve_clnt_test" in the global directory.
12141fbaed0Stron */
12241fbaed0Stron
12341fbaed0Stron /*
12441fbaed0Stron * Sanity check.
12541fbaed0Stron */
12641fbaed0Stron if (tree->head == 0)
12741fbaed0Stron msg_panic("rewrite_tree: empty tree");
12841fbaed0Stron
12941fbaed0Stron /*
13041fbaed0Stron * An empty address is a special case.
13141fbaed0Stron */
13241fbaed0Stron if (tree->head == tree->tail
13341fbaed0Stron && tree->tail->type == TOK822_QSTRING
13441fbaed0Stron && VSTRING_LEN(tree->tail->vstr) == 0)
13541fbaed0Stron return;
13641fbaed0Stron
13741fbaed0Stron /*
13841fbaed0Stron * Treat a lone @ as if it were an empty address.
13941fbaed0Stron */
14041fbaed0Stron if (tree->head == tree->tail
14141fbaed0Stron && tree->tail->type == '@') {
14241fbaed0Stron tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
14341fbaed0Stron tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, ""));
14441fbaed0Stron return;
14541fbaed0Stron }
14641fbaed0Stron
14741fbaed0Stron /*
14841fbaed0Stron * Strip source route.
14941fbaed0Stron */
15041fbaed0Stron if (tree->head->type == '@'
15141fbaed0Stron && (colon = tok822_find_type(tree->head, ':')) != 0
15241fbaed0Stron && colon != tree->tail)
15341fbaed0Stron tok822_free_tree(tok822_sub_keep_after(tree, colon));
15441fbaed0Stron
15541fbaed0Stron /*
15641fbaed0Stron * Optionally, transform address forms without @.
15741fbaed0Stron */
15841fbaed0Stron if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) {
15941fbaed0Stron
16041fbaed0Stron /*
16141fbaed0Stron * Swap domain!user to user@domain.
16241fbaed0Stron */
16341fbaed0Stron if (var_swap_bangpath != 0
16441fbaed0Stron && (bang = tok822_find_type(tree->head, '!')) != 0) {
16541fbaed0Stron tok822_sub_keep_before(tree, bang);
16641fbaed0Stron local = tok822_cut_after(bang);
16741fbaed0Stron tok822_free(bang);
16841fbaed0Stron tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0));
16941fbaed0Stron if (local)
17041fbaed0Stron tok822_sub_prepend(tree, local);
17141fbaed0Stron }
17241fbaed0Stron
17341fbaed0Stron /*
17441fbaed0Stron * Promote user%domain to user@domain.
17541fbaed0Stron */
17641fbaed0Stron else if (var_percent_hack != 0
17741fbaed0Stron && (domain = tok822_rfind_type(tree->tail, '%')) != 0) {
17841fbaed0Stron domain->type = '@';
17941fbaed0Stron }
18041fbaed0Stron
18141fbaed0Stron /*
18241fbaed0Stron * Append missing @origin
18341fbaed0Stron */
18441fbaed0Stron else if (var_append_at_myorigin != 0
18541fbaed0Stron && REW_PARAM_VALUE(context->origin) != 0
18641fbaed0Stron && REW_PARAM_VALUE(context->origin)[0] != 0) {
18741fbaed0Stron domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
18841fbaed0Stron tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin),
18941fbaed0Stron (TOK822 **) 0));
19041fbaed0Stron }
19141fbaed0Stron }
19241fbaed0Stron
19341fbaed0Stron /*
19441fbaed0Stron * Append missing .domain, but leave broken forms ending in @ alone. This
19541fbaed0Stron * merely makes diagnostics more accurate by leaving bogus addresses
19641fbaed0Stron * alone.
197837e7c1aSchristos *
198837e7c1aSchristos * Backwards-compatibility warning: warn for "user@localhost" when there is
199837e7c1aSchristos * no "localhost" in mydestination or in any other address class with an
200837e7c1aSchristos * explicit domain list.
20141fbaed0Stron */
20241fbaed0Stron if (var_append_dot_mydomain != 0
20341fbaed0Stron && REW_PARAM_VALUE(context->domain) != 0
20441fbaed0Stron && REW_PARAM_VALUE(context->domain)[0] != 0
20541fbaed0Stron && (domain = tok822_rfind_type(tree->tail, '@')) != 0
20641fbaed0Stron && domain != tree->tail
20741fbaed0Stron && tok822_find_type(domain, TOK822_DOMLIT) == 0
20841fbaed0Stron && tok822_find_type(domain, '.') == 0) {
209837e7c1aSchristos if (warn_compat_break_app_dot_mydomain
210837e7c1aSchristos && (vstringval = domain->next->vstr) != 0) {
211837e7c1aSchristos if (strcasecmp(vstring_str(vstringval), "localhost") != 0) {
212837e7c1aSchristos msg_info("using backwards-compatible default setting "
213837e7c1aSchristos VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
214837e7c1aSchristos "\"%s.%s\"", vstring_str(vstringval),
215837e7c1aSchristos vstring_str(vstringval), var_mydomain);
216837e7c1aSchristos } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) {
217837e7c1aSchristos msg_info("using backwards-compatible default setting "
218837e7c1aSchristos VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
219837e7c1aSchristos "\"%s.%s\"; please add \"localhost\" to "
220837e7c1aSchristos "mydestination or other address class",
221837e7c1aSchristos vstring_str(vstringval), vstring_str(vstringval),
222837e7c1aSchristos var_mydomain);
223837e7c1aSchristos }
224837e7c1aSchristos }
22541fbaed0Stron tok822_sub_append(tree, tok822_alloc('.', (char *) 0));
22641fbaed0Stron tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain),
22741fbaed0Stron (TOK822 **) 0));
22841fbaed0Stron }
22941fbaed0Stron
23041fbaed0Stron /*
23141fbaed0Stron * Strip trailing dot at end of domain, but not dot-dot or @-dot. This
23241fbaed0Stron * merely makes diagnostics more accurate by leaving bogus addresses
23341fbaed0Stron * alone.
23441fbaed0Stron */
23541fbaed0Stron if (tree->tail->type == '.'
23641fbaed0Stron && tree->tail->prev
23741fbaed0Stron && tree->tail->prev->type != '.'
23841fbaed0Stron && tree->tail->prev->type != '@')
23941fbaed0Stron tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
24041fbaed0Stron }
24141fbaed0Stron
24241fbaed0Stron /* rewrite_proto - read request and send reply */
24341fbaed0Stron
rewrite_proto(VSTREAM * stream)24441fbaed0Stron int rewrite_proto(VSTREAM *stream)
24541fbaed0Stron {
24641fbaed0Stron RWR_CONTEXT *context;
24741fbaed0Stron TOK822 *tree;
24841fbaed0Stron
24941fbaed0Stron if (attr_scan(stream, ATTR_FLAG_STRICT,
250837e7c1aSchristos RECV_ATTR_STR(MAIL_ATTR_RULE, ruleset),
251837e7c1aSchristos RECV_ATTR_STR(MAIL_ATTR_ADDR, address),
25241fbaed0Stron ATTR_TYPE_END) != 2)
25341fbaed0Stron return (-1);
25441fbaed0Stron
25541fbaed0Stron if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0)
25641fbaed0Stron context = &local_context;
25741fbaed0Stron else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0)
25841fbaed0Stron context = &remote_context;
25941fbaed0Stron else {
26041fbaed0Stron msg_warn("unknown context: %s", vstring_str(ruleset));
26141fbaed0Stron return (-1);
26241fbaed0Stron }
26341fbaed0Stron
26441fbaed0Stron /*
26541fbaed0Stron * Sanity check. An address is supposed to be in externalized form.
26641fbaed0Stron */
26741fbaed0Stron if (*vstring_str(address) == 0) {
26841fbaed0Stron msg_warn("rewrite_addr: null address");
26941fbaed0Stron vstring_strcpy(result, vstring_str(address));
27041fbaed0Stron }
27141fbaed0Stron
27241fbaed0Stron /*
27341fbaed0Stron * Convert the address from externalized (quoted) form to token list,
27441fbaed0Stron * rewrite it, and convert back.
27541fbaed0Stron */
27641fbaed0Stron else {
27741fbaed0Stron tree = tok822_scan_addr(vstring_str(address));
27841fbaed0Stron rewrite_tree(context, tree);
27941fbaed0Stron tok822_externalize(result, tree, TOK822_STR_DEFL);
28041fbaed0Stron tok822_free_tree(tree);
28141fbaed0Stron }
28241fbaed0Stron if (msg_verbose)
28341fbaed0Stron msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset),
28441fbaed0Stron vstring_str(address), vstring_str(result));
28541fbaed0Stron
28641fbaed0Stron attr_print(stream, ATTR_FLAG_NONE,
287837e7c1aSchristos SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
288837e7c1aSchristos SEND_ATTR_STR(MAIL_ATTR_ADDR, vstring_str(result)),
28941fbaed0Stron ATTR_TYPE_END);
29041fbaed0Stron
29141fbaed0Stron if (vstream_fflush(stream) != 0) {
29241fbaed0Stron msg_warn("write rewrite reply: %m");
29341fbaed0Stron return (-1);
29441fbaed0Stron }
29541fbaed0Stron return (0);
29641fbaed0Stron }
29741fbaed0Stron
29841fbaed0Stron /* rewrite_init - module initializations */
29941fbaed0Stron
rewrite_init(void)30041fbaed0Stron void rewrite_init(void)
30141fbaed0Stron {
30241fbaed0Stron ruleset = vstring_alloc(100);
30341fbaed0Stron address = vstring_alloc(100);
30441fbaed0Stron result = vstring_alloc(100);
30541fbaed0Stron }
306