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 */
12 #include "rf_functions.h"
16 /* IP connection types */
22 /* Options specific to the iplookup router. */
24 optionlist iplookup_router_options[] = {
25 { "hosts", opt_stringptr,
26 OPT_OFF(iplookup_router_options_block, hosts) },
27 { "optional", opt_bool,
28 OPT_OFF(iplookup_router_options_block, optional) },
30 OPT_OFF(iplookup_router_options_block, port) },
31 { "protocol", opt_stringptr,
32 OPT_OFF(iplookup_router_options_block, protocol_name) },
33 { "query", opt_stringptr,
34 OPT_OFF(iplookup_router_options_block, query) },
35 { "reroute", opt_stringptr,
36 OPT_OFF(iplookup_router_options_block, reroute) },
37 { "response_pattern", opt_stringptr,
38 OPT_OFF(iplookup_router_options_block, response_pattern) },
39 { "timeout", opt_time,
40 OPT_OFF(iplookup_router_options_block, timeout) }
43 /* Size of the options list. An extern variable has to be used so that its
44 address can appear in the tables drtables.c. */
46 int iplookup_router_options_count =
47 sizeof(iplookup_router_options)/sizeof(optionlist);
53 iplookup_router_options_block iplookup_router_option_defaults = {0};
54 void iplookup_router_init(router_instance *rblock) {}
55 int iplookup_router_entry(router_instance *rblock, address_item *addr,
56 struct passwd *pw, int verify, address_item **addr_local,
57 address_item **addr_remote, address_item **addr_new,
58 address_item **addr_succeed) {return 0;}
60 #else /*!MACRO_PREDEF*/
63 /* Default private options block for the iplookup router. */
65 iplookup_router_options_block iplookup_router_option_defaults = {
67 ip_udp, /* protocol */
69 NULL, /* protocol_name */
71 NULL, /* query; NULL => local_part@domain */
72 NULL, /* response_pattern; NULL => don't apply regex */
73 NULL, /* reroute; NULL => just used returned data */
74 NULL, /* re_response_pattern; compiled pattern */
80 /*************************************************
81 * Initialization entry point *
82 *************************************************/
84 /* Called for each instance, after its options have been read, to enable
85 consistency checks to be done, or anything else that needs to be set up. */
88 iplookup_router_init(router_instance * rblock)
90 iplookup_router_options_block * ob =
91 (iplookup_router_options_block *) rblock->options_block;
93 /* A port and a host list must be given */
96 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
97 "a port must be specified", rblock->name);
100 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
101 "a host list must be specified", rblock->name);
103 /* Translate protocol name into value */
105 if (ob->protocol_name)
107 if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
108 else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
109 else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
110 "protocol not specified as udp or tcp", rblock->name);
113 /* If a response pattern is given, compile it now to get the error early. */
115 if (ob->response_pattern)
116 ob->re_response_pattern =
117 regex_must_compile(ob->response_pattern, MCS_NOFLAGS, TRUE);
122 /*************************************************
124 *************************************************/
126 /* See local README for interface details. This router returns:
129 . pattern or identification match on returned data failed
132 . failed to expand the query or rerouting string
133 . failed to create socket ("optional" not set)
134 . failed to find a host, failed to connect, timed out ("optional" not set)
135 . rerouting string is not in the form localpart@domain
136 . verifying the errors address caused a deferment or a big disaster such
137 as an expansion failure (rf_get_errors_address)
138 . expanding a headers_{add,remove} string caused a deferment or another
139 expansion error (rf_get_munge_headers)
142 . failed to create socket ("optional" set)
143 . failed to find a host, failed to connect, timed out ("optional" set)
146 . new address added to addr_new
150 iplookup_router_entry(
151 router_instance *rblock, /* data for this instantiation */
152 address_item *addr, /* address we are working on */
153 struct passwd *pw, /* passwd entry after check_local_user */
154 int verify, /* v_none/v_recipient/v_sender/v_expn */
155 address_item **addr_local, /* add it to this if it's local */
156 address_item **addr_remote, /* add it to this if it's remote */
157 address_item **addr_new, /* put new addresses on here */
158 address_item **addr_succeed) /* put old address here on success */
160 uschar *query = NULL;
162 uschar *hostname, *reroute, *domain;
163 const uschar *listptr;
164 uschar host_buffer[256];
165 host_item *host = store_get(sizeof(host_item), GET_UNTAINTED);
166 address_item *new_addr;
167 iplookup_router_options_block *ob =
168 (iplookup_router_options_block *)(rblock->options_block);
169 const pcre2_code *re = ob->re_response_pattern;
170 int count, query_len, rc;
173 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
174 rblock->name, addr->address, addr->domain);
176 reply = store_get(256, GET_TAINTED);
178 /* Build the query string to send. If not explicitly given, a default of
179 "user@domain user@domain" is used. */
183 query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
184 addr->local_part, addr->domain);
186 if (!(query = expand_string(ob->query)))
188 addr->message = string_sprintf("%s router: failed to expand %s: %s",
189 rblock->name, ob->query, expand_string_message);
193 query_len = Ustrlen(query);
194 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
195 string_printing(query));
197 /* Now connect to the required port for each of the hosts in turn, until a
198 response it received. Initialization insists on the port being set and there
199 being a host list. */
202 /* not expanded so should never be tainted */
203 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
204 sizeof(host_buffer))))
208 DEBUG(D_route) debug_printf("calling host %s\n", hostname);
210 host->name = hostname;
211 host->address = NULL;
212 host->port = PORT_NONE;
216 if (string_is_ip_address(host->name, NULL) != 0)
217 host->address = host->name;
220 /*XXX might want dnssec request/require on an iplookup router? */
221 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
222 if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
225 /* Loop for possible multiple IP addresses for the given name. */
227 for (h = host; h; h = h->next)
230 client_conn_ctx query_cctx = {0};
232 /* Skip any hosts for which we have no address */
234 if (!h->address) continue;
236 /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
237 detected by checking for a colon in the address. */
239 host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
241 query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
243 if (query_cctx.sock < 0)
245 if (ob->optional) return PASS;
246 addr->message = string_sprintf("failed to create socket in %s router",
251 /* Connect to the remote host, under a timeout. In fact, timeouts can occur
252 here only for TCP calls; for a UDP socket, "connect" always works (the
253 router will timeout later on the read call). */
254 /*XXX could take advantage of TFO */
256 if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
257 ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
259 close(query_cctx.sock);
261 debug_printf("connection to %s failed: %s\n", h->address,
266 /* Send the query. If it fails, just continue with the next address. */
268 if (send(query_cctx.sock, query, query_len, 0) < 0)
270 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
271 (void)close(query_cctx.sock);
275 /* Read the response and close the socket. If the read fails, try the
278 count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
279 (void)close(query_cctx.sock);
282 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
283 "timed out" : "recv failed", h->address);
288 /* Success; break the loop */
291 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
292 rblock->name, string_printing(reply), h->address);
296 /* If h == NULL we have tried all the IP addresses and failed on all of them,
297 so we must continue to try more host names. Otherwise we have succeeded. */
303 /* If hostname is NULL, we have failed to find any host, or failed to
304 connect to any of the IP addresses, or timed out while reading or writing to
305 those we have connected to. In all cases, we must pass if optional and
308 if (hostname == NULL)
310 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
311 if (ob->optional) return PASS;
312 addr->message = string_sprintf("%s router: failed to communicate with any "
313 "host", rblock->name);
318 /* If a response pattern was supplied, match the returned string against it. A
319 failure to match causes the router to decline. After a successful match, the
320 numerical variables for expanding the rerouted address are set up. */
324 if (!regex_match_and_setup(re, reply, 0, -1))
326 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
327 rblock->name, ob->response_pattern, reply);
333 /* If no response pattern was supplied, set up $0 as the response up to the
334 first white space (if any). Also, if no query was specified, check that what
335 follows the white space matches user@domain. */
340 while (reply[n] != 0 && !isspace(reply[n])) n++;
342 expand_nstring[0] = reply;
343 expand_nlength[0] = n;
345 if (ob->query == NULL)
348 while (isspace(reply[nn])) nn++;
349 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
351 DEBUG(D_route) debug_printf("%s router: failed to match identification "
352 "in response %s\n", rblock->name, reply);
357 reply[n] = 0; /* Terminate for the default case */
360 /* If an explicit rerouting string is specified, expand it. Otherwise, use
361 what was sent back verbatim. */
363 GET_OPTION("reroute");
366 reroute = expand_string(ob->reroute);
370 addr->message = string_sprintf("%s router: failed to expand %s: %s",
371 rblock->name, ob->reroute, expand_string_message);
378 /* We should now have a new address in the form user@domain. */
380 if (!(domain = Ustrchr(reroute, '@')))
382 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
383 "user@domain", rblock->name, reroute);
384 addr->message = string_sprintf("%s router: reroute string %s is not of the "
385 "form user@domain", rblock->name, reroute);
389 /* Create a child address with the old one as parent. Put the new address on
390 the chain of new addressess. */
392 new_addr = deliver_make_addr(reroute, TRUE);
393 new_addr->parent = addr;
395 new_addr->prop = addr->prop;
397 if (addr->child_count == USHRT_MAX)
398 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
399 "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
401 new_addr->next = *addr_new;
402 *addr_new = new_addr;
404 /* Set up the errors address, if any, and the additional and removable headers
405 for this new address. */
407 rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
408 if (rc != OK) return rc;
410 rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
411 &new_addr->prop.remove_headers);
412 if (rc != OK) return rc;
417 #endif /*!MACRO_PREDEF*/
418 /* End of routers/iplookup.c */