diff options
Diffstat (limited to 'src/org/xbill/DNS/ZoneTransferIn.java')
-rw-r--r-- | src/org/xbill/DNS/ZoneTransferIn.java | 680 |
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); +} + +} |