001: // Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org)
002: // Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright
003: // notice follows.
004:
005: /*
006: * Copyright (C) 1999-2001 Internet Software Consortium.
007: *
008: * Permission to use, copy, modify, and distribute this software for any
009: * purpose with or without fee is hereby granted, provided that the above
010: * copyright notice and this permission notice appear in all copies.
011: *
012: * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
013: * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
014: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
015: * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
016: * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
017: * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
018: * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
019: * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
020: */
021:
022: package org.xbill.DNS;
023:
024: import java.io.*;
025: import java.net.*;
026: import java.util.*;
027:
028: /**
029: * An incoming DNS Zone Transfer. To use this class, first initialize an
030: * object, then call the run() method. If run() doesn't throw an exception
031: * the result will either be an IXFR-style response, an AXFR-style response,
032: * or an indication that the zone is up to date.
033: *
034: * @author Brian Wellington
035: */
036:
037: public class ZoneTransferIn {
038:
039: private static final int INITIALSOA = 0;
040: private static final int FIRSTDATA = 1;
041: private static final int IXFR_DELSOA = 2;
042: private static final int IXFR_DEL = 3;
043: private static final int IXFR_ADDSOA = 4;
044: private static final int IXFR_ADD = 5;
045: private static final int AXFR = 6;
046: private static final int END = 7;
047:
048: private Name zname;
049: private int qtype;
050: private int dclass;
051: private long ixfr_serial;
052: private boolean want_fallback;
053:
054: private SocketAddress localAddress;
055: private SocketAddress address;
056: private TCPClient client;
057: private TSIG tsig;
058: private TSIG.StreamVerifier verifier;
059: private long timeout = 900 * 1000;
060:
061: private int state;
062: private long end_serial;
063: private long current_serial;
064: private Record initialsoa;
065:
066: private int rtype;
067:
068: private List axfr;
069: private List ixfr;
070:
071: public static class Delta {
072: /**
073: * All changes between two versions of a zone in an IXFR response.
074: */
075:
076: /** The starting serial number of this delta. */
077: public long start;
078:
079: /** The ending serial number of this delta. */
080: public long end;
081:
082: /** A list of records added between the start and end versions */
083: public List adds;
084:
085: /** A list of records deleted between the start and end versions */
086: public List deletes;
087:
088: private Delta() {
089: adds = new ArrayList();
090: deletes = new ArrayList();
091: }
092: }
093:
094: private ZoneTransferIn() {
095: }
096:
097: private ZoneTransferIn(Name zone, int xfrtype, long serial,
098: boolean fallback, SocketAddress address, TSIG key) {
099: this .address = address;
100: this .tsig = key;
101: if (zone.isAbsolute())
102: zname = zone;
103: else {
104: try {
105: zname = Name.concatenate(zone, Name.root);
106: } catch (NameTooLongException e) {
107: throw new IllegalArgumentException("ZoneTransferIn: "
108: + "name too long");
109: }
110: }
111: qtype = xfrtype;
112: dclass = DClass.IN;
113: ixfr_serial = serial;
114: want_fallback = fallback;
115: state = INITIALSOA;
116: }
117:
118: /**
119: * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
120: * @param zone The zone to transfer.
121: * @param address The host/port from which to transfer the zone.
122: * @param key The TSIG key used to authenticate the transfer, or null.
123: * @return The ZoneTransferIn object.
124: * @throws UnknownHostException The host does not exist.
125: */
126: public static ZoneTransferIn newAXFR(Name zone,
127: SocketAddress address, TSIG key) {
128: return new ZoneTransferIn(zone, Type.AXFR, 0, false, address,
129: key);
130: }
131:
132: /**
133: * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
134: * @param zone The zone to transfer.
135: * @param host The host from which to transfer the zone.
136: * @param port The port to connect to on the server, or 0 for the default.
137: * @param key The TSIG key used to authenticate the transfer, or null.
138: * @return The ZoneTransferIn object.
139: * @throws UnknownHostException The host does not exist.
140: */
141: public static ZoneTransferIn newAXFR(Name zone, String host,
142: int port, TSIG key) throws UnknownHostException {
143: if (port == 0)
144: port = SimpleResolver.DEFAULT_PORT;
145: return newAXFR(zone, new InetSocketAddress(host, port), key);
146: }
147:
148: /**
149: * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer).
150: * @param zone The zone to transfer.
151: * @param host The host from which to transfer the zone.
152: * @param key The TSIG key used to authenticate the transfer, or null.
153: * @return The ZoneTransferIn object.
154: * @throws UnknownHostException The host does not exist.
155: */
156: public static ZoneTransferIn newAXFR(Name zone, String host,
157: TSIG key) throws UnknownHostException {
158: return newAXFR(zone, host, 0, key);
159: }
160:
161: /**
162: * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
163: * transfer).
164: * @param zone The zone to transfer.
165: * @param serial The existing serial number.
166: * @param fallback If true, fall back to AXFR if IXFR is not supported.
167: * @param address The host/port from which to transfer the zone.
168: * @param key The TSIG key used to authenticate the transfer, or null.
169: * @return The ZoneTransferIn object.
170: * @throws UnknownHostException The host does not exist.
171: */
172: public static ZoneTransferIn newIXFR(Name zone, long serial,
173: boolean fallback, SocketAddress address, TSIG key) {
174: return new ZoneTransferIn(zone, Type.IXFR, serial, fallback,
175: address, key);
176: }
177:
178: /**
179: * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
180: * transfer).
181: * @param zone The zone to transfer.
182: * @param serial The existing serial number.
183: * @param fallback If true, fall back to AXFR if IXFR is not supported.
184: * @param host The host from which to transfer the zone.
185: * @param port The port to connect to on the server, or 0 for the default.
186: * @param key The TSIG key used to authenticate the transfer, or null.
187: * @return The ZoneTransferIn object.
188: * @throws UnknownHostException The host does not exist.
189: */
190: public static ZoneTransferIn newIXFR(Name zone, long serial,
191: boolean fallback, String host, int port, TSIG key)
192: throws UnknownHostException {
193: if (port == 0)
194: port = SimpleResolver.DEFAULT_PORT;
195: return newIXFR(zone, serial, fallback, new InetSocketAddress(
196: host, port), key);
197: }
198:
199: /**
200: * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone
201: * transfer).
202: * @param zone The zone to transfer.
203: * @param serial The existing serial number.
204: * @param fallback If true, fall back to AXFR if IXFR is not supported.
205: * @param host The host from which to transfer the zone.
206: * @param key The TSIG key used to authenticate the transfer, or null.
207: * @return The ZoneTransferIn object.
208: * @throws UnknownHostException The host does not exist.
209: */
210: public static ZoneTransferIn newIXFR(Name zone, long serial,
211: boolean fallback, String host, TSIG key)
212: throws UnknownHostException {
213: return newIXFR(zone, serial, fallback, host, 0, key);
214: }
215:
216: /**
217: * Gets the name of the zone being transferred.
218: */
219: public Name getName() {
220: return zname;
221: }
222:
223: /**
224: * Gets the type of zone transfer (either AXFR or IXFR).
225: */
226: public int getType() {
227: return qtype;
228: }
229:
230: /**
231: * Sets a timeout on this zone transfer. The default is 900 seconds (15
232: * minutes).
233: * @param secs The maximum amount of time that this zone transfer can take.
234: */
235: public void setTimeout(int secs) {
236: if (secs < 0)
237: throw new IllegalArgumentException("timeout cannot be "
238: + "negative");
239: timeout = 1000L * secs;
240: }
241:
242: /**
243: * Sets an alternate DNS class for this zone transfer.
244: * @param dclass The class to use instead of class IN.
245: */
246: public void setDClass(int dclass) {
247: DClass.check(dclass);
248: this .dclass = dclass;
249: }
250:
251: /**
252: * Sets the local address to bind to when sending messages.
253: * @param addr The local address to send messages from.
254: */
255: public void setLocalAddress(SocketAddress addr) {
256: this .localAddress = addr;
257: }
258:
259: private void openConnection() throws IOException {
260: long endTime = System.currentTimeMillis() + timeout;
261: client = new TCPClient(endTime);
262: if (localAddress != null)
263: client.bind(localAddress);
264: client.connect(address);
265: }
266:
267: private void sendQuery() throws IOException {
268: Record question = Record.newRecord(zname, qtype, dclass);
269:
270: Message query = new Message();
271: query.getHeader().setOpcode(Opcode.QUERY);
272: query.addRecord(question, Section.QUESTION);
273: if (qtype == Type.IXFR) {
274: Record soa = new SOARecord(zname, dclass, 0, Name.root,
275: Name.root, ixfr_serial, 0, 0, 0, 0);
276: query.addRecord(soa, Section.AUTHORITY);
277: }
278: if (tsig != null) {
279: tsig.apply(query, null);
280: verifier = new TSIG.StreamVerifier(tsig, query.getTSIG());
281: }
282: byte[] out = query.toWire(Message.MAXLENGTH);
283: client.send(out);
284: }
285:
286: private long getSOASerial(Record rec) {
287: SOARecord soa = (SOARecord) rec;
288: return soa.getSerial();
289: }
290:
291: private void logxfr(String s) {
292: if (Options.check("verbose"))
293: System.out.println(zname + ": " + s);
294: }
295:
296: private void fail(String s) throws ZoneTransferException {
297: throw new ZoneTransferException(s);
298: }
299:
300: private void fallback() throws ZoneTransferException {
301: if (!want_fallback)
302: fail("server doesn't support IXFR");
303:
304: logxfr("falling back to AXFR");
305: qtype = Type.AXFR;
306: state = INITIALSOA;
307: }
308:
309: private void parseRR(Record rec) throws ZoneTransferException {
310: Name name = rec.getName();
311: int type = rec.getType();
312: Delta delta;
313:
314: switch (state) {
315: case INITIALSOA:
316: if (type != Type.SOA)
317: fail("missing initial SOA");
318: initialsoa = rec;
319: // Remember the serial number in the initial SOA; we need it
320: // to recognize the end of an IXFR.
321: end_serial = getSOASerial(rec);
322: if (qtype == Type.IXFR && end_serial <= ixfr_serial) {
323: logxfr("up to date");
324: state = END;
325: break;
326: }
327: state = FIRSTDATA;
328: break;
329:
330: case FIRSTDATA:
331: // If the transfer begins with 1 SOA, it's an AXFR.
332: // If it begins with 2 SOAs, it's an IXFR.
333: if (qtype == Type.IXFR && type == Type.SOA
334: && getSOASerial(rec) == ixfr_serial) {
335: rtype = Type.IXFR;
336: ixfr = new ArrayList();
337: logxfr("got incremental response");
338: state = IXFR_DELSOA;
339: } else {
340: rtype = Type.AXFR;
341: axfr = new ArrayList();
342: axfr.add(initialsoa);
343: logxfr("got nonincremental response");
344: state = AXFR;
345: }
346: parseRR(rec); // Restart...
347: return;
348:
349: case IXFR_DELSOA:
350: delta = new Delta();
351: ixfr.add(delta);
352: delta.start = getSOASerial(rec);
353: delta.deletes.add(rec);
354: state = IXFR_DEL;
355: break;
356:
357: case IXFR_DEL:
358: if (type == Type.SOA) {
359: current_serial = getSOASerial(rec);
360: state = IXFR_ADDSOA;
361: parseRR(rec); // Restart...
362: return;
363: }
364: delta = (Delta) ixfr.get(ixfr.size() - 1);
365: delta.deletes.add(rec);
366: break;
367:
368: case IXFR_ADDSOA:
369: delta = (Delta) ixfr.get(ixfr.size() - 1);
370: delta.end = getSOASerial(rec);
371: delta.adds.add(rec);
372: state = IXFR_ADD;
373: break;
374:
375: case IXFR_ADD:
376: if (type == Type.SOA) {
377: long soa_serial = getSOASerial(rec);
378: if (soa_serial == end_serial) {
379: state = END;
380: break;
381: } else if (soa_serial != current_serial) {
382: fail("IXFR out of sync: expected serial "
383: + current_serial + " , got " + soa_serial);
384: } else {
385: state = IXFR_DELSOA;
386: parseRR(rec); // Restart...
387: return;
388: }
389: }
390: delta = (Delta) ixfr.get(ixfr.size() - 1);
391: delta.adds.add(rec);
392: break;
393:
394: case AXFR:
395: // Old BINDs sent cross class A records for non IN classes.
396: if (type == Type.A && rec.getDClass() != dclass)
397: break;
398: axfr.add(rec);
399: if (type == Type.SOA) {
400: state = END;
401: }
402: break;
403:
404: case END:
405: fail("extra data");
406: break;
407:
408: default:
409: fail("invalid state");
410: break;
411: }
412: }
413:
414: private void closeConnection() {
415: try {
416: if (client != null)
417: client.cleanup();
418: } catch (IOException e) {
419: }
420: }
421:
422: private Message parseMessage(byte[] b) throws WireParseException {
423: try {
424: return new Message(b);
425: } catch (IOException e) {
426: if (e instanceof WireParseException)
427: throw (WireParseException) e;
428: throw new WireParseException("Error parsing message");
429: }
430: }
431:
432: private void doxfr() throws IOException, ZoneTransferException {
433: sendQuery();
434: while (state != END) {
435: byte[] in = client.recv();
436: Message response = parseMessage(in);
437: if (response.getHeader().getRcode() == Rcode.NOERROR
438: && verifier != null) {
439: TSIGRecord tsigrec = response.getTSIG();
440:
441: int error = verifier.verify(response, in);
442: if (error == Rcode.NOERROR && tsigrec != null)
443: response.tsigState = Message.TSIG_VERIFIED;
444: else if (error == Rcode.NOERROR)
445: response.tsigState = Message.TSIG_INTERMEDIATE;
446: else
447: fail("TSIG failure");
448: }
449:
450: Record[] answers = response.getSectionArray(Section.ANSWER);
451:
452: if (state == INITIALSOA) {
453: int rcode = response.getRcode();
454: if (rcode != Rcode.NOERROR) {
455: if (qtype == Type.IXFR && rcode == Rcode.NOTIMP) {
456: fallback();
457: doxfr();
458: return;
459: }
460: fail(Rcode.string(rcode));
461: }
462:
463: Record question = response.getQuestion();
464: if (question != null && question.getType() != qtype) {
465: fail("invalid question section");
466: }
467:
468: if (answers.length == 0 && qtype == Type.IXFR) {
469: fallback();
470: doxfr();
471: return;
472: }
473: }
474:
475: for (int i = 0; i < answers.length; i++) {
476: parseRR(answers[i]);
477: }
478:
479: if (state == END
480: && response.tsigState == Message.TSIG_INTERMEDIATE)
481: fail("last message must be signed");
482: }
483: }
484:
485: /**
486: * Does the zone transfer.
487: * @return A list, which is either an AXFR-style response (List of Records),
488: * and IXFR-style response (List of Deltas), or null, which indicates that
489: * an IXFR was performed and the zone is up to date.
490: * @throws IOException The zone transfer failed to due an IO problem.
491: * @throws ZoneTransferException The zone transfer failed to due a problem
492: * with the zone transfer itself.
493: */
494: public List run() throws IOException, ZoneTransferException {
495: try {
496: openConnection();
497: doxfr();
498: } finally {
499: closeConnection();
500: }
501: if (axfr != null)
502: return axfr;
503: return ixfr;
504: }
505:
506: /**
507: * Returns true if the response is an AXFR-style response (List of Records).
508: * This will be true if either an IXFR was performed, an IXFR was performed
509: * and the server provided a full zone transfer, or an IXFR failed and
510: * fallback to AXFR occurred.
511: */
512: public boolean isAXFR() {
513: return (rtype == Type.AXFR);
514: }
515:
516: /**
517: * Gets the AXFR-style response.
518: */
519: public List getAXFR() {
520: return axfr;
521: }
522:
523: /**
524: * Returns true if the response is an IXFR-style response (List of Deltas).
525: * This will be true only if an IXFR was performed and the server provided
526: * an incremental zone transfer.
527: */
528: public boolean isIXFR() {
529: return (rtype == Type.IXFR);
530: }
531:
532: /**
533: * Gets the IXFR-style response.
534: */
535: public List getIXFR() {
536: return ixfr;
537: }
538:
539: /**
540: * Returns true if the response indicates that the zone is up to date.
541: * This will be true only if an IXFR was performed.
542: */
543: public boolean isCurrent() {
544: return (axfr == null && ixfr == null);
545: }
546:
547: }
|