Home | History | Annotate | Download | only in DNS
      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