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 javax.naming.InvalidNameException;
  29 
  30 
  31 /**
  32  * The ResourceRecord class represents a DNS resource record.
  33  * The string format is based on the master file representation in
  34  * RFC 1035.
  35  *
  36  * @author Scott Seligman
  37  */
  38 
  39 
  40 public class ResourceRecord {
  41 
  42     /*
  43      * Resource record type codes
  44      */
  45     static final int TYPE_A     =  1;
  46     static final int TYPE_NS    =  2;
  47     static final int TYPE_CNAME =  5;
  48     static final int TYPE_SOA   =  6;
  49     static final int TYPE_PTR   = 12;
  50     static final int TYPE_HINFO = 13;
  51     static final int TYPE_MX    = 15;
  52     static final int TYPE_TXT   = 16;
  53     static final int TYPE_AAAA  = 28;
  54     static final int TYPE_SRV   = 33;
  55     static final int TYPE_NAPTR = 35;
  56     static final int QTYPE_AXFR = 252;          // zone transfer
  57     static final int QTYPE_STAR = 255;          // query type "*"
  58 
  59     /*
  60      * Mapping from resource record type codes to type name strings.
  61      */
  62     static final String rrTypeNames[] = {
  63         null, "A", "NS", null, null,
  64         "CNAME", "SOA", null, null, null,
  65         null, null, "PTR", "HINFO", null,
  66         "MX", "TXT", null, null, null,
  67         null, null, null, null, null,
  68         null, null, null, "AAAA", null,
  69         null, null, null, "SRV", null,
  70         "NAPTR"
  71     };
  72 
  73     /*
  74      * Resource record class codes
  75      */
  76     static final int CLASS_INTERNET = 1;
  77     static final int CLASS_HESIOD   = 2;
  78     static final int QCLASS_STAR    = 255;      // query class "*"
  79 
  80     /*
  81      * Mapping from resource record type codes to class name strings.
  82      */
  83     static final String rrClassNames[] = {
  84         null, "IN", null, null, "HS"
  85     };
  86 
  87     /*
  88      * Maximum number of compression references in labels.
  89      * Used to detect compression loops.
  90      */
  91     private static final int MAXIMUM_COMPRESSION_REFERENCES = 16;
  92 
  93     byte[] msg;                 // DNS message
  94     int msgLen;                 // msg size (in octets)
  95     boolean qSection;           // true if this RR is part of question section
  96                                 // and therefore has no ttl or rdata
  97     int offset;                 // offset of RR w/in msg
  98     int rrlen;                  // number of octets in encoded RR
  99     DnsName name;               // name field of RR, including root label
 100     int rrtype;                 // type field of RR
 101     String rrtypeName;          // name of of rrtype
 102     int rrclass;                // class field of RR
 103     String rrclassName;         // name of rrclass
 104     int ttl = 0;                // ttl field of RR
 105     int rdlen = 0;              // number of octets of rdata
 106     Object rdata = null;        // rdata -- most are String, unknown are byte[]
 107 
 108 
 109     /*
 110      * Constructs a new ResourceRecord.  The encoded data of the DNS
 111      * message is contained in msg; data for this RR begins at msg[offset].
 112      * If qSection is true this RR is part of a question section.  It's
 113      * not a true resource record in that case, but is treated as if it
 114      * were a shortened one (with no ttl or rdata).  If decodeRdata is
 115      * false, the rdata is not decoded (and getRdata() will return null)
 116      * unless this is an SOA record.
 117      *
 118      * @throws InvalidNameException if a decoded domain name isn't valid.
 119      * @throws ArrayIndexOutOfBoundsException given certain other corrupt data.
 120      */
 121     ResourceRecord(byte[] msg, int msgLen, int offset,
 122                    boolean qSection, boolean decodeRdata)
 123             throws InvalidNameException {
 124 
 125         this.msg = msg;
 126         this.msgLen = msgLen;
 127         this.offset = offset;
 128         this.qSection = qSection;
 129         decode(decodeRdata);
 130     }
 131 
 132     public String toString() {
 133         String text = name + " " + rrclassName + " " + rrtypeName;
 134         if (!qSection) {
 135             text += " " + ttl + " " +
 136                 ((rdata != null) ? rdata : "[n/a]");
 137         }
 138         return text;
 139     }
 140 
 141     /*
 142      * Returns the name field of this RR, including the root label.
 143      */
 144     public DnsName getName() {
 145         return name;
 146     }
 147 
 148     /*
 149      * Returns the number of octets in the encoded RR.
 150      */
 151     public int size() {
 152         return rrlen;
 153     }
 154 
 155     public int getType() {
 156         return rrtype;
 157     }
 158 
 159     public int getRrclass() {
 160         return rrclass;
 161     }
 162 
 163     public Object getRdata() {
 164         return rdata;
 165     }
 166 
 167 
 168     public static String getTypeName(int rrtype) {
 169         return valueToName(rrtype, rrTypeNames);
 170     }
 171 
 172     public static int getType(String typeName) {
 173         return nameToValue(typeName, rrTypeNames);
 174     }
 175 
 176     public static String getRrclassName(int rrclass) {
 177         return valueToName(rrclass, rrClassNames);
 178     }
 179 
 180     public static int getRrclass(String className) {
 181         return nameToValue(className, rrClassNames);
 182     }
 183 
 184     private static String valueToName(int val, String[] names) {
 185         String name = null;
 186         if ((val > 0) && (val < names.length)) {
 187             name = names[val];
 188         } else if (val == QTYPE_STAR) {         // QTYPE_STAR == QCLASS_STAR
 189             name = "*";
 190         }
 191         if (name == null) {
 192             name = Integer.toString(val);
 193         }
 194         return name;
 195     }
 196 
 197     private static int nameToValue(String name, String[] names) {
 198         if (name.equals("")) {
 199             return -1;                          // invalid name
 200         } else if (name.equals("*")) {
 201             return QTYPE_STAR;                  // QTYPE_STAR == QCLASS_STAR
 202         }
 203         if (Character.isDigit(name.charAt(0))) {
 204             try {
 205                 return Integer.parseInt(name);
 206             } catch (NumberFormatException e) {
 207             }
 208         }
 209         for (int i = 1; i < names.length; i++) {
 210             if ((names[i] != null) &&
 211                     name.equalsIgnoreCase(names[i])) {
 212                 return i;
 213             }
 214         }
 215         return -1;                              // unknown name
 216     }
 217 
 218     /*
 219      * Compares two SOA record serial numbers using 32-bit serial number
 220      * arithmetic as defined in RFC 1982.  Serial numbers are unsigned
 221      * 32-bit quantities.  Returns a negative, zero, or positive value
 222      * as the first serial number is less than, equal to, or greater
 223      * than the second.  If the serial numbers are not comparable the
 224      * result is undefined.  Note that the relation is not transitive.
 225      */
 226     public static int compareSerialNumbers(long s1, long s2) {
 227         long diff = s2 - s1;
 228         if (diff == 0) {
 229             return 0;
 230         } else if ((diff > 0 &&  diff <= 0x7FFFFFFF) ||
 231                    (diff < 0 && -diff >  0x7FFFFFFF)) {
 232             return -1;
 233         } else {
 234             return 1;
 235         }
 236     }
 237 
 238 
 239     /*
 240      * Decodes the binary format of the RR.
 241      * May throw ArrayIndexOutOfBoundsException given corrupt data.
 242      */
 243     private void decode(boolean decodeRdata) throws InvalidNameException {
 244         int pos = offset;       // index of next unread octet
 245 
 246         name = new DnsName();                           // NAME
 247         pos = decodeName(pos, name, 0);
 248 
 249         rrtype = getUShort(pos);                        // TYPE
 250         rrtypeName = (rrtype < rrTypeNames.length)
 251             ? rrTypeNames[rrtype]
 252             : null;
 253         if (rrtypeName == null) {
 254             rrtypeName = Integer.toString(rrtype);
 255         }
 256         pos += 2;
 257 
 258         rrclass = getUShort(pos);                       // CLASS
 259         rrclassName = (rrclass < rrClassNames.length)
 260             ? rrClassNames[rrclass]
 261             : null;
 262         if (rrclassName == null) {
 263             rrclassName = Integer.toString(rrclass);
 264         }
 265         pos += 2;
 266 
 267         if (!qSection) {
 268             ttl = getInt(pos);                          // TTL
 269             pos += 4;
 270 
 271             rdlen = getUShort(pos);                     // RDLENGTH
 272             pos += 2;
 273 
 274             rdata = (decodeRdata ||                     // RDATA
 275                      (rrtype == TYPE_SOA))
 276                 ? decodeRdata(pos)
 277                 : null;
 278             if (rdata instanceof DnsName) {
 279                 rdata = rdata.toString();
 280             }
 281             pos += rdlen;
 282         }
 283 
 284         rrlen = pos - offset;
 285 
 286         msg = null;     // free up for GC
 287     }
 288 
 289     /*
 290      * Returns the 1-byte unsigned value at msg[pos].
 291      */
 292     private int getUByte(int pos) {
 293         return (msg[pos] & 0xFF);
 294     }
 295 
 296     /*
 297      * Returns the 2-byte unsigned value at msg[pos].  The high
 298      * order byte comes first.
 299      */
 300     private int getUShort(int pos) {
 301         return (((msg[pos] & 0xFF) << 8) |
 302                 (msg[pos + 1] & 0xFF));
 303     }
 304 
 305     /*
 306      * Returns the 4-byte signed value at msg[pos].  The high
 307      * order byte comes first.
 308      */
 309     private int getInt(int pos) {
 310         return ((getUShort(pos) << 16) | getUShort(pos + 2));
 311     }
 312 
 313     /*
 314      * Returns the 4-byte unsigned value at msg[pos].  The high
 315      * order byte comes first.
 316      */
 317     private long getUInt(int pos) {
 318         return (getInt(pos) & 0xffffffffL);
 319     }
 320 
 321     /*
 322      * Returns the name encoded at msg[pos], including the root label.
 323      */
 324     private DnsName decodeName(int pos) throws InvalidNameException {
 325         DnsName n = new DnsName();
 326         decodeName(pos, n, 0);
 327         return n;
 328     }
 329 
 330     /*
 331      * Prepends to "n" the domain name encoded at msg[pos], including the root
 332      * label.  Returns the index into "msg" following the name.
 333      */
 334     private int decodeName(int pos, DnsName n, int level)
 335         throws InvalidNameException {
 336         if (level > MAXIMUM_COMPRESSION_REFERENCES) {
 337             throw new InvalidNameException("Too many compression references");
 338         }
 339         if (msg[pos] == 0) {                            // end of name
 340             n.add(0, "");
 341             return (pos + 1);
 342         } else if ((msg[pos] & 0xC0) != 0) {            // name compression
 343             decodeName(getUShort(pos) & 0x3FFF, n, level + 1);
 344             return (pos + 2);
 345         } else {                                        // append a label
 346             int len = msg[pos++];
 347             try {
 348                 n.add(0, new String(msg, pos, len, "ISO-8859-1"));
 349             } catch (java.io.UnsupportedEncodingException e) {
 350                 // assert false : "ISO-Latin-1 charset unavailable";
 351             }
 352             return decodeName(pos + len, n, level);
 353         }
 354     }
 355 
 356     /*
 357      * Returns the rdata encoded at msg[pos].  The format is dependent
 358      * on the rrtype and rrclass values, which have already been set.
 359      * The length of the encoded data is rdlen, which has already been
 360      * set.
 361      * The rdata of records with unknown type/class combinations is
 362      * returned in a newly-allocated byte array.
 363      */
 364     private Object decodeRdata(int pos) throws InvalidNameException {
 365         if (rrclass == CLASS_INTERNET) {
 366             switch (rrtype) {
 367             case TYPE_A:
 368                 return decodeA(pos);
 369             case TYPE_AAAA:
 370                 return decodeAAAA(pos);
 371             case TYPE_CNAME:
 372             case TYPE_NS:
 373             case TYPE_PTR:
 374                 return decodeName(pos);
 375             case TYPE_MX:
 376                 return decodeMx(pos);
 377             case TYPE_SOA:
 378                 return decodeSoa(pos);
 379             case TYPE_SRV:
 380                 return decodeSrv(pos);
 381             case TYPE_NAPTR:
 382                 return decodeNaptr(pos);
 383             case TYPE_TXT:
 384                 return decodeTxt(pos);
 385             case TYPE_HINFO:
 386                 return decodeHinfo(pos);
 387             }
 388         }
 389         // Unknown RR type/class
 390         byte[] rd = new byte[rdlen];
 391         System.arraycopy(msg, pos, rd, 0, rdlen);
 392         return rd;
 393     }
 394 
 395     /*
 396      * Returns the rdata of an MX record that is encoded at msg[pos].
 397      */
 398     private String decodeMx(int pos) throws InvalidNameException {
 399         int preference = getUShort(pos);
 400         pos += 2;
 401         DnsName name = decodeName(pos);
 402         return (preference + " " + name);
 403     }
 404 
 405     /*
 406      * Returns the rdata of an SOA record that is encoded at msg[pos].
 407      */
 408     private String decodeSoa(int pos) throws InvalidNameException {
 409         DnsName mname = new DnsName();
 410         pos = decodeName(pos, mname, 0);
 411         DnsName rname = new DnsName();
 412         pos = decodeName(pos, rname, 0);
 413 
 414         long serial = getUInt(pos);
 415         pos += 4;
 416         long refresh = getUInt(pos);
 417         pos += 4;
 418         long retry = getUInt(pos);
 419         pos += 4;
 420         long expire = getUInt(pos);
 421         pos += 4;
 422         long minimum = getUInt(pos);    // now used as negative TTL
 423         pos += 4;
 424 
 425         return (mname + " " + rname + " " + serial + " " +
 426                 refresh + " " + retry + " " + expire + " " + minimum);
 427     }
 428 
 429     /*
 430      * Returns the rdata of an SRV record that is encoded at msg[pos].
 431      * See RFC 2782.
 432      */
 433     private String decodeSrv(int pos) throws InvalidNameException {
 434         int priority = getUShort(pos);
 435         pos += 2;
 436         int weight =   getUShort(pos);
 437         pos += 2;
 438         int port =     getUShort(pos);
 439         pos += 2;
 440         DnsName target = decodeName(pos);
 441         return (priority + " " + weight + " " + port + " " + target);
 442     }
 443 
 444     /*
 445      * Returns the rdata of an NAPTR record that is encoded at msg[pos].
 446      * See RFC 2915.
 447      */
 448     private String decodeNaptr(int pos) throws InvalidNameException {
 449         int order = getUShort(pos);
 450         pos += 2;
 451         int preference = getUShort(pos);
 452         pos += 2;
 453         StringBuffer flags = new StringBuffer();
 454         pos += decodeCharString(pos, flags);
 455         StringBuffer services = new StringBuffer();
 456         pos += decodeCharString(pos, services);
 457         StringBuffer regexp = new StringBuffer(rdlen);
 458         pos += decodeCharString(pos, regexp);
 459         DnsName replacement = decodeName(pos);
 460 
 461         return (order + " " + preference + " " + flags + " " +
 462                 services + " " + regexp + " " + replacement);
 463     }
 464 
 465     /*
 466      * Returns the rdata of a TXT record that is encoded at msg[pos].
 467      * The rdata consists of one or more <character-string>s.
 468      */
 469     private String decodeTxt(int pos) {
 470         StringBuffer buf = new StringBuffer(rdlen);
 471         int end = pos + rdlen;
 472         while (pos < end) {
 473             pos += decodeCharString(pos, buf);
 474             if (pos < end) {
 475                 buf.append(' ');
 476             }
 477         }
 478         return buf.toString();
 479     }
 480 
 481     /*
 482      * Returns the rdata of an HINFO record that is encoded at msg[pos].
 483      * The rdata consists of two <character-string>s.
 484      */
 485     private String decodeHinfo(int pos) {
 486         StringBuffer buf = new StringBuffer(rdlen);
 487         pos += decodeCharString(pos, buf);
 488         buf.append(' ');
 489         pos += decodeCharString(pos, buf);
 490         return buf.toString();
 491     }
 492 
 493     /*
 494      * Decodes the <character-string> at msg[pos] and adds it to buf.
 495      * If the string contains one of the meta-characters ' ', '\\', or
 496      * '"', then the result is quoted and any embedded '\\' or '"'
 497      * chars are escaped with '\\'.  Empty strings are also quoted.
 498      * Returns the size of the encoded string, including the initial
 499      * length octet.
 500      */
 501     private int decodeCharString(int pos, StringBuffer buf) {
 502         int start = buf.length();       // starting index of this string
 503         int len = getUByte(pos++);      // encoded string length
 504         boolean quoted = (len == 0);    // quote string if empty
 505         for (int i = 0; i < len; i++) {
 506             int c = getUByte(pos++);
 507             quoted |= (c == ' ');
 508             if ((c == '\\') || (c == '"')) {
 509                 quoted = true;
 510                 buf.append('\\');
 511             }
 512             buf.append((char) c);
 513         }
 514         if (quoted) {
 515             buf.insert(start, '"');
 516             buf.append('"');
 517         }
 518         return (len + 1);       // size includes initial octet
 519     }
 520 
 521     /*
 522      * Returns the rdata of an A record, in dotted-decimal format,
 523      * that is encoded at msg[pos].
 524      */
 525     private String decodeA(int pos) {
 526         return ((msg[pos] & 0xff) + "." +
 527                 (msg[pos + 1] & 0xff) + "." +
 528                 (msg[pos + 2] & 0xff) + "." +
 529                 (msg[pos + 3] & 0xff));
 530     }
 531 
 532     /*
 533      * Returns the rdata of an AAAA record, in colon-separated format,
 534      * that is encoded at msg[pos].  For example:  4321:0:1:2:3:4:567:89ab.
 535      * See RFCs 1886 and 2373.
 536      */
 537     private String decodeAAAA(int pos) {
 538         int[] addr6 = new int[8];  // the unsigned 16-bit words of the address
 539         for (int i = 0; i < 8; i++) {
 540             addr6[i] = getUShort(pos);
 541             pos += 2;
 542         }
 543 
 544         // Find longest sequence of two or more zeros, to compress them.
 545         int curBase = -1;
 546         int curLen = 0;
 547         int bestBase = -1;
 548         int bestLen = 0;
 549         for (int i = 0; i < 8; i++) {
 550             if (addr6[i] == 0) {
 551                 if (curBase == -1) {    // new sequence
 552                     curBase = i;
 553                     curLen = 1;
 554                 } else {                // extend sequence
 555                     ++curLen;
 556                     if ((curLen >= 2) && (curLen > bestLen)) {
 557                         bestBase = curBase;
 558                         bestLen = curLen;
 559                     }
 560                 }
 561             } else {                    // not in sequence
 562                 curBase = -1;
 563             }
 564         }
 565 
 566         // If addr begins with at least 6 zeros and is not :: or ::1,
 567         // or with 5 zeros followed by 0xffff, use the text format for
 568         // IPv4-compatible or IPv4-mapped addresses.
 569         if (bestBase == 0) {
 570             if ((bestLen == 6) ||
 571                     ((bestLen == 7) && (addr6[7] > 1))) {
 572                 return ("::" + decodeA(pos - 4));
 573             } else if ((bestLen == 5) && (addr6[5] == 0xffff)) {
 574                 return ("::ffff:" + decodeA(pos - 4));
 575             }
 576         }
 577 
 578         // If bestBase != -1, compress zeros in [bestBase, bestBase+bestLen)
 579         boolean compress = (bestBase != -1);
 580 
 581         StringBuffer buf = new StringBuffer(40);
 582         if (bestBase == 0) {
 583             buf.append(':');
 584         }
 585         for (int i = 0; i < 8; i++) {
 586             if (!compress || (i < bestBase) || (i >= bestBase + bestLen)) {
 587                 buf.append(Integer.toHexString(addr6[i]));
 588                 if (i < 7) {
 589                     buf.append(':');
 590                 }
 591             } else if (compress && (i == bestBase)) {  // first compressed zero
 592                 buf.append(':');
 593             }
 594         }
 595 
 596         return buf.toString();
 597     }
 598 }