001: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.io.*;
006: import java.util.*;
007:
008: /**
009: * A DNS Zone. This encapsulates all data related to a Zone, and provides
010: * convenient lookup methods.
011: *
012: * @author Brian Wellington
013: */
014:
015: public class Zone {
016:
017: /** A primary zone */
018: public static final int PRIMARY = 1;
019:
020: /** A secondary zone */
021: public static final int SECONDARY = 2;
022:
023: private Map data;
024: private int type;
025: private Name origin;
026: private Object originNode;
027: private int dclass = DClass.IN;
028: private RRset NS;
029: private SOARecord SOA;
030: private boolean hasWild;
031:
032: class ZoneIterator implements Iterator {
033: private Iterator zentries;
034: private RRset[] current;
035: private int count;
036: private boolean wantLastSOA;
037:
038: ZoneIterator(boolean axfr) {
039: zentries = data.entrySet().iterator();
040: wantLastSOA = axfr;
041: RRset[] sets = allRRsets(originNode);
042: current = new RRset[sets.length];
043: for (int i = 0, j = 2; i < sets.length; i++) {
044: int type = sets[i].getType();
045: if (type == Type.SOA)
046: current[0] = sets[i];
047: else if (type == Type.NS)
048: current[1] = sets[i];
049: else
050: current[j++] = sets[i];
051: }
052: }
053:
054: public boolean hasNext() {
055: return (current != null || wantLastSOA);
056: }
057:
058: public Object next() {
059: if (!hasNext()) {
060: throw new NoSuchElementException();
061: }
062: if (current == null && wantLastSOA) {
063: wantLastSOA = false;
064: return oneRRset(originNode, Type.SOA);
065: }
066: Object set = current[count++];
067: if (count == current.length) {
068: current = null;
069: while (zentries.hasNext()) {
070: Map.Entry entry = (Map.Entry) zentries.next();
071: if (entry.getKey().equals(origin))
072: continue;
073: RRset[] sets = allRRsets(entry.getValue());
074: if (sets.length == 0)
075: continue;
076: current = sets;
077: count = 0;
078: break;
079: }
080: }
081: return set;
082: }
083:
084: public void remove() {
085: throw new UnsupportedOperationException();
086: }
087: }
088:
089: private void validate() throws IOException {
090: originNode = exactName(origin);
091: if (originNode == null)
092: throw new IOException(origin + ": no data specified");
093:
094: RRset rrset = oneRRset(originNode, Type.SOA);
095: if (rrset == null || rrset.size() != 1)
096: throw new IOException(origin
097: + ": exactly 1 SOA must be specified");
098: Iterator it = rrset.rrs();
099: SOA = (SOARecord) it.next();
100:
101: NS = oneRRset(originNode, Type.NS);
102: if (NS == null)
103: throw new IOException(origin + ": no NS set specified");
104: }
105:
106: private final void maybeAddRecord(Record record) throws IOException {
107: int rtype = record.getType();
108: Name name = record.getName();
109:
110: if (rtype == Type.SOA && !name.equals(origin)) {
111: throw new IOException("SOA owner " + name
112: + " does not match zone origin " + origin);
113: }
114: if (name.subdomain(origin))
115: addRecord(record);
116: }
117:
118: /**
119: * Creates a Zone from the records in the specified master file.
120: * @param zone The name of the zone.
121: * @param file The master file to read from.
122: * @see Master
123: */
124: public Zone(Name zone, String file) throws IOException {
125: data = new HashMap();
126: type = PRIMARY;
127:
128: if (zone == null)
129: throw new IllegalArgumentException("no zone name specified");
130: Master m = new Master(file, zone);
131: Record record;
132:
133: origin = zone;
134: while ((record = m.nextRecord()) != null)
135: maybeAddRecord(record);
136: validate();
137: }
138:
139: /**
140: * Creates a Zone from an array of records.
141: * @param zone The name of the zone.
142: * @param records The records to add to the zone.
143: * @see Master
144: */
145: public Zone(Name zone, Record[] records) throws IOException {
146: data = new HashMap();
147: type = PRIMARY;
148:
149: if (zone == null)
150: throw new IllegalArgumentException("no zone name specified");
151: origin = zone;
152: for (int i = 0; i < records.length; i++)
153: maybeAddRecord(records[i]);
154: validate();
155: }
156:
157: private void fromXFR(ZoneTransferIn xfrin) throws IOException,
158: ZoneTransferException {
159: data = new HashMap();
160: type = SECONDARY;
161:
162: if (!xfrin.isAXFR())
163: throw new IllegalArgumentException("zones can only be "
164: + "created from AXFRs");
165: origin = xfrin.getName();
166: List records = xfrin.run();
167: for (Iterator it = records.iterator(); it.hasNext();) {
168: Record record = (Record) it.next();
169: maybeAddRecord(record);
170: }
171: validate();
172: }
173:
174: /**
175: * Creates a Zone by doing the specified zone transfer.
176: * @param xfrin The incoming zone transfer to execute.
177: * @see ZoneTransferIn
178: */
179: public Zone(ZoneTransferIn xfrin) throws IOException,
180: ZoneTransferException {
181: fromXFR(xfrin);
182: }
183:
184: /**
185: * Creates a Zone by performing a zone transfer to the specified host.
186: * @see ZoneTransferIn
187: */
188: public Zone(Name zone, int dclass, String remote)
189: throws IOException, ZoneTransferException {
190: ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote,
191: null);
192: xfrin.setDClass(dclass);
193: fromXFR(xfrin);
194: }
195:
196: /** Returns the Zone's origin */
197: public Name getOrigin() {
198: return origin;
199: }
200:
201: /** Returns the Zone origin's NS records */
202: public RRset getNS() {
203: return NS;
204: }
205:
206: /** Returns the Zone's SOA record */
207: public SOARecord getSOA() {
208: return SOA;
209: }
210:
211: /** Returns the Zone's class */
212: public int getDClass() {
213: return dclass;
214: }
215:
216: private synchronized Object exactName(Name name) {
217: return data.get(name);
218: }
219:
220: private synchronized RRset[] allRRsets(Object types) {
221: if (types instanceof List) {
222: List typelist = (List) types;
223: return (RRset[]) typelist
224: .toArray(new RRset[typelist.size()]);
225: } else {
226: RRset set = (RRset) types;
227: return new RRset[] { set };
228: }
229: }
230:
231: private synchronized RRset oneRRset(Object types, int type) {
232: if (type == Type.ANY)
233: throw new IllegalArgumentException("oneRRset(ANY)");
234: if (types instanceof List) {
235: List list = (List) types;
236: for (int i = 0; i < list.size(); i++) {
237: RRset set = (RRset) list.get(i);
238: if (set.getType() == type)
239: return set;
240: }
241: } else {
242: RRset set = (RRset) types;
243: if (set.getType() == type)
244: return set;
245: }
246: return null;
247: }
248:
249: private synchronized RRset findRRset(Name name, int type) {
250: Object types = exactName(name);
251: if (types == null)
252: return null;
253: return oneRRset(types, type);
254: }
255:
256: private synchronized void addRRset(Name name, RRset rrset) {
257: if (!hasWild && name.isWild())
258: hasWild = true;
259: Object types = data.get(name);
260: if (types == null) {
261: data.put(name, rrset);
262: return;
263: }
264: int rtype = rrset.getType();
265: if (types instanceof List) {
266: List list = (List) types;
267: for (int i = 0; i < list.size(); i++) {
268: RRset set = (RRset) list.get(i);
269: if (set.getType() == rtype) {
270: list.set(i, rrset);
271: return;
272: }
273: }
274: list.add(rrset);
275: } else {
276: RRset set = (RRset) types;
277: if (set.getType() == rtype)
278: data.put(name, rrset);
279: else {
280: LinkedList list = new LinkedList();
281: list.add(set);
282: list.add(rrset);
283: data.put(name, list);
284: }
285: }
286: }
287:
288: private synchronized void removeRRset(Name name, int type) {
289: Object types = data.get(name);
290: if (types == null) {
291: return;
292: }
293: if (types instanceof List) {
294: List list = (List) types;
295: for (int i = 0; i < list.size(); i++) {
296: RRset set = (RRset) list.get(i);
297: if (set.getType() == type) {
298: list.remove(i);
299: if (list.size() == 0)
300: data.remove(name);
301: return;
302: }
303: }
304: } else {
305: RRset set = (RRset) types;
306: if (set.getType() != type)
307: return;
308: data.remove(name);
309: }
310: }
311:
312: private synchronized SetResponse lookup(Name name, int type) {
313: int labels;
314: int olabels;
315: int tlabels;
316: RRset rrset;
317: Name tname;
318: Object types;
319: SetResponse sr;
320:
321: if (!name.subdomain(origin))
322: return SetResponse.ofType(SetResponse.NXDOMAIN);
323:
324: labels = name.labels();
325: olabels = origin.labels();
326:
327: for (tlabels = olabels; tlabels <= labels; tlabels++) {
328: boolean isOrigin = (tlabels == olabels);
329: boolean isExact = (tlabels == labels);
330:
331: if (isOrigin)
332: tname = origin;
333: else if (isExact)
334: tname = name;
335: else
336: tname = new Name(name, labels - tlabels);
337:
338: types = exactName(tname);
339: if (types == null)
340: continue;
341:
342: /* If this is a delegation, return that. */
343: if (!isOrigin) {
344: RRset ns = oneRRset(types, Type.NS);
345: if (ns != null)
346: return new SetResponse(SetResponse.DELEGATION, ns);
347: }
348:
349: /* If this is an ANY lookup, return everything. */
350: if (isExact && type == Type.ANY) {
351: sr = new SetResponse(SetResponse.SUCCESSFUL);
352: RRset[] sets = allRRsets(types);
353: for (int i = 0; i < sets.length; i++)
354: sr.addRRset(sets[i]);
355: return sr;
356: }
357:
358: /*
359: * If this is the name, look for the actual type or a CNAME.
360: * Otherwise, look for a DNAME.
361: */
362: if (isExact) {
363: rrset = oneRRset(types, type);
364: if (rrset != null) {
365: sr = new SetResponse(SetResponse.SUCCESSFUL);
366: sr.addRRset(rrset);
367: return sr;
368: }
369: rrset = oneRRset(types, Type.CNAME);
370: if (rrset != null)
371: return new SetResponse(SetResponse.CNAME, rrset);
372: } else {
373: rrset = oneRRset(types, Type.DNAME);
374: if (rrset != null)
375: return new SetResponse(SetResponse.DNAME, rrset);
376: }
377:
378: /* We found the name, but not the type. */
379: if (isExact)
380: return SetResponse.ofType(SetResponse.NXRRSET);
381: }
382:
383: if (hasWild) {
384: for (int i = 0; i < labels - olabels; i++) {
385: tname = name.wild(i + 1);
386:
387: types = exactName(tname);
388: if (types == null)
389: continue;
390:
391: rrset = oneRRset(types, type);
392: if (rrset != null) {
393: sr = new SetResponse(SetResponse.SUCCESSFUL);
394: sr.addRRset(rrset);
395: return sr;
396: }
397: }
398: }
399:
400: return SetResponse.ofType(SetResponse.NXDOMAIN);
401: }
402:
403: /**
404: * Looks up Records in the Zone. This follows CNAMEs and wildcards.
405: * @param name The name to look up
406: * @param type The type to look up
407: * @return A SetResponse object
408: * @see SetResponse
409: */
410: public SetResponse findRecords(Name name, int type) {
411: return lookup(name, type);
412: }
413:
414: /**
415: * Looks up Records in the zone, finding exact matches only.
416: * @param name The name to look up
417: * @param type The type to look up
418: * @return The matching RRset
419: * @see RRset
420: */
421: public RRset findExactMatch(Name name, int type) {
422: Object types = exactName(name);
423: if (types == null)
424: return null;
425: return oneRRset(types, type);
426: }
427:
428: /**
429: * Adds an RRset to the Zone
430: * @param rrset The RRset to be added
431: * @see RRset
432: */
433: public void addRRset(RRset rrset) {
434: Name name = rrset.getName();
435: addRRset(name, rrset);
436: }
437:
438: /**
439: * Adds a Record to the Zone
440: * @param r The record to be added
441: * @see Record
442: */
443: public void addRecord(Record r) {
444: Name name = r.getName();
445: int rtype = r.getRRsetType();
446: synchronized (this ) {
447: RRset rrset = findRRset(name, rtype);
448: if (rrset == null) {
449: rrset = new RRset(r);
450: addRRset(name, rrset);
451: } else {
452: rrset.addRR(r);
453: }
454: }
455: }
456:
457: /**
458: * Removes a record from the Zone
459: * @param r The record to be removed
460: * @see Record
461: */
462: public void removeRecord(Record r) {
463: Name name = r.getName();
464: int rtype = r.getRRsetType();
465: synchronized (this ) {
466: RRset rrset = findRRset(name, rtype);
467: if (rrset == null)
468: return;
469: rrset.deleteRR(r);
470: if (rrset.size() == 0)
471: removeRRset(name, rtype);
472: }
473: }
474:
475: /**
476: * Returns an Iterator over the RRsets in the zone.
477: */
478: public Iterator iterator() {
479: return new ZoneIterator(false);
480: }
481:
482: /**
483: * Returns an Iterator over the RRsets in the zone that can be used to
484: * construct an AXFR response. This is identical to {@link #iterator} except
485: * that the SOA is returned at the end as well as the beginning.
486: */
487: public Iterator AXFR() {
488: return new ZoneIterator(true);
489: }
490:
491: private void nodeToString(StringBuffer sb, Object node) {
492: RRset[] sets = allRRsets(node);
493: for (int i = 0; i < sets.length; i++) {
494: RRset rrset = sets[i];
495: Iterator it = rrset.rrs();
496: while (it.hasNext())
497: sb.append(it.next() + "\n");
498: it = rrset.sigs();
499: while (it.hasNext())
500: sb.append(it.next() + "\n");
501: }
502: }
503:
504: /**
505: * Returns the contents of the Zone in master file format.
506: */
507: public String toMasterFile() {
508: Iterator zentries = data.entrySet().iterator();
509: StringBuffer sb = new StringBuffer();
510: nodeToString(sb, originNode);
511: while (zentries.hasNext()) {
512: Map.Entry entry = (Map.Entry) zentries.next();
513: if (!origin.equals(entry.getKey()))
514: nodeToString(sb, entry.getValue());
515: }
516: return sb.toString();
517: }
518:
519: /**
520: * Returns the contents of the Zone as a string (in master file format).
521: */
522: public String toString() {
523: return toMasterFile();
524: }
525:
526: }
|