update to pre-4.87 master
[exim.git] / src / src / routers / iplookup.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
bfe645c1 5/* Copyright (c) University of Cambridge 1995 - 2015 */
0756eb3c
PH
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
22optionlist 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
42address can appear in the tables drtables.c. */
43
44int iplookup_router_options_count =
45 sizeof(iplookup_router_options)/sizeof(optionlist);
46
47/* Default private options block for the iplookup router. */
48
49iplookup_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
69consistency checks to be done, or anything else that needs to be set up. */
70
71void
72iplookup_router_init(router_instance *rblock)
73{
74iplookup_router_options_block *ob =
75 (iplookup_router_options_block *)(rblock->options_block);
76
77/* A port and a host list must be given */
78
79if (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
83if (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
89if (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
99if (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
112DECLINE
113 . pattern or identification match on returned data failed
114
115DEFER
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
125PASS
126 . failed to create socket ("optional" set)
127 . failed to find a host, failed to connect, timed out ("optional" set)
128
129OK
130 . new address added to addr_new
131*/
132
133int
134iplookup_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 */
fd6de02e 138 int verify, /* v_none/v_recipient/v_sender/v_expn */
0756eb3c
PH
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{
144uschar *query = NULL;
08488c86 145uschar *reply;
bfe645c1
JH
146uschar *hostname, *reroute, *domain;
147const uschar *listptr;
0756eb3c
PH
148uschar host_buffer[256];
149host_item *host = store_get(sizeof(host_item));
150address_item *new_addr;
151iplookup_router_options_block *ob =
152 (iplookup_router_options_block *)(rblock->options_block);
153const pcre *re = ob->re_response_pattern;
154int count, query_len, rc;
155int sep = 0;
156
157addr_local = addr_local; /* Keep picky compilers happy */
158addr_remote = addr_remote;
159addr_succeed = addr_succeed;
160pw = pw;
161
162DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
163 rblock->name, addr->address, addr->domain);
164
08488c86
PP
165reply = store_get(256);
166
0756eb3c
PH
167/* Build the query string to send. If not explicitly given, a default of
168"user@domain user@domain" is used. */
169
170if (ob->query == NULL)
171 query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
172 addr->local_part, addr->domain);
173else
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
184query_len = Ustrlen(query);
185DEBUG(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
189response it received. Initialization insists on the port being set and there
190being a host list. */
191
192listptr = ob->hosts;
193while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
194 sizeof(host_buffer))) != NULL)
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
7e66e54d 206 if (string_is_ip_address(host->name, NULL) != 0)
0756eb3c
PH
207 host->address = host->name;
208 else
209 {
bfe645c1 210/*XXX might want dnssec request/require on an iplookup router? */
322050c2 211 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
0756eb3c
PH
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 != NULL; 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 == NULL) 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 query_socket = ip_socket((ob->protocol == ip_udp)? SOCK_DGRAM:SOCK_STREAM,
230 host_af);
231 if (query_socket < 0)
232 {
233 if (ob->optional) return PASS;
234 addr->message = string_sprintf("failed to create socket in %s router",
235 rblock->name);
236 return DEFER;
237 }
238
239 /* Connect to the remote host, under a timeout. In fact, timeouts can occur
240 here only for TCP calls; for a UDP socket, "connect" always works (the
241 router will timeout later on the read call). */
242
243 if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout) < 0)
244 {
d515a917 245 close(query_socket);
0756eb3c
PH
246 DEBUG(D_route)
247 debug_printf("connection to %s failed: %s\n", h->address,
248 strerror(errno));
249 continue;
250 }
251
252 /* Send the query. If it fails, just continue with the next address. */
253
254 if (send(query_socket, query, query_len, 0) < 0)
255 {
256 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
f1e894f3 257 (void)close(query_socket);
0756eb3c
PH
258 continue;
259 }
260
261 /* Read the response and close the socket. If the read fails, try the
262 next IP address. */
263
264 count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
f1e894f3 265 (void)close(query_socket);
0756eb3c
PH
266 if (count <= 0)
267 {
268 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
269 "timed out" : "recv failed", h->address);
270 *reply = 0;
271 continue;
272 }
273
274 /* Success; break the loop */
275
276 reply[count] = 0;
277 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
278 rblock->name, string_printing(reply), h->address);
279 break;
280 }
281
282 /* If h == NULL we have tried all the IP addresses and failed on all of them,
283 so we must continue to try more host names. Otherwise we have succeeded. */
284
285 if (h != NULL) break;
286 }
287
288
289/* If hostname is NULL, we have failed to find any host, or failed to
290connect to any of the IP addresses, or timed out while reading or writing to
291those we have connected to. In all cases, we must pass if optional and
292defer otherwise. */
293
294if (hostname == NULL)
295 {
296 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
297 if (ob->optional) return PASS;
298 addr->message = string_sprintf("%s router: failed to communicate with any "
299 "host", rblock->name);
300 return DEFER;
301 }
302
303
304/* If a response pattern was supplied, match the returned string against it. A
305failure to match causes the router to decline. After a successful match, the
306numerical variables for expanding the rerouted address are set up. */
307
308if (re != NULL)
309 {
310 if (!regex_match_and_setup(re, reply, 0, -1))
311 {
312 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
313 rblock->name, ob->response_pattern, reply);
314 return DECLINE;
315 }
316 }
317
318
319/* If no response pattern was supplied, set up $0 as the response up to the
320first white space (if any). Also, if no query was specified, check that what
321follows the white space matches user@domain. */
322
323else
324 {
325 int n = 0;
326 while (reply[n] != 0 && !isspace(reply[n])) n++;
327 expand_nmax = 0;
328 expand_nstring[0] = reply;
329 expand_nlength[0] = n;
330
331 if (ob->query == NULL)
332 {
333 int nn = n;
334 while (isspace(reply[nn])) nn++;
335 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
336 {
337 DEBUG(D_route) debug_printf("%s router: failed to match identification "
338 "in response %s\n", rblock->name, reply);
339 return DECLINE;
340 }
341 }
342
343 reply[n] = 0; /* Terminate for the default case */
344 }
345
346/* If an explicit rerouting string is specified, expand it. Otherwise, use
347what was sent back verbatim. */
348
349if (ob->reroute != NULL)
350 {
351 reroute = expand_string(ob->reroute);
352 expand_nmax = -1;
353 if (reroute == NULL)
354 {
355 addr->message = string_sprintf("%s router: failed to expand %s: %s",
356 rblock->name, ob->reroute, expand_string_message);
357 return DEFER;
358 }
359 }
360else reroute = reply;
361
362/* We should now have a new address in the form user@domain. */
363
364domain = Ustrchr(reroute, '@');
365if (domain == NULL)
366 {
367 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
368 "user@domain", rblock->name, reroute);
369 addr->message = string_sprintf("%s router: reroute string %s is not of the "
370 "form user@domain", rblock->name, reroute);
371 return DEFER;
372 }
373
374/* Create a child address with the old one as parent. Put the new address on
375the chain of new addressess. */
376
377new_addr = deliver_make_addr(reroute, TRUE);
378new_addr->parent = addr;
379
380copyflag(new_addr, addr, af_propagate);
bfe645c1 381new_addr->prop = addr->prop;
0756eb3c 382
4362ff0d
PH
383if (addr->child_count == SHRT_MAX)
384 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
385 "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
0756eb3c
PH
386addr->child_count++;
387new_addr->next = *addr_new;
388*addr_new = new_addr;
389
390/* Set up the errors address, if any, and the additional and removeable headers
391for this new address. */
392
bfe645c1 393rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
0756eb3c
PH
394if (rc != OK) return rc;
395
bfe645c1
JH
396rc = rf_get_munge_headers(addr, rblock, &new_addr->prop.extra_headers,
397 &new_addr->prop.remove_headers);
0756eb3c
PH
398if (rc != OK) return rc;
399
400return OK;
401}
402
403/* End of routers/iplookup.c */