1 // Copyright (c) 2003-2004 Brian Wellington (bwelling (at) xbill.org) 2 // Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright 3 // notice follows. 4 5 /* 6 * Copyright (C) 1999-2001 Internet Software Consortium. 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM 13 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 14 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 15 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, 16 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING 17 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 18 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 19 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 */ 21 22 package org.xbill.DNS; 23 24 import java.io.*; 25 import java.net.*; 26 import java.util.*; 27 28 /** 29 * An incoming DNS Zone Transfer. To use this class, first initialize an 30 * object, then call the run() method. If run() doesn't throw an exception 31 * the result will either be an IXFR-style response, an AXFR-style response, 32 * or an indication that the zone is up to date. 33 * 34 * @author Brian Wellington 35 */ 36 37 public class ZoneTransferIn { 38 39 private static final int INITIALSOA = 0; 40 private static final int FIRSTDATA = 1; 41 private static final int IXFR_DELSOA = 2; 42 private static final int IXFR_DEL = 3; 43 private static final int IXFR_ADDSOA = 4; 44 private static final int IXFR_ADD = 5; 45 private static final int AXFR = 6; 46 private static final int END = 7; 47 48 private Name zname; 49 private int qtype; 50 private int dclass; 51 private long ixfr_serial; 52 private boolean want_fallback; 53 private ZoneTransferHandler handler; 54 55 private SocketAddress localAddress; 56 private SocketAddress address; 57 private TCPClient client; 58 private TSIG tsig; 59 private TSIG.StreamVerifier verifier; 60 private long timeout = 900 * 1000; 61 62 private int state; 63 private long end_serial; 64 private long current_serial; 65 private Record initialsoa; 66 67 private int rtype; 68 69 public static class Delta { 70 /** 71 * All changes between two versions of a zone in an IXFR response. 72 */ 73 74 /** The starting serial number of this delta. */ 75 public long start; 76 77 /** The ending serial number of this delta. */ 78 public long end; 79 80 /** A list of records added between the start and end versions */ 81 public List adds; 82 83 /** A list of records deleted between the start and end versions */ 84 public List deletes; 85 86 private 87 Delta() { 88 adds = new ArrayList(); 89 deletes = new ArrayList(); 90 } 91 } 92 93 public static interface ZoneTransferHandler { 94 /** 95 * Handles a Zone Transfer. 96 */ 97 98 /** 99 * Called when an AXFR transfer begins. 100 */ 101 public void startAXFR() throws ZoneTransferException; 102 103 /** 104 * Called when an IXFR transfer begins. 105 */ 106 public void startIXFR() throws ZoneTransferException; 107 108 /** 109 * Called when a series of IXFR deletions begins. 110 * @param soa The starting SOA. 111 */ 112 public void startIXFRDeletes(Record soa) throws ZoneTransferException; 113 114 /** 115 * Called when a series of IXFR adds begins. 116 * @param soa The starting SOA. 117 */ 118 public void startIXFRAdds(Record soa) throws ZoneTransferException; 119 120 /** 121 * Called for each content record in an AXFR. 122 * @param r The DNS record. 123 */ 124 public void handleRecord(Record r) throws ZoneTransferException; 125 }; 126 127 private static class BasicHandler implements ZoneTransferHandler { 128 private List axfr; 129 private List ixfr; 130 131 public void startAXFR() { 132 axfr = new ArrayList(); 133 } 134 135 public void startIXFR() { 136 ixfr = new ArrayList(); 137 } 138 139 public void startIXFRDeletes(Record soa) { 140 Delta delta = new Delta(); 141 delta.deletes.add(soa); 142 delta.start = getSOASerial(soa); 143 ixfr.add(delta); 144 } 145 146 public void startIXFRAdds(Record soa) { 147 Delta delta = (Delta) ixfr.get(ixfr.size() - 1); 148 delta.adds.add(soa); 149 delta.end = getSOASerial(soa); 150 } 151 152 public void handleRecord(Record r) { 153 List list; 154 if (ixfr != null) { 155 Delta delta = (Delta) ixfr.get(ixfr.size() - 1); 156 if (delta.adds.size() > 0) 157 list = delta.adds; 158 else 159 list = delta.deletes; 160 } else 161 list = axfr; 162 list.add(r); 163 } 164 }; 165 166 private 167 ZoneTransferIn() {} 168 169 private 170 ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback, 171 SocketAddress address, TSIG key) 172 { 173 this.address = address; 174 this.tsig = key; 175 if (zone.isAbsolute()) 176 zname = zone; 177 else { 178 try { 179 zname = Name.concatenate(zone, Name.root); 180 } 181 catch (NameTooLongException e) { 182 throw new IllegalArgumentException("ZoneTransferIn: " + 183 "name too long"); 184 } 185 } 186 qtype = xfrtype; 187 dclass = DClass.IN; 188 ixfr_serial = serial; 189 want_fallback = fallback; 190 state = INITIALSOA; 191 } 192 193 /** 194 * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). 195 * @param zone The zone to transfer. 196 * @param address The host/port from which to transfer the zone. 197 * @param key The TSIG key used to authenticate the transfer, or null. 198 * @return The ZoneTransferIn object. 199 * @throws UnknownHostException The host does not exist. 200 */ 201 public static ZoneTransferIn 202 newAXFR(Name zone, SocketAddress address, TSIG key) { 203 return new ZoneTransferIn(zone, Type.AXFR, 0, false, address, key); 204 } 205 206 /** 207 * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). 208 * @param zone The zone to transfer. 209 * @param host The host from which to transfer the zone. 210 * @param port The port to connect to on the server, or 0 for the default. 211 * @param key The TSIG key used to authenticate the transfer, or null. 212 * @return The ZoneTransferIn object. 213 * @throws UnknownHostException The host does not exist. 214 */ 215 public static ZoneTransferIn 216 newAXFR(Name zone, String host, int port, TSIG key) 217 throws UnknownHostException 218 { 219 if (port == 0) 220 port = SimpleResolver.DEFAULT_PORT; 221 return newAXFR(zone, new InetSocketAddress(host, port), key); 222 } 223 224 /** 225 * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). 226 * @param zone The zone to transfer. 227 * @param host The host from which to transfer the zone. 228 * @param key The TSIG key used to authenticate the transfer, or null. 229 * @return The ZoneTransferIn object. 230 * @throws UnknownHostException The host does not exist. 231 */ 232 public static ZoneTransferIn 233 newAXFR(Name zone, String host, TSIG key) 234 throws UnknownHostException 235 { 236 return newAXFR(zone, host, 0, key); 237 } 238 239 /** 240 * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone 241 * transfer). 242 * @param zone The zone to transfer. 243 * @param serial The existing serial number. 244 * @param fallback If true, fall back to AXFR if IXFR is not supported. 245 * @param address The host/port from which to transfer the zone. 246 * @param key The TSIG key used to authenticate the transfer, or null. 247 * @return The ZoneTransferIn object. 248 * @throws UnknownHostException The host does not exist. 249 */ 250 public static ZoneTransferIn 251 newIXFR(Name zone, long serial, boolean fallback, SocketAddress address, 252 TSIG key) 253 { 254 return new ZoneTransferIn(zone, Type.IXFR, serial, fallback, address, 255 key); 256 } 257 258 /** 259 * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone 260 * transfer). 261 * @param zone The zone to transfer. 262 * @param serial The existing serial number. 263 * @param fallback If true, fall back to AXFR if IXFR is not supported. 264 * @param host The host from which to transfer the zone. 265 * @param port The port to connect to on the server, or 0 for the default. 266 * @param key The TSIG key used to authenticate the transfer, or null. 267 * @return The ZoneTransferIn object. 268 * @throws UnknownHostException The host does not exist. 269 */ 270 public static ZoneTransferIn 271 newIXFR(Name zone, long serial, boolean fallback, String host, int port, 272 TSIG key) 273 throws UnknownHostException 274 { 275 if (port == 0) 276 port = SimpleResolver.DEFAULT_PORT; 277 return newIXFR(zone, serial, fallback, 278 new InetSocketAddress(host, port), key); 279 } 280 281 /** 282 * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone 283 * transfer). 284 * @param zone The zone to transfer. 285 * @param serial The existing serial number. 286 * @param fallback If true, fall back to AXFR if IXFR is not supported. 287 * @param host The host from which to transfer the zone. 288 * @param key The TSIG key used to authenticate the transfer, or null. 289 * @return The ZoneTransferIn object. 290 * @throws UnknownHostException The host does not exist. 291 */ 292 public static ZoneTransferIn 293 newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key) 294 throws UnknownHostException 295 { 296 return newIXFR(zone, serial, fallback, host, 0, key); 297 } 298 299 /** 300 * Gets the name of the zone being transferred. 301 */ 302 public Name 303 getName() { 304 return zname; 305 } 306 307 /** 308 * Gets the type of zone transfer (either AXFR or IXFR). 309 */ 310 public int 311 getType() { 312 return qtype; 313 } 314 315 /** 316 * Sets a timeout on this zone transfer. The default is 900 seconds (15 317 * minutes). 318 * @param secs The maximum amount of time that this zone transfer can take. 319 */ 320 public void 321 setTimeout(int secs) { 322 if (secs < 0) 323 throw new IllegalArgumentException("timeout cannot be " + 324 "negative"); 325 timeout = 1000L * secs; 326 } 327 328 /** 329 * Sets an alternate DNS class for this zone transfer. 330 * @param dclass The class to use instead of class IN. 331 */ 332 public void 333 setDClass(int dclass) { 334 DClass.check(dclass); 335 this.dclass = dclass; 336 } 337 338 /** 339 * Sets the local address to bind to when sending messages. 340 * @param addr The local address to send messages from. 341 */ 342 public void 343 setLocalAddress(SocketAddress addr) { 344 this.localAddress = addr; 345 } 346 347 private void 348 openConnection() throws IOException { 349 long endTime = System.currentTimeMillis() + timeout; 350 client = new TCPClient(endTime); 351 if (localAddress != null) 352 client.bind(localAddress); 353 client.connect(address); 354 } 355 356 private void 357 sendQuery() throws IOException { 358 Record question = Record.newRecord(zname, qtype, dclass); 359 360 Message query = new Message(); 361 query.getHeader().setOpcode(Opcode.QUERY); 362 query.addRecord(question, Section.QUESTION); 363 if (qtype == Type.IXFR) { 364 Record soa = new SOARecord(zname, dclass, 0, Name.root, 365 Name.root, ixfr_serial, 366 0, 0, 0, 0); 367 query.addRecord(soa, Section.AUTHORITY); 368 } 369 if (tsig != null) { 370 tsig.apply(query, null); 371 verifier = new TSIG.StreamVerifier(tsig, query.getTSIG()); 372 } 373 byte [] out = query.toWire(Message.MAXLENGTH); 374 client.send(out); 375 } 376 377 private static long 378 getSOASerial(Record rec) { 379 SOARecord soa = (SOARecord) rec; 380 return soa.getSerial(); 381 } 382 383 private void 384 logxfr(String s) { 385 if (Options.check("verbose")) 386 System.out.println(zname + ": " + s); 387 } 388 389 private void 390 fail(String s) throws ZoneTransferException { 391 throw new ZoneTransferException(s); 392 } 393 394 private void 395 fallback() throws ZoneTransferException { 396 if (!want_fallback) 397 fail("server doesn't support IXFR"); 398 399 logxfr("falling back to AXFR"); 400 qtype = Type.AXFR; 401 state = INITIALSOA; 402 } 403 404 private void 405 parseRR(Record rec) throws ZoneTransferException { 406 int type = rec.getType(); 407 Delta delta; 408 409 switch (state) { 410 case INITIALSOA: 411 if (type != Type.SOA) 412 fail("missing initial SOA"); 413 initialsoa = rec; 414 // Remember the serial number in the initial SOA; we need it 415 // to recognize the end of an IXFR. 416 end_serial = getSOASerial(rec); 417 if (qtype == Type.IXFR && 418 Serial.compare(end_serial, ixfr_serial) <= 0) 419 { 420 logxfr("up to date"); 421 state = END; 422 break; 423 } 424 state = FIRSTDATA; 425 break; 426 427 case FIRSTDATA: 428 // If the transfer begins with 1 SOA, it's an AXFR. 429 // If it begins with 2 SOAs, it's an IXFR. 430 if (qtype == Type.IXFR && type == Type.SOA && 431 getSOASerial(rec) == ixfr_serial) 432 { 433 rtype = Type.IXFR; 434 handler.startIXFR(); 435 logxfr("got incremental response"); 436 state = IXFR_DELSOA; 437 } else { 438 rtype = Type.AXFR; 439 handler.startAXFR(); 440 handler.handleRecord(initialsoa); 441 logxfr("got nonincremental response"); 442 state = AXFR; 443 } 444 parseRR(rec); // Restart... 445 return; 446 447 case IXFR_DELSOA: 448 handler.startIXFRDeletes(rec); 449 state = IXFR_DEL; 450 break; 451 452 case IXFR_DEL: 453 if (type == Type.SOA) { 454 current_serial = getSOASerial(rec); 455 state = IXFR_ADDSOA; 456 parseRR(rec); // Restart... 457 return; 458 } 459 handler.handleRecord(rec); 460 break; 461 462 case IXFR_ADDSOA: 463 handler.startIXFRAdds(rec); 464 state = IXFR_ADD; 465 break; 466 467 case IXFR_ADD: 468 if (type == Type.SOA) { 469 long soa_serial = getSOASerial(rec); 470 if (soa_serial == end_serial) { 471 state = END; 472 break; 473 } else if (soa_serial != current_serial) { 474 fail("IXFR out of sync: expected serial " + 475 current_serial + " , got " + soa_serial); 476 } else { 477 state = IXFR_DELSOA; 478 parseRR(rec); // Restart... 479 return; 480 } 481 } 482 handler.handleRecord(rec); 483 break; 484 485 case AXFR: 486 // Old BINDs sent cross class A records for non IN classes. 487 if (type == Type.A && rec.getDClass() != dclass) 488 break; 489 handler.handleRecord(rec); 490 if (type == Type.SOA) { 491 state = END; 492 } 493 break; 494 495 case END: 496 fail("extra data"); 497 break; 498 499 default: 500 fail("invalid state"); 501 break; 502 } 503 } 504 505 private void 506 closeConnection() { 507 try { 508 if (client != null) 509 client.cleanup(); 510 } 511 catch (IOException e) { 512 } 513 } 514 515 private Message 516 parseMessage(byte [] b) throws WireParseException { 517 try { 518 return new Message(b); 519 } 520 catch (IOException e) { 521 if (e instanceof WireParseException) 522 throw (WireParseException) e; 523 throw new WireParseException("Error parsing message"); 524 } 525 } 526 527 private void 528 doxfr() throws IOException, ZoneTransferException { 529 sendQuery(); 530 while (state != END) { 531 byte [] in = client.recv(); 532 Message response = parseMessage(in); 533 if (response.getHeader().getRcode() == Rcode.NOERROR && 534 verifier != null) 535 { 536 TSIGRecord tsigrec = response.getTSIG(); 537 538 int error = verifier.verify(response, in); 539 if (error != Rcode.NOERROR) 540 fail("TSIG failure"); 541 } 542 543 Record [] answers = response.getSectionArray(Section.ANSWER); 544 545 if (state == INITIALSOA) { 546 int rcode = response.getRcode(); 547 if (rcode != Rcode.NOERROR) { 548 if (qtype == Type.IXFR && 549 rcode == Rcode.NOTIMP) 550 { 551 fallback(); 552 doxfr(); 553 return; 554 } 555 fail(Rcode.string(rcode)); 556 } 557 558 Record question = response.getQuestion(); 559 if (question != null && question.getType() != qtype) { 560 fail("invalid question section"); 561 } 562 563 if (answers.length == 0 && qtype == Type.IXFR) { 564 fallback(); 565 doxfr(); 566 return; 567 } 568 } 569 570 for (int i = 0; i < answers.length; i++) { 571 parseRR(answers[i]); 572 } 573 574 if (state == END && verifier != null && 575 !response.isVerified()) 576 fail("last message must be signed"); 577 } 578 } 579 580 /** 581 * Does the zone transfer. 582 * @param handler The callback object that handles the zone transfer data. 583 * @throws IOException The zone transfer failed to due an IO problem. 584 * @throws ZoneTransferException The zone transfer failed to due a problem 585 * with the zone transfer itself. 586 */ 587 public void 588 run(ZoneTransferHandler handler) throws IOException, ZoneTransferException { 589 this.handler = handler; 590 try { 591 openConnection(); 592 doxfr(); 593 } 594 finally { 595 closeConnection(); 596 } 597 } 598 599 /** 600 * Does the zone transfer. 601 * @return A list, which is either an AXFR-style response (List of Records), 602 * and IXFR-style response (List of Deltas), or null, which indicates that 603 * an IXFR was performed and the zone is up to date. 604 * @throws IOException The zone transfer failed to due an IO problem. 605 * @throws ZoneTransferException The zone transfer failed to due a problem 606 * with the zone transfer itself. 607 */ 608 public List 609 run() throws IOException, ZoneTransferException { 610 BasicHandler handler = new BasicHandler(); 611 run(handler); 612 if (handler.axfr != null) 613 return handler.axfr; 614 return handler.ixfr; 615 } 616 617 private BasicHandler 618 getBasicHandler() throws IllegalArgumentException { 619 if (handler instanceof BasicHandler) 620 return (BasicHandler) handler; 621 throw new IllegalArgumentException("ZoneTransferIn used callback " + 622 "interface"); 623 } 624 625 /** 626 * Returns true if the response is an AXFR-style response (List of Records). 627 * This will be true if either an IXFR was performed, an IXFR was performed 628 * and the server provided a full zone transfer, or an IXFR failed and 629 * fallback to AXFR occurred. 630 */ 631 public boolean 632 isAXFR() { 633 return (rtype == Type.AXFR); 634 } 635 636 /** 637 * Gets the AXFR-style response. 638 * @throws IllegalArgumentException The transfer used the callback interface, 639 * so the response was not stored. 640 */ 641 public List 642 getAXFR() { 643 BasicHandler handler = getBasicHandler(); 644 return handler.axfr; 645 } 646 647 /** 648 * Returns true if the response is an IXFR-style response (List of Deltas). 649 * This will be true only if an IXFR was performed and the server provided 650 * an incremental zone transfer. 651 */ 652 public boolean 653 isIXFR() { 654 return (rtype == Type.IXFR); 655 } 656 657 /** 658 * Gets the IXFR-style response. 659 * @throws IllegalArgumentException The transfer used the callback interface, 660 * so the response was not stored. 661 */ 662 public List 663 getIXFR() { 664 BasicHandler handler = getBasicHandler(); 665 return handler.ixfr; 666 } 667 668 /** 669 * Returns true if the response indicates that the zone is up to date. 670 * This will be true only if an IXFR was performed. 671 * @throws IllegalArgumentException The transfer used the callback interface, 672 * so the response was not stored. 673 */ 674 public boolean 675 isCurrent() { 676 BasicHandler handler = getBasicHandler(); 677 return (handler.axfr == null && handler.ixfr == null); 678 } 679 680 } 681