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