1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
13 #ifdef ROUTER_IPLOOKUP /* Remainder of file */
14 #include "rf_functions.h"
18 /* IP connection types */
24 /* Options specific to the iplookup router. */
26 optionlist iplookup_router_options[] = {
27 { "hosts", opt_stringptr,
28 OPT_OFF(iplookup_router_options_block, hosts) },
29 { "optional", opt_bool,
30 OPT_OFF(iplookup_router_options_block, optional) },
32 OPT_OFF(iplookup_router_options_block, port) },
33 { "protocol", opt_stringptr,
34 OPT_OFF(iplookup_router_options_block, protocol_name) },
35 { "query", opt_stringptr,
36 OPT_OFF(iplookup_router_options_block, query) },
37 { "reroute", opt_stringptr,
38 OPT_OFF(iplookup_router_options_block, reroute) },
39 { "response_pattern", opt_stringptr,
40 OPT_OFF(iplookup_router_options_block, response_pattern) },
41 { "timeout", opt_time,
42 OPT_OFF(iplookup_router_options_block, timeout) }
45 /* Size of the options list. An extern variable has to be used so that its
46 address can appear in the tables drtables.c. */
48 int iplookup_router_options_count =
49 sizeof(iplookup_router_options)/sizeof(optionlist);
55 iplookup_router_options_block iplookup_router_option_defaults = {0};
56 void iplookup_router_init(router_instance *rblock) {}
57 int iplookup_router_entry(router_instance *rblock, address_item *addr,
58 struct passwd *pw, int verify, address_item **addr_local,
59 address_item **addr_remote, address_item **addr_new,
60 address_item **addr_succeed) {return 0;}
62 #else /*!MACRO_PREDEF*/
65 /* Default private options block for the iplookup router. */
67 iplookup_router_options_block iplookup_router_option_defaults = {
69 ip_udp, /* protocol */
71 NULL, /* protocol_name */
73 NULL, /* query; NULL => local_part@domain */
74 NULL, /* response_pattern; NULL => don't apply regex */
75 NULL, /* reroute; NULL => just used returned data */
76 NULL, /* re_response_pattern; compiled pattern */
82 /*************************************************
83 * Initialization entry point *
84 *************************************************/
86 /* Called for each instance, after its options have been read, to enable
87 consistency checks to be done, or anything else that needs to be set up. */
90 iplookup_router_init(router_instance * rblock)
92 iplookup_router_options_block * ob =
93 (iplookup_router_options_block *) rblock->options_block;
95 /* A port and a host list must be given */
98 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
99 "a port must be specified", rblock->name);
102 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
103 "a host list must be specified", rblock->name);
105 /* Translate protocol name into value */
107 if (ob->protocol_name)
109 if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
110 else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
111 else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
112 "protocol not specified as udp or tcp", rblock->name);
115 /* If a response pattern is given, compile it now to get the error early. */
117 if (ob->response_pattern)
118 ob->re_response_pattern =
119 regex_must_compile(ob->response_pattern, MCS_NOFLAGS, TRUE);
124 /*************************************************
126 *************************************************/
128 /* See local README for interface details. This router returns:
131 . pattern or identification match on returned data failed
134 . failed to expand the query or rerouting string
135 . failed to create socket ("optional" not set)
136 . failed to find a host, failed to connect, timed out ("optional" not set)
137 . rerouting string is not in the form localpart@domain
138 . verifying the errors address caused a deferment or a big disaster such
139 as an expansion failure (rf_get_errors_address)
140 . expanding a headers_{add,remove} string caused a deferment or another
141 expansion error (rf_get_munge_headers)
144 . failed to create socket ("optional" set)
145 . failed to find a host, failed to connect, timed out ("optional" set)
148 . new address added to addr_new
152 iplookup_router_entry(
153 router_instance *rblock, /* data for this instantiation */
154 address_item *addr, /* address we are working on */
155 struct passwd *pw, /* passwd entry after check_local_user */
156 int verify, /* v_none/v_recipient/v_sender/v_expn */
157 address_item **addr_local, /* add it to this if it's local */
158 address_item **addr_remote, /* add it to this if it's remote */
159 address_item **addr_new, /* put new addresses on here */
160 address_item **addr_succeed) /* put old address here on success */
162 uschar *query = NULL;
164 uschar *hostname, *reroute, *domain;
165 const uschar *listptr;
166 uschar host_buffer[256];
167 host_item *host = store_get(sizeof(host_item), GET_UNTAINTED);
168 address_item *new_addr;
169 iplookup_router_options_block *ob =
170 (iplookup_router_options_block *)(rblock->options_block);
171 const pcre2_code *re = ob->re_response_pattern;
172 int count, query_len, rc;
175 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
176 rblock->name, addr->address, addr->domain);
178 reply = store_get(256, GET_TAINTED);
180 /* Build the query string to send. If not explicitly given, a default of
181 "user@domain user@domain" is used. */
185 query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
186 addr->local_part, addr->domain);
188 if (!(query = expand_string(ob->query)))
190 addr->message = string_sprintf("%s router: failed to expand %s: %s",
191 rblock->name, ob->query, expand_string_message);
195 query_len = Ustrlen(query);
196 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
197 string_printing(query));
199 /* Now connect to the required port for each of the hosts in turn, until a
200 response it received. Initialization insists on the port being set and there
201 being a host list. */
204 /* not expanded so should never be tainted */
205 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
206 sizeof(host_buffer))))
210 DEBUG(D_route) debug_printf("calling host %s\n", hostname);
212 host->name = hostname;
213 host->address = NULL;
214 host->port = PORT_NONE;
218 if (string_is_ip_address(host->name, NULL) != 0)
219 host->address = host->name;
222 /*XXX might want dnssec request/require on an iplookup router? */
223 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
224 if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
227 /* Loop for possible multiple IP addresses for the given name. */
229 for (h = host; h; h = h->next)
232 client_conn_ctx query_cctx = {0};
234 /* Skip any hosts for which we have no address */
236 if (!h->address) continue;
238 /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
239 detected by checking for a colon in the address. */
241 host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
243 query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
245 if (query_cctx.sock < 0)
247 if (ob->optional) return PASS;
248 addr->message = string_sprintf("failed to create socket in %s router",
253 /* Connect to the remote host, under a timeout. In fact, timeouts can occur
254 here only for TCP calls; for a UDP socket, "connect" always works (the
255 router will timeout later on the read call). */
256 /*XXX could take advantage of TFO */
258 if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
259 ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
261 close(query_cctx.sock);
263 debug_printf("connection to %s failed: %s\n", h->address,
268 /* Send the query. If it fails, just continue with the next address. */
270 if (send(query_cctx.sock, query, query_len, 0) < 0)
272 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
273 (void)close(query_cctx.sock);
277 /* Read the response and close the socket. If the read fails, try the
280 count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
281 (void)close(query_cctx.sock);
284 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
285 "timed out" : "recv failed", h->address);
290 /* Success; break the loop */
293 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
294 rblock->name, string_printing(reply), h->address);
298 /* If h == NULL we have tried all the IP addresses and failed on all of them,
299 so we must continue to try more host names. Otherwise we have succeeded. */
305 /* If hostname is NULL, we have failed to find any host, or failed to
306 connect to any of the IP addresses, or timed out while reading or writing to
307 those we have connected to. In all cases, we must pass if optional and
310 if (hostname == NULL)
312 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
313 if (ob->optional) return PASS;
314 addr->message = string_sprintf("%s router: failed to communicate with any "
315 "host", rblock->name);
320 /* If a response pattern was supplied, match the returned string against it. A
321 failure to match causes the router to decline. After a successful match, the
322 numerical variables for expanding the rerouted address are set up. */
326 if (!regex_match_and_setup(re, reply, 0, -1))
328 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
329 rblock->name, ob->response_pattern, reply);
335 /* If no response pattern was supplied, set up $0 as the response up to the
336 first white space (if any). Also, if no query was specified, check that what
337 follows the white space matches user@domain. */
342 while (reply[n] != 0 && !isspace(reply[n])) n++;
344 expand_nstring[0] = reply;
345 expand_nlength[0] = n;
347 if (ob->query == NULL)
350 while (isspace(reply[nn])) nn++;
351 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
353 DEBUG(D_route) debug_printf("%s router: failed to match identification "
354 "in response %s\n", rblock->name, reply);
359 reply[n] = 0; /* Terminate for the default case */
362 /* If an explicit rerouting string is specified, expand it. Otherwise, use
363 what was sent back verbatim. */
365 GET_OPTION("reroute");
368 reroute = expand_string(ob->reroute);
372 addr->message = string_sprintf("%s router: failed to expand %s: %s",
373 rblock->name, ob->reroute, expand_string_message);
380 /* We should now have a new address in the form user@domain. */
382 if (!(domain = Ustrchr(reroute, '@')))
384 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
385 "user@domain", rblock->name, reroute);
386 addr->message = string_sprintf("%s router: reroute string %s is not of the "
387 "form user@domain", rblock->name, reroute);
391 /* Create a child address with the old one as parent. Put the new address on
392 the chain of new addressess. */
394 new_addr = deliver_make_addr(reroute, TRUE);
395 new_addr->parent = addr;
397 new_addr->prop = addr->prop;
399 if (addr->child_count == USHRT_MAX)
400 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
401 "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
403 new_addr->next = *addr_new;
404 *addr_new = new_addr;
406 /* Set up the errors address, if any, and the additional and removable headers
407 for this new address. */
409 rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
410 if (rc != OK) return rc;
412 rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
413 &new_addr->prop.remove_headers);
414 if (rc != OK) return rc;
419 #endif /*!MACRO_PREDEF*/
420 #endif /*ROUTER_IPLOOKUP*/
421 /* End of routers/iplookup.c */