transport.c revision 1.2
1/*	$NetBSD: transport.c,v 1.2 2017/02/14 01:16:48 christos Exp $	*/
2
3/*++
4/* NAME
5/*	transport 3
6/* SUMMARY
7/*	transport mapping
8/* SYNOPSIS
9/*	#include "transport.h"
10/*
11/*	TRANSPORT_INFO *transport_pre_init(maps_name, maps)
12/*	const char *maps_name;
13/*	const char *maps;
14/*
15/*	void	transport_post_init(info)
16/*	TRANSPORT_INFO *info;
17/*
18/*	int	transport_lookup(info, address, rcpt_domain, channel, nexthop)
19/*	TRANSPORT_INFO *info;
20/*	const char *address;
21/*	const char *rcpt_domain;
22/*	VSTRING *channel;
23/*	VSTRING *nexthop;
24/*
25/*	void	transport_free(info);
26/*	TRANSPORT_INFO * info;
27/* DESCRIPTION
28/*	This module implements access to the table that maps transport
29/*	user@domain addresses to (channel, nexthop) tuples.
30/*
31/*	transport_pre_init() performs initializations that should be
32/*	done before the process enters the chroot jail, and
33/*	before calling transport_lookup().
34/*
35/*	transport_post_init() can be invoked after entering the chroot
36/*	jail, and must be called before before calling transport_lookup().
37/*
38/*	transport_lookup() finds the channel and nexthop for the given
39/*	domain, and returns 1 if something was found.	Otherwise, 0
40/*	is returned.
41/* DIAGNOSTICS
42/*	The global \fIdict_errno\fR is non-zero when the lookup
43/*	should be tried again.
44/* SEE ALSO
45/*	maps(3), multi-dictionary search
46/*	strip_addr(3), strip extension from address
47/*	transport(5), format of transport map
48/* CONFIGURATION PARAMETERS
49/*	transport_maps, names of maps to be searched.
50/* LICENSE
51/* .ad
52/* .fi
53/*	The Secure Mailer license must be distributed with this software.
54/* AUTHOR(S)
55/*	Wietse Venema
56/*	IBM T.J. Watson Research
57/*	P.O. Box 704
58/*	Yorktown Heights, NY 10598, USA
59/*--*/
60
61/* System library. */
62
63#include <sys_defs.h>
64#include <string.h>
65
66/* Utility library. */
67
68#include <msg.h>
69#include <stringops.h>
70#include <mymalloc.h>
71#include <vstring.h>
72#include <split_at.h>
73#include <dict.h>
74#include <events.h>
75
76/* Global library. */
77
78#include <strip_addr.h>
79#include <mail_params.h>
80#include <maps.h>
81#include <match_parent_style.h>
82#include <mail_proto.h>
83
84/* Application-specific. */
85
86#include "transport.h"
87
88static int transport_match_parent_style;
89
90#define STR(x)	vstring_str(x)
91
92static void transport_wildcard_init(TRANSPORT_INFO *);
93
94/* transport_pre_init - pre-jail initialization */
95
96TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name,
97				           const char *transport_maps)
98{
99    TRANSPORT_INFO *tp;
100
101    tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp));
102    tp->transport_path = maps_create(transport_maps_name, transport_maps,
103				     DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
104				     | DICT_FLAG_NO_REGSUB
105				     | DICT_FLAG_UTF8_REQUEST);
106    tp->wildcard_channel = tp->wildcard_nexthop = 0;
107    tp->wildcard_errno = 0;
108    tp->expire = 0;
109    return (tp);
110}
111
112/* transport_post_init - post-jail initialization */
113
114void    transport_post_init(TRANSPORT_INFO *tp)
115{
116    transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
117    transport_wildcard_init(tp);
118}
119
120/* transport_free - destroy transport info */
121
122void    transport_free(TRANSPORT_INFO *tp)
123{
124    if (tp->transport_path)
125	maps_free(tp->transport_path);
126    if (tp->wildcard_channel)
127	vstring_free(tp->wildcard_channel);
128    if (tp->wildcard_nexthop)
129	vstring_free(tp->wildcard_nexthop);
130    myfree((void *) tp);
131}
132
133/* update_entry - update from transport table entry */
134
135static void update_entry(const char *new_channel, const char *new_nexthop,
136			         const char *rcpt_domain, VSTRING *channel,
137			         VSTRING *nexthop)
138{
139
140    /*
141     * :[nexthop] means don't change the channel, and don't change the
142     * nexthop unless a non-default nexthop is specified. Thus, a right-hand
143     * side of ":" is the transport table equivalent of a NOOP.
144     */
145    if (*new_channel == 0) {			/* :[nexthop] */
146	if (*new_nexthop != 0)
147	    vstring_strcpy(nexthop, new_nexthop);
148    }
149
150    /*
151     * transport[:[nexthop]] means change the channel, and reset the nexthop
152     * to the default unless a non-default nexthop is specified.
153     */
154    else {
155	vstring_strcpy(channel, new_channel);
156	if (*new_nexthop != 0)
157	    vstring_strcpy(nexthop, new_nexthop);
158	else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0
159		 && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0)
160	    vstring_strcpy(nexthop, rcpt_domain);
161	else
162	    vstring_strcpy(nexthop, "Address is undeliverable");
163    }
164}
165
166/* find_transport_entry - look up and parse transport table entry */
167
168static int find_transport_entry(TRANSPORT_INFO *tp, const char *key,
169				        const char *rcpt_domain, int flags,
170				        VSTRING *channel, VSTRING *nexthop)
171{
172    char   *saved_value;
173    const char *host;
174    const char *value;
175
176#define FOUND		1
177#define NOTFOUND	0
178
179    /*
180     * Look up an entry with extreme prejudice.
181     *
182     * XXX Should report lookup failure status to caller instead of aborting.
183     */
184    if ((value = maps_find(tp->transport_path, key, flags)) == 0)
185	return (NOTFOUND);
186
187    /*
188     * It would be great if we could specify a recipient address in the
189     * lookup result. Unfortunately, we cannot simply run the result through
190     * a parser that recognizes "transport:user@domain" because the lookup
191     * result can have arbitrary content (especially in the case of the error
192     * mailer).
193     */
194    else {
195	saved_value = mystrdup(value);
196	host = split_at(saved_value, ':');
197	update_entry(saved_value, host ? host : "", rcpt_domain,
198		     channel, nexthop);
199	myfree(saved_value);
200	return (FOUND);
201    }
202}
203
204/* transport_wildcard_init - (re) initialize wild-card lookup result */
205
206static void transport_wildcard_init(TRANSPORT_INFO *tp)
207{
208    VSTRING *channel = vstring_alloc(10);
209    VSTRING *nexthop = vstring_alloc(10);
210
211    /*
212     * Both channel and nexthop may be zero-length strings. Therefore we must
213     * use something else to represent "wild-card does not exist". We use
214     * null VSTRING pointers, for historical reasons.
215     */
216    if (tp->wildcard_channel)
217	vstring_free(tp->wildcard_channel);
218    if (tp->wildcard_nexthop)
219	vstring_free(tp->wildcard_nexthop);
220
221    /*
222     * Technically, the wildcard lookup pattern is redundant. A static map
223     * (keys always match, result is fixed string) could achieve the same:
224     *
225     * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
226     *
227     * But the user interface of such an approach would be less intuitive. We
228     * tolerate the continued existence of wildcard lookup patterns because
229     * of human interface considerations.
230     */
231#define WILDCARD	"*"
232#define FULL		0
233#define PARTIAL		DICT_FLAG_FIXED
234
235    if (find_transport_entry(tp, WILDCARD, "", FULL, channel, nexthop)) {
236	tp->wildcard_errno = 0;
237	tp->wildcard_channel = channel;
238	tp->wildcard_nexthop = nexthop;
239	if (msg_verbose)
240	    msg_info("wildcard_{chan:hop}={%s:%s}",
241		     vstring_str(channel), vstring_str(nexthop));
242    } else {
243	tp->wildcard_errno = tp->transport_path->error;
244	vstring_free(channel);
245	vstring_free(nexthop);
246	tp->wildcard_channel = 0;
247	tp->wildcard_nexthop = 0;
248    }
249    tp->expire = event_time() + 30;		/* XXX make configurable */
250}
251
252/* transport_lookup - map a transport domain */
253
254int     transport_lookup(TRANSPORT_INFO *tp, const char *addr,
255			         const char *rcpt_domain,
256			         VSTRING *channel, VSTRING *nexthop)
257{
258    char   *stripped_addr;
259    char   *ratsign = 0;
260    const char *name;
261    const char *next;
262    int     found;
263
264#define STREQ(x,y)	(strcmp((x), (y)) == 0)
265#define DISCARD_EXTENSION ((char **) 0)
266
267    /*
268     * The null recipient is rewritten to the local mailer daemon address.
269     */
270    if (*addr == 0) {
271	msg_warn("transport_lookup: null address - skipping table lookup");
272	return (NOTFOUND);
273    }
274
275    /*
276     * Look up the full address with the FULL flag to include regexp maps in
277     * the query.
278     */
279    if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
280	msg_panic("transport_lookup: bad address: \"%s\"", addr);
281
282    if (find_transport_entry(tp, addr, rcpt_domain, FULL, channel, nexthop))
283	return (FOUND);
284    if (tp->transport_path->error != 0)
285	return (NOTFOUND);
286
287    /*
288     * If the full address did not match, and there is an address extension,
289     * look up the stripped address with the PARTIAL flag to avoid matching
290     * partial lookup keys with regular expressions.
291     */
292    if ((stripped_addr = strip_addr(addr, DISCARD_EXTENSION,
293				    var_rcpt_delim)) != 0) {
294	found = find_transport_entry(tp, stripped_addr, rcpt_domain, PARTIAL,
295				     channel, nexthop);
296
297	myfree(stripped_addr);
298	if (found)
299	    return (FOUND);
300	if (tp->transport_path->error != 0)
301	    return (NOTFOUND);
302    }
303
304    /*
305     * If the full and stripped address lookup fails, try domain name lookup.
306     *
307     * Keep stripping domain components until nothing is left or until a
308     * matching entry is found.
309     *
310     * After checking the full domain name, check for .upper.domain, to
311     * distinguish between the parent domain and it's decendants, a la
312     * sendmail and tcp wrappers.
313     *
314     * Before changing the DB lookup result, make a copy first, in order to
315     * avoid DB cache corruption.
316     *
317     * Specify that the lookup key is partial, to avoid matching partial keys
318     * with regular expressions.
319     */
320    for (name = ratsign + 1; *name != 0; name = next) {
321	if (find_transport_entry(tp, name, rcpt_domain, PARTIAL, channel, nexthop))
322	    return (FOUND);
323	if (tp->transport_path->error != 0)
324	    return (NOTFOUND);
325	if ((next = strchr(name + 1, '.')) == 0)
326	    break;
327	if (transport_match_parent_style == MATCH_FLAG_PARENT)
328	    next++;
329    }
330
331    /*
332     * Fall back to the wild-card entry.
333     */
334    if (tp->wildcard_errno || event_time() > tp->expire)
335	transport_wildcard_init(tp);
336    if (tp->wildcard_errno) {
337	tp->transport_path->error = tp->wildcard_errno;
338	return (NOTFOUND);
339    } else if (tp->wildcard_channel) {
340	update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop),
341		     rcpt_domain, channel, nexthop);
342	return (FOUND);
343    }
344
345    /*
346     * We really did not find it.
347     */
348    return (NOTFOUND);
349}
350