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