1// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.io.*;
6import 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
15public class Zone implements Serializable {
16
17private static final long serialVersionUID = -9220510891189510942L;
18
19/** A primary zone */
20public static final int PRIMARY = 1;
21
22/** A secondary zone */
23public static final int SECONDARY = 2;
24
25private Map data;
26private Name origin;
27private Object originNode;
28private int dclass = DClass.IN;
29private RRset NS;
30private SOARecord SOA;
31private boolean hasWild;
32
33class ZoneIterator implements Iterator {
34	private Iterator zentries;
35	private RRset [] current;
36	private int count;
37	private boolean wantLastSOA;
38
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
58	hasNext() {
59		return (current != null || wantLastSOA);
60	}
61
62	public Object
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
90	remove() {
91		throw new UnsupportedOperationException();
92	}
93}
94
95private void
96validate() 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
113private final void
114maybeAddRecord(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 */
133public
134Zone(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 */
154public
155Zone(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
166private void
167fromXFR(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 */
187public
188Zone(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 */
196public
197Zone(Name zone, int dclass, String remote)
198throws 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 */
206public Name
207getOrigin() {
208	return origin;
209}
210
211/** Returns the Zone origin's NS records */
212public RRset
213getNS() {
214	return NS;
215}
216
217/** Returns the Zone's SOA record */
218public SOARecord
219getSOA() {
220	return SOA;
221}
222
223/** Returns the Zone's class */
224public int
225getDClass() {
226	return dclass;
227}
228
229private synchronized Object
230exactName(Name name) {
231	return data.get(name);
232}
233
234private synchronized RRset []
235allRRsets(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
245private synchronized RRset
246oneRRset(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
264private synchronized RRset
265findRRset(Name name, int type) {
266	Object types = exactName(name);
267	if (types == null)
268		return null;
269	return oneRRset(types, type);
270}
271
272private synchronized void
273addRRset(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
305private synchronized void
306removeRRset(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
330private synchronized SetResponse
331lookup(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 */
432public SetResponse
433findRecords(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 */
444public RRset
445findExactMatch(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 */
457public void
458addRRset(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 */
468public void
469addRecord(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 */
488public void
489removeRecord(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 */
506public Iterator
507iterator() {
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 */
516public Iterator
517AXFR() {
518	return new ZoneIterator(true);
519}
520
521private void
522nodeToString(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 */
538public synchronized String
539toMasterFile() {
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 */
554public String
555toString() {
556	return toMasterFile();
557}
558
559}
560