1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@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
Record()32 Record() {}
33
Record(Name name, int type, int dclass, long ttl)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
getObject()50 getObject();
51
52 private static final Record
getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData)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
rrFromWire(DNSInput in)75 rrFromWire(DNSInput in) throws IOException;
76
77 private static Record
newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in)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
newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data)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
newRecord(Name name, int type, int dclass, long ttl, byte [] data)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
newRecord(Name name, int type, int dclass, long ttl)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
newRecord(Name name, int type, int dclass)171 newRecord(Name name, int type, int dclass) {
172 return newRecord(name, type, dclass, 0);
173 }
174
175 static Record
fromWire(DNSInput in, int section, boolean isUpdate)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
fromWire(DNSInput in, int section)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
fromWire(byte [] b, int section)208 fromWire(byte [] b, int section) throws IOException {
209 return fromWire(new DNSInput(b), section, false);
210 }
211
212 void
toWire(DNSOutput out, int section, Compression c)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 []
toWire(int section)231 toWire(int section) {
232 DNSOutput out = new DNSOutput();
233 toWire(out, section, null);
234 return out.toByteArray();
235 }
236
237 private void
toWireCanonical(DNSOutput out, boolean noTTL)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 []
toWireCanonical(boolean noTTL)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 []
toWireCanonical()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 []
rdataToWireCanonical()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 */
rrToString()288 abstract String rrToString();
289
290 /**
291 * Converts the rdata portion of a Record into a String representation
292 */
293 public String
rdataToString()294 rdataToString() {
295 return rrToString();
296 }
297
298 /**
299 * Converts a Record into a String representation
300 */
301 public String
toString()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
rdataFromString(Tokenizer st, Name origin)332 rdataFromString(Tokenizer st, Name origin) throws IOException;
333
334 /**
335 * Converts a String into a byte array.
336 */
337 protected static byte []
byteArrayFromString(String s)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
byteArrayToString(byte [] array, boolean quote)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
unknownToString(byte [] data)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
fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin)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
fromString(Name name, int type, int dclass, long ttl, String s, Name origin)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
getName()503 getName() {
504 return name;
505 }
506
507 /**
508 * Returns the record's type
509 * @see Type
510 */
511 public int
getType()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
getRRsetType()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
getDClass()538 getDClass() {
539 return dclass;
540 }
541
542 /**
543 * Returns the record's TTL
544 */
545 public long
getTTL()546 getTTL() {
547 return ttl;
548 }
549
550 /**
551 * Converts the type-specific RR to wire format - must be overriden
552 */
553 abstract void
rrToWire(DNSOutput out, Compression c, boolean canonical)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
sameRRset(Record rec)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
equals(Object arg)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
hashCode()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
cloneRecord()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
withName(Name name)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
withDClass(int dclass, long ttl)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
setTTL(long ttl)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
compareTo(Object o)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
getAdditionalName()683 getAdditionalName() {
684 return null;
685 }
686
687 /* Checks that an int contains an unsigned 8 bit value */
688 static int
checkU8(String field, int val)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
checkU16(String field, int val)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
checkU32(String field, long val)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
checkName(String field, Name name)719 checkName(String field, Name name) {
720 if (!name.isAbsolute())
721 throw new RelativeNameException(name);
722 return name;
723 }
724
725 static byte []
checkByteArrayLength(String field, byte [] array, int maxLength)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