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