blob: a895a5e90a9c05c496ea3f8fe8dd683d9803c587 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.net.spi.nameservice.dns;
27
28import java.lang.ref.SoftReference;
29import java.net.InetAddress;
30import java.net.UnknownHostException;
31import javax.naming.*;
32import javax.naming.directory.*;
33import javax.naming.spi.NamingManager;
34import java.util.*;
35import sun.net.util.IPAddressUtil;
36import sun.net.dns.ResolverConfiguration;
37import sun.net.spi.nameservice.*;
38import java.security.AccessController;
39import sun.security.action.*;
40
41/*
42 * A name service provider based on JNDI-DNS.
43 */
44
45public final class DNSNameService implements NameService {
46
47 // List of domains specified by property
48 private LinkedList domainList = null;
49
50 // JNDI-DNS URL for name servers specified via property
51 private String nameProviderUrl = null;
52
53 // Per-thread soft cache of the last temporary context
54 private static ThreadLocal contextRef = new ThreadLocal();
55
56 // Simple class to encapsulate the temporary context
57 private static class ThreadContext {
58 private DirContext dirCtxt;
59 private List nsList;
60
61 public ThreadContext(DirContext dirCtxt, List nsList) {
62 this.dirCtxt = dirCtxt;
63 this.nsList = nsList;
64 }
65
66 public DirContext dirContext() {
67 return dirCtxt;
68 }
69
70 public List nameservers() {
71 return nsList;
72 }
73 }
74
75 // Returns a per-thread DirContext
76 private DirContext getTemporaryContext() throws NamingException {
77 SoftReference ref = (SoftReference)contextRef.get();
78 ThreadContext thrCtxt = null;
79 List nsList = null;
80
81 // if no property specified we need to obtain the list of servers
82 //
83 if (nameProviderUrl == null)
84 nsList = ResolverConfiguration.open().nameservers();
85
86 // if soft reference hasn't been gc'ed no property has been
87 // specified then we need to check if the DNS configuration
88 // has changed.
89 //
90 if ((ref != null) && ((thrCtxt = (ThreadContext)ref.get()) != null)) {
91 if (nameProviderUrl == null) {
92 if (!thrCtxt.nameservers().equals(nsList)) {
93 // DNS configuration has changed
94 thrCtxt = null;
95 }
96 }
97 }
98
99 // new thread context needs to be created
100 if (thrCtxt == null) {
101 final Hashtable<String,Object> env = new Hashtable<String,Object>();
102 env.put("java.naming.factory.initial",
103 "com.sun.jndi.dns.DnsContextFactory");
104
105 // If no nameservers property specified we create provider URL
106 // based on system configured name servers
107 //
108 String provUrl = nameProviderUrl;
109 if (provUrl == null) {
110 provUrl = createProviderURL(nsList);
111 if (provUrl.length() == 0) {
112 throw new RuntimeException("bad nameserver configuration");
113 }
114 }
115 env.put("java.naming.provider.url", provUrl);
116
117 // Need to create directory context in privileged block
118 // as JNDI-DNS needs to resolve the name servers.
119 //
120 DirContext dirCtxt;
121 try {
122 dirCtxt = (DirContext)
123 java.security.AccessController.doPrivileged(
124 new java.security.PrivilegedExceptionAction() {
125 public Object run() throws NamingException {
126 // Create the DNS context using NamingManager rather than using
127 // the initial context constructor. This avoids having the initial
128 // context constructor call itself.
129 Context ctx = NamingManager.getInitialContext(env);
130 if (!(ctx instanceof DirContext)) {
131 return null; // cannot create a DNS context
132 }
133 return ctx;
134 }
135 });
136 } catch (java.security.PrivilegedActionException pae) {
137 throw (NamingException)pae.getException();
138 }
139
140 // create new soft reference to our thread context
141 //
142 thrCtxt = new ThreadContext(dirCtxt, nsList);
143 contextRef.set(new SoftReference(thrCtxt));
144 }
145
146 return thrCtxt.dirContext();
147 }
148
149 /**
150 * Resolves the specified entry in DNS.
151 *
152 * Canonical name records are recursively resolved (to a maximum
153 * of 5 to avoid performance hit and potential CNAME loops).
154 *
155 * @param ctx JNDI directory context
156 * @param name name to resolve
157 * @param ids record types to search
158 * @param depth call depth - pass as 0.
159 *
160 * @return array list with results (will have at least on entry)
161 *
162 * @throws UnknownHostException if lookup fails or other error.
163 */
164 private ArrayList resolve(final DirContext ctx, final String name, final String[] ids,
165 int depth) throws UnknownHostException
166 {
167 ArrayList results = new ArrayList();
168 Attributes attrs;
169
170 // do the query
171 try {
172 attrs = (Attributes)
173 java.security.AccessController.doPrivileged(
174 new java.security.PrivilegedExceptionAction() {
175 public Object run() throws NamingException {
176 return ctx.getAttributes(name, ids);
177 }
178 });
179 } catch (java.security.PrivilegedActionException pae) {
180 throw new UnknownHostException(pae.getException().getMessage());
181 }
182
183 // non-requested type returned so enumeration is empty
184 NamingEnumeration ne = attrs.getAll();
185 if (!ne.hasMoreElements()) {
186 throw new UnknownHostException("DNS record not found");
187 }
188
189 // iterate through the returned attributes
190 UnknownHostException uhe = null;
191 try {
192 while (ne.hasMoreElements()) {
193 Attribute attr = (Attribute)ne.next();
194 String attrID = attr.getID();
195
196 for (NamingEnumeration e = attr.getAll(); e.hasMoreElements();) {
197 String addr = (String)e.next();
198
199 // for canoncical name records do recursive lookup
200 // - also check for CNAME loops to avoid stack overflow
201
202 if (attrID.equals("CNAME")) {
203 if (depth > 4) {
204 throw new UnknownHostException(name + ": possible CNAME loop");
205 }
206 try {
207 results.addAll(resolve(ctx, addr, ids, depth+1));
208 } catch (UnknownHostException x) {
209 // canonical name can't be resolved.
210 if (uhe == null)
211 uhe = x;
212 }
213 } else {
214 results.add(addr);
215 }
216 }
217 }
218 } catch (NamingException nx) {
219 throw new UnknownHostException(nx.getMessage());
220 }
221
222 // pending exception as canonical name could not be resolved.
223 if (results.isEmpty() && uhe != null) {
224 throw uhe;
225 }
226
227 return results;
228 }
229
230 public DNSNameService() throws Exception {
231
232 // default domain
233 String domain = AccessController.doPrivileged(
234 new GetPropertyAction("sun.net.spi.nameservice.domain"));
235 if (domain != null && domain.length() > 0) {
236 domainList = new LinkedList();
237 domainList.add(domain);
238 }
239
240 // name servers
241 String nameservers = AccessController.doPrivileged(
242 new GetPropertyAction("sun.net.spi.nameservice.nameservers"));
243 if (nameservers != null && nameservers.length() > 0) {
244 nameProviderUrl = createProviderURL(nameservers);
245 if (nameProviderUrl.length() == 0) {
246 throw new RuntimeException("malformed nameservers property");
247 }
248
249 } else {
250
251 // no property specified so check host DNS resolver configured
252 // with at least one nameserver in dotted notation.
253 //
254 List nsList = ResolverConfiguration.open().nameservers();
255 if (nsList.size() == 0)
256 throw new RuntimeException("no nameservers provided");
257 boolean found = false;
258 Iterator i = nsList.iterator();
259 while (i.hasNext()) {
260 String addr = (String)i.next();
261 if (IPAddressUtil.isIPv4LiteralAddress(addr) ||
262 IPAddressUtil.isIPv6LiteralAddress(addr)) {
263 found = true;
264 break;
265 }
266 }
267 if (!found) {
268 throw new RuntimeException("bad nameserver configuration");
269 }
270 }
271 }
272
273 public InetAddress[] lookupAllHostAddr(String host) throws UnknownHostException {
274
275 // DNS records that we search for
276 String[] ids = {"A", "AAAA", "CNAME"};
277
278 // first get directory context
279 DirContext ctx;
280 try {
281 ctx = getTemporaryContext();
282 } catch (NamingException nx) {
283 throw new Error(nx);
284 }
285
286 ArrayList results = null;
287 UnknownHostException uhe = null;
288
289 // If host already contains a domain name then just look it up
290 if (host.indexOf('.') >= 0) {
291 try {
292 results = resolve(ctx, host, ids, 0);
293 } catch (UnknownHostException x) {
294 uhe = x;
295 }
296 }
297
298 // Here we try to resolve the host using the domain suffix or
299 // the domain suffix search list. If the host cannot be resolved
300 // using the domain suffix then we attempt devolution of
301 // the suffix - eg: if we are searching for "foo" and our
302 // domain suffix is "eng.sun.com" we will try to resolve
303 // "foo.eng.sun.com" and "foo.sun.com".
304 // It's not normal to attempt devolation with domains on the
305 // domain suffix search list - however as ResolverConfiguration
306 // doesn't distinguish domain or search list in the list it
307 // returns we approximate by doing devolution on the domain
308 // suffix if the list has one entry.
309
310 if (results == null) {
311 List searchList = null;
312 Iterator i;
313 boolean usingSearchList = false;
314
315 if (domainList != null) {
316 i = domainList.iterator();
317 } else {
318 searchList = ResolverConfiguration.open().searchlist();
319 if (searchList.size() > 1) {
320 usingSearchList = true;
321 }
322 i = searchList.iterator();
323 }
324
325 // iterator through each domain suffix
326 while (i.hasNext()) {
327 String parentDomain = (String)i.next();
328 int start = 0;
329 while ((start = parentDomain.indexOf(".")) != -1
330 && start < parentDomain.length() -1) {
331 try {
332 results = resolve(ctx, host+"."+parentDomain, ids, 0);
333 break;
334 } catch (UnknownHostException x) {
335 uhe = x;
336 if (usingSearchList) {
337 break;
338 }
339
340 // devolve
341 parentDomain = parentDomain.substring(start+1);
342 }
343 }
344 if (results != null) {
345 break;
346 }
347 }
348 }
349
350 // finally try the host if it doesn't have a domain name
351 if (results == null && (host.indexOf('.') < 0)) {
352 results = resolve(ctx, host, ids, 0);
353 }
354
355 // if not found then throw the (last) exception thrown.
356 if (results == null) {
357 assert uhe != null;
358 throw uhe;
359 }
360
361 /**
362 * Convert the array list into a byte aray list - this
363 * filters out any invalid IPv4/IPv6 addresses.
364 */
365 assert results.size() > 0;
366 InetAddress[] addrs = new InetAddress[results.size()];
367 int count = 0;
368 for (int i=0; i<results.size(); i++) {
369 String addrString = (String)results.get(i);
370 byte addr[] = IPAddressUtil.textToNumericFormatV4(addrString);
371 if (addr == null) {
372 addr = IPAddressUtil.textToNumericFormatV6(addrString);
373 }
374 if (addr != null) {
375 addrs[count++] = InetAddress.getByAddress(host, addr);
376 }
377 }
378
379 /**
380 * If addresses are filtered then we need to resize the
381 * array. Additionally if all addresses are filtered then
382 * we throw an exception.
383 */
384 if (count == 0) {
385 throw new UnknownHostException(host + ": no valid DNS records");
386 }
387 if (count < results.size()) {
388 InetAddress[] tmp = new InetAddress[count];
389 for (int i=0; i<count; i++) {
390 tmp[i] = addrs[i];
391 }
392 addrs = tmp;
393 }
394
395 return addrs;
396 }
397
398 /**
399 * Reverse lookup code. I.E: find a host name from an IP address.
400 * IPv4 addresses are mapped in the IN-ADDR.ARPA. top domain, while
401 * IPv6 addresses can be in IP6.ARPA or IP6.INT.
402 * In both cases the address has to be converted into a dotted form.
403 */
404 public String getHostByAddr(byte[] addr) throws UnknownHostException {
405 String host = null;
406 try {
407 String literalip = "";
408 String[] ids = { "PTR" };
409 DirContext ctx;
410 ArrayList results = null;
411 try {
412 ctx = getTemporaryContext();
413 } catch (NamingException nx) {
414 throw new Error(nx);
415 }
416 if (addr.length == 4) { // IPv4 Address
417 for (int i = addr.length-1; i >= 0; i--) {
418 literalip += (addr[i] & 0xff) +".";
419 }
420 literalip += "IN-ADDR.ARPA.";
421
422 results = resolve(ctx, literalip, ids, 0);
423 host = (String)results.get(0);
424 } else if (addr.length == 16) { // IPv6 Address
425 /**
426 * Because RFC 3152 changed the root domain name for reverse
427 * lookups from IP6.INT. to IP6.ARPA., we need to check
428 * both. I.E. first the new one, IP6.ARPA, then if it fails
429 * the older one, IP6.INT
430 */
431
432 for (int i = addr.length-1; i >= 0; i--) {
433 literalip += Integer.toHexString((addr[i] & 0x0f)) +"."
434 +Integer.toHexString((addr[i] & 0xf0) >> 4) +".";
435 }
436 String ip6lit = literalip + "IP6.ARPA.";
437
438 try {
439 results = resolve(ctx, ip6lit, ids, 0);
440 host = (String)results.get(0);
441 } catch (UnknownHostException e) {
442 host = null;
443 }
444 if (host == null) {
445 // IP6.ARPA lookup failed, let's try the older IP6.INT
446 ip6lit = literalip + "IP6.INT.";
447 results = resolve(ctx, ip6lit, ids, 0);
448 host = (String)results.get(0);
449 }
450 }
451 } catch (Exception e) {
452 throw new UnknownHostException(e.getMessage());
453 }
454 // Either we couldn't find it or the address was neither IPv4 or IPv6
455 if (host == null)
456 throw new UnknownHostException();
457 // remove trailing dot
458 if (host.endsWith(".")) {
459 host = host.substring(0, host.length() - 1);
460 }
461 return host;
462 }
463
464
465 // ---------
466
467 private static void appendIfLiteralAddress(String addr, StringBuffer sb) {
468 if (IPAddressUtil.isIPv4LiteralAddress(addr)) {
469 sb.append("dns://" + addr + " ");
470 } else {
471 if (IPAddressUtil.isIPv6LiteralAddress(addr)) {
472 sb.append("dns://[" + addr + "] ");
473 }
474 }
475 }
476
477 /*
478 * @return String containing the JNDI-DNS provider URL
479 * corresponding to the supplied List of nameservers.
480 */
481 private static String createProviderURL(List nsList) {
482 Iterator i = nsList.iterator();
483 StringBuffer sb = new StringBuffer();
484 while (i.hasNext()) {
485 appendIfLiteralAddress((String)i.next(), sb);
486 }
487 return sb.toString();
488 }
489
490 /*
491 * @return String containing the JNDI-DNS provider URL
492 * corresponding to the list of nameservers
493 * contained in the provided str.
494 */
495 private static String createProviderURL(String str) {
496 StringBuffer sb = new StringBuffer();
497 StringTokenizer st = new StringTokenizer(str, ",");
498 while (st.hasMoreTokens()) {
499 appendIfLiteralAddress(st.nextToken(), sb);
500 }
501 return sb.toString();
502 }
503}