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