(1) Typo in redirect router; (2) Update version number; (3) Update
[exim.git] / src / src / routers / iplookup.c
1 /* $Cambridge: exim/src/src/routers/iplookup.c,v 1.2 2005/01/04 10:00:44 ph10 Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2005 */
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
24 optionlist 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
44 address can appear in the tables drtables.c. */
45
46 int iplookup_router_options_count =
47   sizeof(iplookup_router_options)/sizeof(optionlist);
48
49 /* Default private options block for the iplookup router. */
50
51 iplookup_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
71 consistency checks to be done, or anything else that needs to be set up. */
72
73 void
74 iplookup_router_init(router_instance *rblock)
75 {
76 iplookup_router_options_block *ob =
77   (iplookup_router_options_block *)(rblock->options_block);
78
79 /* A port and a host list must be given */
80
81 if (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
85 if (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
91 if (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
101 if (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
114 DECLINE
115   . pattern or identification match on returned data failed
116
117 DEFER
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
127 PASS
128   . failed to create socket ("optional" set)
129   . failed to find a host, failed to connect, timed out ("optional" set)
130
131 OK
132   . new address added to addr_new
133 */
134
135 int
136 iplookup_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 {
146 uschar *query = NULL;
147 uschar reply[256];
148 uschar *hostname, *reroute, *domain, *listptr;
149 uschar host_buffer[256];
150 host_item *host = store_get(sizeof(host_item));
151 address_item *new_addr;
152 iplookup_router_options_block *ob =
153   (iplookup_router_options_block *)(rblock->options_block);
154 const pcre *re = ob->re_response_pattern;
155 int count, query_len, rc;
156 int sep = 0;
157
158 addr_local = addr_local;    /* Keep picky compilers happy */
159 addr_remote = addr_remote;
160 addr_succeed = addr_succeed;
161 pw = pw;
162
163 DEBUG(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
169 if (ob->query == NULL)
170   query = string_sprintf("%s@%s %s@%s", addr->local_part, addr->domain,
171     addr->local_part, addr->domain);
172 else
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
183 query_len = Ustrlen(query);
184 DEBUG(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
188 response it received. Initialization insists on the port being set and there
189 being a host list. */
190
191 listptr = ob->hosts;
192 while ((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
287 connect to any of the IP addresses, or timed out while reading or writing to
288 those we have connected to. In all cases, we must pass if optional and
289 defer otherwise. */
290
291 if (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
302 failure to match causes the router to decline. After a successful match, the
303 numerical variables for expanding the rerouted address are set up. */
304
305 if (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
317 first white space (if any). Also, if no query was specified, check that what
318 follows the white space matches user@domain. */
319
320 else
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
344 what was sent back verbatim. */
345
346 if (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   }
357 else reroute = reply;
358
359 /* We should now have a new address in the form user@domain. */
360
361 domain = Ustrchr(reroute, '@');
362 if (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
372 the chain of new addressess. */
373
374 new_addr = deliver_make_addr(reroute, TRUE);
375 new_addr->parent = addr;
376
377 copyflag(new_addr, addr, af_propagate);
378 new_addr->p = addr->p;
379
380 addr->child_count++;
381 new_addr->next = *addr_new;
382 *addr_new = new_addr;
383
384 /* Set up the errors address, if any, and the additional and removeable headers
385 for this new address. */
386
387 rc = rf_get_errors_address(addr, rblock, verify, &(new_addr->p.errors_address));
388 if (rc != OK) return rc;
389
390 rc = rf_get_munge_headers(addr, rblock, &(new_addr->p.extra_headers),
391   &(new_addr->p.remove_headers));
392 if (rc != OK) return rc;
393
394 return OK;
395 }
396
397 /* End of routers/iplookup.c */