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