1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
11 #include "rf_functions.h"
15 /* IP connection types */
21 /* Options specific to the iplookup router. */
23 optionlist iplookup_router_options[] = {
24 { "hosts", opt_stringptr,
25 OPT_OFF(iplookup_router_options_block, hosts) },
26 { "optional", opt_bool,
27 OPT_OFF(iplookup_router_options_block, optional) },
29 OPT_OFF(iplookup_router_options_block, port) },
30 { "protocol", opt_stringptr,
31 OPT_OFF(iplookup_router_options_block, protocol_name) },
32 { "query", opt_stringptr,
33 OPT_OFF(iplookup_router_options_block, query) },
34 { "reroute", opt_stringptr,
35 OPT_OFF(iplookup_router_options_block, reroute) },
36 { "response_pattern", opt_stringptr,
37 OPT_OFF(iplookup_router_options_block, response_pattern) },
38 { "timeout", opt_time,
39 OPT_OFF(iplookup_router_options_block, timeout) }
42 /* Size of the options list. An extern variable has to be used so that its
43 address can appear in the tables drtables.c. */
45 int iplookup_router_options_count =
46 sizeof(iplookup_router_options)/sizeof(optionlist);
52 iplookup_router_options_block iplookup_router_option_defaults = {0};
53 void iplookup_router_init(router_instance *rblock) {}
54 int 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,
57 address_item **addr_succeed) {return 0;}
59 #else /*!MACRO_PREDEF*/
62 /* Default private options block for the iplookup router. */
64 iplookup_router_options_block iplookup_router_option_defaults = {
66 ip_udp, /* protocol */
68 NULL, /* protocol_name */
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 */
79 /*************************************************
80 * Initialization entry point *
81 *************************************************/
83 /* Called for each instance, after its options have been read, to enable
84 consistency checks to be done, or anything else that needs to be set up. */
87 iplookup_router_init(router_instance *rblock)
89 iplookup_router_options_block *ob =
90 (iplookup_router_options_block *)(rblock->options_block);
92 /* A port and a host list must be given */
95 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
96 "a port must be specified", rblock->name);
98 if (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);
102 /* Translate protocol name into value */
104 if (ob->protocol_name != NULL)
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);
112 /* If a response pattern is given, compile it now to get the error early. */
114 if (ob->response_pattern != NULL)
115 ob->re_response_pattern =
116 regex_must_compile(ob->response_pattern, FALSE, TRUE);
121 /*************************************************
123 *************************************************/
125 /* See local README for interface details. This router returns:
128 . pattern or identification match on returned data failed
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)
141 . failed to create socket ("optional" set)
142 . failed to find a host, failed to connect, timed out ("optional" set)
145 . new address added to addr_new
149 iplookup_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 */
153 int verify, /* v_none/v_recipient/v_sender/v_expn */
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 */
159 uschar *query = NULL;
161 uschar *hostname, *reroute, *domain;
162 const uschar *listptr;
163 uschar host_buffer[256];
164 host_item *host = store_get(sizeof(host_item), FALSE);
165 address_item *new_addr;
166 iplookup_router_options_block *ob =
167 (iplookup_router_options_block *)(rblock->options_block);
168 const pcre *re = ob->re_response_pattern;
169 int count, query_len, rc;
172 addr_local = addr_local; /* Keep picky compilers happy */
173 addr_remote = addr_remote;
174 addr_succeed = addr_succeed;
177 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
178 rblock->name, addr->address, addr->domain);
180 reply = store_get(256, TRUE); /* tainted data */
182 /* Build the query string to send. If not explicitly given, a default of
183 "user@domain user@domain" is used. */
185 if (ob->query == NULL)
186 query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
187 addr->local_part, addr->domain);
190 query = expand_string(ob->query);
193 addr->message = string_sprintf("%s router: failed to expand %s: %s",
194 rblock->name, ob->query, expand_string_message);
199 query_len = Ustrlen(query);
200 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
201 string_printing(query));
203 /* Now connect to the required port for each of the hosts in turn, until a
204 response it received. Initialization insists on the port being set and there
205 being a host list. */
208 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
209 sizeof(host_buffer))))
213 DEBUG(D_route) debug_printf("calling host %s\n", hostname);
215 host->name = hostname;
216 host->address = NULL;
217 host->port = PORT_NONE;
221 if (string_is_ip_address(host->name, NULL) != 0)
222 host->address = host->name;
225 /*XXX might want dnssec request/require on an iplookup router? */
226 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
227 if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
230 /* Loop for possible multiple IP addresses for the given name. */
232 for (h = host; h; h = h->next)
235 client_conn_ctx query_cctx = {0};
237 /* Skip any hosts for which we have no address */
239 if (!h->address) continue;
241 /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
242 detected by checking for a colon in the address. */
244 host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
246 query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
248 if (query_cctx.sock < 0)
250 if (ob->optional) return PASS;
251 addr->message = string_sprintf("failed to create socket in %s router",
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). */
259 /*XXX could take advantage of TFO */
261 if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
262 ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
264 close(query_cctx.sock);
266 debug_printf("connection to %s failed: %s\n", h->address,
271 /* Send the query. If it fails, just continue with the next address. */
273 if (send(query_cctx.sock, query, query_len, 0) < 0)
275 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
276 (void)close(query_cctx.sock);
280 /* Read the response and close the socket. If the read fails, try the
283 count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
284 (void)close(query_cctx.sock);
287 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
288 "timed out" : "recv failed", h->address);
293 /* Success; break the loop */
296 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
297 rblock->name, string_printing(reply), h->address);
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. */
308 /* If hostname is NULL, we have failed to find any host, or failed to
309 connect to any of the IP addresses, or timed out while reading or writing to
310 those we have connected to. In all cases, we must pass if optional and
313 if (hostname == NULL)
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);
323 /* If a response pattern was supplied, match the returned string against it. A
324 failure to match causes the router to decline. After a successful match, the
325 numerical variables for expanding the rerouted address are set up. */
329 if (!regex_match_and_setup(re, reply, 0, -1))
331 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
332 rblock->name, ob->response_pattern, reply);
338 /* If no response pattern was supplied, set up $0 as the response up to the
339 first white space (if any). Also, if no query was specified, check that what
340 follows the white space matches user@domain. */
345 while (reply[n] != 0 && !isspace(reply[n])) n++;
347 expand_nstring[0] = reply;
348 expand_nlength[0] = n;
350 if (ob->query == NULL)
353 while (isspace(reply[nn])) nn++;
354 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
356 DEBUG(D_route) debug_printf("%s router: failed to match identification "
357 "in response %s\n", rblock->name, reply);
362 reply[n] = 0; /* Terminate for the default case */
365 /* If an explicit rerouting string is specified, expand it. Otherwise, use
366 what was sent back verbatim. */
368 if (ob->reroute != NULL)
370 reroute = expand_string(ob->reroute);
374 addr->message = string_sprintf("%s router: failed to expand %s: %s",
375 rblock->name, ob->reroute, expand_string_message);
379 else reroute = reply;
381 /* We should now have a new address in the form user@domain. */
383 domain = Ustrchr(reroute, '@');
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);
393 /* Create a child address with the old one as parent. Put the new address on
394 the chain of new addressess. */
396 new_addr = deliver_make_addr(reroute, TRUE);
397 new_addr->parent = addr;
399 new_addr->prop = addr->prop;
401 if (addr->child_count == USHRT_MAX)
402 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
403 "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
405 new_addr->next = *addr_new;
406 *addr_new = new_addr;
408 /* Set up the errors address, if any, and the additional and removable headers
409 for this new address. */
411 rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
412 if (rc != OK) return rc;
414 rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
415 &new_addr->prop.remove_headers);
416 if (rc != OK) return rc;
421 #endif /*!MACRO_PREDEF*/
422 /* End of routers/iplookup.c */