21a65f30e26661efc34adf94c468ff7d3539822a
[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 /* See the file NOTICE for conditions of use and distribution. */
7
8
9 #include "../exim.h"
10 #include "rf_functions.h"
11 #include "iplookup.h"
12
13
14 /* IP connection types */
15
16 #define ip_udp 0
17 #define ip_tcp 1
18
19
20 /* Options specific to the iplookup router. */
21
22 optionlist iplookup_router_options[] = {
23   { "hosts",    opt_stringptr,
24       OPT_OFF(iplookup_router_options_block, hosts) },
25   { "optional", opt_bool,
26       OPT_OFF(iplookup_router_options_block, optional) },
27   { "port",     opt_int,
28       OPT_OFF(iplookup_router_options_block, port) },
29   { "protocol", opt_stringptr,
30       OPT_OFF(iplookup_router_options_block, protocol_name) },
31   { "query",    opt_stringptr,
32       OPT_OFF(iplookup_router_options_block, query) },
33   { "reroute",  opt_stringptr,
34       OPT_OFF(iplookup_router_options_block, reroute) },
35   { "response_pattern", opt_stringptr,
36       OPT_OFF(iplookup_router_options_block, response_pattern) },
37   { "timeout",  opt_time,
38       OPT_OFF(iplookup_router_options_block, timeout) }
39 };
40
41 /* Size of the options list. An extern variable has to be used so that its
42 address can appear in the tables drtables.c. */
43
44 int iplookup_router_options_count =
45   sizeof(iplookup_router_options)/sizeof(optionlist);
46
47
48 #ifdef MACRO_PREDEF
49
50 /* Dummy entries */
51 iplookup_router_options_block iplookup_router_option_defaults = {0};
52 void iplookup_router_init(router_instance *rblock) {}
53 int iplookup_router_entry(router_instance *rblock, address_item *addr,
54   struct passwd *pw, int verify, address_item **addr_local,
55   address_item **addr_remote, address_item **addr_new,
56   address_item **addr_succeed) {return 0;}
57
58 #else   /*!MACRO_PREDEF*/
59
60
61 /* Default private options block for the iplookup router. */
62
63 iplookup_router_options_block iplookup_router_option_defaults = {
64   -1,       /* port */
65   ip_udp,   /* protocol */
66   5,        /* timeout */
67   NULL,     /* protocol_name */
68   NULL,     /* hosts */
69   NULL,     /* query; NULL => local_part@domain */
70   NULL,     /* response_pattern; NULL => don't apply regex */
71   NULL,     /* reroute; NULL => just used returned data */
72   NULL,     /* re_response_pattern; compiled pattern */
73   FALSE     /* optional */
74 };
75
76
77
78 /*************************************************
79 *          Initialization entry point            *
80 *************************************************/
81
82 /* Called for each instance, after its options have been read, to enable
83 consistency checks to be done, or anything else that needs to be set up. */
84
85 void
86 iplookup_router_init(router_instance *rblock)
87 {
88 iplookup_router_options_block *ob =
89   (iplookup_router_options_block *)(rblock->options_block);
90
91 /* A port and a host list must be given */
92
93 if (ob->port < 0)
94   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
95     "a port must be specified", rblock->name);
96
97 if (ob->hosts == NULL)
98   log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
99     "a host list must be specified", rblock->name);
100
101 /* Translate protocol name into value */
102
103 if (ob->protocol_name != NULL)
104   {
105   if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
106   else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
107   else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n  "
108     "protocol not specified as udp or tcp", rblock->name);
109   }
110
111 /* If a response pattern is given, compile it now to get the error early. */
112
113 if (ob->response_pattern != NULL)
114   ob->re_response_pattern =
115     regex_must_compile(ob->response_pattern, FALSE, TRUE);
116 }
117
118
119
120 /*************************************************
121 *              Main entry point                  *
122 *************************************************/
123
124 /* See local README for interface details. This router returns:
125
126 DECLINE
127   . pattern or identification match on returned data failed
128
129 DEFER
130   . failed to expand the query or rerouting string
131   . failed to create socket ("optional" not set)
132   . failed to find a host, failed to connect, timed out ("optional" not set)
133   . rerouting string is not in the form localpart@domain
134   . verifying the errors address caused a deferment or a big disaster such
135       as an expansion failure (rf_get_errors_address)
136   . expanding a headers_{add,remove} string caused a deferment or another
137       expansion error (rf_get_munge_headers)
138
139 PASS
140   . failed to create socket ("optional" set)
141   . failed to find a host, failed to connect, timed out ("optional" set)
142
143 OK
144   . new address added to addr_new
145 */
146
147 int
148 iplookup_router_entry(
149   router_instance *rblock,        /* data for this instantiation */
150   address_item *addr,             /* address we are working on */
151   struct passwd *pw,              /* passwd entry after check_local_user */
152   int verify,                     /* v_none/v_recipient/v_sender/v_expn */
153   address_item **addr_local,      /* add it to this if it's local */
154   address_item **addr_remote,     /* add it to this if it's remote */
155   address_item **addr_new,        /* put new addresses on here */
156   address_item **addr_succeed)    /* put old address here on success */
157 {
158 uschar *query = NULL;
159 uschar *reply;
160 uschar *hostname, *reroute, *domain;
161 const uschar *listptr;
162 uschar host_buffer[256];
163 host_item *host = store_get(sizeof(host_item), FALSE);
164 address_item *new_addr;
165 iplookup_router_options_block *ob =
166   (iplookup_router_options_block *)(rblock->options_block);
167 const pcre *re = ob->re_response_pattern;
168 int count, query_len, rc;
169 int sep = 0;
170
171 addr_local = addr_local;    /* Keep picky compilers happy */
172 addr_remote = addr_remote;
173 addr_succeed = addr_succeed;
174 pw = pw;
175
176 DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
177   rblock->name, addr->address, addr->domain);
178
179 reply = store_get(256, TRUE);   /* tainted data */
180
181 /* Build the query string to send. If not explicitly given, a default of
182 "user@domain user@domain" is used. */
183
184 if (ob->query == NULL)
185   query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
186     addr->local_part, addr->domain);
187 else
188   {
189   query = expand_string(ob->query);
190   if (query == NULL)
191     {
192     addr->message = string_sprintf("%s router: failed to expand %s: %s",
193       rblock->name, ob->query, expand_string_message);
194     return DEFER;
195     }
196   }
197
198 query_len = Ustrlen(query);
199 DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
200   string_printing(query));
201
202 /* Now connect to the required port for each of the hosts in turn, until a
203 response it received. Initialization insists on the port being set and there
204 being a host list. */
205
206 listptr = ob->hosts;
207 while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
208        sizeof(host_buffer))))
209   {
210   host_item *h;
211
212   DEBUG(D_route) debug_printf("calling host %s\n", hostname);
213
214   host->name = hostname;
215   host->address = NULL;
216   host->port = PORT_NONE;
217   host->mx = MX_NONE;
218   host->next = NULL;
219
220   if (string_is_ip_address(host->name, NULL) != 0)
221     host->address = host->name;
222   else
223     {
224 /*XXX might want dnssec request/require on an iplookup router? */
225     int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
226     if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
227     }
228
229   /* Loop for possible multiple IP addresses for the given name. */
230
231   for (h = host; h; h = h->next)
232     {
233     int host_af;
234     client_conn_ctx query_cctx = {0};
235
236     /* Skip any hosts for which we have no address */
237
238     if (!h->address) continue;
239
240     /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
241     detected by checking for a colon in the address. */
242
243     host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
244
245     query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
246       host_af);
247     if (query_cctx.sock < 0)
248       {
249       if (ob->optional) return PASS;
250       addr->message = string_sprintf("failed to create socket in %s router",
251         rblock->name);
252       return DEFER;
253       }
254
255     /* Connect to the remote host, under a timeout. In fact, timeouts can occur
256     here only for TCP calls; for a UDP socket, "connect" always works (the
257     router will timeout later on the read call). */
258 /*XXX could take advantage of TFO */
259
260     if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
261                 ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
262       {
263       close(query_cctx.sock);
264       DEBUG(D_route)
265         debug_printf("connection to %s failed: %s\n", h->address,
266           strerror(errno));
267       continue;
268       }
269
270     /* Send the query. If it fails, just continue with the next address. */
271
272     if (send(query_cctx.sock, query, query_len, 0) < 0)
273       {
274       DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
275       (void)close(query_cctx.sock);
276       continue;
277       }
278
279     /* Read the response and close the socket. If the read fails, try the
280     next IP address. */
281
282     count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
283     (void)close(query_cctx.sock);
284     if (count <= 0)
285       {
286       DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
287         "timed out" : "recv failed", h->address);
288       *reply = 0;
289       continue;
290       }
291
292     /* Success; break the loop */
293
294     reply[count] = 0;
295     DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
296       rblock->name, string_printing(reply), h->address);
297     break;
298     }
299
300   /* If h == NULL we have tried all the IP addresses and failed on all of them,
301   so we must continue to try more host names. Otherwise we have succeeded. */
302
303   if (h) break;
304   }
305
306
307 /* If hostname is NULL, we have failed to find any host, or failed to
308 connect to any of the IP addresses, or timed out while reading or writing to
309 those we have connected to. In all cases, we must pass if optional and
310 defer otherwise. */
311
312 if (hostname == NULL)
313   {
314   DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
315   if (ob->optional) return PASS;
316   addr->message = string_sprintf("%s router: failed to communicate with any "
317     "host", rblock->name);
318   return DEFER;
319   }
320
321
322 /* If a response pattern was supplied, match the returned string against it. A
323 failure to match causes the router to decline. After a successful match, the
324 numerical variables for expanding the rerouted address are set up. */
325
326 if (re != NULL)
327   {
328   if (!regex_match_and_setup(re, reply, 0, -1))
329     {
330     DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
331       rblock->name, ob->response_pattern, reply);
332     return DECLINE;
333     }
334   }
335
336
337 /* If no response pattern was supplied, set up $0 as the response up to the
338 first white space (if any). Also, if no query was specified, check that what
339 follows the white space matches user@domain. */
340
341 else
342   {
343   int n = 0;
344   while (reply[n] != 0 && !isspace(reply[n])) n++;
345   expand_nmax = 0;
346   expand_nstring[0] = reply;
347   expand_nlength[0] = n;
348
349   if (ob->query == NULL)
350     {
351     int nn = n;
352     while (isspace(reply[nn])) nn++;
353     if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
354       {
355       DEBUG(D_route) debug_printf("%s router: failed to match identification "
356         "in response %s\n", rblock->name, reply);
357       return DECLINE;
358       }
359     }
360
361   reply[n] = 0;  /* Terminate for the default case */
362   }
363
364 /* If an explicit rerouting string is specified, expand it. Otherwise, use
365 what was sent back verbatim. */
366
367 if (ob->reroute != NULL)
368   {
369   reroute = expand_string(ob->reroute);
370   expand_nmax = -1;
371   if (reroute == NULL)
372     {
373     addr->message = string_sprintf("%s router: failed to expand %s: %s",
374       rblock->name, ob->reroute, expand_string_message);
375     return DEFER;
376     }
377   }
378 else reroute = reply;
379
380 /* We should now have a new address in the form user@domain. */
381
382 domain = Ustrchr(reroute, '@');
383 if (domain == NULL)
384   {
385   log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
386     "user@domain", rblock->name, reroute);
387   addr->message = string_sprintf("%s router: reroute string %s is not of the "
388     "form user@domain", rblock->name, reroute);
389   return DEFER;
390   }
391
392 /* Create a child address with the old one as parent. Put the new address on
393 the chain of new addressess. */
394
395 new_addr = deliver_make_addr(reroute, TRUE);
396 new_addr->parent = addr;
397
398 new_addr->prop = addr->prop;
399
400 if (addr->child_count == USHRT_MAX)
401   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
402     "child addresses for <%s>", rblock->name, USHRT_MAX, addr->address);
403 addr->child_count++;
404 new_addr->next = *addr_new;
405 *addr_new = new_addr;
406
407 /* Set up the errors address, if any, and the additional and removable headers
408 for this new address. */
409
410 rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
411 if (rc != OK) return rc;
412
413 rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
414   &new_addr->prop.remove_headers);
415 if (rc != OK) return rc;
416
417 return OK;
418 }
419
420 #endif   /*!MACRO_PREDEF*/
421 /* End of routers/iplookup.c */