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