xref: /netbsd-src/external/ibm-public/postfix/dist/src/trivial-rewrite/transport.c (revision d11b170b9000ada93db553723522a63d5deac310)
1 /*	$NetBSD: transport.c,v 1.1.1.2 2013/01/02 18:59:11 tron 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 
88 static int transport_match_parent_style;
89 
90 #define STR(x)	vstring_str(x)
91 
92 static void transport_wildcard_init(TRANSPORT_INFO *);
93 
94 /* transport_pre_init - pre-jail initialization */
95 
96 TRANSPORT_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     tp->wildcard_channel = tp->wildcard_nexthop = 0;
106     tp->wildcard_errno = 0;
107     tp->expire = 0;
108     return (tp);
109 }
110 
111 /* transport_post_init - post-jail initialization */
112 
113 void    transport_post_init(TRANSPORT_INFO *tp)
114 {
115     transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
116     transport_wildcard_init(tp);
117 }
118 
119 /* transport_free - destroy transport info */
120 
121 void    transport_free(TRANSPORT_INFO *tp)
122 {
123     if (tp->transport_path)
124 	maps_free(tp->transport_path);
125     if (tp->wildcard_channel)
126 	vstring_free(tp->wildcard_channel);
127     if (tp->wildcard_nexthop)
128 	vstring_free(tp->wildcard_nexthop);
129     myfree((char *) tp);
130 }
131 
132 /* update_entry - update from transport table entry */
133 
134 static void update_entry(const char *new_channel, const char *new_nexthop,
135 			         const char *rcpt_domain, VSTRING *channel,
136 			         VSTRING *nexthop)
137 {
138 
139     /*
140      * :[nexthop] means don't change the channel, and don't change the
141      * nexthop unless a non-default nexthop is specified. Thus, a right-hand
142      * side of ":" is the transport table equivalent of a NOOP.
143      */
144     if (*new_channel == 0) {			/* :[nexthop] */
145 	if (*new_nexthop != 0)
146 	    vstring_strcpy(nexthop, new_nexthop);
147     }
148 
149     /*
150      * transport[:[nexthop]] means change the channel, and reset the nexthop
151      * to the default unless a non-default nexthop is specified.
152      */
153     else {
154 	vstring_strcpy(channel, new_channel);
155 	if (*new_nexthop != 0)
156 	    vstring_strcpy(nexthop, new_nexthop);
157 	else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0)
158 	    vstring_strcpy(nexthop, rcpt_domain);
159 	else
160 	    vstring_strcpy(nexthop, "Address is undeliverable");
161     }
162 }
163 
164 /* find_transport_entry - look up and parse transport table entry */
165 
166 static int find_transport_entry(TRANSPORT_INFO *tp, const char *key,
167 				        const char *rcpt_domain, int flags,
168 				        VSTRING *channel, VSTRING *nexthop)
169 {
170     char   *saved_value;
171     const char *host;
172     const char *value;
173 
174 #define FOUND		1
175 #define NOTFOUND	0
176 
177     /*
178      * Look up an entry with extreme prejudice.
179      *
180      * XXX Should report lookup failure status to caller instead of aborting.
181      */
182     if ((value = maps_find(tp->transport_path, key, flags)) == 0)
183 	return (NOTFOUND);
184 
185     /*
186      * It would be great if we could specify a recipient address in the
187      * lookup result. Unfortunately, we cannot simply run the result through
188      * a parser that recognizes "transport:user@domain" because the lookup
189      * result can have arbitrary content (especially in the case of the error
190      * mailer).
191      */
192     else {
193 	saved_value = mystrdup(value);
194 	host = split_at(saved_value, ':');
195 	update_entry(saved_value, host ? host : "", rcpt_domain,
196 		     channel, nexthop);
197 	myfree(saved_value);
198 	return (FOUND);
199     }
200 }
201 
202 /* transport_wildcard_init - (re) initialize wild-card lookup result */
203 
204 static void transport_wildcard_init(TRANSPORT_INFO *tp)
205 {
206     VSTRING *channel = vstring_alloc(10);
207     VSTRING *nexthop = vstring_alloc(10);
208 
209     /*
210      * Both channel and nexthop may be zero-length strings. Therefore we must
211      * use something else to represent "wild-card does not exist". We use
212      * null VSTRING pointers, for historical reasons.
213      */
214     if (tp->wildcard_channel)
215 	vstring_free(tp->wildcard_channel);
216     if (tp->wildcard_nexthop)
217 	vstring_free(tp->wildcard_nexthop);
218 
219     /*
220      * Technically, the wildcard lookup pattern is redundant. A static map
221      * (keys always match, result is fixed string) could achieve the same:
222      *
223      * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
224      *
225      * But the user interface of such an approach would be less intuitive. We
226      * tolerate the continued existence of wildcard lookup patterns because
227      * of human interface considerations.
228      */
229 #define WILDCARD	"*"
230 #define FULL		0
231 #define PARTIAL		DICT_FLAG_FIXED
232 
233     if (find_transport_entry(tp, WILDCARD, "", FULL, channel, nexthop)) {
234 	tp->wildcard_errno = 0;
235 	tp->wildcard_channel = channel;
236 	tp->wildcard_nexthop = nexthop;
237 	if (msg_verbose)
238 	    msg_info("wildcard_{chan:hop}={%s:%s}",
239 		     vstring_str(channel), vstring_str(nexthop));
240     } else {
241 	tp->wildcard_errno = tp->transport_path->error;
242 	vstring_free(channel);
243 	vstring_free(nexthop);
244 	tp->wildcard_channel = 0;
245 	tp->wildcard_nexthop = 0;
246     }
247     tp->expire = event_time() + 30;		/* XXX make configurable */
248 }
249 
250 /* transport_lookup - map a transport domain */
251 
252 int     transport_lookup(TRANSPORT_INFO *tp, const char *addr,
253 			         const char *rcpt_domain,
254 			         VSTRING *channel, VSTRING *nexthop)
255 {
256     char   *stripped_addr;
257     char   *ratsign = 0;
258     const char *name;
259     const char *next;
260     int     found;
261 
262 #define STREQ(x,y)	(strcmp((x), (y)) == 0)
263 #define DISCARD_EXTENSION ((char **) 0)
264 
265     /*
266      * The null recipient is rewritten to the local mailer daemon address.
267      */
268     if (*addr == 0) {
269 	msg_warn("transport_lookup: null address - skipping table lookup");
270 	return (NOTFOUND);
271     }
272 
273     /*
274      * Look up the full address with the FULL flag to include regexp maps in
275      * the query.
276      */
277     if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
278 	msg_panic("transport_lookup: bad address: \"%s\"", addr);
279 
280     if (find_transport_entry(tp, addr, rcpt_domain, FULL, channel, nexthop))
281 	return (FOUND);
282     if (tp->transport_path->error != 0)
283 	return (NOTFOUND);
284 
285     /*
286      * If the full address did not match, and there is an address extension,
287      * look up the stripped address with the PARTIAL flag to avoid matching
288      * partial lookup keys with regular expressions.
289      */
290     if ((stripped_addr = strip_addr(addr, DISCARD_EXTENSION,
291 				    *var_rcpt_delim)) != 0) {
292 	found = find_transport_entry(tp, stripped_addr, rcpt_domain, PARTIAL,
293 				     channel, nexthop);
294 
295 	myfree(stripped_addr);
296 	if (found)
297 	    return (FOUND);
298 	if (tp->transport_path->error != 0)
299 	    return (NOTFOUND);
300     }
301 
302     /*
303      * If the full and stripped address lookup fails, try domain name lookup.
304      *
305      * Keep stripping domain components until nothing is left or until a
306      * matching entry is found.
307      *
308      * After checking the full domain name, check for .upper.domain, to
309      * distinguish between the parent domain and it's decendants, a la
310      * sendmail and tcp wrappers.
311      *
312      * Before changing the DB lookup result, make a copy first, in order to
313      * avoid DB cache corruption.
314      *
315      * Specify that the lookup key is partial, to avoid matching partial keys
316      * with regular expressions.
317      */
318     for (name = ratsign + 1; *name != 0; name = next) {
319 	if (find_transport_entry(tp, name, rcpt_domain, PARTIAL, channel, nexthop))
320 	    return (FOUND);
321 	if (tp->transport_path->error != 0)
322 	    return (NOTFOUND);
323 	if ((next = strchr(name + 1, '.')) == 0)
324 	    break;
325 	if (transport_match_parent_style == MATCH_FLAG_PARENT)
326 	    next++;
327     }
328 
329     /*
330      * Fall back to the wild-card entry.
331      */
332     if (tp->wildcard_errno || event_time() > tp->expire)
333 	transport_wildcard_init(tp);
334     if (tp->wildcard_errno) {
335 	tp->transport_path->error = tp->wildcard_errno;
336 	return (NOTFOUND);
337     } else if (tp->wildcard_channel) {
338 	update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop),
339 		     rcpt_domain, channel, nexthop);
340 	return (FOUND);
341     }
342 
343     /*
344      * We really did not find it.
345      */
346     return (NOTFOUND);
347 }
348