• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2 
3 package org.xbill.DNS;
4 
5 import java.io.*;
6 import java.util.*;
7 
8 /**
9  * A DNS Zone.  This encapsulates all data related to a Zone, and provides
10  * convenient lookup methods.
11  *
12  * @author Brian Wellington
13  */
14 
15 public class Zone implements Serializable {
16 
17 private static final long serialVersionUID = -9220510891189510942L;
18 
19 /** A primary zone */
20 public static final int PRIMARY = 1;
21 
22 /** A secondary zone */
23 public static final int SECONDARY = 2;
24 
25 private Map data;
26 private Name origin;
27 private Object originNode;
28 private int dclass = DClass.IN;
29 private RRset NS;
30 private SOARecord SOA;
31 private boolean hasWild;
32 
33 class ZoneIterator implements Iterator {
34 	private Iterator zentries;
35 	private RRset [] current;
36 	private int count;
37 	private boolean wantLastSOA;
38 
ZoneIterator(boolean axfr)39 	ZoneIterator(boolean axfr) {
40 		synchronized (Zone.this) {
41 			zentries = data.entrySet().iterator();
42 		}
43 		wantLastSOA = axfr;
44 		RRset [] sets = allRRsets(originNode);
45 		current = new RRset[sets.length];
46 		for (int i = 0, j = 2; i < sets.length; i++) {
47 			int type = sets[i].getType();
48 			if (type == Type.SOA)
49 				current[0] = sets[i];
50 			else if (type == Type.NS)
51 				current[1] = sets[i];
52 			else
53 				current[j++] = sets[i];
54 		}
55 	}
56 
57 	public boolean
hasNext()58 	hasNext() {
59 		return (current != null || wantLastSOA);
60 	}
61 
62 	public Object
next()63 	next() {
64 		if (!hasNext()) {
65 			throw new NoSuchElementException();
66 		}
67 		if (current == null) {
68 			wantLastSOA = false;
69 			return oneRRset(originNode, Type.SOA);
70 		}
71 		Object set = current[count++];
72 		if (count == current.length) {
73 			current = null;
74 			while (zentries.hasNext()) {
75 				Map.Entry entry = (Map.Entry) zentries.next();
76 				if (entry.getKey().equals(origin))
77 					continue;
78 				RRset [] sets = allRRsets(entry.getValue());
79 				if (sets.length == 0)
80 					continue;
81 				current = sets;
82 				count = 0;
83 				break;
84 			}
85 		}
86 		return set;
87 	}
88 
89 	public void
remove()90 	remove() {
91 		throw new UnsupportedOperationException();
92 	}
93 }
94 
95 private void
validate()96 validate() throws IOException {
97 	originNode = exactName(origin);
98 	if (originNode == null)
99 		throw new IOException(origin + ": no data specified");
100 
101 	RRset rrset = oneRRset(originNode, Type.SOA);
102 	if (rrset == null || rrset.size() != 1)
103 		throw new IOException(origin +
104 				      ": exactly 1 SOA must be specified");
105 	Iterator it = rrset.rrs();
106 	SOA = (SOARecord) it.next();
107 
108 	NS = oneRRset(originNode, Type.NS);
109 	if (NS == null)
110 		throw new IOException(origin + ": no NS set specified");
111 }
112 
113 private final void
maybeAddRecord(Record record)114 maybeAddRecord(Record record) throws IOException {
115 	int rtype = record.getType();
116 	Name name = record.getName();
117 
118 	if (rtype == Type.SOA && !name.equals(origin)) {
119 		throw new IOException("SOA owner " + name +
120 				      " does not match zone origin " +
121 				      origin);
122 	}
123 	if (name.subdomain(origin))
124 		addRecord(record);
125 }
126 
127 /**
128  * Creates a Zone from the records in the specified master file.
129  * @param zone The name of the zone.
130  * @param file The master file to read from.
131  * @see Master
132  */
133 public
Zone(Name zone, String file)134 Zone(Name zone, String file) throws IOException {
135 	data = new TreeMap();
136 
137 	if (zone == null)
138 		throw new IllegalArgumentException("no zone name specified");
139 	Master m = new Master(file, zone);
140 	Record record;
141 
142 	origin = zone;
143 	while ((record = m.nextRecord()) != null)
144 		maybeAddRecord(record);
145 	validate();
146 }
147 
148 /**
149  * Creates a Zone from an array of records.
150  * @param zone The name of the zone.
151  * @param records The records to add to the zone.
152  * @see Master
153  */
154 public
Zone(Name zone, Record [] records)155 Zone(Name zone, Record [] records) throws IOException {
156 	data = new TreeMap();
157 
158 	if (zone == null)
159 		throw new IllegalArgumentException("no zone name specified");
160 	origin = zone;
161 	for (int i = 0; i < records.length; i++)
162 		maybeAddRecord(records[i]);
163 	validate();
164 }
165 
166 private void
fromXFR(ZoneTransferIn xfrin)167 fromXFR(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
168 	data = new TreeMap();
169 
170 	origin = xfrin.getName();
171 	List records = xfrin.run();
172 	for (Iterator it = records.iterator(); it.hasNext(); ) {
173 		Record record = (Record) it.next();
174 		maybeAddRecord(record);
175 	}
176 	if (!xfrin.isAXFR())
177 		throw new IllegalArgumentException("zones can only be " +
178 						   "created from AXFRs");
179 	validate();
180 }
181 
182 /**
183  * Creates a Zone by doing the specified zone transfer.
184  * @param xfrin The incoming zone transfer to execute.
185  * @see ZoneTransferIn
186  */
187 public
Zone(ZoneTransferIn xfrin)188 Zone(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
189 	fromXFR(xfrin);
190 }
191 
192 /**
193  * Creates a Zone by performing a zone transfer to the specified host.
194  * @see ZoneTransferIn
195  */
196 public
Zone(Name zone, int dclass, String remote)197 Zone(Name zone, int dclass, String remote)
198 throws IOException, ZoneTransferException
199 {
200 	ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote, null);
201 	xfrin.setDClass(dclass);
202 	fromXFR(xfrin);
203 }
204 
205 /** Returns the Zone's origin */
206 public Name
getOrigin()207 getOrigin() {
208 	return origin;
209 }
210 
211 /** Returns the Zone origin's NS records */
212 public RRset
getNS()213 getNS() {
214 	return NS;
215 }
216 
217 /** Returns the Zone's SOA record */
218 public SOARecord
getSOA()219 getSOA() {
220 	return SOA;
221 }
222 
223 /** Returns the Zone's class */
224 public int
getDClass()225 getDClass() {
226 	return dclass;
227 }
228 
229 private synchronized Object
exactName(Name name)230 exactName(Name name) {
231 	return data.get(name);
232 }
233 
234 private synchronized RRset []
allRRsets(Object types)235 allRRsets(Object types) {
236 	if (types instanceof List) {
237 		List typelist = (List) types;
238 		return (RRset []) typelist.toArray(new RRset[typelist.size()]);
239 	} else {
240 		RRset set = (RRset) types;
241 		return new RRset [] {set};
242 	}
243 }
244 
245 private synchronized RRset
oneRRset(Object types, int type)246 oneRRset(Object types, int type) {
247 	if (type == Type.ANY)
248 		throw new IllegalArgumentException("oneRRset(ANY)");
249 	if (types instanceof List) {
250 		List list = (List) types;
251 		for (int i = 0; i < list.size(); i++) {
252 			RRset set = (RRset) list.get(i);
253 			if (set.getType() == type)
254 				return set;
255 		}
256 	} else {
257 		RRset set = (RRset) types;
258 		if (set.getType() == type)
259 			return set;
260 	}
261 	return null;
262 }
263 
264 private synchronized RRset
findRRset(Name name, int type)265 findRRset(Name name, int type) {
266 	Object types = exactName(name);
267 	if (types == null)
268 		return null;
269 	return oneRRset(types, type);
270 }
271 
272 private synchronized void
addRRset(Name name, RRset rrset)273 addRRset(Name name, RRset rrset) {
274 	if (!hasWild && name.isWild())
275 		hasWild = true;
276 	Object types = data.get(name);
277 	if (types == null) {
278 		data.put(name, rrset);
279 		return;
280 	}
281 	int rtype = rrset.getType();
282 	if (types instanceof List) {
283 		List list = (List) types;
284 		for (int i = 0; i < list.size(); i++) {
285 			RRset set = (RRset) list.get(i);
286 			if (set.getType() == rtype) {
287 				list.set(i, rrset);
288 				return;
289 			}
290 		}
291 		list.add(rrset);
292 	} else {
293 		RRset set = (RRset) types;
294 		if (set.getType() == rtype)
295 			data.put(name, rrset);
296 		else {
297 			LinkedList list = new LinkedList();
298 			list.add(set);
299 			list.add(rrset);
300 			data.put(name, list);
301 		}
302 	}
303 }
304 
305 private synchronized void
removeRRset(Name name, int type)306 removeRRset(Name name, int type) {
307 	Object types = data.get(name);
308 	if (types == null) {
309 		return;
310 	}
311 	if (types instanceof List) {
312 		List list = (List) types;
313 		for (int i = 0; i < list.size(); i++) {
314 			RRset set = (RRset) list.get(i);
315 			if (set.getType() == type) {
316 				list.remove(i);
317 				if (list.size() == 0)
318 					data.remove(name);
319 				return;
320 			}
321 		}
322 	} else {
323 		RRset set = (RRset) types;
324 		if (set.getType() != type)
325 			return;
326 		data.remove(name);
327 	}
328 }
329 
330 private synchronized SetResponse
lookup(Name name, int type)331 lookup(Name name, int type) {
332 	int labels;
333 	int olabels;
334 	int tlabels;
335 	RRset rrset;
336 	Name tname;
337 	Object types;
338 	SetResponse sr;
339 
340 	if (!name.subdomain(origin))
341 		return SetResponse.ofType(SetResponse.NXDOMAIN);
342 
343 	labels = name.labels();
344 	olabels = origin.labels();
345 
346 	for (tlabels = olabels; tlabels <= labels; tlabels++) {
347 		boolean isOrigin = (tlabels == olabels);
348 		boolean isExact = (tlabels == labels);
349 
350 		if (isOrigin)
351 			tname = origin;
352 		else if (isExact)
353 			tname = name;
354 		else
355 			tname = new Name(name, labels - tlabels);
356 
357 		types = exactName(tname);
358 		if (types == null)
359 			continue;
360 
361 		/* If this is a delegation, return that. */
362 		if (!isOrigin) {
363 			RRset ns = oneRRset(types, Type.NS);
364 			if (ns != null)
365 				return new SetResponse(SetResponse.DELEGATION,
366 						       ns);
367 		}
368 
369 		/* If this is an ANY lookup, return everything. */
370 		if (isExact && type == Type.ANY) {
371 			sr = new SetResponse(SetResponse.SUCCESSFUL);
372 			RRset [] sets = allRRsets(types);
373 			for (int i = 0; i < sets.length; i++)
374 				sr.addRRset(sets[i]);
375 			return sr;
376 		}
377 
378 		/*
379 		 * If this is the name, look for the actual type or a CNAME.
380 		 * Otherwise, look for a DNAME.
381 		 */
382 		if (isExact) {
383 			rrset = oneRRset(types, type);
384 			if (rrset != null) {
385 				sr = new SetResponse(SetResponse.SUCCESSFUL);
386 				sr.addRRset(rrset);
387 				return sr;
388 			}
389 			rrset = oneRRset(types, Type.CNAME);
390 			if (rrset != null)
391 				return new SetResponse(SetResponse.CNAME,
392 						       rrset);
393 		} else {
394 			rrset = oneRRset(types, Type.DNAME);
395 			if (rrset != null)
396 				return new SetResponse(SetResponse.DNAME,
397 						       rrset);
398 		}
399 
400 		/* We found the name, but not the type. */
401 		if (isExact)
402 			return SetResponse.ofType(SetResponse.NXRRSET);
403 	}
404 
405 	if (hasWild) {
406 		for (int i = 0; i < labels - olabels; i++) {
407 			tname = name.wild(i + 1);
408 
409 			types = exactName(tname);
410 			if (types == null)
411 				continue;
412 
413 			rrset = oneRRset(types, type);
414 			if (rrset != null) {
415 				sr = new SetResponse(SetResponse.SUCCESSFUL);
416 				sr.addRRset(rrset);
417 				return sr;
418 			}
419 		}
420 	}
421 
422 	return SetResponse.ofType(SetResponse.NXDOMAIN);
423 }
424 
425 /**
426  * Looks up Records in the Zone.  This follows CNAMEs and wildcards.
427  * @param name The name to look up
428  * @param type The type to look up
429  * @return A SetResponse object
430  * @see SetResponse
431  */
432 public SetResponse
findRecords(Name name, int type)433 findRecords(Name name, int type) {
434 	return lookup(name, type);
435 }
436 
437 /**
438  * Looks up Records in the zone, finding exact matches only.
439  * @param name The name to look up
440  * @param type The type to look up
441  * @return The matching RRset
442  * @see RRset
443  */
444 public RRset
findExactMatch(Name name, int type)445 findExactMatch(Name name, int type) {
446 	Object types = exactName(name);
447 	if (types == null)
448 		return null;
449 	return oneRRset(types, type);
450 }
451 
452 /**
453  * Adds an RRset to the Zone
454  * @param rrset The RRset to be added
455  * @see RRset
456  */
457 public void
addRRset(RRset rrset)458 addRRset(RRset rrset) {
459 	Name name = rrset.getName();
460 	addRRset(name, rrset);
461 }
462 
463 /**
464  * Adds a Record to the Zone
465  * @param r The record to be added
466  * @see Record
467  */
468 public void
addRecord(Record r)469 addRecord(Record r) {
470 	Name name = r.getName();
471 	int rtype = r.getRRsetType();
472 	synchronized (this) {
473 		RRset rrset = findRRset(name, rtype);
474 		if (rrset == null) {
475 			rrset = new RRset(r);
476 			addRRset(name, rrset);
477 		} else {
478 			rrset.addRR(r);
479 		}
480 	}
481 }
482 
483 /**
484  * Removes a record from the Zone
485  * @param r The record to be removed
486  * @see Record
487  */
488 public void
removeRecord(Record r)489 removeRecord(Record r) {
490 	Name name = r.getName();
491 	int rtype = r.getRRsetType();
492 	synchronized (this) {
493 		RRset rrset = findRRset(name, rtype);
494 		if (rrset == null)
495 			return;
496 		if (rrset.size() == 1 && rrset.first().equals(r))
497 			removeRRset(name, rtype);
498 		else
499 			rrset.deleteRR(r);
500 	}
501 }
502 
503 /**
504  * Returns an Iterator over the RRsets in the zone.
505  */
506 public Iterator
iterator()507 iterator() {
508 	return new ZoneIterator(false);
509 }
510 
511 /**
512  * Returns an Iterator over the RRsets in the zone that can be used to
513  * construct an AXFR response.  This is identical to {@link #iterator} except
514  * that the SOA is returned at the end as well as the beginning.
515  */
516 public Iterator
AXFR()517 AXFR() {
518 	return new ZoneIterator(true);
519 }
520 
521 private void
nodeToString(StringBuffer sb, Object node)522 nodeToString(StringBuffer sb, Object node) {
523 	RRset [] sets = allRRsets(node);
524 	for (int i = 0; i < sets.length; i++) {
525 		RRset rrset = sets[i];
526 		Iterator it = rrset.rrs();
527 		while (it.hasNext())
528 			sb.append(it.next() + "\n");
529 		it = rrset.sigs();
530 		while (it.hasNext())
531 			sb.append(it.next() + "\n");
532 	}
533 }
534 
535 /**
536  * Returns the contents of the Zone in master file format.
537  */
538 public synchronized String
toMasterFile()539 toMasterFile() {
540 	Iterator zentries = data.entrySet().iterator();
541 	StringBuffer sb = new StringBuffer();
542 	nodeToString(sb, originNode);
543 	while (zentries.hasNext()) {
544 		Map.Entry entry = (Map.Entry) zentries.next();
545 		if (!origin.equals(entry.getKey()))
546 			nodeToString(sb, entry.getValue());
547 	}
548 	return sb.toString();
549 }
550 
551 /**
552  * Returns the contents of the Zone as a string (in master file format).
553  */
554 public String
toString()555 toString() {
556 	return toMasterFile();
557 }
558 
559 }
560