constification
[exim.git] / src / src / routers / iplookup.c
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
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))) != 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
206 if (string_is_ip_address(host->name, NULL) != 0)
207 host->address = host->name;
208 else
209 {
210 int rc = host_find_byname(host, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, TRUE);
211 if (rc == HOST_FIND_FAILED || rc == HOST_FIND_AGAIN) continue;
212 }
213
214 /* Loop for possible multiple IP addresses for the given name. */
215
216 for (h = host; h != NULL; h = h->next)
217 {
218 int host_af, query_socket;
219
220 /* Skip any hosts for which we have no address */
221
222 if (h->address == NULL) continue;
223
224 /* Create a socket, for UDP or TCP, as configured. IPv6 addresses are
225 detected by checking for a colon in the address. */
226
227 host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
228 query_socket = ip_socket((ob->protocol == ip_udp)? SOCK_DGRAM:SOCK_STREAM,
229 host_af);
230 if (query_socket < 0)
231 {
232 if (ob->optional) return PASS;
233 addr->message = string_sprintf("failed to create socket in %s router",
234 rblock->name);
235 return DEFER;
236 }
237
238 /* Connect to the remote host, under a timeout. In fact, timeouts can occur
239 here only for TCP calls; for a UDP socket, "connect" always works (the
240 router will timeout later on the read call). */
241
242 if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout) < 0)
243 {
244 close(query_socket);
245 DEBUG(D_route)
246 debug_printf("connection to %s failed: %s\n", h->address,
247 strerror(errno));
248 continue;
249 }
250
251 /* Send the query. If it fails, just continue with the next address. */
252
253 if (send(query_socket, query, query_len, 0) < 0)
254 {
255 DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
256 (void)close(query_socket);
257 continue;
258 }
259
260 /* Read the response and close the socket. If the read fails, try the
261 next IP address. */
262
263 count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
264 (void)close(query_socket);
265 if (count <= 0)
266 {
267 DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
268 "timed out" : "recv failed", h->address);
269 *reply = 0;
270 continue;
271 }
272
273 /* Success; break the loop */
274
275 reply[count] = 0;
276 DEBUG(D_route) debug_printf("%s router received \"%s\" from %s\n",
277 rblock->name, string_printing(reply), h->address);
278 break;
279 }
280
281 /* If h == NULL we have tried all the IP addresses and failed on all of them,
282 so we must continue to try more host names. Otherwise we have succeeded. */
283
284 if (h != NULL) break;
285 }
286
287
288 /* If hostname is NULL, we have failed to find any host, or failed to
289 connect to any of the IP addresses, or timed out while reading or writing to
290 those we have connected to. In all cases, we must pass if optional and
291 defer otherwise. */
292
293 if (hostname == NULL)
294 {
295 DEBUG(D_route) debug_printf("%s router failed to get anything\n", rblock->name);
296 if (ob->optional) return PASS;
297 addr->message = string_sprintf("%s router: failed to communicate with any "
298 "host", rblock->name);
299 return DEFER;
300 }
301
302
303 /* If a response pattern was supplied, match the returned string against it. A
304 failure to match causes the router to decline. After a successful match, the
305 numerical variables for expanding the rerouted address are set up. */
306
307 if (re != NULL)
308 {
309 if (!regex_match_and_setup(re, reply, 0, -1))
310 {
311 DEBUG(D_route) debug_printf("%s router: %s failed to match response %s\n",
312 rblock->name, ob->response_pattern, reply);
313 return DECLINE;
314 }
315 }
316
317
318 /* If no response pattern was supplied, set up $0 as the response up to the
319 first white space (if any). Also, if no query was specified, check that what
320 follows the white space matches user@domain. */
321
322 else
323 {
324 int n = 0;
325 while (reply[n] != 0 && !isspace(reply[n])) n++;
326 expand_nmax = 0;
327 expand_nstring[0] = reply;
328 expand_nlength[0] = n;
329
330 if (ob->query == NULL)
331 {
332 int nn = n;
333 while (isspace(reply[nn])) nn++;
334 if (Ustrcmp(query + query_len/2 + 1, reply+nn) != 0)
335 {
336 DEBUG(D_route) debug_printf("%s router: failed to match identification "
337 "in response %s\n", rblock->name, reply);
338 return DECLINE;
339 }
340 }
341
342 reply[n] = 0; /* Terminate for the default case */
343 }
344
345 /* If an explicit rerouting string is specified, expand it. Otherwise, use
346 what was sent back verbatim. */
347
348 if (ob->reroute != NULL)
349 {
350 reroute = expand_string(ob->reroute);
351 expand_nmax = -1;
352 if (reroute == NULL)
353 {
354 addr->message = string_sprintf("%s router: failed to expand %s: %s",
355 rblock->name, ob->reroute, expand_string_message);
356 return DEFER;
357 }
358 }
359 else reroute = reply;
360
361 /* We should now have a new address in the form user@domain. */
362
363 domain = Ustrchr(reroute, '@');
364 if (domain == NULL)
365 {
366 log_write(0, LOG_MAIN, "%s router: reroute string %s is not of the form "
367 "user@domain", rblock->name, reroute);
368 addr->message = string_sprintf("%s router: reroute string %s is not of the "
369 "form user@domain", rblock->name, reroute);
370 return DEFER;
371 }
372
373 /* Create a child address with the old one as parent. Put the new address on
374 the chain of new addressess. */
375
376 new_addr = deliver_make_addr(reroute, TRUE);
377 new_addr->parent = addr;
378
379 copyflag(new_addr, addr, af_propagate);
380 new_addr->p = addr->p;
381
382 if (addr->child_count == SHRT_MAX)
383 log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
384 "child addresses for <%s>", rblock->name, SHRT_MAX, addr->address);
385 addr->child_count++;
386 new_addr->next = *addr_new;
387 *addr_new = new_addr;
388
389 /* Set up the errors address, if any, and the additional and removeable headers
390 for this new address. */
391
392 rc = rf_get_errors_address(addr, rblock, verify, &(new_addr->p.errors_address));
393 if (rc != OK) return rc;
394
395 rc = rf_get_munge_headers(addr, rblock, &(new_addr->p.extra_headers),
396 &(new_addr->p.remove_headers));
397 if (rc != OK) return rc;
398
399 return OK;
400 }
401
402 /* End of routers/iplookup.c */