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