1 /*
   2  * Copyright (c) 2000, 2014, 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 = ipkt.getData();
 415                     if (isMatchResponse(data, xid)) {
 416                         return data;
 417                     }
 418                     timeoutLeft = pktTimeout - ((int) (end - start));
 419                 } while (timeoutLeft > minTimeout);
 420 
 421             } finally {
 422                 udpSocket.disconnect();
 423             }
 424             return null; // no matching packet received within the timeout
 425         }
 426     }
 427 
 428     /*
 429      * Sends a TCP query, and returns the first DNS message in the response.
 430      */
 431     private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
 432 
 433         int len = pkt.length();
 434         // Send 2-byte message length, then send message.
 435         tcp.out.write(len >> 8);
 436         tcp.out.write(len);
 437         tcp.out.write(pkt.getData(), 0, len);
 438         tcp.out.flush();
 439 
 440         byte[] msg = continueTcpQuery(tcp);
 441         if (msg == null) {
 442             throw new IOException("DNS error: no response");
 443         }
 444         return msg;
 445     }
 446 
 447     /*
 448      * Returns the next DNS message from the TCP socket, or null on EOF.
 449      */
 450     private byte[] continueTcpQuery(Tcp tcp) throws IOException {
 451 
 452         int lenHi = tcp.in.read();      // high-order byte of response length
 453         if (lenHi == -1) {
 454             return null;        // EOF
 455         }
 456         int lenLo = tcp.in.read();      // low-order byte of response length
 457         if (lenLo == -1) {
 458             throw new IOException("Corrupted DNS response: bad length");
 459         }
 460         int len = (lenHi << 8) | lenLo;
 461         byte[] msg = new byte[len];
 462         int pos = 0;                    // next unfilled position in msg
 463         while (len > 0) {
 464             int n = tcp.in.read(msg, pos, len);
 465             if (n == -1) {
 466                 throw new IOException(
 467                         "Corrupted DNS response: too little data");
 468             }
 469             len -= n;
 470             pos += n;
 471         }
 472         return msg;
 473     }
 474 
 475     private Packet makeQueryPacket(DnsName fqdn, int xid,
 476                                    int qclass, int qtype, boolean recursion) {
 477         int qnameLen = fqdn.getOctets();
 478         int pktLen = DNS_HDR_SIZE + qnameLen + 4;
 479         Packet pkt = new Packet(pktLen);
 480 
 481         short flags = recursion ? Header.RD_BIT : 0;
 482 
 483         pkt.putShort(xid, IDENT_OFFSET);
 484         pkt.putShort(flags, FLAGS_OFFSET);
 485         pkt.putShort(1, NUMQ_OFFSET);
 486         pkt.putShort(0, NUMANS_OFFSET);
 487         pkt.putInt(0, NUMAUTH_OFFSET);
 488 
 489         makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
 490         pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
 491         pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
 492 
 493         return pkt;
 494     }
 495 
 496     // Builds a query name in pkt according to the RFC spec.
 497     private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
 498 
 499         // Loop through labels, least-significant first.
 500         for (int i = fqdn.size() - 1; i >= 0; i--) {
 501             String label = fqdn.get(i);
 502             int len = label.length();
 503 
 504             pkt.putByte(len, off++);
 505             for (int j = 0; j < len; j++) {
 506                 pkt.putByte(label.charAt(j), off++);
 507             }
 508         }
 509         if (!fqdn.hasRootLabel()) {
 510             pkt.putByte(0, off);
 511         }
 512     }
 513 
 514     //-------------------------------------------------------------------------
 515 
 516     private byte[] lookupResponse(Integer xid) throws NamingException {
 517         //
 518         // Check the queued responses: some other thread in between
 519         // received the response for this request.
 520         //
 521         if (debug) {
 522             dprint("LOOKUP for: " + xid +
 523                 "\tResponse Q:" + resps);
 524         }
 525         byte[] pkt;
 526         if ((pkt = resps.get(xid)) != null) {
 527             checkResponseCode(new Header(pkt, pkt.length));
 528             synchronized (queuesLock) {
 529                 resps.remove(xid);
 530                 reqs.remove(xid);
 531             }
 532 
 533             if (debug) {
 534                 dprint("FOUND (" + Thread.currentThread() +
 535                     ") for:" + xid);
 536             }
 537         }
 538         return pkt;
 539     }
 540 
 541     /*
 542      * Checks the header of an incoming DNS response.
 543      * Returns true if it matches the given xid and throws a naming
 544      * exception, if appropriate, based on the response code.
 545      */
 546     private boolean isMatchResponse(byte[] pkt, int xid)
 547                 throws NamingException {
 548 
 549         Header hdr = new Header(pkt, pkt.length);
 550         if (hdr.query) {
 551             throw new CommunicationException("DNS error: expecting response");
 552         }
 553 
 554         if (!reqs.contains(xid)) { // already received, ignore the response
 555             return false;
 556         }
 557 
 558         // common case- the request sent matches the subsequent response read
 559         if (hdr.xid == xid) {
 560             if (debug) {
 561                 dprint("XID MATCH:" + xid);
 562             }
 563 
 564             checkResponseCode(hdr);
 565             // remove the response for the xid if received by some other thread.
 566             synchronized (queuesLock) {
 567                 resps.remove(xid);
 568                 reqs.remove(xid);
 569             }
 570             return true;
 571         }
 572 
 573         //
 574         // xid mis-match: enqueue the response, it may belong to some other
 575         // thread that has not yet had a chance to read its response.
 576         // enqueue only the first response, responses for retries are ignored.
 577         //
 578         synchronized (queuesLock) {
 579             if (reqs.contains(hdr.xid)) { // enqueue only the first response
 580                 resps.put(hdr.xid, pkt);
 581             }
 582         }
 583 
 584         if (debug) {
 585             dprint("NO-MATCH SEND ID:" +
 586                                 xid + " RECVD ID:" + hdr.xid +
 587                                 "    Response Q:" + resps +
 588                                 "    Reqs size:" + reqs.size());
 589         }
 590         return false;
 591     }
 592 
 593     /*
 594      * Throws an exception if appropriate for the response code of a
 595      * given header.
 596      */
 597     private void checkResponseCode(Header hdr) throws NamingException {
 598 
 599         int rcode = hdr.rcode;
 600         if (rcode == NO_ERROR) {
 601             return;
 602         }
 603         String msg = (rcode < rcodeDescription.length)
 604             ? rcodeDescription[rcode]
 605             : "DNS error";
 606         msg += " [response code " + rcode + "]";
 607 
 608         switch (rcode) {
 609         case SERVER_FAILURE:
 610             throw new ServiceUnavailableException(msg);
 611         case NAME_ERROR:
 612             throw new NameNotFoundException(msg);
 613         case NOT_IMPL:
 614         case REFUSED:
 615             throw new OperationNotSupportedException(msg);
 616         case FORMAT_ERROR:
 617         default:
 618             throw new NamingException(msg);
 619         }
 620     }
 621 
 622     //-------------------------------------------------------------------------
 623 
 624     private static final boolean debug = false;
 625 
 626     private static void dprint(String mess) {
 627         if (debug) {
 628             System.err.println("DNS: " + mess);
 629         }
 630     }
 631 
 632 }
 633 
 634 class Tcp {
 635 
 636     private Socket sock;
 637     java.io.InputStream in;
 638     java.io.OutputStream out;
 639 
 640     Tcp(InetAddress server, int port) throws IOException {
 641         sock = new Socket(server, port);
 642         sock.setTcpNoDelay(true);
 643         out = new java.io.BufferedOutputStream(sock.getOutputStream());
 644         in = new java.io.BufferedInputStream(sock.getInputStream());
 645     }
 646 
 647     void close() throws IOException {
 648         sock.close();
 649     }
 650 }
 651 
 652 /*
 653  * javaos emulation -cj
 654  */
 655 class Packet {
 656         byte buf[];
 657 
 658         Packet(int len) {
 659                 buf = new byte[len];
 660         }
 661 
 662         Packet(byte data[], int len) {
 663                 buf = new byte[len];
 664                 System.arraycopy(data, 0, buf, 0, len);
 665         }
 666 
 667         void putInt(int x, int off) {
 668                 buf[off + 0] = (byte)(x >> 24);
 669                 buf[off + 1] = (byte)(x >> 16);
 670                 buf[off + 2] = (byte)(x >> 8);
 671                 buf[off + 3] = (byte)x;
 672         }
 673 
 674         void putShort(int x, int off) {
 675                 buf[off + 0] = (byte)(x >> 8);
 676                 buf[off + 1] = (byte)x;
 677         }
 678 
 679         void putByte(int x, int off) {
 680                 buf[off] = (byte)x;
 681         }
 682 
 683         void putBytes(byte src[], int src_offset, int dst_offset, int len) {
 684                 System.arraycopy(src, src_offset, buf, dst_offset, len);
 685         }
 686 
 687         int length() {
 688                 return buf.length;
 689         }
 690 
 691         byte[] getData() {
 692                 return buf;
 693         }
 694 }