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