1/*	$NetBSD: rewrite.c,v 1.3 2020/03/18 19:05:21 christos Exp $	*/
2
3/*++
4/* NAME
5/*	rewrite 3
6/* SUMMARY
7/*	mail address rewriter
8/* SYNOPSIS
9/*	#include "trivial-rewrite.h"
10/*
11/*	void	rewrite_init(void)
12/*
13/*	void	rewrite_proto(stream)
14/*	VSTREAM	*stream;
15/*
16/*	void	rewrite_addr(context, addr, result)
17/*	RWR_CONTEXT *context;
18/*	char	*addr;
19/*	VSTRING *result;
20/*
21/*	void	rewrite_tree(context, tree)
22/*	RWR_CONTEXT *context;
23/*	TOK822	*tree;
24/*
25/*	RWR_CONTEXT local_context;
26/*	RWR_CONTEXT remote_context;
27/* DESCRIPTION
28/*	This module implements the trivial address rewriting engine.
29/*
30/*	rewrite_init() initializes data structures that are private
31/*	to this module. It should be called once before using the
32/*	actual rewriting routines.
33/*
34/*	rewrite_proto() implements the client-server protocol: read
35/*	one rule set name and one address in external (quoted) form,
36/*	reply with the rewritten address in external form.
37/*
38/*	rewrite_addr() rewrites an address string to another string.
39/*	Both input and output are in external (quoted) form.
40/*
41/*	rewrite_tree() rewrites a parse tree with a single address to
42/*	another tree.  A tree is a dummy node on top of a token list.
43/*
44/*	local_context and remote_context provide domain names for
45/*	completing incomplete address forms.
46/* STANDARDS
47/* DIAGNOSTICS
48/*	Problems and transactions are logged to \fBsyslogd\fR(8)
49/*	or \fBpostlogd\fR(8).
50/* BUGS
51/* SEE ALSO
52/* LICENSE
53/* .ad
54/* .fi
55/*	The Secure Mailer license must be distributed with this software.
56/* AUTHOR(S)
57/*	Wietse Venema
58/*	IBM T.J. Watson Research
59/*	P.O. Box 704
60/*	Yorktown Heights, NY 10598, USA
61/*--*/
62
63/* System library. */
64
65#include <sys_defs.h>
66#include <stdlib.h>
67#include <string.h>
68
69#ifdef STRCASECMP_IN_STRINGS_H
70#include <strings.h>
71#endif
72
73/* Utility library. */
74
75#include <msg.h>
76#include <vstring.h>
77#include <vstream.h>
78#include <vstring_vstream.h>
79#include <split_at.h>
80
81/* Global library. */
82
83#include <mail_params.h>
84#include <mail_proto.h>
85#include <resolve_local.h>
86#include <tok822.h>
87#include <mail_conf.h>
88
89/* Application-specific. */
90
91#include "trivial-rewrite.h"
92
93RWR_CONTEXT local_context = {
94    VAR_MYORIGIN, &var_myorigin,
95    VAR_MYDOMAIN, &var_mydomain,
96};
97
98RWR_CONTEXT remote_context = {
99    VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
100    VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
101};
102
103static VSTRING *ruleset;
104static VSTRING *address;
105static VSTRING *result;
106
107/* rewrite_tree - rewrite address according to rule set */
108
109void    rewrite_tree(RWR_CONTEXT *context, TOK822 *tree)
110{
111    TOK822 *colon;
112    TOK822 *domain;
113    TOK822 *bang;
114    TOK822 *local;
115    VSTRING *vstringval;
116
117    /*
118     * XXX If you change this module, quote_822_local.c, or tok822_parse.c,
119     * be sure to re-run the tests under "make rewrite_clnt_test" and "make
120     * resolve_clnt_test" in the global directory.
121     */
122
123    /*
124     * Sanity check.
125     */
126    if (tree->head == 0)
127	msg_panic("rewrite_tree: empty tree");
128
129    /*
130     * An empty address is a special case.
131     */
132    if (tree->head == tree->tail
133	&& tree->tail->type == TOK822_QSTRING
134	&& VSTRING_LEN(tree->tail->vstr) == 0)
135	return;
136
137    /*
138     * Treat a lone @ as if it were an empty address.
139     */
140    if (tree->head == tree->tail
141	&& tree->tail->type == '@') {
142	tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
143	tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, ""));
144	return;
145    }
146
147    /*
148     * Strip source route.
149     */
150    if (tree->head->type == '@'
151	&& (colon = tok822_find_type(tree->head, ':')) != 0
152	&& colon != tree->tail)
153	tok822_free_tree(tok822_sub_keep_after(tree, colon));
154
155    /*
156     * Optionally, transform address forms without @.
157     */
158    if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) {
159
160	/*
161	 * Swap domain!user to user@domain.
162	 */
163	if (var_swap_bangpath != 0
164	    && (bang = tok822_find_type(tree->head, '!')) != 0) {
165	    tok822_sub_keep_before(tree, bang);
166	    local = tok822_cut_after(bang);
167	    tok822_free(bang);
168	    tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0));
169	    if (local)
170		tok822_sub_prepend(tree, local);
171	}
172
173	/*
174	 * Promote user%domain to user@domain.
175	 */
176	else if (var_percent_hack != 0
177		 && (domain = tok822_rfind_type(tree->tail, '%')) != 0) {
178	    domain->type = '@';
179	}
180
181	/*
182	 * Append missing @origin
183	 */
184	else if (var_append_at_myorigin != 0
185		 && REW_PARAM_VALUE(context->origin) != 0
186		 && REW_PARAM_VALUE(context->origin)[0] != 0) {
187	    domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
188	    tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin),
189						(TOK822 **) 0));
190	}
191    }
192
193    /*
194     * Append missing .domain, but leave broken forms ending in @ alone. This
195     * merely makes diagnostics more accurate by leaving bogus addresses
196     * alone.
197     *
198     * Backwards-compatibility warning: warn for "user@localhost" when there is
199     * no "localhost" in mydestination or in any other address class with an
200     * explicit domain list.
201     */
202    if (var_append_dot_mydomain != 0
203	&& REW_PARAM_VALUE(context->domain) != 0
204	&& REW_PARAM_VALUE(context->domain)[0] != 0
205	&& (domain = tok822_rfind_type(tree->tail, '@')) != 0
206	&& domain != tree->tail
207	&& tok822_find_type(domain, TOK822_DOMLIT) == 0
208	&& tok822_find_type(domain, '.') == 0) {
209	if (warn_compat_break_app_dot_mydomain
210	    && (vstringval = domain->next->vstr) != 0) {
211	    if (strcasecmp(vstring_str(vstringval), "localhost") != 0) {
212		msg_info("using backwards-compatible default setting "
213			 VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
214			 "\"%s.%s\"", vstring_str(vstringval),
215			 vstring_str(vstringval), var_mydomain);
216	    } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) {
217		msg_info("using backwards-compatible default setting "
218			 VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
219			 "\"%s.%s\"; please add \"localhost\" to "
220			 "mydestination or other address class",
221			 vstring_str(vstringval), vstring_str(vstringval),
222			 var_mydomain);
223	    }
224	}
225	tok822_sub_append(tree, tok822_alloc('.', (char *) 0));
226	tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain),
227					    (TOK822 **) 0));
228    }
229
230    /*
231     * Strip trailing dot at end of domain, but not dot-dot or @-dot. This
232     * merely makes diagnostics more accurate by leaving bogus addresses
233     * alone.
234     */
235    if (tree->tail->type == '.'
236	&& tree->tail->prev
237	&& tree->tail->prev->type != '.'
238	&& tree->tail->prev->type != '@')
239	tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
240}
241
242/* rewrite_proto - read request and send reply */
243
244int     rewrite_proto(VSTREAM *stream)
245{
246    RWR_CONTEXT *context;
247    TOK822 *tree;
248
249    if (attr_scan(stream, ATTR_FLAG_STRICT,
250		  RECV_ATTR_STR(MAIL_ATTR_RULE, ruleset),
251		  RECV_ATTR_STR(MAIL_ATTR_ADDR, address),
252		  ATTR_TYPE_END) != 2)
253	return (-1);
254
255    if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0)
256	context = &local_context;
257    else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0)
258	context = &remote_context;
259    else {
260	msg_warn("unknown context: %s", vstring_str(ruleset));
261	return (-1);
262    }
263
264    /*
265     * Sanity check. An address is supposed to be in externalized form.
266     */
267    if (*vstring_str(address) == 0) {
268	msg_warn("rewrite_addr: null address");
269	vstring_strcpy(result, vstring_str(address));
270    }
271
272    /*
273     * Convert the address from externalized (quoted) form to token list,
274     * rewrite it, and convert back.
275     */
276    else {
277	tree = tok822_scan_addr(vstring_str(address));
278	rewrite_tree(context, tree);
279	tok822_externalize(result, tree, TOK822_STR_DEFL);
280	tok822_free_tree(tree);
281    }
282    if (msg_verbose)
283	msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset),
284		 vstring_str(address), vstring_str(result));
285
286    attr_print(stream, ATTR_FLAG_NONE,
287	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
288	       SEND_ATTR_STR(MAIL_ATTR_ADDR, vstring_str(result)),
289	       ATTR_TYPE_END);
290
291    if (vstream_fflush(stream) != 0) {
292	msg_warn("write rewrite reply: %m");
293	return (-1);
294    }
295    return (0);
296}
297
298/* rewrite_init - module initializations */
299
300void    rewrite_init(void)
301{
302    ruleset = vstring_alloc(100);
303    address = vstring_alloc(100);
304    result = vstring_alloc(100);
305}
306