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