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