• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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