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