Import Upstream version 4.84.2
[hcoop/debian/exim4.git] / src / routers / iplookup.c
CommitLineData
420a0d19
CE
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) University of Cambridge 1995 - 2009 */
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 */
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{
144uschar *query = NULL;
145uschar *reply;
146uschar *hostname, *reroute, *domain, *listptr;
147uschar host_buffer[256];
148host_item *host = store_get(sizeof(host_item));
149address_item *new_addr;
150iplookup_router_options_block *ob =
151 (iplookup_router_options_block *)(rblock->options_block);
152const pcre *re = ob->re_response_pattern;
153int count, query_len, rc;
154int sep = 0;
155
156addr_local = addr_local; /* Keep picky compilers happy */
157addr_remote = addr_remote;
158addr_succeed = addr_succeed;
159pw = pw;
160
161DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
162 rblock->name, addr->address, addr->domain);
163
164reply = store_get(256);
165
166/* Build the query string to send. If not explicitly given, a default of
167"user@domain user@domain" is used. */
168
169if (ob->query == NULL)
170 query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
171 addr->local_part, addr->domain);
172else
173 {
174 query = expand_string(ob->query);
175 if (query == NULL)
176 {
177 addr->message = string_sprintf("%s router: failed to expand %s: %s",
178 rblock->name, ob->query, expand_string_message);
179 return DEFER;
180 }
181 }
182
183query_len = Ustrlen(query);
184DEBUG(D_route) debug_printf("%s router query is \"%s\"\n", rblock->name,
185 string_printing(query));
186
187/* Now connect to the required port for each of the hosts in turn, until a
188response it received. Initialization insists on the port being set and there
189being a host list. */
190
191listptr = ob->hosts;
192while ((hostname = string_nextinlist(&listptr, &sep, host_buffer,
193 sizeof(host_buffer))) != NULL)
194 {
195 host_item *h;
196
197 DEBUG(D_route) debug_printf("calling host %s\n", hostname);
198
199 host->name = hostname;
200 host->address = NULL;
201 host->port = PORT_NONE;
202 host->mx = MX_NONE;
203 host->next = NULL;
204
205 if (string_is_ip_address(host->name, NULL) != 0)
206 host->address = host->name;
207 else
208 {
209 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
210 if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
211 }
212
213 /* Loop for possible multiple IP addresses for the given name. */
214
215 for (h = host; h != NULL; h = h->next)
216 {
217 int host_af, query_socket;
218
219 /* Skip any hosts for which we have no address */
220
221 if (h->address == NULL) continue;
222
223 /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
224 detected by checking for a colon in the address. */
225
226 host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
227 query_socket = ip_socket((ob->protocol == ip_udp)? SOCK_DGRAM:SOCK_STREAM,
228 host_af);
229 if (query_socket < 0)
230 {
231 if (ob->optional) return PASS;
232 addr->message = string_sprintf("failed to create socket in %s router",
233 rblock->name);
234 return DEFER;
235 }
236
237 /* Connect to the remote host, under a timeout. In fact, timeouts can occur
238 here only for TCP calls; for a UDP socket, "connect" always works (the
239 router will timeout later on the read call). */
240
241 if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout) < 0)
242 {
243 close(query_socket);
244 DEBUG(D_route)
245 debug_printf("connection to %s failed: %s\n", h->address,
246 strerror(errno));
247 continue;
248 }
249
250 /* Send the query. If it fails, just continue with the next address. */
251
252 if (send(query_socket, query, query_len, 0) < 0)
253 {
254 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
255 (void)close(query_socket);
256 continue;
257 }
258
259 /* Read the response and close the socket. If the read fails, try the
260 next IP address. */
261
262 count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
263 (void)close(query_socket);
264 if (count <= 0)
265 {
266 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
267 "timed out" : "recv failed", h->address);
268 *reply = 0;
269 continue;
270 }
271
272 /* Success; break the loop */
273
274 reply[count] = 0;
275 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
276 rblock->name, string_printing(reply), h->address);
277 break;
278 }
279
280 /* If h == NULL we have tried all the IP addresses and failed on all of them,
281 so we must continue to try more host names. Otherwise we have succeeded. */
282
283 if (h != NULL) break;
284 }
285
286
287/* If hostname is NULL, we have failed to find any host, or failed to
288connect to any of the IP addresses, or timed out while reading or writing to
289those we have connected to. In all cases, we must pass if optional and
290defer otherwise. */
291
292if (hostname == NULL)
293 {
294 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
295 if (ob->optional) return PASS;
296 addr->message = string_sprintf("%s router: failed to communicate with any "
297 "host", rblock->name);
298 return DEFER;
299 }
300
301
302/* If a response pattern was supplied, match the returned string against it. A
303failure to match causes the router to decline. After a successful match, the
304numerical variables for expanding the rerouted address are set up. */
305
306if (re != NULL)
307 {
308 if (!regex_match_and_setup(re, reply, 0, -1))
309 {
310 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
311 rblock->name, ob->response_pattern, reply);
312 return DECLINE;
313 }
314 }
315
316
317/* If no response pattern was supplied, set up $0 as the response up to the
318first white space (if any). Also, if no query was specified, check that what
319follows the white space matches user@domain. */
320
321else
322 {
323 int n = 0;
324 while (reply[n] != 0 && !isspace(reply[n])) n++;
325 expand_nmax = 0;
326 expand_nstring[0] = reply;
327 expand_nlength[0] = n;
328
329 if (ob->query == NULL)
330 {
331 int nn = n;
332 while (isspace(reply[nn])) nn++;
333 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
334 {
335 DEBUG(D_route) debug_printf("%s router: failed to match identification "
336 "in response %s\n", rblock->name, reply);
337 return DECLINE;
338 }
339 }
340
341 reply[n] = 0; /* Terminate for the default case */
342 }
343
344/* If an explicit rerouting string is specified, expand it. Otherwise, use
345what was sent back verbatim. */
346
347if (ob->reroute != NULL)
348 {
349 reroute = expand_string(ob->reroute);
350 expand_nmax = -1;
351 if (reroute == NULL)
352 {
353 addr->message = string_sprintf("%s router: failed to expand %s: %s",
354 rblock->name, ob->reroute, expand_string_message);
355 return DEFER;
356 }
357 }
358else reroute = reply;
359
360/* We should now have a new address in the form user@domain. */
361
362domain = Ustrchr(reroute, '@');
363if (domain == NULL)
364 {
365 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
366 "user@domain", rblock->name, reroute);
367 addr->message = string_sprintf("%s router: reroute string %s is not of the "
368 "form user@domain", rblock->name, reroute);
369 return DEFER;
370 }
371
372/* Create a child address with the old one as parent. Put the new address on
373the chain of new addressess. */
374
375new_addr = deliver_make_addr(reroute, TRUE);
376new_addr->parent = addr;
377
378copyflag(new_addr, addr, af_propagate);
379new_addr->p = addr->p;
380
381if (addr->child_count == SHRT_MAX)
382 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
383 "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
384addr->child_count++;
385new_addr->next = *addr_new;
386*addr_new = new_addr;
387
388/* Set up the errors address, if any, and the additional and removeable headers
389for this new address. */
390
391rc = rf_get_errors_address(addr, rblock, verify, &(new_addr->p.errors_address));
392if (rc != OK) return rc;
393
394rc = rf_get_munge_headers(addr, rblock, &(new_addr->p.extra_headers),
395 &(new_addr->p.remove_headers));
396if (rc != OK) return rc;
397
398return OK;
399}
400
401/* End of routers/iplookup.c */