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