001: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.io.*;
006: import java.text.*;
007: import java.util.*;
008: import org.xbill.DNS.utils.*;
009:
010: /**
011: * A generic DNS resource record. The specific record types extend this class.
012: * A record contains a name, type, class, ttl, and rdata.
013: *
014: * @author Brian Wellington
015: */
016:
017: public abstract class Record implements Cloneable, Comparable {
018:
019: protected Name name;
020: protected int type, dclass;
021: protected long ttl;
022:
023: private static final Record unknownRecord = new UNKRecord();
024: private static final Class[] emptyClassArray = new Class[0];
025: private static final Object[] emptyObjectArray = new Object[0];
026:
027: private static final DecimalFormat byteFormat = new DecimalFormat();
028:
029: static {
030: byteFormat.setMinimumIntegerDigits(3);
031: }
032:
033: protected Record() {
034: }
035:
036: Record(Name name, int type, int dclass, long ttl) {
037: if (!name.isAbsolute())
038: throw new RelativeNameException(name);
039: Type.check(type);
040: DClass.check(dclass);
041: TTL.check(ttl);
042: this .name = name;
043: this .type = type;
044: this .dclass = dclass;
045: this .ttl = ttl;
046: }
047:
048: /**
049: * Creates an empty record of the correct type; must be overriden
050: */
051: abstract Record getObject();
052:
053: private static final Record getEmptyRecord(Name name, int type,
054: int dclass, long ttl, boolean hasData) {
055: Record proto, rec;
056:
057: if (hasData) {
058: proto = Type.getProto(type);
059: if (proto != null)
060: rec = proto.getObject();
061: else
062: rec = new UNKRecord();
063: } else
064: rec = new EmptyRecord();
065: rec.name = name;
066: rec.type = type;
067: rec.dclass = dclass;
068: rec.ttl = ttl;
069: return rec;
070: }
071:
072: /**
073: * Converts the type-specific RR to wire format - must be overriden
074: */
075: abstract void rrFromWire(DNSInput in) throws IOException;
076:
077: private static Record newRecord(Name name, int type, int dclass,
078: long ttl, int length, DNSInput in) throws IOException {
079: Record rec;
080: int recstart;
081: rec = getEmptyRecord(name, type, dclass, ttl, in != null);
082: if (in != null) {
083: if (in.remaining() < length)
084: throw new WireParseException("truncated record");
085: in.setActive(length);
086:
087: rec.rrFromWire(in);
088:
089: if (in.remaining() > 0)
090: throw new WireParseException("invalid record length");
091: in.clearActive();
092: }
093: return rec;
094: }
095:
096: /**
097: * Creates a new record, with the given parameters.
098: * @param name The owner name of the record.
099: * @param type The record's type.
100: * @param dclass The record's class.
101: * @param ttl The record's time to live.
102: * @param length The length of the record's data.
103: * @param data The rdata of the record, in uncompressed DNS wire format. Only
104: * the first length bytes are used.
105: */
106: public static Record newRecord(Name name, int type, int dclass,
107: long ttl, int length, byte[] data) {
108: if (!name.isAbsolute())
109: throw new RelativeNameException(name);
110: Type.check(type);
111: DClass.check(dclass);
112: TTL.check(ttl);
113:
114: DNSInput in;
115: if (data != null)
116: in = new DNSInput(data);
117: else
118: in = null;
119: try {
120: return newRecord(name, type, dclass, ttl, length, in);
121: } catch (IOException e) {
122: return null;
123: }
124: }
125:
126: /**
127: * Creates a new record, with the given parameters.
128: * @param name The owner name of the record.
129: * @param type The record's type.
130: * @param dclass The record's class.
131: * @param ttl The record's time to live.
132: * @param data The complete rdata of the record, in uncompressed DNS wire
133: * format.
134: */
135: public static Record newRecord(Name name, int type, int dclass,
136: long ttl, byte[] data) {
137: return newRecord(name, type, dclass, ttl, data.length, data);
138: }
139:
140: /**
141: * Creates a new empty record, with the given parameters.
142: * @param name The owner name of the record.
143: * @param type The record's type.
144: * @param dclass The record's class.
145: * @param ttl The record's time to live.
146: * @return An object of a subclass of Record
147: */
148: public static Record newRecord(Name name, int type, int dclass,
149: long ttl) {
150: if (!name.isAbsolute())
151: throw new RelativeNameException(name);
152: Type.check(type);
153: DClass.check(dclass);
154: TTL.check(ttl);
155:
156: return getEmptyRecord(name, type, dclass, ttl, false);
157: }
158:
159: /**
160: * Creates a new empty record, with the given parameters. This method is
161: * designed to create records that will be added to the QUERY section
162: * of a message.
163: * @param name The owner name of the record.
164: * @param type The record's type.
165: * @param dclass The record's class.
166: * @return An object of a subclass of Record
167: */
168: public static Record newRecord(Name name, int type, int dclass) {
169: return newRecord(name, type, dclass, 0);
170: }
171:
172: static Record fromWire(DNSInput in, int section, boolean isUpdate)
173: throws IOException {
174: int type, dclass;
175: long ttl;
176: int length;
177: Name name;
178: Record rec;
179:
180: name = new Name(in);
181: type = in.readU16();
182: dclass = in.readU16();
183:
184: if (section == Section.QUESTION)
185: return newRecord(name, type, dclass);
186:
187: ttl = in.readU32();
188: length = in.readU16();
189: if (length == 0 && isUpdate)
190: return newRecord(name, type, dclass, ttl);
191: rec = newRecord(name, type, dclass, ttl, length, in);
192: return rec;
193: }
194:
195: static Record fromWire(DNSInput in, int section) throws IOException {
196: return fromWire(in, section, false);
197: }
198:
199: /**
200: * Builds a Record from DNS uncompressed wire format.
201: */
202: public static Record fromWire(byte[] b, int section)
203: throws IOException {
204: return fromWire(new DNSInput(b), section, false);
205: }
206:
207: void toWire(DNSOutput out, int section, Compression c) {
208: name.toWire(out, c);
209: out.writeU16(type);
210: out.writeU16(dclass);
211: if (section == Section.QUESTION)
212: return;
213: out.writeU32(ttl);
214: int lengthPosition = out.current();
215: out.writeU16(0); /* until we know better */
216: rrToWire(out, c, false);
217: int rrlength = out.current() - lengthPosition - 2;
218: out.save();
219: out.jump(lengthPosition);
220: out.writeU16(rrlength);
221: out.restore();
222: }
223:
224: /**
225: * Converts a Record into DNS uncompressed wire format.
226: */
227: public byte[] toWire(int section) {
228: DNSOutput out = new DNSOutput();
229: toWire(out, section, null);
230: return out.toByteArray();
231: }
232:
233: private void toWireCanonical(DNSOutput out, boolean noTTL) {
234: name.toWireCanonical(out);
235: out.writeU16(type);
236: out.writeU16(dclass);
237: if (noTTL) {
238: out.writeU32(0);
239: } else {
240: out.writeU32(ttl);
241: }
242: int lengthPosition = out.current();
243: out.writeU16(0); /* until we know better */
244: rrToWire(out, null, true);
245: int rrlength = out.current() - lengthPosition - 2;
246: out.save();
247: out.jump(lengthPosition);
248: out.writeU16(rrlength);
249: out.restore();
250: }
251:
252: /*
253: * Converts a Record into canonical DNS uncompressed wire format (all names are
254: * converted to lowercase), optionally ignoring the TTL.
255: */
256: private byte[] toWireCanonical(boolean noTTL) {
257: DNSOutput out = new DNSOutput();
258: toWireCanonical(out, noTTL);
259: return out.toByteArray();
260: }
261:
262: /**
263: * Converts a Record into canonical DNS uncompressed wire format (all names are
264: * converted to lowercase).
265: */
266: public byte[] toWireCanonical() {
267: return toWireCanonical(false);
268: }
269:
270: /**
271: * Converts the rdata in a Record into canonical DNS uncompressed wire format
272: * (all names are converted to lowercase).
273: */
274: public byte[] rdataToWireCanonical() {
275: DNSOutput out = new DNSOutput();
276: rrToWire(out, null, true);
277: return out.toByteArray();
278: }
279:
280: /**
281: * Converts the type-specific RR to text format - must be overriden
282: */
283: abstract String rrToString();
284:
285: /**
286: * Converts the rdata portion of a Record into a String representation
287: */
288: public String rdataToString() {
289: return rrToString();
290: }
291:
292: /**
293: * Converts a Record into a String representation
294: */
295: public String toString() {
296: StringBuffer sb = new StringBuffer();
297: sb.append(name);
298: if (sb.length() < 8)
299: sb.append("\t");
300: if (sb.length() < 16)
301: sb.append("\t");
302: sb.append("\t");
303: if (Options.check("BINDTTL"))
304: sb.append(TTL.format(ttl));
305: else
306: sb.append(ttl);
307: sb.append("\t");
308: if (dclass != DClass.IN || !Options.check("noPrintIN")) {
309: sb.append(DClass.string(dclass));
310: sb.append("\t");
311: }
312: sb.append(Type.string(type));
313: String rdata = rrToString();
314: if (!rdata.equals("")) {
315: sb.append("\t");
316: sb.append(rdata);
317: }
318: return sb.toString();
319: }
320:
321: /**
322: * Converts the text format of an RR to the internal format - must be overriden
323: */
324: abstract void rdataFromString(Tokenizer st, Name origin)
325: throws IOException;
326:
327: /**
328: * Converts a String into a byte array.
329: */
330: protected static byte[] byteArrayFromString(String s)
331: throws TextParseException {
332: byte[] array = s.getBytes();
333: boolean escaped = false;
334: boolean hasEscapes = false;
335:
336: for (int i = 0; i < array.length; i++) {
337: if (array[i] == '\\') {
338: hasEscapes = true;
339: break;
340: }
341: }
342: if (!hasEscapes) {
343: if (array.length > 255) {
344: throw new TextParseException("text string too long");
345: }
346: return array;
347: }
348:
349: ByteArrayOutputStream os = new ByteArrayOutputStream();
350:
351: int digits = 0;
352: int intval = 0;
353: for (int i = 0; i < array.length; i++) {
354: byte b = array[i];
355: if (escaped) {
356: if (b >= '0' && b <= '9' && digits < 3) {
357: digits++;
358: intval *= 10;
359: intval += (b - '0');
360: if (intval > 255)
361: throw new TextParseException("bad escape");
362: if (digits < 3)
363: continue;
364: b = (byte) intval;
365: } else if (digits > 0 && digits < 3)
366: throw new TextParseException("bad escape");
367: os.write(b);
368: escaped = false;
369: } else if (array[i] == '\\') {
370: escaped = true;
371: digits = 0;
372: intval = 0;
373: } else
374: os.write(array[i]);
375: }
376: if (digits > 0 && digits < 3)
377: throw new TextParseException("bad escape");
378: array = os.toByteArray();
379: if (array.length > 255) {
380: throw new TextParseException("text string too long");
381: }
382:
383: return os.toByteArray();
384: }
385:
386: /**
387: * Converts a byte array into a String.
388: */
389: protected static String byteArrayToString(byte[] array,
390: boolean quote) {
391: StringBuffer sb = new StringBuffer();
392: if (quote)
393: sb.append('"');
394: for (int i = 0; i < array.length; i++) {
395: int b = array[i] & 0xFF;
396: if (b < 0x20 || b >= 0x7f) {
397: sb.append('\\');
398: sb.append(byteFormat.format(b));
399: } else if (b == '"' || b == ';' || b == '\\') {
400: sb.append('\\');
401: sb.append((char) b);
402: } else
403: sb.append((char) b);
404: }
405: if (quote)
406: sb.append('"');
407: return sb.toString();
408: }
409:
410: /**
411: * Converts a byte array into the unknown RR format.
412: */
413: protected static String unknownToString(byte[] data) {
414: StringBuffer sb = new StringBuffer();
415: sb.append("\\# ");
416: sb.append(data.length);
417: sb.append(" ");
418: sb.append(base16.toString(data));
419: return sb.toString();
420: }
421:
422: /**
423: * Builds a new Record from its textual representation
424: * @param name The owner name of the record.
425: * @param type The record's type.
426: * @param dclass The record's class.
427: * @param ttl The record's time to live.
428: * @param st A tokenizer containing the textual representation of the rdata.
429: * @param origin The default origin to be appended to relative domain names.
430: * @return The new record
431: * @throws IOException The text format was invalid.
432: */
433: public static Record fromString(Name name, int type, int dclass,
434: long ttl, Tokenizer st, Name origin) throws IOException {
435: Record rec;
436:
437: if (!name.isAbsolute())
438: throw new RelativeNameException(name);
439: Type.check(type);
440: DClass.check(dclass);
441: TTL.check(ttl);
442:
443: Tokenizer.Token t = st.get();
444: if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) {
445: int length = st.getUInt16();
446: byte[] data = st.getHex();
447: if (data == null) {
448: data = new byte[0];
449: }
450: if (length != data.length)
451: throw st.exception("invalid unknown RR encoding: "
452: + "length mismatch");
453: DNSInput in = new DNSInput(data);
454: return newRecord(name, type, dclass, ttl, length, in);
455: }
456: st.unget();
457: rec = getEmptyRecord(name, type, dclass, ttl, true);
458: rec.rdataFromString(st, origin);
459: t = st.get();
460: if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) {
461: throw st.exception("unexpected tokens at end of record");
462: }
463: return rec;
464: }
465:
466: /**
467: * Builds a new Record from its textual representation
468: * @param name The owner name of the record.
469: * @param type The record's type.
470: * @param dclass The record's class.
471: * @param ttl The record's time to live.
472: * @param s The textual representation of the rdata.
473: * @param origin The default origin to be appended to relative domain names.
474: * @return The new record
475: * @throws IOException The text format was invalid.
476: */
477: public static Record fromString(Name name, int type, int dclass,
478: long ttl, String s, Name origin) throws IOException {
479: return fromString(name, type, dclass, ttl, new Tokenizer(s),
480: origin);
481: }
482:
483: /**
484: * Returns the record's name
485: * @see Name
486: */
487: public Name getName() {
488: return name;
489: }
490:
491: /**
492: * Returns the record's type
493: * @see Type
494: */
495: public int getType() {
496: return type;
497: }
498:
499: /**
500: * Returns the type of RRset that this record would belong to. For all types
501: * except RRSIGRecord, this is equivalent to getType().
502: * @return The type of record, if not SIGRecord. If the type is RRSIGRecord,
503: * the type covered is returned.
504: * @see Type
505: * @see RRset
506: * @see SIGRecord
507: */
508: public int getRRsetType() {
509: if (type == Type.RRSIG) {
510: RRSIGRecord sig = (RRSIGRecord) this ;
511: return sig.getTypeCovered();
512: }
513: return type;
514: }
515:
516: /**
517: * Returns the record's class
518: */
519: public int getDClass() {
520: return dclass;
521: }
522:
523: /**
524: * Returns the record's TTL
525: */
526: public long getTTL() {
527: return ttl;
528: }
529:
530: /**
531: * Converts the type-specific RR to wire format - must be overriden
532: */
533: abstract void rrToWire(DNSOutput out, Compression c,
534: boolean canonical);
535:
536: /**
537: * Determines if two Records could be part of the same RRset.
538: * This compares the name, type, and class of the Records; the ttl and
539: * rdata are not compared.
540: */
541: public boolean sameRRset(Record rec) {
542: return (getRRsetType() == rec.getRRsetType()
543: && dclass == rec.dclass && name.equals(rec.name));
544: }
545:
546: /**
547: * Determines if two Records are identical. This compares the name, type,
548: * class, and rdata (with names canonicalized). The TTLs are not compared.
549: * @param arg The record to compare to
550: * @return true if the records are equal, false otherwise.
551: */
552: public boolean equals(Object arg) {
553: if (arg == null || !(arg instanceof Record))
554: return false;
555: Record r = (Record) arg;
556: if (type != r.type || dclass != r.dclass
557: || !name.equals(r.name))
558: return false;
559: byte[] array1 = rdataToWireCanonical();
560: byte[] array2 = r.rdataToWireCanonical();
561: return Arrays.equals(array1, array2);
562: }
563:
564: /**
565: * Generates a hash code based on the Record's data.
566: */
567: public int hashCode() {
568: byte[] array = toWireCanonical(true);
569: int code = 0;
570: for (int i = 0; i < array.length; i++)
571: code += ((code << 3) + (array[i] & 0xFF));
572: return code;
573: }
574:
575: Record cloneRecord() {
576: try {
577: return (Record) clone();
578: } catch (CloneNotSupportedException e) {
579: throw new IllegalStateException();
580: }
581: }
582:
583: /**
584: * Creates a new record identical to the current record, but with a different
585: * name. This is most useful for replacing the name of a wildcard record.
586: */
587: public Record withName(Name name) {
588: if (!name.isAbsolute())
589: throw new RelativeNameException(name);
590: Record rec = cloneRecord();
591: rec.name = name;
592: return rec;
593: }
594:
595: /**
596: * Creates a new record identical to the current record, but with a different
597: * class and ttl. This is most useful for dynamic update.
598: */
599: Record withDClass(int dclass, long ttl) {
600: Record rec = cloneRecord();
601: rec.dclass = dclass;
602: rec.ttl = ttl;
603: return rec;
604: }
605:
606: /* Sets the TTL to the specified value. This is intentionally not public. */
607: void setTTL(long ttl) {
608: this .ttl = ttl;
609: }
610:
611: /**
612: * Compares this Record to another Object.
613: * @param o The Object to be compared.
614: * @return The value 0 if the argument is a record equivalent to this record;
615: * a value less than 0 if the argument is less than this record in the
616: * canonical ordering, and a value greater than 0 if the argument is greater
617: * than this record in the canonical ordering. The canonical ordering
618: * is defined to compare by name, class, type, and rdata.
619: * @throws ClassCastException if the argument is not a Record.
620: */
621: public int compareTo(Object o) {
622: Record arg = (Record) o;
623:
624: if (this == arg)
625: return (0);
626:
627: int n = name.compareTo(arg.name);
628: if (n != 0)
629: return (n);
630: n = dclass - arg.dclass;
631: if (n != 0)
632: return (n);
633: n = type - arg.type;
634: if (n != 0)
635: return (n);
636: byte[] rdata1 = rdataToWireCanonical();
637: byte[] rdata2 = arg.rdataToWireCanonical();
638: for (int i = 0; i < rdata1.length && i < rdata2.length; i++) {
639: n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF);
640: if (n != 0)
641: return (n);
642: }
643: return (rdata1.length - rdata2.length);
644: }
645:
646: /**
647: * Returns the name for which additional data processing should be done
648: * for this record. This can be used both for building responses and
649: * parsing responses.
650: * @return The name to used for additional data processing, or null if this
651: * record type does not require additional data processing.
652: */
653: public Name getAdditionalName() {
654: return null;
655: }
656:
657: /* Checks that an int contains an unsigned 8 bit value */
658: static int checkU8(String field, int val) {
659: if (val < 0 || val > 0xFF)
660: throw new IllegalArgumentException("\"" + field + "\" "
661: + val + " must be an unsigned 8 " + "bit value");
662: return val;
663: }
664:
665: /* Checks that an int contains an unsigned 16 bit value */
666: static int checkU16(String field, int val) {
667: if (val < 0 || val > 0xFFFF)
668: throw new IllegalArgumentException("\"" + field + "\" "
669: + val + " must be an unsigned 16 " + "bit value");
670: return val;
671: }
672:
673: /* Checks that a long contains an unsigned 32 bit value */
674: static long checkU32(String field, long val) {
675: if (val < 0 || val > 0xFFFFFFFFL)
676: throw new IllegalArgumentException("\"" + field + "\" "
677: + val + " must be an unsigned 32 " + "bit value");
678: return val;
679: }
680:
681: /* Checks that a name is absolute */
682: static Name checkName(String field, Name name) {
683: if (!name.isAbsolute())
684: throw new RelativeNameException(name);
685: return name;
686: }
687:
688: }
|