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