Merge branch '4.next'
[exim.git] / src / src / routers / iplookup.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
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
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) },
28   { "port",     opt_int,
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) }
40 };
41
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. */
44
45 int iplookup_router_options_count =
46   sizeof(iplookup_router_options)/sizeof(optionlist);
47
48
49 #ifdef MACRO_PREDEF
50
51 /* Dummy entries */
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;}
58
59 #else   /*!MACRO_PREDEF*/
60
61
62 /* Default private options block for the iplookup router. */
63
64 iplookup_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
84 consistency checks to be done, or anything else that needs to be set up. */
85
86 void
87 iplookup_router_init(router_instance * rblock)
88 {
89 iplookup_router_options_block * ob =
90   (iplookup_router_options_block *) rblock->options_block;
91
92 /* A port and a host list must be given */
93
94 if (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
98 if (!ob->hosts)
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
104 if (ob->protocol_name)
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
114 if (ob->response_pattern)
115   ob->re_response_pattern =
116     regex_must_compile(ob->response_pattern, MCS_NOFLAGS, TRUE);
117 }
118
119
120
121 /*************************************************
122 *              Main entry point                  *
123 *************************************************/
124
125 /* See local README for interface details. This router returns:
126
127 DECLINE
128   . pattern or identification match on returned data failed
129
130 DEFER
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
140 PASS
141   . failed to create socket ("optional" set)
142   . failed to find a host, failed to connect, timed out ("optional" set)
143
144 OK
145   . new address added to addr_new
146 */
147
148 int
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 */
158 {
159 uschar *query = NULL;
160 uschar *reply;
161 uschar *hostname, *reroute, *domain;
162 const uschar *listptr;
163 uschar host_buffer[256];
164 host_item *host = store_get(sizeof(host_item), GET_UNTAINTED);
165 address_item *new_addr;
166 iplookup_router_options_block *ob =
167   (iplookup_router_options_block *)(rblock->options_block);
168 const pcre2_code *re = ob->re_response_pattern;
169 int count, query_len, rc;
170 int sep = 0;
171
172 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
173   rblock->name, addr->address, addr->domain);
174
175 reply = store_get(256, GET_TAINTED);
176
177 /* Build the query string to send. If not explicitly given, a default of
178 "user@domain user@domain" is used. */
179
180 if (ob->query == NULL)
181   query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
182     addr->local_part, addr->domain);
183 else
184   {
185   query = expand_string(ob->query);
186   if (query == NULL)
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
194 query_len = Ustrlen(query);
195 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
196   string_printing(query));
197
198 /* Now connect to the required port for each of the hosts in turn, until a
199 response it received. Initialization insists on the port being set and there
200 being a host list. */
201
202 listptr = ob->hosts;
203 /* not expanded so should never be tainted */
204 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
205        sizeof(host_buffer))))
206   {
207   host_item *h;
208
209   DEBUG(D_route) debug_printf("calling host %s\n", hostname);
210
211   host->name = hostname;
212   host->address = NULL;
213   host->port = PORT_NONE;
214   host->mx = MX_NONE;
215   host->next = NULL;
216
217   if (string_is_ip_address(host->name, NULL) != 0)
218     host->address = host->name;
219   else
220     {
221 /*XXX might want dnssec request/require on an iplookup router? */
222     int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
223     if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
224     }
225
226   /* Loop for possible multiple IP addresses for the given name. */
227
228   for (h = host; h; h = h->next)
229     {
230     int host_af;
231     client_conn_ctx query_cctx = {0};
232
233     /* Skip any hosts for which we have no address */
234
235     if (!h->address) continue;
236
237     /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
238     detected by checking for a colon in the address. */
239
240     host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
241
242     query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
243       host_af);
244     if (query_cctx.sock < 0)
245       {
246       if (ob->optional) return PASS;
247       addr->message = string_sprintf("failed to create socket in %s router",
248         rblock->name);
249       return DEFER;
250       }
251
252     /* Connect to the remote host, under a timeout. In fact, timeouts can occur
253     here only for TCP calls; for a UDP socket, "connect" always works (the
254     router will timeout later on the read call). */
255 /*XXX could take advantage of TFO */
256
257     if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
258                 ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
259       {
260       close(query_cctx.sock);
261       DEBUG(D_route)
262         debug_printf("connection to %s failed: %s\n", h->address,
263           strerror(errno));
264       continue;
265       }
266
267     /* Send the query. If it fails, just continue with the next address. */
268
269     if (send(query_cctx.sock, query, query_len, 0) < 0)
270       {
271       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
272       (void)close(query_cctx.sock);
273       continue;
274       }
275
276     /* Read the response and close the socket. If the read fails, try the
277     next IP address. */
278
279     count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
280     (void)close(query_cctx.sock);
281     if (count <= 0)
282       {
283       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
284         "timed out" : "recv failed", h->address);
285       *reply = 0;
286       continue;
287       }
288
289     /* Success; break the loop */
290
291     reply[count] = 0;
292     DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
293       rblock->name, string_printing(reply), h->address);
294     break;
295     }
296
297   /* If h == NULL we have tried all the IP addresses and failed on all of them,
298   so we must continue to try more host names. Otherwise we have succeeded. */
299
300   if (h) break;
301   }
302
303
304 /* If hostname is NULL, we have failed to find any host, or failed to
305 connect to any of the IP addresses, or timed out while reading or writing to
306 those we have connected to. In all cases, we must pass if optional and
307 defer otherwise. */
308
309 if (hostname == NULL)
310   {
311   DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
312   if (ob->optional) return PASS;
313   addr->message = string_sprintf("%s router: failed to communicate with any "
314     "host", rblock->name);
315   return DEFER;
316   }
317
318
319 /* If a response pattern was supplied, match the returned string against it. A
320 failure to match causes the router to decline. After a successful match, the
321 numerical variables for expanding the rerouted address are set up. */
322
323 if (re != NULL)
324   {
325   if (!regex_match_and_setup(re, reply, 0, -1))
326     {
327     DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
328       rblock->name, ob->response_pattern, reply);
329     return DECLINE;
330     }
331   }
332
333
334 /* If no response pattern was supplied, set up $0 as the response up to the
335 first white space (if any). Also, if no query was specified, check that what
336 follows the white space matches user@domain. */
337
338 else
339   {
340   int n = 0;
341   while (reply[n] != 0 && !isspace(reply[n])) n++;
342   expand_nmax = 0;
343   expand_nstring[0] = reply;
344   expand_nlength[0] = n;
345
346   if (ob->query == NULL)
347     {
348     int nn = n;
349     while (isspace(reply[nn])) nn++;
350     if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
351       {
352       DEBUG(D_route) debug_printf("%s router: failed to match identification "
353         "in response %s\n", rblock->name, reply);
354       return DECLINE;
355       }
356     }
357
358   reply[n] = 0;  /* Terminate for the default case */
359   }
360
361 /* If an explicit rerouting string is specified, expand it. Otherwise, use
362 what was sent back verbatim. */
363
364 if (ob->reroute != NULL)
365   {
366   reroute = expand_string(ob->reroute);
367   expand_nmax = -1;
368   if (reroute == NULL)
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 reroute = reply;
376
377 /* We should now have a new address in the form user@domain. */
378
379 domain = Ustrchr(reroute, '@');
380 if (domain == NULL)
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 */