Debug: option access for expansion
[exim.git] / src / src / routers / iplookup.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
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 */
9
10
11 #include "../exim.h"
12 #include "rf_functions.h"
13 #include "iplookup.h"
14
15
16 /* IP connection types */
17
18 #define ip_udp 0
19 #define ip_tcp 1
20
21
22 /* Options specific to the iplookup router. */
23
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) },
29   { "port",     opt_int,
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) }
41 };
42
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. */
45
46 int iplookup_router_options_count =
47   sizeof(iplookup_router_options)/sizeof(optionlist);
48
49
50 #ifdef MACRO_PREDEF
51
52 /* Dummy entries */
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;}
59
60 #else   /*!MACRO_PREDEF*/
61
62
63 /* Default private options block for the iplookup router. */
64
65 iplookup_router_options_block iplookup_router_option_defaults = {
66   -1,       /* port */
67   ip_udp,   /* protocol */
68   5,        /* timeout */
69   NULL,     /* protocol_name */
70   NULL,     /* hosts */
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 */
75   FALSE     /* optional */
76 };
77
78
79
80 /*************************************************
81 *          Initialization entry point            *
82 *************************************************/
83
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. */
86
87 void
88 iplookup_router_init(router_instance * rblock)
89 {
90 iplookup_router_options_block * ob =
91   (iplookup_router_options_block *) rblock->options_block;
92
93 /* A port and a host list must be given */
94
95 if (ob->port < 0)
96   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
97     "a port must be specified", rblock->name);
98
99 if (!ob->hosts)
100   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
101     "a host list must be specified", rblock->name);
102
103 /* Translate protocol name into value */
104
105 if (ob->protocol_name)
106   {
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);
111   }
112
113 /* If a response pattern is given, compile it now to get the error early. */
114
115 if (ob->response_pattern)
116   ob->re_response_pattern =
117     regex_must_compile(ob->response_pattern, MCS_NOFLAGS, TRUE);
118 }
119
120
121
122 /*************************************************
123 *              Main entry point                  *
124 *************************************************/
125
126 /* See local README for interface details. This router returns:
127
128 DECLINE
129   . pattern or identification match on returned data failed
130
131 DEFER
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)
140
141 PASS
142   . failed to create socket ("optional" set)
143   . failed to find a host, failed to connect, timed out ("optional" set)
144
145 OK
146   . new address added to addr_new
147 */
148
149 int
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 */
159 {
160 uschar *query = NULL;
161 uschar *reply;
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;
171 int sep = 0;
172
173 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
174   rblock->name, addr->address, addr->domain);
175
176 reply = store_get(256, GET_TAINTED);
177
178 /* Build the query string to send. If not explicitly given, a default of
179 "user@domain user@domain" is used. */
180
181 GET_OPTION("query");
182 if (!ob->query)
183   query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
184     addr->local_part, addr->domain);
185 else
186   if (!(query = expand_string(ob->query)))
187     {
188     addr->message = string_sprintf("%s router: failed to expand %s: %s",
189       rblock->name, ob->query, expand_string_message);
190     return DEFER;
191     }
192
193 query_len = Ustrlen(query);
194 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
195   string_printing(query));
196
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. */
200
201 listptr = ob->hosts;
202 /* not expanded so should never be tainted */
203 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
204        sizeof(host_buffer))))
205   {
206   host_item *h;
207
208   DEBUG(D_route) debug_printf("calling host %s\n", hostname);
209
210   host->name = hostname;
211   host->address = NULL;
212   host->port = PORT_NONE;
213   host->mx = MX_NONE;
214   host->next = NULL;
215
216   if (string_is_ip_address(host->name, NULL) != 0)
217     host->address = host->name;
218   else
219     {
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;
223     }
224
225   /* Loop for possible multiple IP addresses for the given name. */
226
227   for (h = host; h; h = h->next)
228     {
229     int host_af;
230     client_conn_ctx query_cctx = {0};
231
232     /* Skip any hosts for which we have no address */
233
234     if (!h->address) continue;
235
236     /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
237     detected by checking for a colon in the address. */
238
239     host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
240
241     query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
242       host_af);
243     if (query_cctx.sock < 0)
244       {
245       if (ob->optional) return PASS;
246       addr->message = string_sprintf("failed to create socket in %s router",
247         rblock->name);
248       return DEFER;
249       }
250
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 */
255
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)
258       {
259       close(query_cctx.sock);
260       DEBUG(D_route)
261         debug_printf("connection to %s failed: %s\n", h->address,
262           strerror(errno));
263       continue;
264       }
265
266     /* Send the query. If it fails, just continue with the next address. */
267
268     if (send(query_cctx.sock, query, query_len, 0) < 0)
269       {
270       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
271       (void)close(query_cctx.sock);
272       continue;
273       }
274
275     /* Read the response and close the socket. If the read fails, try the
276     next IP address. */
277
278     count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
279     (void)close(query_cctx.sock);
280     if (count <= 0)
281       {
282       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
283         "timed out" : "recv failed", h->address);
284       *reply = 0;
285       continue;
286       }
287
288     /* Success; break the loop */
289
290     reply[count] = 0;
291     DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
292       rblock->name, string_printing(reply), h->address);
293     break;
294     }
295
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. */
298
299   if (h) break;
300   }
301
302
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
306 defer otherwise. */
307
308 if (hostname == NULL)
309   {
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);
314   return DEFER;
315   }
316
317
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. */
321
322 if (re != NULL)
323   {
324   if (!regex_match_and_setup(re, reply, 0, -1))
325     {
326     DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
327       rblock->name, ob->response_pattern, reply);
328     return DECLINE;
329     }
330   }
331
332
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. */
336
337 else
338   {
339   int n = 0;
340   while (reply[n] != 0 && !isspace(reply[n])) n++;
341   expand_nmax = 0;
342   expand_nstring[0] = reply;
343   expand_nlength[0] = n;
344
345   if (ob->query == NULL)
346     {
347     int nn = n;
348     while (isspace(reply[nn])) nn++;
349     if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
350       {
351       DEBUG(D_route) debug_printf("%s router: failed to match identification "
352         "in response %s\n", rblock->name, reply);
353       return DECLINE;
354       }
355     }
356
357   reply[n] = 0;  /* Terminate for the default case */
358   }
359
360 /* If an explicit rerouting string is specified, expand it. Otherwise, use
361 what was sent back verbatim. */
362
363 GET_OPTION("reroute");
364 if (ob->reroute)
365   {
366   reroute = expand_string(ob->reroute);
367   expand_nmax = -1;
368   if (!reroute)
369     {
370     addr->message = string_sprintf("%s router: failed to expand %s: %s",
371       rblock->name, ob->reroute, expand_string_message);
372     return DEFER;
373     }
374   }
375 else
376   reroute = reply;
377
378 /* We should now have a new address in the form user@domain. */
379
380 if (!(domain = Ustrchr(reroute, '@')))
381   {
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);
386   return DEFER;
387   }
388
389 /* Create a child address with the old one as parent. Put the new address on
390 the chain of new addressess. */
391
392 new_addr = deliver_make_addr(reroute, TRUE);
393 new_addr->parent = addr;
394
395 new_addr->prop = addr->prop;
396
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);
400 addr->child_count++;
401 new_addr->next = *addr_new;
402 *addr_new = new_addr;
403
404 /* Set up the errors address, if any, and the additional and removable headers
405 for this new address. */
406
407 rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
408 if (rc != OK) return rc;
409
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;
413
414 return OK;
415 }
416
417 #endif   /*!MACRO_PREDEF*/
418 /* End of routers/iplookup.c */