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
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
Delta()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 */
startAXFR()101 public void startAXFR() throws ZoneTransferException;
102
103 /**
104 * Called when an IXFR transfer begins.
105 */
startIXFR()106 public void startIXFR() throws ZoneTransferException;
107
108 /**
109 * Called when a series of IXFR deletions begins.
110 * @param soa The starting SOA.
111 */
startIXFRDeletes(Record soa)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 */
startIXFRAdds(Record soa)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 */
handleRecord(Record r)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
startAXFR()131 public void startAXFR() {
132 axfr = new ArrayList();
133 }
134
startIXFR()135 public void startIXFR() {
136 ixfr = new ArrayList();
137 }
138
startIXFRDeletes(Record soa)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
startIXFRAdds(Record soa)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
handleRecord(Record r)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
ZoneTransferIn()167 ZoneTransferIn() {}
168
169 private
ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback, SocketAddress address, TSIG key)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
newAXFR(Name zone, SocketAddress address, TSIG key)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
newAXFR(Name zone, String host, int port, TSIG key)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
newAXFR(Name zone, String host, TSIG key)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
newIXFR(Name zone, long serial, boolean fallback, SocketAddress address, TSIG key)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
newIXFR(Name zone, long serial, boolean fallback, String host, int port, TSIG key)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
newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key)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
getName()303 getName() {
304 return zname;
305 }
306
307 /**
308 * Gets the type of zone transfer (either AXFR or IXFR).
309 */
310 public int
getType()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
setTimeout(int secs)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
setDClass(int dclass)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
setLocalAddress(SocketAddress addr)343 setLocalAddress(SocketAddress addr) {
344 this.localAddress = addr;
345 }
346
347 private void
openConnection()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
sendQuery()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
getSOASerial(Record rec)378 getSOASerial(Record rec) {
379 SOARecord soa = (SOARecord) rec;
380 return soa.getSerial();
381 }
382
383 private void
logxfr(String s)384 logxfr(String s) {
385 if (Options.check("verbose"))
386 System.out.println(zname + ": " + s);
387 }
388
389 private void
fail(String s)390 fail(String s) throws ZoneTransferException {
391 throw new ZoneTransferException(s);
392 }
393
394 private void
fallback()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
parseRR(Record rec)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
closeConnection()506 closeConnection() {
507 try {
508 if (client != null)
509 client.cleanup();
510 }
511 catch (IOException e) {
512 }
513 }
514
515 private Message
parseMessage(byte [] b)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
doxfr()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
run(ZoneTransferHandler handler)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
run()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
getBasicHandler()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
isAXFR()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
getAXFR()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
isIXFR()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
getIXFR()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
isCurrent()675 isCurrent() {
676 BasicHandler handler = getBasicHandler();
677 return (handler.axfr == null && handler.ixfr == null);
678 }
679
680 }
681