Home | History | Annotate | Download | only in DNS
      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