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