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