Copyright updates:
[exim.git] / src / src / routers / iplookup.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
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. */
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 == NULL)
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 != NULL)
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 != NULL)
115   ob->re_response_pattern =
116     regex_must_compile(ob->response_pattern, FALSE, 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), 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;
170 int sep = 0;
171
172 addr_local = addr_local;    /* Keep picky compilers happy */
173 addr_remote = addr_remote;
174 addr_succeed = addr_succeed;
175 pw = pw;
176
177 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
178   rblock->name, addr->address, addr->domain);
179
180 reply = store_get(256, TRUE);   /* tainted data */
181
182 /* Build the query string to send. If not explicitly given, a default of
183 "user@domain user@domain" is used. */
184
185 if (ob->query == NULL)
186   query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
187     addr->local_part, addr->domain);
188 else
189   {
190   query = expand_string(ob->query);
191   if (query == NULL)
192     {
193     addr->message = string_sprintf("%s router: failed to expand %s: %s",
194       rblock->name, ob->query, expand_string_message);
195     return DEFER;
196     }
197   }
198
199 query_len = Ustrlen(query);
200 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
201   string_printing(query));
202
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. */
206
207 listptr = ob->hosts;
208 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
209        sizeof(host_buffer))))
210   {
211   host_item *h;
212
213   DEBUG(D_route) debug_printf("calling host %s\n", hostname);
214
215   host->name = hostname;
216   host->address = NULL;
217   host->port = PORT_NONE;
218   host->mx = MX_NONE;
219   host->next = NULL;
220
221   if (string_is_ip_address(host->name, NULL) != 0)
222     host->address = host->name;
223   else
224     {
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;
228     }
229
230   /* Loop for possible multiple IP addresses for the given name. */
231
232   for (h = host; h; h = h->next)
233     {
234     int host_af;
235     client_conn_ctx query_cctx = {0};
236
237     /* Skip any hosts for which we have no address */
238
239     if (!h->address) continue;
240
241     /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
242     detected by checking for a colon in the address. */
243
244     host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
245
246     query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
247       host_af);
248     if (query_cctx.sock < 0)
249       {
250       if (ob->optional) return PASS;
251       addr->message = string_sprintf("failed to create socket in %s router",
252         rblock->name);
253       return DEFER;
254       }
255
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 */
260
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)
263       {
264       close(query_cctx.sock);
265       DEBUG(D_route)
266         debug_printf("connection to %s failed: %s\n", h->address,
267           strerror(errno));
268       continue;
269       }
270
271     /* Send the query. If it fails, just continue with the next address. */
272
273     if (send(query_cctx.sock, query, query_len, 0) < 0)
274       {
275       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
276       (void)close(query_cctx.sock);
277       continue;
278       }
279
280     /* Read the response and close the socket. If the read fails, try the
281     next IP address. */
282
283     count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
284     (void)close(query_cctx.sock);
285     if (count <= 0)
286       {
287       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
288         "timed out" : "recv failed", h->address);
289       *reply = 0;
290       continue;
291       }
292
293     /* Success; break the loop */
294
295     reply[count] = 0;
296     DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
297       rblock->name, string_printing(reply), h->address);
298     break;
299     }
300
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. */
303
304   if (h) break;
305   }
306
307
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
311 defer otherwise. */
312
313 if (hostname == NULL)
314   {
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);
319   return DEFER;
320   }
321
322
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. */
326
327 if (re != NULL)
328   {
329   if (!regex_match_and_setup(re, reply, 0, -1))
330     {
331     DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
332       rblock->name, ob->response_pattern, reply);
333     return DECLINE;
334     }
335   }
336
337
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. */
341
342 else
343   {
344   int n = 0;
345   while (reply[n] != 0 && !isspace(reply[n])) n++;
346   expand_nmax = 0;
347   expand_nstring[0] = reply;
348   expand_nlength[0] = n;
349
350   if (ob->query == NULL)
351     {
352     int nn = n;
353     while (isspace(reply[nn])) nn++;
354     if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
355       {
356       DEBUG(D_route) debug_printf("%s router: failed to match identification "
357         "in response %s\n", rblock->name, reply);
358       return DECLINE;
359       }
360     }
361
362   reply[n] = 0;  /* Terminate for the default case */
363   }
364
365 /* If an explicit rerouting string is specified, expand it. Otherwise, use
366 what was sent back verbatim. */
367
368 if (ob->reroute != NULL)
369   {
370   reroute = expand_string(ob->reroute);
371   expand_nmax = -1;
372   if (reroute == NULL)
373     {
374     addr->message = string_sprintf("%s router: failed to expand %s: %s",
375       rblock->name, ob->reroute, expand_string_message);
376     return DEFER;
377     }
378   }
379 else reroute = reply;
380
381 /* We should now have a new address in the form user@domain. */
382
383 domain = Ustrchr(reroute, '@');
384 if (domain == NULL)
385   {
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);
390   return DEFER;
391   }
392
393 /* Create a child address with the old one as parent. Put the new address on
394 the chain of new addressess. */
395
396 new_addr = deliver_make_addr(reroute, TRUE);
397 new_addr->parent = addr;
398
399 new_addr->prop = addr->prop;
400
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);
404 addr->child_count++;
405 new_addr->next = *addr_new;
406 *addr_new = new_addr;
407
408 /* Set up the errors address, if any, and the additional and removable headers
409 for this new address. */
410
411 rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
412 if (rc != OK) return rc;
413
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;
417
418 return OK;
419 }
420
421 #endif   /*!MACRO_PREDEF*/
422 /* End of routers/iplookup.c */