1 /*
   2  * Copyright (c) 2000, 2004, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.dns;
  27 
  28 import java.io.IOException;
  29 import java.net.DatagramSocket;
  30 import java.net.DatagramPacket;
  31 import java.net.InetAddress;
  32 import java.net.Socket;
  33 import javax.naming.*;
  34 
  35 import java.util.Collections;
  36 import java.util.Map;
  37 import java.util.HashMap;
  38 import java.util.Set;
  39 import java.util.HashSet;
  40 
  41 // Some of this code began life as part of sun.javaos.net.DnsClient
  42 // originally by sritchie@eng 1/96.  It was first hacked up for JNDI
  43 // use by caveh@eng 6/97.
  44 
  45 
  46 /**
  47  * The DnsClient class performs DNS client operations in support of DnsContext.
  48  *
  49  */
  50 
  51 public class DnsClient {
  52 
  53     // DNS packet header field offsets
  54     private static final int IDENT_OFFSET = 0;
  55     private static final int FLAGS_OFFSET = 2;
  56     private static final int NUMQ_OFFSET  = 4;
  57     private static final int NUMANS_OFFSET = 6;
  58     private static final int NUMAUTH_OFFSET = 8;
  59     private static final int NUMADD_OFFSET = 10;
  60     private static final int DNS_HDR_SIZE = 12;
  61 
  62     // DNS response codes
  63     private static final int NO_ERROR       = 0;
  64     private static final int FORMAT_ERROR   = 1;
  65     private static final int SERVER_FAILURE = 2;
  66     private static final int NAME_ERROR     = 3;
  67     private static final int NOT_IMPL       = 4;
  68     private static final int REFUSED        = 5;
  69 
  70     private static final String[] rcodeDescription = {
  71         "No error",
  72         "DNS format error",
  73         "DNS server failure",
  74         "DNS name not found",
  75         "DNS operation not supported",
  76         "DNS service refused"
  77     };
  78 
  79     private static final int DEFAULT_PORT = 53;
  80     private InetAddress[] servers;
  81     private int[] serverPorts;
  82     private int timeout;                // initial timeout on UDP queries in ms
  83     private int retries;                // number of UDP retries
  84 
  85     private DatagramSocket udpSocket;
  86 
  87     // Requests sent
  88     private Set<Integer> reqs;
  89 
  90     // Responses received
  91     private Map<Integer, byte[]> resps;
  92 
  93     //-------------------------------------------------------------------------
  94 
  95     /*
  96      * Each server is of the form "server[:port]".  IPv6 literal host names
  97      * include delimiting brackets.
  98      * "timeout" is the initial timeout interval (in ms) for UDP queries,
  99      * and "retries" gives the number of retries per server.
 100      */
 101     public DnsClient(String[] servers, int timeout, int retries)
 102             throws NamingException {
 103         this.timeout = timeout;
 104         this.retries = retries;
 105         try {
 106             udpSocket = new DatagramSocket();
 107         } catch (java.net.SocketException e) {
 108             NamingException ne = new ConfigurationException();
 109             ne.setRootCause(e);
 110             throw ne;
 111         }
 112 
 113         this.servers = new InetAddress[servers.length];
 114         serverPorts = new int[servers.length];
 115 
 116         for (int i = 0; i < servers.length; i++) {
 117 
 118             // Is optional port given?
 119             int colon = servers[i].indexOf(':',
 120                                            servers[i].indexOf(']') + 1);
 121 
 122             serverPorts[i] = (colon < 0)
 123                 ? DEFAULT_PORT
 124                 : Integer.parseInt(servers[i].substring(colon + 1));
 125             String server = (colon < 0)
 126                 ? servers[i]
 127                 : servers[i].substring(0, colon);
 128             try {
 129                 this.servers[i] = InetAddress.getByName(server);
 130             } catch (java.net.UnknownHostException e) {
 131                 NamingException ne = new ConfigurationException(
 132                         "Unknown DNS server: " + server);
 133                 ne.setRootCause(e);
 134                 throw ne;
 135             }
 136         }
 137         reqs = Collections.synchronizedSet(new HashSet<Integer>());
 138         resps = Collections.synchronizedMap(new HashMap<Integer, byte[]>());
 139     }
 140 
 141     protected void finalize() {
 142         close();
 143     }
 144 
 145     // A lock to access the request and response queues in tandem.
 146     private Object queuesLock = new Object();
 147 
 148     public void close() {
 149         udpSocket.close();
 150         synchronized (queuesLock) {
 151             reqs.clear();
 152             resps.clear();
 153         }
 154     }
 155 
 156 
 157     private int ident = 0;              // used to set the msg ID field
 158     private Object identLock = new Object();
 159 
 160     /*
 161      * If recursion is true, recursion is requested on the query.
 162      * If auth is true, only authoritative responses are accepted; other
 163      * responses throw NameNotFoundException.
 164      */
 165     ResourceRecords query(DnsName fqdn, int qclass, int qtype,
 166                           boolean recursion, boolean auth)
 167             throws NamingException {
 168 
 169         int xid;
 170         synchronized (identLock) {
 171             ident = 0xFFFF & (ident + 1);
 172             xid = ident;
 173         }
 174 
 175         // enqueue the outstanding request
 176         reqs.add(xid);
 177 
 178         Packet pkt = makeQueryPacket(fqdn, xid, qclass, qtype, recursion);
 179 
 180         Exception caughtException = null;
 181         boolean[] doNotRetry = new boolean[servers.length];
 182 
 183         //
 184         // The UDP retry strategy is to try the 1st server, and then
 185         // each server in order. If no answer, double the timeout
 186         // and try each server again.
 187         //
 188         for (int retry = 0; retry < retries; retry++) {
 189 
 190             // Try each name server.
 191             for (int i = 0; i < servers.length; i++) {
 192                 if (doNotRetry[i]) {
 193                     continue;
 194                 }
 195 
 196                 // send the request packet and wait for a response.
 197                 try {
 198                     if (debug) {
 199                         dprint("SEND ID (" + (retry + 1) + "): " + xid);
 200                     }
 201 
 202                     byte[] msg = null;
 203                     msg = doUdpQuery(pkt, servers[i], serverPorts[i],
 204                                         retry, xid);
 205                     //
 206                     // If the matching response is not got within the
 207                     // given timeout, check if the response was enqueued
 208                     // by some other thread, if not proceed with the next
 209                     // server or retry.
 210                     //
 211                     if (msg == null) {
 212                         if (resps.size() > 0) {
 213                             msg = lookupResponse(xid);
 214                         }
 215                         if (msg == null) { // try next server or retry
 216                             continue;
 217                         }
 218                     }
 219                     Header hdr = new Header(msg, msg.length);
 220 
 221                     if (auth && !hdr.authoritative) {
 222                         caughtException = new NameNotFoundException(
 223                                 "DNS response not authoritative");
 224                         doNotRetry[i] = true;
 225                         continue;
 226                     }
 227                     if (hdr.truncated) {    // message is truncated -- try TCP
 228 
 229                         // Try each server, starting with the one that just
 230                         // provided the truncated message.
 231                         for (int j = 0; j < servers.length; j++) {
 232                             int ij = (i + j) % servers.length;
 233                             if (doNotRetry[ij]) {
 234                                 continue;
 235                             }
 236                             try {
 237                                 Tcp tcp =
 238                                     new Tcp(servers[ij], serverPorts[ij]);
 239                                 byte[] msg2;
 240                                 try {
 241                                     msg2 = doTcpQuery(tcp, pkt);
 242                                 } finally {
 243                                     tcp.close();
 244                                 }
 245                                 Header hdr2 = new Header(msg2, msg2.length);
 246                                 if (hdr2.query) {
 247                                     throw new CommunicationException(
 248                                         "DNS error: expecting response");
 249                                 }
 250                                 checkResponseCode(hdr2);
 251 
 252                                 if (!auth || hdr2.authoritative) {
 253                                     // Got a valid response
 254                                     hdr = hdr2;
 255                                     msg = msg2;
 256                                     break;
 257                                 } else {
 258                                     doNotRetry[ij] = true;
 259                                 }
 260                             } catch (Exception e) {
 261                                 // Try next server, or use UDP response
 262                             }
 263                         } // servers
 264                     }
 265                     return new ResourceRecords(msg, msg.length, hdr, false);
 266 
 267                 } catch (IOException e) {
 268                     if (debug) {
 269                         dprint("Caught IOException:" + e);
 270                     }
 271                     if (caughtException == null) {
 272                         caughtException = e;
 273                     }
 274                     // Use reflection to allow pre-1.4 compilation.
 275                     // This won't be needed much longer.
 276                     if (e.getClass().getName().equals(
 277                             "java.net.PortUnreachableException")) {
 278                         doNotRetry[i] = true;
 279                     }
 280                 } catch (NameNotFoundException e) {
 281                     throw e;
 282                 } catch (CommunicationException e) {
 283                     if (caughtException == null) {
 284                         caughtException = e;
 285                     }
 286                 } catch (NamingException e) {
 287                     if (caughtException == null) {
 288                         caughtException = e;
 289                     }
 290                     doNotRetry[i] = true;
 291                 }
 292             } // servers
 293         } // retries
 294 
 295         reqs.remove(xid);
 296         if (caughtException instanceof NamingException) {
 297             throw (NamingException) caughtException;
 298         }
 299         // A network timeout or other error occurred.
 300         NamingException ne = new CommunicationException("DNS error");
 301         ne.setRootCause(caughtException);
 302         throw ne;
 303     }
 304 
 305     ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)
 306             throws NamingException {
 307 
 308         int xid;
 309         synchronized (identLock) {
 310             ident = 0xFFFF & (ident + 1);
 311             xid = ident;
 312         }
 313         Packet pkt = makeQueryPacket(zone, xid, qclass,
 314                                      ResourceRecord.QTYPE_AXFR, recursion);
 315         Exception caughtException = null;
 316 
 317         // Try each name server.
 318         for (int i = 0; i < servers.length; i++) {
 319             try {
 320                 Tcp tcp = new Tcp(servers[i], serverPorts[i]);
 321                 byte[] msg;
 322                 try {
 323                     msg = doTcpQuery(tcp, pkt);
 324                     Header hdr = new Header(msg, msg.length);
 325                     // Check only rcode as per
 326                     // draft-ietf-dnsext-axfr-clarify-04
 327                     checkResponseCode(hdr);
 328                     ResourceRecords rrs =
 329                         new ResourceRecords(msg, msg.length, hdr, true);
 330                     if (rrs.getFirstAnsType() != ResourceRecord.TYPE_SOA) {
 331                         throw new CommunicationException(
 332                                 "DNS error: zone xfer doesn't begin with SOA");
 333                     }
 334 
 335                     if (rrs.answer.size() == 1 ||
 336                             rrs.getLastAnsType() != ResourceRecord.TYPE_SOA) {
 337                         // The response is split into multiple DNS messages.
 338                         do {
 339                             msg = continueTcpQuery(tcp);
 340                             if (msg == null) {
 341                                 throw new CommunicationException(
 342                                         "DNS error: incomplete zone transfer");
 343                             }
 344                             hdr = new Header(msg, msg.length);
 345                             checkResponseCode(hdr);
 346                             rrs.add(msg, msg.length, hdr);
 347                         } while (rrs.getLastAnsType() !=
 348                                  ResourceRecord.TYPE_SOA);
 349                     }
 350 
 351                     // Delete the duplicate SOA record.
 352                     rrs.answer.removeElementAt(rrs.answer.size() - 1);
 353                     return rrs;
 354 
 355                 } finally {
 356                     tcp.close();
 357                 }
 358 
 359             } catch (IOException e) {
 360                 caughtException = e;
 361             } catch (NameNotFoundException e) {
 362                 throw e;
 363             } catch (NamingException e) {
 364                 caughtException = e;
 365             }
 366         }
 367         if (caughtException instanceof NamingException) {
 368             throw (NamingException) caughtException;
 369         }
 370         NamingException ne = new CommunicationException(
 371                 "DNS error during zone transfer");
 372         ne.setRootCause(caughtException);
 373         throw ne;
 374     }
 375 
 376 
 377     /**
 378      * Tries to retreive an UDP packet matching the given xid
 379      * received within the timeout.
 380      * If a packet with different xid is received, the received packet
 381      * is enqueued with the corresponding xid in 'resps'.
 382      */
 383     private byte[] doUdpQuery(Packet pkt, InetAddress server,
 384                                      int port, int retry, int xid)
 385             throws IOException, NamingException {
 386 
 387         int minTimeout = 50; // msec after which there are no retries.
 388 
 389         synchronized (udpSocket) {
 390             DatagramPacket opkt = new DatagramPacket(
 391                     pkt.getData(), pkt.length(), server, port);
 392             DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
 393             udpSocket.connect(server, port);
 394             int pktTimeout = (timeout * (1 << retry));
 395             try {
 396                 udpSocket.send(opkt);
 397 
 398                 // timeout remaining after successive 'receive()'
 399                 int timeoutLeft = pktTimeout;
 400                 int cnt = 0;
 401                 do {
 402                     if (debug) {
 403                        cnt++;
 404                         dprint("Trying RECEIVE(" +
 405                                 cnt + ") retry(" + (retry + 1) +
 406                                 ") for:" + xid  + "    sock-timeout:" +
 407                                 timeoutLeft + " ms.");
 408                     }
 409                     udpSocket.setSoTimeout(timeoutLeft);
 410                     long start = System.currentTimeMillis();
 411                     udpSocket.receive(ipkt);
 412                     long end = System.currentTimeMillis();
 413 
 414                     byte[] data = new byte[ipkt.getLength()];
 415                     data = ipkt.getData();
 416                     if (isMatchResponse(data, xid)) {
 417                         return data;
 418                     }
 419                     timeoutLeft = pktTimeout - ((int) (end - start));
 420                 } while (timeoutLeft > minTimeout);
 421 
 422             } finally {
 423                 udpSocket.disconnect();
 424             }
 425             return null; // no matching packet received within the timeout
 426         }
 427     }
 428 
 429     /*
 430      * Sends a TCP query, and returns the first DNS message in the response.
 431      */
 432     private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
 433 
 434         int len = pkt.length();
 435         // Send 2-byte message length, then send message.
 436         tcp.out.write(len >> 8);
 437         tcp.out.write(len);
 438         tcp.out.write(pkt.getData(), 0, len);
 439         tcp.out.flush();
 440 
 441         byte[] msg = continueTcpQuery(tcp);
 442         if (msg == null) {
 443             throw new IOException("DNS error: no response");
 444         }
 445         return msg;
 446     }
 447 
 448     /*
 449      * Returns the next DNS message from the TCP socket, or null on EOF.
 450      */
 451     private byte[] continueTcpQuery(Tcp tcp) throws IOException {
 452 
 453         int lenHi = tcp.in.read();      // high-order byte of response length
 454         if (lenHi == -1) {
 455             return null;        // EOF
 456         }
 457         int lenLo = tcp.in.read();      // low-order byte of response length
 458         if (lenLo == -1) {
 459             throw new IOException("Corrupted DNS response: bad length");
 460         }
 461         int len = (lenHi << 8) | lenLo;
 462         byte[] msg = new byte[len];
 463         int pos = 0;                    // next unfilled position in msg
 464         while (len > 0) {
 465             int n = tcp.in.read(msg, pos, len);
 466             if (n == -1) {
 467                 throw new IOException(
 468                         "Corrupted DNS response: too little data");
 469             }
 470             len -= n;
 471             pos += n;
 472         }
 473         return msg;
 474     }
 475 
 476     private Packet makeQueryPacket(DnsName fqdn, int xid,
 477                                    int qclass, int qtype, boolean recursion) {
 478         int qnameLen = fqdn.getOctets();
 479         int pktLen = DNS_HDR_SIZE + qnameLen + 4;
 480         Packet pkt = new Packet(pktLen);
 481 
 482         short flags = recursion ? Header.RD_BIT : 0;
 483 
 484         pkt.putShort(xid, IDENT_OFFSET);
 485         pkt.putShort(flags, FLAGS_OFFSET);
 486         pkt.putShort(1, NUMQ_OFFSET);
 487         pkt.putShort(0, NUMANS_OFFSET);
 488         pkt.putInt(0, NUMAUTH_OFFSET);
 489 
 490         makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
 491         pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
 492         pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
 493 
 494         return pkt;
 495     }
 496 
 497     // Builds a query name in pkt according to the RFC spec.
 498     private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
 499 
 500         // Loop through labels, least-significant first.
 501         for (int i = fqdn.size() - 1; i >= 0; i--) {
 502             String label = fqdn.get(i);
 503             int len = label.length();
 504 
 505             pkt.putByte(len, off++);
 506             for (int j = 0; j < len; j++) {
 507                 pkt.putByte(label.charAt(j), off++);
 508             }
 509         }
 510         if (!fqdn.hasRootLabel()) {
 511             pkt.putByte(0, off);
 512         }
 513     }
 514 
 515     //-------------------------------------------------------------------------
 516 
 517     private byte[] lookupResponse(Integer xid) throws NamingException {
 518         //
 519         // Check the queued responses: some other thread in between
 520         // received the response for this request.
 521         //
 522         if (debug) {
 523             dprint("LOOKUP for: " + xid +
 524                 "\tResponse Q:" + resps);
 525         }
 526         byte[] pkt;
 527         if ((pkt = (byte[]) resps.get(xid)) != null) {
 528             checkResponseCode(new Header(pkt, pkt.length));
 529             synchronized (queuesLock) {
 530                 resps.remove(xid);
 531                 reqs.remove(xid);
 532             }
 533 
 534             if (debug) {
 535                 dprint("FOUND (" + Thread.currentThread() +
 536                     ") for:" + xid);
 537             }
 538         }
 539         return pkt;
 540     }
 541 
 542     /*
 543      * Checks the header of an incoming DNS response.
 544      * Returns true if it matches the given xid and throws a naming
 545      * exception, if appropriate, based on the response code.
 546      */
 547     private boolean isMatchResponse(byte[] pkt, int xid)
 548                 throws NamingException {
 549 
 550         Header hdr = new Header(pkt, pkt.length);
 551         if (hdr.query) {
 552             throw new CommunicationException("DNS error: expecting response");
 553         }
 554 
 555         if (!reqs.contains(xid)) { // already received, ignore the response
 556             return false;
 557         }
 558 
 559         // common case- the request sent matches the subsequent response read
 560         if (hdr.xid == xid) {
 561             if (debug) {
 562                 dprint("XID MATCH:" + xid);
 563             }
 564 
 565             checkResponseCode(hdr);
 566             // remove the response for the xid if received by some other thread.
 567             synchronized (queuesLock) {
 568                 resps.remove(xid);
 569                 reqs.remove(xid);
 570             }
 571             return true;
 572         }
 573 
 574         //
 575         // xid mis-match: enqueue the response, it may belong to some other
 576         // thread that has not yet had a chance to read its response.
 577         // enqueue only the first response, responses for retries are ignored.
 578         //
 579         synchronized (queuesLock) {
 580             if (reqs.contains(xid)) { // enqueue only the first response
 581                 resps.put(xid, pkt);
 582             }
 583         }
 584 
 585         if (debug) {
 586             dprint("NO-MATCH SEND ID:" +
 587                                 xid + " RECVD ID:" + hdr.xid +
 588                                 "    Response Q:" + resps +
 589                                 "    Reqs size:" + reqs.size());
 590         }
 591         return false;
 592     }
 593 
 594     /*
 595      * Throws an exception if appropriate for the response code of a
 596      * given header.
 597      */
 598     private void checkResponseCode(Header hdr) throws NamingException {
 599 
 600         int rcode = hdr.rcode;
 601         if (rcode == NO_ERROR) {
 602             return;
 603         }
 604         String msg = (rcode < rcodeDescription.length)
 605             ? rcodeDescription[rcode]
 606             : "DNS error";
 607         msg += " [response code " + rcode + "]";
 608 
 609         switch (rcode) {
 610         case SERVER_FAILURE:
 611             throw new ServiceUnavailableException(msg);
 612         case NAME_ERROR:
 613             throw new NameNotFoundException(msg);
 614         case NOT_IMPL:
 615         case REFUSED:
 616             throw new OperationNotSupportedException(msg);
 617         case FORMAT_ERROR:
 618         default:
 619             throw new NamingException(msg);
 620         }
 621     }
 622 
 623     //-------------------------------------------------------------------------
 624 
 625     private static boolean debug = false;
 626 
 627     public static void setDebug(boolean flag) {
 628         debug = flag;
 629     }
 630 
 631     private static void dprint(String mess) {
 632         if (debug) {
 633             System.err.println("DNS: " + mess);
 634         }
 635     }
 636 
 637 }
 638 
 639 class Tcp {
 640 
 641     private Socket sock;
 642     java.io.InputStream in;
 643     java.io.OutputStream out;
 644 
 645     Tcp(InetAddress server, int port) throws IOException {
 646         sock = new Socket(server, port);
 647         sock.setTcpNoDelay(true);
 648         out = new java.io.BufferedOutputStream(sock.getOutputStream());
 649         in = new java.io.BufferedInputStream(sock.getInputStream());
 650     }
 651 
 652     void close() throws IOException {
 653         sock.close();
 654     }
 655 }
 656 
 657 /*
 658  * javaos emulation -cj
 659  */
 660 class Packet {
 661         byte buf[];
 662 
 663         Packet(int len) {
 664                 buf = new byte[len];
 665         }
 666 
 667         Packet(byte data[], int len) {
 668                 buf = new byte[len];
 669                 System.arraycopy(data, 0, buf, 0, len);
 670         }
 671 
 672         void putInt(int x, int off) {
 673                 buf[off + 0] = (byte)(x >> 24);
 674                 buf[off + 1] = (byte)(x >> 16);
 675                 buf[off + 2] = (byte)(x >> 8);
 676                 buf[off + 3] = (byte)x;
 677         }
 678 
 679         void putShort(int x, int off) {
 680                 buf[off + 0] = (byte)(x >> 8);
 681                 buf[off + 1] = (byte)x;
 682         }
 683 
 684         void putByte(int x, int off) {
 685                 buf[off] = (byte)x;
 686         }
 687 
 688         void putBytes(byte src[], int src_offset, int dst_offset, int len) {
 689                 System.arraycopy(src, src_offset, buf, dst_offset, len);
 690         }
 691 
 692         int length() {
 693                 return buf.length;
 694         }
 695 
 696         byte[] getData() {
 697                 return buf;
 698         }
 699 }