• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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