Track tainted data and refuse to expand it
[exim.git] / src / src / routers / iplookup.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
0756eb3c
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8
9#include "../exim.h"
10#include "rf_functions.h"
11#include "iplookup.h"
12
13
14/* IP connection types */
15
16#define ip_udp 0
17#define ip_tcp 1
18
19
20/* Options specific to the iplookup router. */
21
22optionlist iplookup_router_options[] = {
23 { "hosts", opt_stringptr,
24 (void *)(offsetof(iplookup_router_options_block, hosts)) },
25 { "optional", opt_bool,
26 (void *)(offsetof(iplookup_router_options_block, optional)) },
27 { "port", opt_int,
28 (void *)(offsetof(iplookup_router_options_block, port)) },
29 { "protocol", opt_stringptr,
30 (void *)(offsetof(iplookup_router_options_block, protocol_name)) },
31 { "query", opt_stringptr,
32 (void *)(offsetof(iplookup_router_options_block, query)) },
33 { "reroute", opt_stringptr,
34 (void *)(offsetof(iplookup_router_options_block, reroute)) },
35 { "response_pattern", opt_stringptr,
36 (void *)(offsetof(iplookup_router_options_block, response_pattern)) },
37 { "timeout", opt_time,
38 (void *)(offsetof(iplookup_router_options_block, timeout)) }
39};
40
41/* Size of the options list. An extern variable has to be used so that its
42address can appear in the tables drtables.c. */
43
44int iplookup_router_options_count =
45 sizeof(iplookup_router_options)/sizeof(optionlist);
46
d185889f
JH
47
48#ifdef MACRO_PREDEF
49
50/* Dummy entries */
51iplookup_router_options_block iplookup_router_option_defaults = {0};
52void iplookup_router_init(router_instance *rblock) {}
53int iplookup_router_entry(router_instance *rblock, address_item *addr,
54 struct passwd *pw, int verify, address_item **addr_local,
55 address_item **addr_remote, address_item **addr_new,
cab0c277 56 address_item **addr_succeed) {return 0;}
d185889f
JH
57
58#else /*!MACRO_PREDEF*/
59
60
0756eb3c
PH
61/* Default private options block for the iplookup router. */
62
63iplookup_router_options_block iplookup_router_option_defaults = {
64 -1, /* port */
65 ip_udp, /* protocol */
66 5, /* timeout */
67 NULL, /* protocol_name */
68 NULL, /* hosts */
69 NULL, /* query; NULL => local_part@domain */
70 NULL, /* response_pattern; NULL => don't apply regex */
71 NULL, /* reroute; NULL => just used returned data */
72 NULL, /* re_response_pattern; compiled pattern */
73 FALSE /* optional */
74};
75
76
77
78/*************************************************
79* Initialization entry point *
80*************************************************/
81
82/* Called for each instance, after its options have been read, to enable
83consistency checks to be done, or anything else that needs to be set up. */
84
85void
86iplookup_router_init(router_instance *rblock)
87{
88iplookup_router_options_block *ob =
89 (iplookup_router_options_block *)(rblock->options_block);
90
91/* A port and a host list must be given */
92
93if (ob->port < 0)
94 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
95 "a port must be specified", rblock->name);
96
97if (ob->hosts == NULL)
98 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
99 "a host list must be specified", rblock->name);
100
101/* Translate protocol name into value */
102
103if (ob->protocol_name != NULL)
104 {
105 if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
106 else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
107 else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
108 "protocol not specified as udp or tcp", rblock->name);
109 }
110
111/* If a response pattern is given, compile it now to get the error early. */
112
113if (ob->response_pattern != NULL)
114 ob->re_response_pattern =
115 regex_must_compile(ob->response_pattern, FALSE, TRUE);
116}
117
118
119
120/*************************************************
121* Main entry point *
122*************************************************/
123
124/* See local README for interface details. This router returns:
125
126DECLINE
127 . pattern or identification match on returned data failed
128
129DEFER
130 . failed to expand the query or rerouting string
131 . failed to create socket ("optional" not set)
132 . failed to find a host, failed to connect, timed out ("optional" not set)
133 . rerouting string is not in the form localpart@domain
134 . verifying the errors address caused a deferment or a big disaster such
135 as an expansion failure (rf_get_errors_address)
136 . expanding a headers_{add,remove} string caused a deferment or another
137 expansion error (rf_get_munge_headers)
138
139PASS
140 . failed to create socket ("optional" set)
141 . failed to find a host, failed to connect, timed out ("optional" set)
142
143OK
144 . new address added to addr_new
145*/
146
147int
148iplookup_router_entry(
149 router_instance *rblock, /* data for this instantiation */
150 address_item *addr, /* address we are working on */
151 struct passwd *pw, /* passwd entry after check_local_user */
fd6de02e 152 int verify, /* v_none/v_recipient/v_sender/v_expn */
0756eb3c
PH
153 address_item **addr_local, /* add it to this if it's local */
154 address_item **addr_remote, /* add it to this if it's remote */
155 address_item **addr_new, /* put new addresses on here */
156 address_item **addr_succeed) /* put old address here on success */
157{
158uschar *query = NULL;
08488c86 159uschar *reply;
55414b25
JH
160uschar *hostname, *reroute, *domain;
161const uschar *listptr;
0756eb3c 162uschar host_buffer[256];
f3ebb786 163host_item *host = store_get(sizeof(host_item), FALSE);
0756eb3c
PH
164address_item *new_addr;
165iplookup_router_options_block *ob =
166 (iplookup_router_options_block *)(rblock->options_block);
167const pcre *re = ob->re_response_pattern;
168int count, query_len, rc;
169int sep = 0;
170
171addr_local = addr_local; /* Keep picky compilers happy */
172addr_remote = addr_remote;
173addr_succeed = addr_succeed;
174pw = pw;
175
176DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
177 rblock->name, addr->address, addr->domain);
178
f3ebb786 179reply = store_get(256, TRUE); /* tainted data */
08488c86 180
0756eb3c
PH
181/* Build the query string to send. If not explicitly given, a default of
182"user@domain user@domain" is used. */
183
184if (ob->query == NULL)
185 query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
186 addr->local_part, addr->domain);
187else
188 {
189 query = expand_string(ob->query);
190 if (query == NULL)
191 {
192 addr->message = string_sprintf("%s router: failed to expand %s: %s",
193 rblock->name, ob->query, expand_string_message);
194 return DEFER;
195 }
196 }
197
198query_len = Ustrlen(query);
199DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
200 string_printing(query));
201
202/* Now connect to the required port for each of the hosts in turn, until a
203response it received. Initialization insists on the port being set and there
204being a host list. */
205
206listptr = ob->hosts;
207while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
fb05276a 208 sizeof(host_buffer))))
0756eb3c
PH
209 {
210 host_item *h;
211
212 DEBUG(D_route) debug_printf("calling host %s\n", hostname);
213
214 host->name = hostname;
215 host->address = NULL;
216 host->port = PORT_NONE;
217 host->mx = MX_NONE;
218 host->next = NULL;
219
7e66e54d 220 if (string_is_ip_address(host->name, NULL) != 0)
0756eb3c
PH
221 host->address = host->name;
222 else
223 {
1f155f8e 224/*XXX might want dnssec request/require on an iplookup router? */
322050c2 225 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
0756eb3c
PH
226 if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
227 }
228
229 /* Loop for possible multiple IP addresses for the given name. */
230
fb05276a 231 for (h = host; h; h = h->next)
0756eb3c 232 {
74f1a423
JH
233 int host_af;
234 client_conn_ctx query_cctx = {0};
0756eb3c
PH
235
236 /* Skip any hosts for which we have no address */
237
fb05276a 238 if (!h->address) continue;
0756eb3c
PH
239
240 /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
241 detected by checking for a colon in the address. */
242
243 host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
fb05276a 244
74f1a423 245 query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
0756eb3c 246 host_af);
74f1a423 247 if (query_cctx.sock < 0)
0756eb3c
PH
248 {
249 if (ob->optional) return PASS;
250 addr->message = string_sprintf("failed to create socket in %s router",
251 rblock->name);
252 return DEFER;
253 }
254
255 /* Connect to the remote host, under a timeout. In fact, timeouts can occur
256 here only for TCP calls; for a UDP socket, "connect" always works (the
257 router will timeout later on the read call). */
0ab63f3d 258/*XXX could take advantage of TFO */
0756eb3c 259
74f1a423 260 if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
0ab63f3d 261 ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
0756eb3c 262 {
74f1a423 263 close(query_cctx.sock);
0756eb3c
PH
264 DEBUG(D_route)
265 debug_printf("connection to %s failed: %s\n", h->address,
266 strerror(errno));
267 continue;
268 }
269
270 /* Send the query. If it fails, just continue with the next address. */
271
74f1a423 272 if (send(query_cctx.sock, query, query_len, 0) < 0)
0756eb3c
PH
273 {
274 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
74f1a423 275 (void)close(query_cctx.sock);
0756eb3c
PH
276 continue;
277 }
278
279 /* Read the response and close the socket. If the read fails, try the
280 next IP address. */
281
0a5441fc 282 count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
74f1a423 283 (void)close(query_cctx.sock);
0756eb3c
PH
284 if (count <= 0)
285 {
286 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
287 "timed out" : "recv failed", h->address);
288 *reply = 0;
289 continue;
290 }
291
292 /* Success; break the loop */
293
294 reply[count] = 0;
295 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
296 rblock->name, string_printing(reply), h->address);
297 break;
298 }
299
300 /* If h == NULL we have tried all the IP addresses and failed on all of them,
301 so we must continue to try more host names. Otherwise we have succeeded. */
302
fb05276a 303 if (h) break;
0756eb3c
PH
304 }
305
306
307/* If hostname is NULL, we have failed to find any host, or failed to
308connect to any of the IP addresses, or timed out while reading or writing to
309those we have connected to. In all cases, we must pass if optional and
310defer otherwise. */
311
312if (hostname == NULL)
313 {
314 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
315 if (ob->optional) return PASS;
316 addr->message = string_sprintf("%s router: failed to communicate with any "
317 "host", rblock->name);
318 return DEFER;
319 }
320
321
322/* If a response pattern was supplied, match the returned string against it. A
323failure to match causes the router to decline. After a successful match, the
324numerical variables for expanding the rerouted address are set up. */
325
326if (re != NULL)
327 {
328 if (!regex_match_and_setup(re, reply, 0, -1))
329 {
330 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
331 rblock->name, ob->response_pattern, reply);
332 return DECLINE;
333 }
334 }
335
336
337/* If no response pattern was supplied, set up $0 as the response up to the
338first white space (if any). Also, if no query was specified, check that what
339follows the white space matches user@domain. */
340
341else
342 {
343 int n = 0;
344 while (reply[n] != 0 && !isspace(reply[n])) n++;
345 expand_nmax = 0;
346 expand_nstring[0] = reply;
347 expand_nlength[0] = n;
348
349 if (ob->query == NULL)
350 {
351 int nn = n;
352 while (isspace(reply[nn])) nn++;
353 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
354 {
355 DEBUG(D_route) debug_printf("%s router: failed to match identification "
356 "in response %s\n", rblock->name, reply);
357 return DECLINE;
358 }
359 }
360
361 reply[n] = 0; /* Terminate for the default case */
362 }
363
364/* If an explicit rerouting string is specified, expand it. Otherwise, use
365what was sent back verbatim. */
366
367if (ob->reroute != NULL)
368 {
369 reroute = expand_string(ob->reroute);
370 expand_nmax = -1;
371 if (reroute == NULL)
372 {
373 addr->message = string_sprintf("%s router: failed to expand %s: %s",
374 rblock->name, ob->reroute, expand_string_message);
375 return DEFER;
376 }
377 }
378else reroute = reply;
379
380/* We should now have a new address in the form user@domain. */
381
382domain = Ustrchr(reroute, '@');
383if (domain == NULL)
384 {
385 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
386 "user@domain", rblock->name, reroute);
387 addr->message = string_sprintf("%s router: reroute string %s is not of the "
388 "form user@domain", rblock->name, reroute);
389 return DEFER;
390 }
391
392/* Create a child address with the old one as parent. Put the new address on
393the chain of new addressess. */
394
395new_addr = deliver_make_addr(reroute, TRUE);
396new_addr->parent = addr;
397
d43cbe25 398new_addr->prop = addr->prop;
0756eb3c 399
82f90600 400if (addr->child_count == USHRT_MAX)
4362ff0d 401 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
82f90600 402 "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
0756eb3c
PH
403addr->child_count++;
404new_addr->next = *addr_new;
405*addr_new = new_addr;
406
aded2255 407/* Set up the errors address, if any, and the additional and removable headers
0756eb3c
PH
408for this new address. */
409
d43cbe25 410rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
0756eb3c
PH
411if (rc != OK) return rc;
412
d43cbe25
JH
413rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
414 &new_addr->prop.remove_headers);
0756eb3c
PH
415if (rc != OK) return rc;
416
417return OK;
418}
419
d185889f 420#endif /*!MACRO_PREDEF*/
0756eb3c 421/* End of routers/iplookup.c */