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 }