blob: 866be77e9fd4ca77d51bda845fdeece78cd2bdbf [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001// 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}