1 /*	$NetBSD: transport.c,v 1.4 2022/10/08 16:12:50 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 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 /*	info->transport_path->error 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 /*	Wietse Venema
61 /*	Google, Inc.
62 /*	111 8th Avenue
63 /*	New York, NY 10011, USA
64 /*--*/
65 
66 /* System library. */
67 
68 #include <sys_defs.h>
69 #include <string.h>
70 
71 /* Utility library. */
72 
73 #include <msg.h>
74 #include <stringops.h>
75 #include <mymalloc.h>
76 #include <vstring.h>
77 #include <split_at.h>
78 #include <dict.h>
79 #include <events.h>
80 
81 /* Global library. */
82 
83 #include <strip_addr.h>
84 #include <mail_params.h>
85 #include <mail_addr_find.h>
86 #include <match_parent_style.h>
87 #include <mail_proto.h>
88 
89 /* Application-specific. */
90 
91 #include "transport.h"
92 
93 static int transport_match_parent_style;
94 
95 #define STR(x)	vstring_str(x)
96 
97 static void transport_wildcard_init(TRANSPORT_INFO *);
98 
99 /* transport_pre_init - pre-jail initialization */
100 
transport_pre_init(const char * transport_maps_name,const char * transport_maps)101 TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name,
102 				           const char *transport_maps)
103 {
104     TRANSPORT_INFO *tp;
105 
106     tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp));
107     tp->transport_path = maps_create(transport_maps_name, transport_maps,
108 				     DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
109 				     | DICT_FLAG_NO_REGSUB
110 				     | DICT_FLAG_UTF8_REQUEST);
111     tp->wildcard_channel = tp->wildcard_nexthop = 0;
112     tp->wildcard_errno = 0;
113     tp->expire = 0;
114     return (tp);
115 }
116 
117 /* transport_post_init - post-jail initialization */
118 
transport_post_init(TRANSPORT_INFO * tp)119 void    transport_post_init(TRANSPORT_INFO *tp)
120 {
121     transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
122     transport_wildcard_init(tp);
123 }
124 
125 /* transport_free - destroy transport info */
126 
transport_free(TRANSPORT_INFO * tp)127 void    transport_free(TRANSPORT_INFO *tp)
128 {
129     if (tp->transport_path)
130 	maps_free(tp->transport_path);
131     if (tp->wildcard_channel)
132 	vstring_free(tp->wildcard_channel);
133     if (tp->wildcard_nexthop)
134 	vstring_free(tp->wildcard_nexthop);
135     myfree((void *) tp);
136 }
137 
138 /* update_entry - update from transport table entry */
139 
update_entry(const char * new_channel,const char * new_nexthop,const char * rcpt_domain,VSTRING * channel,VSTRING * nexthop)140 static void update_entry(const char *new_channel, const char *new_nexthop,
141 			         const char *rcpt_domain, VSTRING *channel,
142 			         VSTRING *nexthop)
143 {
144 
145     /*
146      * :[nexthop] means don't change the channel, and don't change the
147      * nexthop unless a non-default nexthop is specified. Thus, a right-hand
148      * side of ":" is the transport table equivalent of a NOOP.
149      */
150     if (*new_channel == 0) {			/* :[nexthop] */
151 	if (*new_nexthop != 0)
152 	    vstring_strcpy(nexthop, new_nexthop);
153     }
154 
155     /*
156      * transport[:[nexthop]] means change the channel, and reset the nexthop
157      * to the default unless a non-default nexthop is specified.
158      */
159     else {
160 	vstring_strcpy(channel, new_channel);
161 	if (*new_nexthop != 0)
162 	    vstring_strcpy(nexthop, new_nexthop);
163 	else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0
164 		 && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0)
165 	    vstring_strcpy(nexthop, rcpt_domain);
166 	else
167 	    vstring_strcpy(nexthop, "Address is undeliverable");
168     }
169 }
170 
171 /* parse_transport_entry - parse transport table entry */
172 
parse_transport_entry(const char * value,const char * rcpt_domain,VSTRING * channel,VSTRING * nexthop)173 static void parse_transport_entry(const char *value, const char *rcpt_domain,
174 				          VSTRING *channel, VSTRING *nexthop)
175 {
176     char   *saved_value;
177     const char *host;
178 
179 #define FOUND		1
180 #define NOTFOUND	0
181 
182     /*
183      * It would be great if we could specify a recipient address in the
184      * lookup result. Unfortunately, we cannot simply run the result through
185      * a parser that recognizes "transport:user@domain" because the lookup
186      * result can have arbitrary content (especially in the case of the error
187      * mailer).
188      */
189     saved_value = mystrdup(value);
190     host = split_at(saved_value, ':');
191     update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop);
192     myfree(saved_value);
193 }
194 
195 /* transport_wildcard_init - (re) initialize wild-card lookup result */
196 
transport_wildcard_init(TRANSPORT_INFO * tp)197 static void transport_wildcard_init(TRANSPORT_INFO *tp)
198 {
199     VSTRING *channel = vstring_alloc(10);
200     VSTRING *nexthop = vstring_alloc(10);
201     const char *value;
202 
203     /*
204      * Both channel and nexthop may be zero-length strings. Therefore we must
205      * use something else to represent "wild-card does not exist". We use
206      * null VSTRING pointers, for historical reasons.
207      */
208     if (tp->wildcard_channel)
209 	vstring_free(tp->wildcard_channel);
210     if (tp->wildcard_nexthop)
211 	vstring_free(tp->wildcard_nexthop);
212 
213     /*
214      * Technically, the wildcard lookup pattern is redundant. A static map
215      * (keys always match, result is fixed string) could achieve the same:
216      *
217      * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
218      *
219      * But the user interface of such an approach would be less intuitive. We
220      * tolerate the continued existence of wildcard lookup patterns because
221      * of human interface considerations.
222      */
223 #define WILDCARD	"*"
224 #define FULL		0
225 #define PARTIAL		DICT_FLAG_FIXED
226 
227     if ((value = maps_find(tp->transport_path, WILDCARD, FULL)) != 0) {
228 	parse_transport_entry(value, "", channel, nexthop);
229 	tp->wildcard_errno = 0;
230 	tp->wildcard_channel = channel;
231 	tp->wildcard_nexthop = nexthop;
232 	if (msg_verbose)
233 	    msg_info("wildcard_{chan:hop}={%s:%s}",
234 		     vstring_str(channel), vstring_str(nexthop));
235     } else {
236 	tp->wildcard_errno = tp->transport_path->error;
237 	vstring_free(channel);
238 	vstring_free(nexthop);
239 	tp->wildcard_channel = 0;
240 	tp->wildcard_nexthop = 0;
241     }
242     tp->expire = event_time() + 30;		/* XXX make configurable */
243 }
244 
245 /* transport_lookup - map a transport domain */
246 
transport_lookup(TRANSPORT_INFO * tp,const char * addr,const char * rcpt_domain,VSTRING * channel,VSTRING * nexthop)247 int     transport_lookup(TRANSPORT_INFO *tp, const char *addr,
248 			         const char *rcpt_domain,
249 			         VSTRING *channel, VSTRING *nexthop)
250 {
251     char   *ratsign = 0;
252     const char *value;
253 
254 #define STREQ(x,y)	(strcmp((x), (y)) == 0)
255 #define DISCARD_EXTENSION ((char **) 0)
256 
257     /*
258      * The null recipient is rewritten to the local mailer daemon address.
259      */
260     if (*addr == 0) {
261 	msg_warn("transport_lookup: null address - skipping table lookup");
262 	return (NOTFOUND);
263     }
264 
265     /*
266      * Look up the full and extension-stripped address, then match the domain
267      * and subdomains. Try the external form before the backwards-compatible
268      * internal form.
269      */
270 #define LOOKUP_STRATEGY \
271 	(MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN | \
272 	(transport_match_parent_style == MATCH_FLAG_PARENT ? \
273 		MA_FIND_PDMS : MA_FIND_PDDMDS))
274 
275     if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
276 	msg_panic("transport_lookup: bad address: \"%s\"", addr);
277 
278     if ((value = mail_addr_find_strategy(tp->transport_path, addr, (char **) 0,
279 					 LOOKUP_STRATEGY)) != 0) {
280 	parse_transport_entry(value, rcpt_domain, channel, nexthop);
281 	return (FOUND);
282     }
283     if (tp->transport_path->error != 0)
284 	return (NOTFOUND);
285 
286     /*
287      * Fall back to the wild-card entry.
288      */
289     if (tp->wildcard_errno || event_time() > tp->expire)
290 	transport_wildcard_init(tp);
291     if (tp->wildcard_errno) {
292 	tp->transport_path->error = tp->wildcard_errno;
293 	return (NOTFOUND);
294     } else if (tp->wildcard_channel) {
295 	update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop),
296 		     rcpt_domain, channel, nexthop);
297 	return (FOUND);
298     }
299 
300     /*
301      * We really did not find it.
302      */
303     return (NOTFOUND);
304 }
305 
306 #ifdef TEST
307 
308  /*
309   * Proof-of-concept test program. Read an address from stdin, and spit out
310   * the lookup result.
311   */
312 
313 #include <string.h>
314 
315 #include <mail_conf.h>
316 #include <vstream.h>
317 #include <vstring_vstream.h>
318 
usage(const char * progname)319 static NORETURN usage(const char *progname)
320 {
321     msg_fatal("usage: %s [-v] database", progname);
322 }
323 
main(int argc,char ** argv)324 int     main(int argc, char **argv)
325 {
326     VSTRING *buffer = vstring_alloc(100);
327     VSTRING *channel = vstring_alloc(100);
328     VSTRING *nexthop = vstring_alloc(100);
329     TRANSPORT_INFO *tp;
330     char   *bp;
331     char   *addr_field;
332     char   *rcpt_domain;
333     char   *expect_channel;
334     char   *expect_nexthop;
335     int     status;
336     int     ch;
337     int     errs = 0;
338 
339     /*
340      * Parse JCL.
341      */
342     while ((ch = GETOPT(argc, argv, "v")) > 0) {
343 	switch (ch) {
344 	case 'v':
345 	    msg_verbose++;
346 	    break;
347 	default:
348 	    usage(argv[0]);
349 	}
350     }
351     if (argc != optind + 1)
352 	usage(argv[0]);
353 
354     /*
355      * Initialize.
356      */
357 #define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
358 
359     mail_conf_read();				/* XXX eliminate dependency. */
360     UPDATE(var_rcpt_delim, "+");
361     UPDATE(var_mydomain, "localdomain");
362     UPDATE(var_myorigin, "localhost.localdomain");
363     UPDATE(var_mydest, "localhost.localdomain");
364 
365     tp = transport_pre_init("transport map", argv[optind]);
366     transport_post_init(tp);
367 
368     while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
369 	bp = STR(buffer);
370 
371 	/*
372 	 * Parse the input and expectations. XXX We can't expect empty
373 	 * fields, so require '-' instead.
374 	 */
375 	if ((addr_field = mystrtok(&bp, ":")) == 0)
376 	    msg_fatal("no address field");
377 	if ((rcpt_domain = strrchr(addr_field, '@')) == 0)
378 	    msg_fatal("no recipient domain");
379 	rcpt_domain += 1;
380 	expect_channel = mystrtok(&bp, ":");
381 	expect_nexthop = mystrtok(&bp, ":");
382 	if ((expect_channel != 0) != (expect_nexthop != 0))
383 	    msg_fatal("specify both channel and nexthop, or specify neither");
384 	if (expect_channel) {
385 	    if (strcmp(expect_channel, "-") == 0)
386 		*expect_channel = 0;
387 	    if (strcmp(expect_nexthop, "-") == 0)
388 		*expect_nexthop = 0;
389 	    vstring_strcpy(channel, "DEFAULT");
390 	    vstring_strcpy(nexthop, rcpt_domain);
391 	}
392 	if (mystrtok(&bp, ":") != 0)
393 	    msg_fatal("garbage after nexthop field");
394 
395 	/*
396 	 * Lookups.
397 	 */
398 	status = transport_lookup(tp, addr_field, rcpt_domain,
399 				  channel, nexthop);
400 
401 	/*
402 	 * Enforce expectations.
403 	 */
404 	if (expect_nexthop && status) {
405 	    vstream_printf("%s:%s -> %s:%s \n",
406 			   addr_field, rcpt_domain,
407 			   STR(channel), STR(nexthop));
408 	    vstream_fflush(VSTREAM_OUT);
409 	    if (strcmp(expect_channel, STR(channel)) != 0) {
410 		msg_warn("expect channel '%s' but got '%s'",
411 			 expect_channel, STR(channel));
412 		errs = 1;
413 	    }
414 	    if (strcmp(expect_nexthop, STR(nexthop)) != 0) {
415 		msg_warn("expect nexthop '%s' but got '%s'",
416 			 expect_nexthop, STR(nexthop));
417 		errs = 1;
418 	    }
419 	} else if (expect_nexthop && !status) {
420 	    vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
421 			   tp->transport_path->error ?
422 			   "(try again)" : "(not found)");
423 	    vstream_fflush(VSTREAM_OUT);
424 	    msg_warn("expect channel '%s' but got none", expect_channel);
425 	    msg_warn("expect nexthop '%s' but got none", expect_nexthop);
426 	    errs = 1;
427 	} else if (!status) {
428 	    vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
429 			   tp->transport_path->error ?
430 			   "(try again)" : "(not found)");
431 	}
432     }
433     transport_free(tp);
434     vstring_free(nexthop);
435     vstring_free(channel);
436     vstring_free(buffer);
437     exit(errs != 0);
438 }
439 
440 #endif
441