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