xref: /netbsd-src/external/ibm-public/postfix/dist/src/trivial-rewrite/rewrite.c (revision 33881f779a77dce6440bdc44610d94de75bebefe)
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 
93 RWR_CONTEXT local_context = {
94     VAR_MYORIGIN, &var_myorigin,
95     VAR_MYDOMAIN, &var_mydomain,
96 };
97 
98 RWR_CONTEXT remote_context = {
99     VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
100     VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
101 };
102 
103 static VSTRING *ruleset;
104 static VSTRING *address;
105 static VSTRING *result;
106 
107 /* rewrite_tree - rewrite address according to rule set */
108 
rewrite_tree(RWR_CONTEXT * context,TOK822 * tree)109 void    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 
rewrite_proto(VSTREAM * stream)244 int     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 
rewrite_init(void)300 void    rewrite_init(void)
301 {
302     ruleset = vstring_alloc(100);
303     address = vstring_alloc(100);
304     result = vstring_alloc(100);
305 }
306