Start
[exim.git] / src / src / routers / iplookup.c
CommitLineData
0756eb3c
PH
1/* $Cambridge: exim/src/src/routers/iplookup.c,v 1.1 2004/10/07 13:10:02 ph10 Exp $ */
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
7/* Copyright (c) University of Cambridge 1995 - 2004 */
8/* See the file NOTICE for conditions of use and distribution. */
9
10
11#include "../exim.h"
12#include "rf_functions.h"
13#include "iplookup.h"
14
15
16/* IP connection types */
17
18#define ip_udp 0
19#define ip_tcp 1
20
21
22/* Options specific to the iplookup router. */
23
24optionlist iplookup_router_options[] = {
25 { "hosts", opt_stringptr,
26 (void *)(offsetof(iplookup_router_options_block, hosts)) },
27 { "optional", opt_bool,
28 (void *)(offsetof(iplookup_router_options_block, optional)) },
29 { "port", opt_int,
30 (void *)(offsetof(iplookup_router_options_block, port)) },
31 { "protocol", opt_stringptr,
32 (void *)(offsetof(iplookup_router_options_block, protocol_name)) },
33 { "query", opt_stringptr,
34 (void *)(offsetof(iplookup_router_options_block, query)) },
35 { "reroute", opt_stringptr,
36 (void *)(offsetof(iplookup_router_options_block, reroute)) },
37 { "response_pattern", opt_stringptr,
38 (void *)(offsetof(iplookup_router_options_block, response_pattern)) },
39 { "timeout", opt_time,
40 (void *)(offsetof(iplookup_router_options_block, timeout)) }
41};
42
43/* Size of the options list. An extern variable has to be used so that its
44address can appear in the tables drtables.c. */
45
46int iplookup_router_options_count =
47 sizeof(iplookup_router_options)/sizeof(optionlist);
48
49/* Default private options block for the iplookup router. */
50
51iplookup_router_options_block iplookup_router_option_defaults = {
52 -1, /* port */
53 ip_udp, /* protocol */
54 5, /* timeout */
55 NULL, /* protocol_name */
56 NULL, /* hosts */
57 NULL, /* query; NULL => local_part@domain */
58 NULL, /* response_pattern; NULL => don't apply regex */
59 NULL, /* reroute; NULL => just used returned data */
60 NULL, /* re_response_pattern; compiled pattern */
61 FALSE /* optional */
62};
63
64
65
66/*************************************************
67* Initialization entry point *
68*************************************************/
69
70/* Called for each instance, after its options have been read, to enable
71consistency checks to be done, or anything else that needs to be set up. */
72
73void
74iplookup_router_init(router_instance *rblock)
75{
76iplookup_router_options_block *ob =
77 (iplookup_router_options_block *)(rblock->options_block);
78
79/* A port and a host list must be given */
80
81if (ob->port < 0)
82 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
83 "a port must be specified", rblock->name);
84
85if (ob->hosts == NULL)
86 log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
87 "a host list must be specified", rblock->name);
88
89/* Translate protocol name into value */
90
91if (ob->protocol_name != NULL)
92 {
93 if (Ustrcmp(ob->protocol_name, "udp") == 0) ob->protocol = ip_udp;
94 else if (Ustrcmp(ob->protocol_name, "tcp") == 0) ob->protocol = ip_tcp;
95 else log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
96 "protocol not specified as udp or tcp", rblock->name);
97 }
98
99/* If a response pattern is given, compile it now to get the error early. */
100
101if (ob->response_pattern != NULL)
102 ob->re_response_pattern =
103 regex_must_compile(ob->response_pattern, FALSE, TRUE);
104}
105
106
107
108/*************************************************
109* Main entry point *
110*************************************************/
111
112/* See local README for interface details. This router returns:
113
114DECLINE
115 . pattern or identification match on returned data failed
116
117DEFER
118 . failed to expand the query or rerouting string
119 . failed to create socket ("optional" not set)
120 . failed to find a host, failed to connect, timed out ("optional" not set)
121 . rerouting string is not in the form localpart@domain
122 . verifying the errors address caused a deferment or a big disaster such
123 as an expansion failure (rf_get_errors_address)
124 . expanding a headers_{add,remove} string caused a deferment or another
125 expansion error (rf_get_munge_headers)
126
127PASS
128 . failed to create socket ("optional" set)
129 . failed to find a host, failed to connect, timed out ("optional" set)
130
131OK
132 . new address added to addr_new
133*/
134
135int
136iplookup_router_entry(
137 router_instance *rblock, /* data for this instantiation */
138 address_item *addr, /* address we are working on */
139 struct passwd *pw, /* passwd entry after check_local_user */
140 BOOL verify, /* TRUE when verifying */
141 address_item **addr_local, /* add it to this if it's local */
142 address_item **addr_remote, /* add it to this if it's remote */
143 address_item **addr_new, /* put new addresses on here */
144 address_item **addr_succeed) /* put old address here on success */
145{
146uschar *query = NULL;
147uschar reply[256];
148uschar *hostname, *reroute, *domain, *listptr;
149uschar host_buffer[256];
150host_item *host = store_get(sizeof(host_item));
151address_item *new_addr;
152iplookup_router_options_block *ob =
153 (iplookup_router_options_block *)(rblock->options_block);
154const pcre *re = ob->re_response_pattern;
155int count, query_len, rc;
156int sep = 0;
157
158addr_local = addr_local; /* Keep picky compilers happy */
159addr_remote = addr_remote;
160addr_succeed = addr_succeed;
161pw = pw;
162
163DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n",
164 rblock->name, addr->address, addr->domain);
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))
206 host->address = host->name;
207 else
208 {
209 int rc = host_find_byname(host, NULL, 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 DEBUG(D_route)
244 debug_printf("connection to %s failed: %s\n", h->address,
245 strerror(errno));
246 continue;
247 }
248
249 /* Send the query. If it fails, just continue with the next address. */
250
251 if (send(query_socket, query, query_len, 0) < 0)
252 {
253 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
254 close(query_socket);
255 continue;
256 }
257
258 /* Read the response and close the socket. If the read fails, try the
259 next IP address. */
260
261 count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
262 close(query_socket);
263 if (count <= 0)
264 {
265 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
266 "timed out" : "recv failed", h->address);
267 *reply = 0;
268 continue;
269 }
270
271 /* Success; break the loop */
272
273 reply[count] = 0;
274 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
275 rblock->name, string_printing(reply), h->address);
276 break;
277 }
278
279 /* If h == NULL we have tried all the IP addresses and failed on all of them,
280 so we must continue to try more host names. Otherwise we have succeeded. */
281
282 if (h != NULL) break;
283 }
284
285
286/* If hostname is NULL, we have failed to find any host, or failed to
287connect to any of the IP addresses, or timed out while reading or writing to
288those we have connected to. In all cases, we must pass if optional and
289defer otherwise. */
290
291if (hostname == NULL)
292 {
293 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
294 if (ob->optional) return PASS;
295 addr->message = string_sprintf("%s router: failed to communicate with any "
296 "host", rblock->name);
297 return DEFER;
298 }
299
300
301/* If a response pattern was supplied, match the returned string against it. A
302failure to match causes the router to decline. After a successful match, the
303numerical variables for expanding the rerouted address are set up. */
304
305if (re != NULL)
306 {
307 if (!regex_match_and_setup(re, reply, 0, -1))
308 {
309 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
310 rblock->name, ob->response_pattern, reply);
311 return DECLINE;
312 }
313 }
314
315
316/* If no response pattern was supplied, set up $0 as the response up to the
317first white space (if any). Also, if no query was specified, check that what
318follows the white space matches user@domain. */
319
320else
321 {
322 int n = 0;
323 while (reply[n] != 0 && !isspace(reply[n])) n++;
324 expand_nmax = 0;
325 expand_nstring[0] = reply;
326 expand_nlength[0] = n;
327
328 if (ob->query == NULL)
329 {
330 int nn = n;
331 while (isspace(reply[nn])) nn++;
332 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
333 {
334 DEBUG(D_route) debug_printf("%s router: failed to match identification "
335 "in response %s\n", rblock->name, reply);
336 return DECLINE;
337 }
338 }
339
340 reply[n] = 0; /* Terminate for the default case */
341 }
342
343/* If an explicit rerouting string is specified, expand it. Otherwise, use
344what was sent back verbatim. */
345
346if (ob->reroute != NULL)
347 {
348 reroute = expand_string(ob->reroute);
349 expand_nmax = -1;
350 if (reroute == NULL)
351 {
352 addr->message = string_sprintf("%s router: failed to expand %s: %s",
353 rblock->name, ob->reroute, expand_string_message);
354 return DEFER;
355 }
356 }
357else reroute = reply;
358
359/* We should now have a new address in the form user@domain. */
360
361domain = Ustrchr(reroute, '@');
362if (domain == NULL)
363 {
364 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
365 "user@domain", rblock->name, reroute);
366 addr->message = string_sprintf("%s router: reroute string %s is not of the "
367 "form user@domain", rblock->name, reroute);
368 return DEFER;
369 }
370
371/* Create a child address with the old one as parent. Put the new address on
372the chain of new addressess. */
373
374new_addr = deliver_make_addr(reroute, TRUE);
375new_addr->parent = addr;
376
377copyflag(new_addr, addr, af_propagate);
378new_addr->p = addr->p;
379
380addr->child_count++;
381new_addr->next = *addr_new;
382*addr_new = new_addr;
383
384/* Set up the errors address, if any, and the additional and removeable headers
385for this new address. */
386
387rc = rf_get_errors_address(addr, rblock, verify, &(new_addr->p.errors_address));
388if (rc != OK) return rc;
389
390rc = rf_get_munge_headers(addr, rblock, &(new_addr->p.extra_headers),
391 &(new_addr->p.remove_headers));
392if (rc != OK) return rc;
393
394return OK;
395}
396
397/* End of routers/iplookup.c */