1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) xbill.org) 2 3 package org.xbill.DNS; 4 5 import java.io.*; 6 import java.text.*; 7 import java.util.*; 8 import 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 17 public abstract class Record implements Cloneable, Comparable, Serializable { 18 19 private static final long serialVersionUID = 2694906050116005466L; 20 21 protected Name name; 22 protected int type, dclass; 23 protected long ttl; 24 25 private static final DecimalFormat byteFormat = new DecimalFormat(); 26 27 static { 28 byteFormat.setMinimumIntegerDigits(3); 29 } 30 31 protected 32 Record() {} 33 34 Record(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 */ 49 abstract Record 50 getObject(); 51 52 private static final Record 53 getEmptyRecord(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 */ 74 abstract void 75 rrFromWire(DNSInput in) throws IOException; 76 77 private static Record 78 newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in) 79 throws 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 */ 107 public static Record 108 newRecord(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 */ 137 public static Record 138 newRecord(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 */ 150 public static Record 151 newRecord(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 */ 170 public static Record 171 newRecord(Name name, int type, int dclass) { 172 return newRecord(name, type, dclass, 0); 173 } 174 175 static Record 176 fromWire(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 199 static Record 200 fromWire(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 */ 207 public static Record 208 fromWire(byte [] b, int section) throws IOException { 209 return fromWire(new DNSInput(b), section, false); 210 } 211 212 void 213 toWire(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 */ 230 public byte [] 231 toWire(int section) { 232 DNSOutput out = new DNSOutput(); 233 toWire(out, section, null); 234 return out.toByteArray(); 235 } 236 237 private void 238 toWireCanonical(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 */ 258 private byte [] 259 toWireCanonical(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 */ 269 public byte [] 270 toWireCanonical() { 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 */ 278 public byte [] 279 rdataToWireCanonical() { 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 */ 288 abstract String rrToString(); 289 290 /** 291 * Converts the rdata portion of a Record into a String representation 292 */ 293 public String 294 rdataToString() { 295 return rrToString(); 296 } 297 298 /** 299 * Converts a Record into a String representation 300 */ 301 public String 302 toString() { 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 */ 331 abstract void 332 rdataFromString(Tokenizer st, Name origin) throws IOException; 333 334 /** 335 * Converts a String into a byte array. 336 */ 337 protected static byte [] 338 byteArrayFromString(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 */ 400 protected static String 401 byteArrayToString(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 */ 424 protected static String 425 unknownToString(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 */ 445 public static Record 446 fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) 447 throws 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 */ 491 public static Record 492 fromString(Name name, int type, int dclass, long ttl, String s, Name origin) 493 throws 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 */ 502 public Name 503 getName() { 504 return name; 505 } 506 507 /** 508 * Returns the record's type 509 * @see Type 510 */ 511 public int 512 getType() { 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 */ 525 public int 526 getRRsetType() { 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 */ 537 public int 538 getDClass() { 539 return dclass; 540 } 541 542 /** 543 * Returns the record's TTL 544 */ 545 public long 546 getTTL() { 547 return ttl; 548 } 549 550 /** 551 * Converts the type-specific RR to wire format - must be overriden 552 */ 553 abstract void 554 rrToWire(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 */ 561 public boolean 562 sameRRset(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 */ 574 public boolean 575 equals(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 */ 589 public int 590 hashCode() { 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 598 Record 599 cloneRecord() { 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 */ 612 public Record 613 withName(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 */ 625 Record 626 withDClass(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. */ 634 void 635 setTTL(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 */ 649 public int 650 compareTo(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 */ 682 public Name 683 getAdditionalName() { 684 return null; 685 } 686 687 /* Checks that an int contains an unsigned 8 bit value */ 688 static int 689 checkU8(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 */ 698 static int 699 checkU16(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 */ 708 static long 709 checkU32(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 */ 718 static Name 719 checkName(String field, Name name) { 720 if (!name.isAbsolute()) 721 throw new RelativeNameException(name); 722 return name; 723 } 724 725 static byte [] 726 checkByteArrayLength(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