1// Copyright (c) 2003-2004 Brian Wellington (bwelling@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 22package org.xbill.DNS; 23 24import java.io.*; 25import java.net.*; 26import 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 37public class ZoneTransferIn { 38 39private static final int INITIALSOA = 0; 40private static final int FIRSTDATA = 1; 41private static final int IXFR_DELSOA = 2; 42private static final int IXFR_DEL = 3; 43private static final int IXFR_ADDSOA = 4; 44private static final int IXFR_ADD = 5; 45private static final int AXFR = 6; 46private static final int END = 7; 47 48private Name zname; 49private int qtype; 50private int dclass; 51private long ixfr_serial; 52private boolean want_fallback; 53private ZoneTransferHandler handler; 54 55private SocketAddress localAddress; 56private SocketAddress address; 57private TCPClient client; 58private TSIG tsig; 59private TSIG.StreamVerifier verifier; 60private long timeout = 900 * 1000; 61 62private int state; 63private long end_serial; 64private long current_serial; 65private Record initialsoa; 66 67private int rtype; 68 69public 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 93public 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 127private 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 166private 167ZoneTransferIn() {} 168 169private 170ZoneTransferIn(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 */ 201public static ZoneTransferIn 202newAXFR(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 */ 215public static ZoneTransferIn 216newAXFR(Name zone, String host, int port, TSIG key) 217throws 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 */ 232public static ZoneTransferIn 233newAXFR(Name zone, String host, TSIG key) 234throws 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 */ 250public static ZoneTransferIn 251newIXFR(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 */ 270public static ZoneTransferIn 271newIXFR(Name zone, long serial, boolean fallback, String host, int port, 272 TSIG key) 273throws 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 */ 292public static ZoneTransferIn 293newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key) 294throws UnknownHostException 295{ 296 return newIXFR(zone, serial, fallback, host, 0, key); 297} 298 299/** 300 * Gets the name of the zone being transferred. 301 */ 302public Name 303getName() { 304 return zname; 305} 306 307/** 308 * Gets the type of zone transfer (either AXFR or IXFR). 309 */ 310public int 311getType() { 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 */ 320public void 321setTimeout(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 */ 332public void 333setDClass(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 */ 342public void 343setLocalAddress(SocketAddress addr) { 344 this.localAddress = addr; 345} 346 347private void 348openConnection() 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 356private void 357sendQuery() 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 377private static long 378getSOASerial(Record rec) { 379 SOARecord soa = (SOARecord) rec; 380 return soa.getSerial(); 381} 382 383private void 384logxfr(String s) { 385 if (Options.check("verbose")) 386 System.out.println(zname + ": " + s); 387} 388 389private void 390fail(String s) throws ZoneTransferException { 391 throw new ZoneTransferException(s); 392} 393 394private void 395fallback() 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 404private void 405parseRR(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 505private void 506closeConnection() { 507 try { 508 if (client != null) 509 client.cleanup(); 510 } 511 catch (IOException e) { 512 } 513} 514 515private Message 516parseMessage(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 527private void 528doxfr() 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 */ 587public void 588run(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 */ 608public List 609run() 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 617private BasicHandler 618getBasicHandler() 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 */ 631public boolean 632isAXFR() { 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 */ 641public List 642getAXFR() { 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 */ 652public boolean 653isIXFR() { 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 */ 662public List 663getIXFR() { 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 */ 674public boolean 675isCurrent() { 676 BasicHandler handler = getBasicHandler(); 677 return (handler.axfr == null && handler.ixfr == null); 678} 679 680} 681