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.util.*;
      6 import java.io.*;
      7 
      8 /**
      9  * A DNS Message.  A message is the basic unit of communication between
     10  * the client and server of a DNS operation.  A message consists of a Header
     11  * and 4 message sections.
     12  * @see Resolver
     13  * @see Header
     14  * @see Section
     15  *
     16  * @author Brian Wellington
     17  */
     18 
     19 public class Message implements Cloneable {
     20 
     21 /** The maximum length of a message in wire format. */
     22 public static final int MAXLENGTH = 65535;
     23 
     24 private Header header;
     25 private List [] sections;
     26 private int size;
     27 private TSIG tsigkey;
     28 private TSIGRecord querytsig;
     29 private int tsigerror;
     30 
     31 int tsigstart;
     32 int tsigState;
     33 int sig0start;
     34 
     35 /* The message was not signed */
     36 static final int TSIG_UNSIGNED = 0;
     37 
     38 /* The message was signed and verification succeeded */
     39 static final int TSIG_VERIFIED = 1;
     40 
     41 /* The message was an unsigned message in multiple-message response */
     42 static final int TSIG_INTERMEDIATE = 2;
     43 
     44 /* The message was signed and no verification was attempted.  */
     45 static final int TSIG_SIGNED = 3;
     46 
     47 /*
     48  * The message was signed and verification failed, or was not signed
     49  * when it should have been.
     50  */
     51 static final int TSIG_FAILED = 4;
     52 
     53 private static Record [] emptyRecordArray = new Record[0];
     54 private static RRset [] emptyRRsetArray = new RRset[0];
     55 
     56 private
     57 Message(Header header) {
     58 	sections = new List[4];
     59 	this.header = header;
     60 }
     61 
     62 /** Creates a new Message with the specified Message ID */
     63 public
     64 Message(int id) {
     65 	this(new Header(id));
     66 }
     67 
     68 /** Creates a new Message with a random Message ID */
     69 public
     70 Message() {
     71 	this(new Header());
     72 }
     73 
     74 /**
     75  * Creates a new Message with a random Message ID suitable for sending as a
     76  * query.
     77  * @param r A record containing the question
     78  */
     79 public static Message
     80 newQuery(Record r) {
     81 	Message m = new Message();
     82 	m.header.setOpcode(Opcode.QUERY);
     83 	m.header.setFlag(Flags.RD);
     84 	m.addRecord(r, Section.QUESTION);
     85 	return m;
     86 }
     87 
     88 /**
     89  * Creates a new Message to contain a dynamic update.  A random Message ID
     90  * and the zone are filled in.
     91  * @param zone The zone to be updated
     92  */
     93 public static Message
     94 newUpdate(Name zone) {
     95 	return new Update(zone);
     96 }
     97 
     98 Message(DNSInput in) throws IOException {
     99 	this(new Header(in));
    100 	boolean isUpdate = (header.getOpcode() == Opcode.UPDATE);
    101 	boolean truncated = header.getFlag(Flags.TC);
    102 	try {
    103 		for (int i = 0; i < 4; i++) {
    104 			int count = header.getCount(i);
    105 			if (count > 0)
    106 				sections[i] = new ArrayList(count);
    107 			for (int j = 0; j < count; j++) {
    108 				int pos = in.current();
    109 				Record rec = Record.fromWire(in, i, isUpdate);
    110 				sections[i].add(rec);
    111 				if (i == Section.ADDITIONAL) {
    112 					if (rec.getType() == Type.TSIG)
    113 						tsigstart = pos;
    114 					if (rec.getType() == Type.SIG) {
    115 						SIGRecord sig = (SIGRecord) rec;
    116 						if (sig.getTypeCovered() == 0)
    117 							sig0start = pos;
    118 					}
    119 				}
    120 			}
    121 		}
    122 	} catch (WireParseException e) {
    123 		if (!truncated)
    124 			throw e;
    125 	}
    126 	size = in.current();
    127 }
    128 
    129 /**
    130  * Creates a new Message from its DNS wire format representation
    131  * @param b A byte array containing the DNS Message.
    132  */
    133 public
    134 Message(byte [] b) throws IOException {
    135 	this(new DNSInput(b));
    136 }
    137 
    138 /**
    139  * Replaces the Header with a new one.
    140  * @see Header
    141  */
    142 public void
    143 setHeader(Header h) {
    144 	header = h;
    145 }
    146 
    147 /**
    148  * Retrieves the Header.
    149  * @see Header
    150  */
    151 public Header
    152 getHeader() {
    153 	return header;
    154 }
    155 
    156 /**
    157  * Adds a record to a section of the Message, and adjusts the header.
    158  * @see Record
    159  * @see Section
    160  */
    161 public void
    162 addRecord(Record r, int section) {
    163 	if (sections[section] == null)
    164 		sections[section] = new LinkedList();
    165 	header.incCount(section);
    166 	sections[section].add(r);
    167 }
    168 
    169 /**
    170  * Removes a record from a section of the Message, and adjusts the header.
    171  * @see Record
    172  * @see Section
    173  */
    174 public boolean
    175 removeRecord(Record r, int section) {
    176 	if (sections[section] != null && sections[section].remove(r)) {
    177 		header.decCount(section);
    178 		return true;
    179 	}
    180 	else
    181 		return false;
    182 }
    183 
    184 /**
    185  * Removes all records from a section of the Message, and adjusts the header.
    186  * @see Record
    187  * @see Section
    188  */
    189 public void
    190 removeAllRecords(int section) {
    191 	sections[section] = null;
    192 	header.setCount(section, 0);
    193 }
    194 
    195 /**
    196  * Determines if the given record is already present in the given section.
    197  * @see Record
    198  * @see Section
    199  */
    200 public boolean
    201 findRecord(Record r, int section) {
    202 	return (sections[section] != null && sections[section].contains(r));
    203 }
    204 
    205 /**
    206  * Determines if the given record is already present in any section.
    207  * @see Record
    208  * @see Section
    209  */
    210 public boolean
    211 findRecord(Record r) {
    212 	for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++)
    213 		if (sections[i] != null && sections[i].contains(r))
    214 			return true;
    215 	return false;
    216 }
    217 
    218 /**
    219  * Determines if an RRset with the given name and type is already
    220  * present in the given section.
    221  * @see RRset
    222  * @see Section
    223  */
    224 public boolean
    225 findRRset(Name name, int type, int section) {
    226 	if (sections[section] == null)
    227 		return false;
    228 	for (int i = 0; i < sections[section].size(); i++) {
    229 		Record r = (Record) sections[section].get(i);
    230 		if (r.getType() == type && name.equals(r.getName()))
    231 			return true;
    232 	}
    233 	return false;
    234 }
    235 
    236 /**
    237  * Determines if an RRset with the given name and type is already
    238  * present in any section.
    239  * @see RRset
    240  * @see Section
    241  */
    242 public boolean
    243 findRRset(Name name, int type) {
    244 	return (findRRset(name, type, Section.ANSWER) ||
    245 		findRRset(name, type, Section.AUTHORITY) ||
    246 		findRRset(name, type, Section.ADDITIONAL));
    247 }
    248 
    249 /**
    250  * Returns the first record in the QUESTION section.
    251  * @see Record
    252  * @see Section
    253  */
    254 public Record
    255 getQuestion() {
    256 	List l = sections[Section.QUESTION];
    257 	if (l == null || l.size() == 0)
    258 		return null;
    259 	return (Record) l.get(0);
    260 }
    261 
    262 /**
    263  * Returns the TSIG record from the ADDITIONAL section, if one is present.
    264  * @see TSIGRecord
    265  * @see TSIG
    266  * @see Section
    267  */
    268 public TSIGRecord
    269 getTSIG() {
    270 	int count = header.getCount(Section.ADDITIONAL);
    271 	if (count == 0)
    272 		return null;
    273 	List l = sections[Section.ADDITIONAL];
    274 	Record rec = (Record) l.get(count - 1);
    275 	if (rec.type !=  Type.TSIG)
    276 		return null;
    277 	return (TSIGRecord) rec;
    278 }
    279 
    280 /**
    281  * Was this message signed by a TSIG?
    282  * @see TSIG
    283  */
    284 public boolean
    285 isSigned() {
    286 	return (tsigState == TSIG_SIGNED ||
    287 		tsigState == TSIG_VERIFIED ||
    288 		tsigState == TSIG_FAILED);
    289 }
    290 
    291 /**
    292  * If this message was signed by a TSIG, was the TSIG verified?
    293  * @see TSIG
    294  */
    295 public boolean
    296 isVerified() {
    297 	return (tsigState == TSIG_VERIFIED);
    298 }
    299 
    300 /**
    301  * Returns the OPT record from the ADDITIONAL section, if one is present.
    302  * @see OPTRecord
    303  * @see Section
    304  */
    305 public OPTRecord
    306 getOPT() {
    307 	Record [] additional = getSectionArray(Section.ADDITIONAL);
    308 	for (int i = 0; i < additional.length; i++)
    309 		if (additional[i] instanceof OPTRecord)
    310 			return (OPTRecord) additional[i];
    311 	return null;
    312 }
    313 
    314 /**
    315  * Returns the message's rcode (error code).  This incorporates the EDNS
    316  * extended rcode.
    317  */
    318 public int
    319 getRcode() {
    320 	int rcode = header.getRcode();
    321 	OPTRecord opt = getOPT();
    322 	if (opt != null)
    323 		rcode += (opt.getExtendedRcode() << 4);
    324 	return rcode;
    325 }
    326 
    327 /**
    328  * Returns an array containing all records in the given section, or an
    329  * empty array if the section is empty.
    330  * @see Record
    331  * @see Section
    332  */
    333 public Record []
    334 getSectionArray(int section) {
    335 	if (sections[section] == null)
    336 		return emptyRecordArray;
    337 	List l = sections[section];
    338 	return (Record []) l.toArray(new Record[l.size()]);
    339 }
    340 
    341 private static boolean
    342 sameSet(Record r1, Record r2) {
    343 	return (r1.getRRsetType() == r2.getRRsetType() &&
    344 		r1.getDClass() == r2.getDClass() &&
    345 		r1.getName().equals(r2.getName()));
    346 }
    347 
    348 /**
    349  * Returns an array containing all records in the given section grouped into
    350  * RRsets.
    351  * @see RRset
    352  * @see Section
    353  */
    354 public RRset []
    355 getSectionRRsets(int section) {
    356 	if (sections[section] == null)
    357 		return emptyRRsetArray;
    358 	List sets = new LinkedList();
    359 	Record [] recs = getSectionArray(section);
    360 	Set hash = new HashSet();
    361 	for (int i = 0; i < recs.length; i++) {
    362 		Name name = recs[i].getName();
    363 		boolean newset = true;
    364 		if (hash.contains(name)) {
    365 			for (int j = sets.size() - 1; j >= 0; j--) {
    366 				RRset set = (RRset) sets.get(j);
    367 				if (set.getType() == recs[i].getRRsetType() &&
    368 				    set.getDClass() == recs[i].getDClass() &&
    369 				    set.getName().equals(name))
    370 				{
    371 					set.addRR(recs[i]);
    372 					newset = false;
    373 					break;
    374 				}
    375 			}
    376 		}
    377 		if (newset) {
    378 			RRset set = new RRset(recs[i]);
    379 			sets.add(set);
    380 			hash.add(name);
    381 		}
    382 	}
    383 	return (RRset []) sets.toArray(new RRset[sets.size()]);
    384 }
    385 
    386 void
    387 toWire(DNSOutput out) {
    388 	header.toWire(out);
    389 	Compression c = new Compression();
    390 	for (int i = 0; i < 4; i++) {
    391 		if (sections[i] == null)
    392 			continue;
    393 		for (int j = 0; j < sections[i].size(); j++) {
    394 			Record rec = (Record)sections[i].get(j);
    395 			rec.toWire(out, i, c);
    396 		}
    397 	}
    398 }
    399 
    400 /* Returns the number of records not successfully rendered. */
    401 private int
    402 sectionToWire(DNSOutput out, int section, Compression c,
    403 	      int maxLength)
    404 {
    405 	int n = sections[section].size();
    406 	int pos = out.current();
    407 	int rendered = 0;
    408 	Record lastrec = null;
    409 
    410 	for (int i = 0; i < n; i++) {
    411 		Record rec = (Record)sections[section].get(i);
    412 		if (lastrec != null && !sameSet(rec, lastrec)) {
    413 			pos = out.current();
    414 			rendered = i;
    415 		}
    416 		lastrec = rec;
    417 		rec.toWire(out, section, c);
    418 		if (out.current() > maxLength) {
    419 			out.jump(pos);
    420 			return n - rendered;
    421 		}
    422 	}
    423 	return 0;
    424 }
    425 
    426 /* Returns true if the message could be rendered. */
    427 private boolean
    428 toWire(DNSOutput out, int maxLength) {
    429 	if (maxLength < Header.LENGTH)
    430 		return false;
    431 
    432 	Header newheader = null;
    433 
    434 	int tempMaxLength = maxLength;
    435 	if (tsigkey != null)
    436 		tempMaxLength -= tsigkey.recordLength();
    437 
    438 	int startpos = out.current();
    439 	header.toWire(out);
    440 	Compression c = new Compression();
    441 	for (int i = 0; i < 4; i++) {
    442 		int skipped;
    443 		if (sections[i] == null)
    444 			continue;
    445 		skipped = sectionToWire(out, i, c, tempMaxLength);
    446 		if (skipped != 0) {
    447 			if (newheader == null)
    448 				newheader = (Header) header.clone();
    449 			if (i != Section.ADDITIONAL)
    450 				newheader.setFlag(Flags.TC);
    451 			int count = newheader.getCount(i);
    452 			newheader.setCount(i, count - skipped);
    453 			for (int j = i + 1; j < 4; j++)
    454 				newheader.setCount(j, 0);
    455 
    456 			out.save();
    457 			out.jump(startpos);
    458 			newheader.toWire(out);
    459 			out.restore();
    460 			break;
    461 		}
    462 	}
    463 
    464 	if (tsigkey != null) {
    465 		TSIGRecord tsigrec = tsigkey.generate(this, out.toByteArray(),
    466 						      tsigerror, querytsig);
    467 
    468 		if (newheader == null)
    469 			newheader = (Header) header.clone();
    470 		tsigrec.toWire(out, Section.ADDITIONAL, c);
    471 		newheader.incCount(Section.ADDITIONAL);
    472 
    473 		out.save();
    474 		out.jump(startpos);
    475 		newheader.toWire(out);
    476 		out.restore();
    477 	}
    478 
    479 	return true;
    480 }
    481 
    482 /**
    483  * Returns an array containing the wire format representation of the Message.
    484  */
    485 public byte []
    486 toWire() {
    487 	DNSOutput out = new DNSOutput();
    488 	toWire(out);
    489 	size = out.current();
    490 	return out.toByteArray();
    491 }
    492 
    493 /**
    494  * Returns an array containing the wire format representation of the Message
    495  * with the specified maximum length.  This will generate a truncated
    496  * message (with the TC bit) if the message doesn't fit, and will also
    497  * sign the message with the TSIG key set by a call to setTSIG().  This
    498  * method may return null if the message could not be rendered at all; this
    499  * could happen if maxLength is smaller than a DNS header, for example.
    500  * @param maxLength The maximum length of the message.
    501  * @return The wire format of the message, or null if the message could not be
    502  * rendered into the specified length.
    503  * @see Flags
    504  * @see TSIG
    505  */
    506 public byte []
    507 toWire(int maxLength) {
    508 	DNSOutput out = new DNSOutput();
    509 	toWire(out, maxLength);
    510 	size = out.current();
    511 	return out.toByteArray();
    512 }
    513 
    514 /**
    515  * Sets the TSIG key and other necessary information to sign a message.
    516  * @param key The TSIG key.
    517  * @param error The value of the TSIG error field.
    518  * @param querytsig If this is a response, the TSIG from the request.
    519  */
    520 public void
    521 setTSIG(TSIG key, int error, TSIGRecord querytsig) {
    522 	this.tsigkey = key;
    523 	this.tsigerror = error;
    524 	this.querytsig = querytsig;
    525 }
    526 
    527 /**
    528  * Returns the size of the message.  Only valid if the message has been
    529  * converted to or from wire format.
    530  */
    531 public int
    532 numBytes() {
    533 	return size;
    534 }
    535 
    536 /**
    537  * Converts the given section of the Message to a String.
    538  * @see Section
    539  */
    540 public String
    541 sectionToString(int i) {
    542 	if (i > 3)
    543 		return null;
    544 
    545 	StringBuffer sb = new StringBuffer();
    546 
    547 	Record [] records = getSectionArray(i);
    548 	for (int j = 0; j < records.length; j++) {
    549 		Record rec = records[j];
    550 		if (i == Section.QUESTION) {
    551 			sb.append(";;\t" + rec.name);
    552 			sb.append(", type = " + Type.string(rec.type));
    553 			sb.append(", class = " + DClass.string(rec.dclass));
    554 		}
    555 		else
    556 			sb.append(rec);
    557 		sb.append("\n");
    558 	}
    559 	return sb.toString();
    560 }
    561 
    562 /**
    563  * Converts the Message to a String.
    564  */
    565 public String
    566 toString() {
    567 	StringBuffer sb = new StringBuffer();
    568 	OPTRecord opt = getOPT();
    569 	if (opt != null)
    570 		sb.append(header.toStringWithRcode(getRcode()) + "\n");
    571 	else
    572 		sb.append(header + "\n");
    573 	if (isSigned()) {
    574 		sb.append(";; TSIG ");
    575 		if (isVerified())
    576 			sb.append("ok");
    577 		else
    578 			sb.append("invalid");
    579 		sb.append('\n');
    580 	}
    581 	for (int i = 0; i < 4; i++) {
    582 		if (header.getOpcode() != Opcode.UPDATE)
    583 			sb.append(";; " + Section.longString(i) + ":\n");
    584 		else
    585 			sb.append(";; " + Section.updString(i) + ":\n");
    586 		sb.append(sectionToString(i) + "\n");
    587 	}
    588 	sb.append(";; Message size: " + numBytes() + " bytes");
    589 	return sb.toString();
    590 }
    591 
    592 /**
    593  * Creates a copy of this Message.  This is done by the Resolver before adding
    594  * TSIG and OPT records, for example.
    595  * @see Resolver
    596  * @see TSIGRecord
    597  * @see OPTRecord
    598  */
    599 public Object
    600 clone() {
    601 	Message m = new Message();
    602 	for (int i = 0; i < sections.length; i++) {
    603 		if (sections[i] != null)
    604 			m.sections[i] = new LinkedList(sections[i]);
    605 	}
    606 	m.header = (Header) header.clone();
    607 	m.size = size;
    608 	return m;
    609 }
    610 
    611 }
    612