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