1// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) 2 3package org.xbill.DNS; 4 5import java.io.*; 6import java.text.*; 7import java.util.*; 8import org.xbill.DNS.utils.*; 9 10/** 11 * A generic DNS resource record. The specific record types extend this class. 12 * A record contains a name, type, class, ttl, and rdata. 13 * 14 * @author Brian Wellington 15 */ 16 17public abstract class Record implements Cloneable, Comparable, Serializable { 18 19private static final long serialVersionUID = 2694906050116005466L; 20 21protected Name name; 22protected int type, dclass; 23protected long ttl; 24 25private static final DecimalFormat byteFormat = new DecimalFormat(); 26 27static { 28 byteFormat.setMinimumIntegerDigits(3); 29} 30 31protected 32Record() {} 33 34Record(Name name, int type, int dclass, long ttl) { 35 if (!name.isAbsolute()) 36 throw new RelativeNameException(name); 37 Type.check(type); 38 DClass.check(dclass); 39 TTL.check(ttl); 40 this.name = name; 41 this.type = type; 42 this.dclass = dclass; 43 this.ttl = ttl; 44} 45 46/** 47 * Creates an empty record of the correct type; must be overriden 48 */ 49abstract Record 50getObject(); 51 52private static final Record 53getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) { 54 Record proto, rec; 55 56 if (hasData) { 57 proto = Type.getProto(type); 58 if (proto != null) 59 rec = proto.getObject(); 60 else 61 rec = new UNKRecord(); 62 } else 63 rec = new EmptyRecord(); 64 rec.name = name; 65 rec.type = type; 66 rec.dclass = dclass; 67 rec.ttl = ttl; 68 return rec; 69} 70 71/** 72 * Converts the type-specific RR to wire format - must be overriden 73 */ 74abstract void 75rrFromWire(DNSInput in) throws IOException; 76 77private static Record 78newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in) 79throws IOException 80{ 81 Record rec; 82 rec = getEmptyRecord(name, type, dclass, ttl, in != null); 83 if (in != null) { 84 if (in.remaining() < length) 85 throw new WireParseException("truncated record"); 86 in.setActive(length); 87 88 rec.rrFromWire(in); 89 90 if (in.remaining() > 0) 91 throw new WireParseException("invalid record length"); 92 in.clearActive(); 93 } 94 return rec; 95} 96 97/** 98 * Creates a new record, with the given parameters. 99 * @param name The owner name of the record. 100 * @param type The record's type. 101 * @param dclass The record's class. 102 * @param ttl The record's time to live. 103 * @param length The length of the record's data. 104 * @param data The rdata of the record, in uncompressed DNS wire format. Only 105 * the first length bytes are used. 106 */ 107public static Record 108newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) { 109 if (!name.isAbsolute()) 110 throw new RelativeNameException(name); 111 Type.check(type); 112 DClass.check(dclass); 113 TTL.check(ttl); 114 115 DNSInput in; 116 if (data != null) 117 in = new DNSInput(data); 118 else 119 in = null; 120 try { 121 return newRecord(name, type, dclass, ttl, length, in); 122 } 123 catch (IOException e) { 124 return null; 125 } 126} 127 128/** 129 * Creates a new record, with the given parameters. 130 * @param name The owner name of the record. 131 * @param type The record's type. 132 * @param dclass The record's class. 133 * @param ttl The record's time to live. 134 * @param data The complete rdata of the record, in uncompressed DNS wire 135 * format. 136 */ 137public static Record 138newRecord(Name name, int type, int dclass, long ttl, byte [] data) { 139 return newRecord(name, type, dclass, ttl, data.length, data); 140} 141 142/** 143 * Creates a new empty record, with the given parameters. 144 * @param name The owner name of the record. 145 * @param type The record's type. 146 * @param dclass The record's class. 147 * @param ttl The record's time to live. 148 * @return An object of a subclass of Record 149 */ 150public static Record 151newRecord(Name name, int type, int dclass, long ttl) { 152 if (!name.isAbsolute()) 153 throw new RelativeNameException(name); 154 Type.check(type); 155 DClass.check(dclass); 156 TTL.check(ttl); 157 158 return getEmptyRecord(name, type, dclass, ttl, false); 159} 160 161/** 162 * Creates a new empty record, with the given parameters. This method is 163 * designed to create records that will be added to the QUERY section 164 * of a message. 165 * @param name The owner name of the record. 166 * @param type The record's type. 167 * @param dclass The record's class. 168 * @return An object of a subclass of Record 169 */ 170public static Record 171newRecord(Name name, int type, int dclass) { 172 return newRecord(name, type, dclass, 0); 173} 174 175static Record 176fromWire(DNSInput in, int section, boolean isUpdate) throws IOException { 177 int type, dclass; 178 long ttl; 179 int length; 180 Name name; 181 Record rec; 182 183 name = new Name(in); 184 type = in.readU16(); 185 dclass = in.readU16(); 186 187 if (section == Section.QUESTION) 188 return newRecord(name, type, dclass); 189 190 ttl = in.readU32(); 191 length = in.readU16(); 192 if (length == 0 && isUpdate && 193 (section == Section.PREREQ || section == Section.UPDATE)) 194 return newRecord(name, type, dclass, ttl); 195 rec = newRecord(name, type, dclass, ttl, length, in); 196 return rec; 197} 198 199static Record 200fromWire(DNSInput in, int section) throws IOException { 201 return fromWire(in, section, false); 202} 203 204/** 205 * Builds a Record from DNS uncompressed wire format. 206 */ 207public static Record 208fromWire(byte [] b, int section) throws IOException { 209 return fromWire(new DNSInput(b), section, false); 210} 211 212void 213toWire(DNSOutput out, int section, Compression c) { 214 name.toWire(out, c); 215 out.writeU16(type); 216 out.writeU16(dclass); 217 if (section == Section.QUESTION) 218 return; 219 out.writeU32(ttl); 220 int lengthPosition = out.current(); 221 out.writeU16(0); /* until we know better */ 222 rrToWire(out, c, false); 223 int rrlength = out.current() - lengthPosition - 2; 224 out.writeU16At(rrlength, lengthPosition); 225} 226 227/** 228 * Converts a Record into DNS uncompressed wire format. 229 */ 230public byte [] 231toWire(int section) { 232 DNSOutput out = new DNSOutput(); 233 toWire(out, section, null); 234 return out.toByteArray(); 235} 236 237private void 238toWireCanonical(DNSOutput out, boolean noTTL) { 239 name.toWireCanonical(out); 240 out.writeU16(type); 241 out.writeU16(dclass); 242 if (noTTL) { 243 out.writeU32(0); 244 } else { 245 out.writeU32(ttl); 246 } 247 int lengthPosition = out.current(); 248 out.writeU16(0); /* until we know better */ 249 rrToWire(out, null, true); 250 int rrlength = out.current() - lengthPosition - 2; 251 out.writeU16At(rrlength, lengthPosition); 252} 253 254/* 255 * Converts a Record into canonical DNS uncompressed wire format (all names are 256 * converted to lowercase), optionally ignoring the TTL. 257 */ 258private byte [] 259toWireCanonical(boolean noTTL) { 260 DNSOutput out = new DNSOutput(); 261 toWireCanonical(out, noTTL); 262 return out.toByteArray(); 263} 264 265/** 266 * Converts a Record into canonical DNS uncompressed wire format (all names are 267 * converted to lowercase). 268 */ 269public byte [] 270toWireCanonical() { 271 return toWireCanonical(false); 272} 273 274/** 275 * Converts the rdata in a Record into canonical DNS uncompressed wire format 276 * (all names are converted to lowercase). 277 */ 278public byte [] 279rdataToWireCanonical() { 280 DNSOutput out = new DNSOutput(); 281 rrToWire(out, null, true); 282 return out.toByteArray(); 283} 284 285/** 286 * Converts the type-specific RR to text format - must be overriden 287 */ 288abstract String rrToString(); 289 290/** 291 * Converts the rdata portion of a Record into a String representation 292 */ 293public String 294rdataToString() { 295 return rrToString(); 296} 297 298/** 299 * Converts a Record into a String representation 300 */ 301public String 302toString() { 303 StringBuffer sb = new StringBuffer(); 304 sb.append(name); 305 if (sb.length() < 8) 306 sb.append("\t"); 307 if (sb.length() < 16) 308 sb.append("\t"); 309 sb.append("\t"); 310 if (Options.check("BINDTTL")) 311 sb.append(TTL.format(ttl)); 312 else 313 sb.append(ttl); 314 sb.append("\t"); 315 if (dclass != DClass.IN || !Options.check("noPrintIN")) { 316 sb.append(DClass.string(dclass)); 317 sb.append("\t"); 318 } 319 sb.append(Type.string(type)); 320 String rdata = rrToString(); 321 if (!rdata.equals("")) { 322 sb.append("\t"); 323 sb.append(rdata); 324 } 325 return sb.toString(); 326} 327 328/** 329 * Converts the text format of an RR to the internal format - must be overriden 330 */ 331abstract void 332rdataFromString(Tokenizer st, Name origin) throws IOException; 333 334/** 335 * Converts a String into a byte array. 336 */ 337protected static byte [] 338byteArrayFromString(String s) throws TextParseException { 339 byte [] array = s.getBytes(); 340 boolean escaped = false; 341 boolean hasEscapes = false; 342 343 for (int i = 0; i < array.length; i++) { 344 if (array[i] == '\\') { 345 hasEscapes = true; 346 break; 347 } 348 } 349 if (!hasEscapes) { 350 if (array.length > 255) { 351 throw new TextParseException("text string too long"); 352 } 353 return array; 354 } 355 356 ByteArrayOutputStream os = new ByteArrayOutputStream(); 357 358 int digits = 0; 359 int intval = 0; 360 for (int i = 0; i < array.length; i++) { 361 byte b = array[i]; 362 if (escaped) { 363 if (b >= '0' && b <= '9' && digits < 3) { 364 digits++; 365 intval *= 10; 366 intval += (b - '0'); 367 if (intval > 255) 368 throw new TextParseException 369 ("bad escape"); 370 if (digits < 3) 371 continue; 372 b = (byte) intval; 373 } 374 else if (digits > 0 && digits < 3) 375 throw new TextParseException("bad escape"); 376 os.write(b); 377 escaped = false; 378 } 379 else if (array[i] == '\\') { 380 escaped = true; 381 digits = 0; 382 intval = 0; 383 } 384 else 385 os.write(array[i]); 386 } 387 if (digits > 0 && digits < 3) 388 throw new TextParseException("bad escape"); 389 array = os.toByteArray(); 390 if (array.length > 255) { 391 throw new TextParseException("text string too long"); 392 } 393 394 return os.toByteArray(); 395} 396 397/** 398 * Converts a byte array into a String. 399 */ 400protected static String 401byteArrayToString(byte [] array, boolean quote) { 402 StringBuffer sb = new StringBuffer(); 403 if (quote) 404 sb.append('"'); 405 for (int i = 0; i < array.length; i++) { 406 int b = array[i] & 0xFF; 407 if (b < 0x20 || b >= 0x7f) { 408 sb.append('\\'); 409 sb.append(byteFormat.format(b)); 410 } else if (b == '"' || b == '\\') { 411 sb.append('\\'); 412 sb.append((char)b); 413 } else 414 sb.append((char)b); 415 } 416 if (quote) 417 sb.append('"'); 418 return sb.toString(); 419} 420 421/** 422 * Converts a byte array into the unknown RR format. 423 */ 424protected static String 425unknownToString(byte [] data) { 426 StringBuffer sb = new StringBuffer(); 427 sb.append("\\# "); 428 sb.append(data.length); 429 sb.append(" "); 430 sb.append(base16.toString(data)); 431 return sb.toString(); 432} 433 434/** 435 * Builds a new Record from its textual representation 436 * @param name The owner name of the record. 437 * @param type The record's type. 438 * @param dclass The record's class. 439 * @param ttl The record's time to live. 440 * @param st A tokenizer containing the textual representation of the rdata. 441 * @param origin The default origin to be appended to relative domain names. 442 * @return The new record 443 * @throws IOException The text format was invalid. 444 */ 445public static Record 446fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) 447throws IOException 448{ 449 Record rec; 450 451 if (!name.isAbsolute()) 452 throw new RelativeNameException(name); 453 Type.check(type); 454 DClass.check(dclass); 455 TTL.check(ttl); 456 457 Tokenizer.Token t = st.get(); 458 if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) { 459 int length = st.getUInt16(); 460 byte [] data = st.getHex(); 461 if (data == null) { 462 data = new byte[0]; 463 } 464 if (length != data.length) 465 throw st.exception("invalid unknown RR encoding: " + 466 "length mismatch"); 467 DNSInput in = new DNSInput(data); 468 return newRecord(name, type, dclass, ttl, length, in); 469 } 470 st.unget(); 471 rec = getEmptyRecord(name, type, dclass, ttl, true); 472 rec.rdataFromString(st, origin); 473 t = st.get(); 474 if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { 475 throw st.exception("unexpected tokens at end of record"); 476 } 477 return rec; 478} 479 480/** 481 * Builds a new Record from its textual representation 482 * @param name The owner name of the record. 483 * @param type The record's type. 484 * @param dclass The record's class. 485 * @param ttl The record's time to live. 486 * @param s The textual representation of the rdata. 487 * @param origin The default origin to be appended to relative domain names. 488 * @return The new record 489 * @throws IOException The text format was invalid. 490 */ 491public static Record 492fromString(Name name, int type, int dclass, long ttl, String s, Name origin) 493throws IOException 494{ 495 return fromString(name, type, dclass, ttl, new Tokenizer(s), origin); 496} 497 498/** 499 * Returns the record's name 500 * @see Name 501 */ 502public Name 503getName() { 504 return name; 505} 506 507/** 508 * Returns the record's type 509 * @see Type 510 */ 511public int 512getType() { 513 return type; 514} 515 516/** 517 * Returns the type of RRset that this record would belong to. For all types 518 * except RRSIG, this is equivalent to getType(). 519 * @return The type of record, if not RRSIG. If the type is RRSIG, 520 * the type covered is returned. 521 * @see Type 522 * @see RRset 523 * @see SIGRecord 524 */ 525public int 526getRRsetType() { 527 if (type == Type.RRSIG) { 528 RRSIGRecord sig = (RRSIGRecord) this; 529 return sig.getTypeCovered(); 530 } 531 return type; 532} 533 534/** 535 * Returns the record's class 536 */ 537public int 538getDClass() { 539 return dclass; 540} 541 542/** 543 * Returns the record's TTL 544 */ 545public long 546getTTL() { 547 return ttl; 548} 549 550/** 551 * Converts the type-specific RR to wire format - must be overriden 552 */ 553abstract void 554rrToWire(DNSOutput out, Compression c, boolean canonical); 555 556/** 557 * Determines if two Records could be part of the same RRset. 558 * This compares the name, type, and class of the Records; the ttl and 559 * rdata are not compared. 560 */ 561public boolean 562sameRRset(Record rec) { 563 return (getRRsetType() == rec.getRRsetType() && 564 dclass == rec.dclass && 565 name.equals(rec.name)); 566} 567 568/** 569 * Determines if two Records are identical. This compares the name, type, 570 * class, and rdata (with names canonicalized). The TTLs are not compared. 571 * @param arg The record to compare to 572 * @return true if the records are equal, false otherwise. 573 */ 574public boolean 575equals(Object arg) { 576 if (arg == null || !(arg instanceof Record)) 577 return false; 578 Record r = (Record) arg; 579 if (type != r.type || dclass != r.dclass || !name.equals(r.name)) 580 return false; 581 byte [] array1 = rdataToWireCanonical(); 582 byte [] array2 = r.rdataToWireCanonical(); 583 return Arrays.equals(array1, array2); 584} 585 586/** 587 * Generates a hash code based on the Record's data. 588 */ 589public int 590hashCode() { 591 byte [] array = toWireCanonical(true); 592 int code = 0; 593 for (int i = 0; i < array.length; i++) 594 code += ((code << 3) + (array[i] & 0xFF)); 595 return code; 596} 597 598Record 599cloneRecord() { 600 try { 601 return (Record) clone(); 602 } 603 catch (CloneNotSupportedException e) { 604 throw new IllegalStateException(); 605 } 606} 607 608/** 609 * Creates a new record identical to the current record, but with a different 610 * name. This is most useful for replacing the name of a wildcard record. 611 */ 612public Record 613withName(Name name) { 614 if (!name.isAbsolute()) 615 throw new RelativeNameException(name); 616 Record rec = cloneRecord(); 617 rec.name = name; 618 return rec; 619} 620 621/** 622 * Creates a new record identical to the current record, but with a different 623 * class and ttl. This is most useful for dynamic update. 624 */ 625Record 626withDClass(int dclass, long ttl) { 627 Record rec = cloneRecord(); 628 rec.dclass = dclass; 629 rec.ttl = ttl; 630 return rec; 631} 632 633/* Sets the TTL to the specified value. This is intentionally not public. */ 634void 635setTTL(long ttl) { 636 this.ttl = ttl; 637} 638 639/** 640 * Compares this Record to another Object. 641 * @param o The Object to be compared. 642 * @return The value 0 if the argument is a record equivalent to this record; 643 * a value less than 0 if the argument is less than this record in the 644 * canonical ordering, and a value greater than 0 if the argument is greater 645 * than this record in the canonical ordering. The canonical ordering 646 * is defined to compare by name, class, type, and rdata. 647 * @throws ClassCastException if the argument is not a Record. 648 */ 649public int 650compareTo(Object o) { 651 Record arg = (Record) o; 652 653 if (this == arg) 654 return (0); 655 656 int n = name.compareTo(arg.name); 657 if (n != 0) 658 return (n); 659 n = dclass - arg.dclass; 660 if (n != 0) 661 return (n); 662 n = type - arg.type; 663 if (n != 0) 664 return (n); 665 byte [] rdata1 = rdataToWireCanonical(); 666 byte [] rdata2 = arg.rdataToWireCanonical(); 667 for (int i = 0; i < rdata1.length && i < rdata2.length; i++) { 668 n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF); 669 if (n != 0) 670 return (n); 671 } 672 return (rdata1.length - rdata2.length); 673} 674 675/** 676 * Returns the name for which additional data processing should be done 677 * for this record. This can be used both for building responses and 678 * parsing responses. 679 * @return The name to used for additional data processing, or null if this 680 * record type does not require additional data processing. 681 */ 682public Name 683getAdditionalName() { 684 return null; 685} 686 687/* Checks that an int contains an unsigned 8 bit value */ 688static int 689checkU8(String field, int val) { 690 if (val < 0 || val > 0xFF) 691 throw new IllegalArgumentException("\"" + field + "\" " + val + 692 " must be an unsigned 8 " + 693 "bit value"); 694 return val; 695} 696 697/* Checks that an int contains an unsigned 16 bit value */ 698static int 699checkU16(String field, int val) { 700 if (val < 0 || val > 0xFFFF) 701 throw new IllegalArgumentException("\"" + field + "\" " + val + 702 " must be an unsigned 16 " + 703 "bit value"); 704 return val; 705} 706 707/* Checks that a long contains an unsigned 32 bit value */ 708static long 709checkU32(String field, long val) { 710 if (val < 0 || val > 0xFFFFFFFFL) 711 throw new IllegalArgumentException("\"" + field + "\" " + val + 712 " must be an unsigned 32 " + 713 "bit value"); 714 return val; 715} 716 717/* Checks that a name is absolute */ 718static Name 719checkName(String field, Name name) { 720 if (!name.isAbsolute()) 721 throw new RelativeNameException(name); 722 return name; 723} 724 725static byte [] 726checkByteArrayLength(String field, byte [] array, int maxLength) { 727 if (array.length > 0xFFFF) 728 throw new IllegalArgumentException("\"" + field + "\" array " + 729 "must have no more than " + 730 maxLength + " elements"); 731 byte [] out = new byte[array.length]; 732 System.arraycopy(array, 0, out, 0, array.length); 733 return out; 734} 735 736} 737