aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/ZoneTransferIn.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/ZoneTransferIn.java')
-rw-r--r--src/org/xbill/DNS/ZoneTransferIn.java680
1 files changed, 680 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/ZoneTransferIn.java b/src/org/xbill/DNS/ZoneTransferIn.java
new file mode 100644
index 0000000..8a19992
--- /dev/null
+++ b/src/org/xbill/DNS/ZoneTransferIn.java
@@ -0,0 +1,680 @@
+// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
+// Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright
+// notice follows.
+
+/*
+ * Copyright (C) 1999-2001 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * An incoming DNS Zone Transfer. To use this class, first initialize an
+ * object, then call the run() method. If run() doesn't throw an exception
+ * the result will either be an IXFR-style response, an AXFR-style response,
+ * or an indication that the zone is up to date.
+ *
+ * @author Brian Wellington
+ */
+
+public class ZoneTransferIn {
+
+private static final int INITIALSOA = 0;
+private static final int FIRSTDATA = 1;
+private static final int IXFR_DELSOA = 2;
+private static final int IXFR_DEL = 3;
+private static final int IXFR_ADDSOA = 4;
+private static final int IXFR_ADD = 5;
+private static final int AXFR = 6;
+private static final int END = 7;
+
+private Name zname;
+private int qtype;
+private int dclass;
+private long ixfr_serial;
+private boolean want_fallback;
+private ZoneTransferHandler handler;
+
+private SocketAddress localAddress;
+private SocketAddress address;
+private TCPClient client;
+private TSIG tsig;
+private TSIG.StreamVerifier verifier;
+private long timeout = 900 * 1000;
+
+private int state;
+private long end_serial;
+private long current_serial;
+private Record initialsoa;
+
+private int rtype;
+
+public static class Delta {
+ /**
+ * All changes between two versions of a zone in an IXFR response.
+ */
+
+ /** The starting serial number of this delta. */
+ public long start;
+
+ /** The ending serial number of this delta. */
+ public long end;
+
+ /** A list of records added between the start and end versions */
+ public List adds;
+
+ /** A list of records deleted between the start and end versions */
+ public List deletes;
+
+ private
+ Delta() {
+ adds = new ArrayList();
+ deletes = new ArrayList();
+ }
+}
+
+public static interface ZoneTransferHandler {
+ /**
+ * Handles a Zone Transfer.
+ */
+
+ /**
+ * Called when an AXFR transfer begins.
+ */
+ public void startAXFR() throws ZoneTransferException;
+
+ /**
+ * Called when an IXFR transfer begins.
+ */
+ public void startIXFR() throws ZoneTransferException;
+
+ /**
+ * Called when a series of IXFR deletions begins.
+ * @param soa The starting SOA.
+ */
+ public void startIXFRDeletes(Record soa) throws ZoneTransferException;
+
+ /**
+ * Called when a series of IXFR adds begins.
+ * @param soa The starting SOA.
+ */
+ public void startIXFRAdds(Record soa) throws ZoneTransferException;
+
+ /**
+ * Called for each content record in an AXFR.
+ * @param r The DNS record.
+ */
+ public void handleRecord(Record r) throws ZoneTransferException;
+};
+
+private static class BasicHandler implements ZoneTransferHandler {
+ private List axfr;
+ private List ixfr;
+
+ public void startAXFR() {
+ axfr = new ArrayList();
+ }
+
+ public void startIXFR() {
+ ixfr = new ArrayList();
+ }
+
+ public void startIXFRDeletes(Record soa) {
+ Delta delta = new Delta();
+ delta.deletes.add(soa);
+ delta.start = getSOASerial(soa);
+ ixfr.add(delta);
+ }
+
+ public void startIXFRAdds(Record soa) {
+ Delta delta = (Delta) ixfr.get(ixfr.size() - 1);
+ delta.adds.add(soa);
+ delta.end = getSOASerial(soa);
+ }
+
+ public void handleRecord(Record r) {
+ List list;
+ if (ixfr != null) {
+ Delta delta = (Delta) ixfr.get(ixfr.size() - 1);
+ if (delta.adds.size() > 0)
+ list = delta.adds;
+ else
+ list = delta.deletes;
+ } else
+ list = axfr;
+ list.add(r);
+ }
+};
+
+private
+ZoneTransferIn() {}
+
+private
+ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback,
+ SocketAddress address, TSIG key)
+{
+ this.address = address;
+ this.tsig = key;
+ if (zone.isAbsolute())
+ zname = zone;
+ else {
+ try {
+ zname = Name.concatenate(zone, Name.root);
+ }
+ catch (NameTooLongException e) {
+ throw new IllegalArgumentException("ZoneTransferIn: " +
+ "name too long");
+ }
+ }
+ qtype = xfrtype;
+ dclass = DClass.IN;
+ ixfr_serial = serial;
+ want_fallback = fallback;
+ state = INITIALSOA;
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
+ * @param zone The zone to transfer.
+ * @param address The host/port from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newAXFR(Name zone, SocketAddress address, TSIG key) {
+ return new ZoneTransferIn(zone, Type.AXFR, 0, false, address, key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
+ * @param zone The zone to transfer.
+ * @param host The host from which to transfer the zone.
+ * @param port The port to connect to on the server, or 0 for the default.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newAXFR(Name zone, String host, int port, TSIG key)
+throws UnknownHostException
+{
+ if (port == 0)
+ port = SimpleResolver.DEFAULT_PORT;
+ return newAXFR(zone, new InetSocketAddress(host, port), key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
+ * @param zone The zone to transfer.
+ * @param host The host from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newAXFR(Name zone, String host, TSIG key)
+throws UnknownHostException
+{
+ return newAXFR(zone, host, 0, key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
+ * transfer).
+ * @param zone The zone to transfer.
+ * @param serial The existing serial number.
+ * @param fallback If true, fall back to AXFR if IXFR is not supported.
+ * @param address The host/port from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newIXFR(Name zone, long serial, boolean fallback, SocketAddress address,
+ TSIG key)
+{
+ return new ZoneTransferIn(zone, Type.IXFR, serial, fallback, address,
+ key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
+ * transfer).
+ * @param zone The zone to transfer.
+ * @param serial The existing serial number.
+ * @param fallback If true, fall back to AXFR if IXFR is not supported.
+ * @param host The host from which to transfer the zone.
+ * @param port The port to connect to on the server, or 0 for the default.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newIXFR(Name zone, long serial, boolean fallback, String host, int port,
+ TSIG key)
+throws UnknownHostException
+{
+ if (port == 0)
+ port = SimpleResolver.DEFAULT_PORT;
+ return newIXFR(zone, serial, fallback,
+ new InetSocketAddress(host, port), key);
+}
+
+/**
+ * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
+ * transfer).
+ * @param zone The zone to transfer.
+ * @param serial The existing serial number.
+ * @param fallback If true, fall back to AXFR if IXFR is not supported.
+ * @param host The host from which to transfer the zone.
+ * @param key The TSIG key used to authenticate the transfer, or null.
+ * @return The ZoneTransferIn object.
+ * @throws UnknownHostException The host does not exist.
+ */
+public static ZoneTransferIn
+newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key)
+throws UnknownHostException
+{
+ return newIXFR(zone, serial, fallback, host, 0, key);
+}
+
+/**
+ * Gets the name of the zone being transferred.
+ */
+public Name
+getName() {
+ return zname;
+}
+
+/**
+ * Gets the type of zone transfer (either AXFR or IXFR).
+ */
+public int
+getType() {
+ return qtype;
+}
+
+/**
+ * Sets a timeout on this zone transfer. The default is 900 seconds (15
+ * minutes).
+ * @param secs The maximum amount of time that this zone transfer can take.
+ */
+public void
+setTimeout(int secs) {
+ if (secs < 0)
+ throw new IllegalArgumentException("timeout cannot be " +
+ "negative");
+ timeout = 1000L * secs;
+}
+
+/**
+ * Sets an alternate DNS class for this zone transfer.
+ * @param dclass The class to use instead of class IN.
+ */
+public void
+setDClass(int dclass) {
+ DClass.check(dclass);
+ this.dclass = dclass;
+}
+
+/**
+ * Sets the local address to bind to when sending messages.
+ * @param addr The local address to send messages from.
+ */
+public void
+setLocalAddress(SocketAddress addr) {
+ this.localAddress = addr;
+}
+
+private void
+openConnection() throws IOException {
+ long endTime = System.currentTimeMillis() + timeout;
+ client = new TCPClient(endTime);
+ if (localAddress != null)
+ client.bind(localAddress);
+ client.connect(address);
+}
+
+private void
+sendQuery() throws IOException {
+ Record question = Record.newRecord(zname, qtype, dclass);
+
+ Message query = new Message();
+ query.getHeader().setOpcode(Opcode.QUERY);
+ query.addRecord(question, Section.QUESTION);
+ if (qtype == Type.IXFR) {
+ Record soa = new SOARecord(zname, dclass, 0, Name.root,
+ Name.root, ixfr_serial,
+ 0, 0, 0, 0);
+ query.addRecord(soa, Section.AUTHORITY);
+ }
+ if (tsig != null) {
+ tsig.apply(query, null);
+ verifier = new TSIG.StreamVerifier(tsig, query.getTSIG());
+ }
+ byte [] out = query.toWire(Message.MAXLENGTH);
+ client.send(out);
+}
+
+private static long
+getSOASerial(Record rec) {
+ SOARecord soa = (SOARecord) rec;
+ return soa.getSerial();
+}
+
+private void
+logxfr(String s) {
+ if (Options.check("verbose"))
+ System.out.println(zname + ": " + s);
+}
+
+private void
+fail(String s) throws ZoneTransferException {
+ throw new ZoneTransferException(s);
+}
+
+private void
+fallback() throws ZoneTransferException {
+ if (!want_fallback)
+ fail("server doesn't support IXFR");
+
+ logxfr("falling back to AXFR");
+ qtype = Type.AXFR;
+ state = INITIALSOA;
+}
+
+private void
+parseRR(Record rec) throws ZoneTransferException {
+ int type = rec.getType();
+ Delta delta;
+
+ switch (state) {
+ case INITIALSOA:
+ if (type != Type.SOA)
+ fail("missing initial SOA");
+ initialsoa = rec;
+ // Remember the serial number in the initial SOA; we need it
+ // to recognize the end of an IXFR.
+ end_serial = getSOASerial(rec);
+ if (qtype == Type.IXFR &&
+ Serial.compare(end_serial, ixfr_serial) <= 0)
+ {
+ logxfr("up to date");
+ state = END;
+ break;
+ }
+ state = FIRSTDATA;
+ break;
+
+ case FIRSTDATA:
+ // If the transfer begins with 1 SOA, it's an AXFR.
+ // If it begins with 2 SOAs, it's an IXFR.
+ if (qtype == Type.IXFR && type == Type.SOA &&
+ getSOASerial(rec) == ixfr_serial)
+ {
+ rtype = Type.IXFR;
+ handler.startIXFR();
+ logxfr("got incremental response");
+ state = IXFR_DELSOA;
+ } else {
+ rtype = Type.AXFR;
+ handler.startAXFR();
+ handler.handleRecord(initialsoa);
+ logxfr("got nonincremental response");
+ state = AXFR;
+ }
+ parseRR(rec); // Restart...
+ return;
+
+ case IXFR_DELSOA:
+ handler.startIXFRDeletes(rec);
+ state = IXFR_DEL;
+ break;
+
+ case IXFR_DEL:
+ if (type == Type.SOA) {
+ current_serial = getSOASerial(rec);
+ state = IXFR_ADDSOA;
+ parseRR(rec); // Restart...
+ return;
+ }
+ handler.handleRecord(rec);
+ break;
+
+ case IXFR_ADDSOA:
+ handler.startIXFRAdds(rec);
+ state = IXFR_ADD;
+ break;
+
+ case IXFR_ADD:
+ if (type == Type.SOA) {
+ long soa_serial = getSOASerial(rec);
+ if (soa_serial == end_serial) {
+ state = END;
+ break;
+ } else if (soa_serial != current_serial) {
+ fail("IXFR out of sync: expected serial " +
+ current_serial + " , got " + soa_serial);
+ } else {
+ state = IXFR_DELSOA;
+ parseRR(rec); // Restart...
+ return;
+ }
+ }
+ handler.handleRecord(rec);
+ break;
+
+ case AXFR:
+ // Old BINDs sent cross class A records for non IN classes.
+ if (type == Type.A && rec.getDClass() != dclass)
+ break;
+ handler.handleRecord(rec);
+ if (type == Type.SOA) {
+ state = END;
+ }
+ break;
+
+ case END:
+ fail("extra data");
+ break;
+
+ default:
+ fail("invalid state");
+ break;
+ }
+}
+
+private void
+closeConnection() {
+ try {
+ if (client != null)
+ client.cleanup();
+ }
+ catch (IOException e) {
+ }
+}
+
+private Message
+parseMessage(byte [] b) throws WireParseException {
+ try {
+ return new Message(b);
+ }
+ catch (IOException e) {
+ if (e instanceof WireParseException)
+ throw (WireParseException) e;
+ throw new WireParseException("Error parsing message");
+ }
+}
+
+private void
+doxfr() throws IOException, ZoneTransferException {
+ sendQuery();
+ while (state != END) {
+ byte [] in = client.recv();
+ Message response = parseMessage(in);
+ if (response.getHeader().getRcode() == Rcode.NOERROR &&
+ verifier != null)
+ {
+ TSIGRecord tsigrec = response.getTSIG();
+
+ int error = verifier.verify(response, in);
+ if (error != Rcode.NOERROR)
+ fail("TSIG failure");
+ }
+
+ Record [] answers = response.getSectionArray(Section.ANSWER);
+
+ if (state == INITIALSOA) {
+ int rcode = response.getRcode();
+ if (rcode != Rcode.NOERROR) {
+ if (qtype == Type.IXFR &&
+ rcode == Rcode.NOTIMP)
+ {
+ fallback();
+ doxfr();
+ return;
+ }
+ fail(Rcode.string(rcode));
+ }
+
+ Record question = response.getQuestion();
+ if (question != null && question.getType() != qtype) {
+ fail("invalid question section");
+ }
+
+ if (answers.length == 0 && qtype == Type.IXFR) {
+ fallback();
+ doxfr();
+ return;
+ }
+ }
+
+ for (int i = 0; i < answers.length; i++) {
+ parseRR(answers[i]);
+ }
+
+ if (state == END && verifier != null &&
+ !response.isVerified())
+ fail("last message must be signed");
+ }
+}
+
+/**
+ * Does the zone transfer.
+ * @param handler The callback object that handles the zone transfer data.
+ * @throws IOException The zone transfer failed to due an IO problem.
+ * @throws ZoneTransferException The zone transfer failed to due a problem
+ * with the zone transfer itself.
+ */
+public void
+run(ZoneTransferHandler handler) throws IOException, ZoneTransferException {
+ this.handler = handler;
+ try {
+ openConnection();
+ doxfr();
+ }
+ finally {
+ closeConnection();
+ }
+}
+
+/**
+ * Does the zone transfer.
+ * @return A list, which is either an AXFR-style response (List of Records),
+ * and IXFR-style response (List of Deltas), or null, which indicates that
+ * an IXFR was performed and the zone is up to date.
+ * @throws IOException The zone transfer failed to due an IO problem.
+ * @throws ZoneTransferException The zone transfer failed to due a problem
+ * with the zone transfer itself.
+ */
+public List
+run() throws IOException, ZoneTransferException {
+ BasicHandler handler = new BasicHandler();
+ run(handler);
+ if (handler.axfr != null)
+ return handler.axfr;
+ return handler.ixfr;
+}
+
+private BasicHandler
+getBasicHandler() throws IllegalArgumentException {
+ if (handler instanceof BasicHandler)
+ return (BasicHandler) handler;
+ throw new IllegalArgumentException("ZoneTransferIn used callback " +
+ "interface");
+}
+
+/**
+ * Returns true if the response is an AXFR-style response (List of Records).
+ * This will be true if either an IXFR was performed, an IXFR was performed
+ * and the server provided a full zone transfer, or an IXFR failed and
+ * fallback to AXFR occurred.
+ */
+public boolean
+isAXFR() {
+ return (rtype == Type.AXFR);
+}
+
+/**
+ * Gets the AXFR-style response.
+ * @throws IllegalArgumentException The transfer used the callback interface,
+ * so the response was not stored.
+ */
+public List
+getAXFR() {
+ BasicHandler handler = getBasicHandler();
+ return handler.axfr;
+}
+
+/**
+ * Returns true if the response is an IXFR-style response (List of Deltas).
+ * This will be true only if an IXFR was performed and the server provided
+ * an incremental zone transfer.
+ */
+public boolean
+isIXFR() {
+ return (rtype == Type.IXFR);
+}
+
+/**
+ * Gets the IXFR-style response.
+ * @throws IllegalArgumentException The transfer used the callback interface,
+ * so the response was not stored.
+ */
+public List
+getIXFR() {
+ BasicHandler handler = getBasicHandler();
+ return handler.ixfr;
+}
+
+/**
+ * Returns true if the response indicates that the zone is up to date.
+ * This will be true only if an IXFR was performed.
+ * @throws IllegalArgumentException The transfer used the callback interface,
+ * so the response was not stored.
+ */
+public boolean
+isCurrent() {
+ BasicHandler handler = getBasicHandler();
+ return (handler.axfr == null && handler.ixfr == null);
+}
+
+}