// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
package org.xbill.DNS;
import java.io.*;
import java.util.*;
/**
* A DNS Zone. This encapsulates all data related to a Zone, and provides
* convenient lookup methods.
*
* @author Brian Wellington
*/
public class Zone implements Serializable {
private static final long serialVersionUID = -9220510891189510942L;
/** A primary zone */
public static final int PRIMARY = 1;
/** A secondary zone */
public static final int SECONDARY = 2;
private Map data;
private Name origin;
private Object originNode;
private int dclass = DClass.IN;
private RRset NS;
private SOARecord SOA;
private boolean hasWild;
class ZoneIterator implements Iterator {
private Iterator zentries;
private RRset [] current;
private int count;
private boolean wantLastSOA;
ZoneIterator(boolean axfr) {
synchronized (Zone.this) {
zentries = data.entrySet().iterator();
}
wantLastSOA = axfr;
RRset [] sets = allRRsets(originNode);
current = new RRset[sets.length];
for (int i = 0, j = 2; i < sets.length; i++) {
int type = sets[i].getType();
if (type == Type.SOA)
current[0] = sets[i];
else if (type == Type.NS)
current[1] = sets[i];
else
current[j++] = sets[i];
}
}
public boolean
hasNext() {
return (current != null || wantLastSOA);
}
public Object
next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (current == null) {
wantLastSOA = false;
return oneRRset(originNode, Type.SOA);
}
Object set = current[count++];
if (count == current.length) {
current = null;
while (zentries.hasNext()) {
Map.Entry entry = (Map.Entry) zentries.next();
if (entry.getKey().equals(origin))
continue;
RRset [] sets = allRRsets(entry.getValue());
if (sets.length == 0)
continue;
current = sets;
count = 0;
break;
}
}
return set;
}
public void
remove() {
throw new UnsupportedOperationException();
}
}
private void
validate() throws IOException {
originNode = exactName(origin);
if (originNode == null)
throw new IOException(origin + ": no data specified");
RRset rrset = oneRRset(originNode, Type.SOA);
if (rrset == null || rrset.size() != 1)
throw new IOException(origin +
": exactly 1 SOA must be specified");
Iterator it = rrset.rrs();
SOA = (SOARecord) it.next();
NS = oneRRset(originNode, Type.NS);
if (NS == null)
throw new IOException(origin + ": no NS set specified");
}
private final void
maybeAddRecord(Record record) throws IOException {
int rtype = record.getType();
Name name = record.getName();
if (rtype == Type.SOA && !name.equals(origin)) {
throw new IOException("SOA owner " + name +
" does not match zone origin " +
origin);
}
if (name.subdomain(origin))
addRecord(record);
}
/**
* Creates a Zone from the records in the specified master file.
* @param zone The name of the zone.
* @param file The master file to read from.
* @see Master
*/
public
Zone(Name zone, String file) throws IOException {
data = new TreeMap();
if (zone == null)
throw new IllegalArgumentException("no zone name specified");
Master m = new Master(file, zone);
Record record;
origin = zone;
while ((record = m.nextRecord()) != null)
maybeAddRecord(record);
validate();
}
/**
* Creates a Zone from an array of records.
* @param zone The name of the zone.
* @param records The records to add to the zone.
* @see Master
*/
public
Zone(Name zone, Record [] records) throws IOException {
data = new TreeMap();
if (zone == null)
throw new IllegalArgumentException("no zone name specified");
origin = zone;
for (int i = 0; i < records.length; i++)
maybeAddRecord(records[i]);
validate();
}
private void
fromXFR(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
data = new TreeMap();
origin = xfrin.getName();
List records = xfrin.run();
for (Iterator it = records.iterator(); it.hasNext(); ) {
Record record = (Record) it.next();
maybeAddRecord(record);
}
if (!xfrin.isAXFR())
throw new IllegalArgumentException("zones can only be " +
"created from AXFRs");
validate();
}
/**
* Creates a Zone by doing the specified zone transfer.
* @param xfrin The incoming zone transfer to execute.
* @see ZoneTransferIn
*/
public
Zone(ZoneTransferIn xfrin) throws IOException, ZoneTransferException {
fromXFR(xfrin);
}
/**
* Creates a Zone by performing a zone transfer to the specified host.
* @see ZoneTransferIn
*/
public
Zone(Name zone, int dclass, String remote)
throws IOException, ZoneTransferException
{
ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote, null);
xfrin.setDClass(dclass);
fromXFR(xfrin);
}
/** Returns the Zone's origin */
public Name
getOrigin() {
return origin;
}
/** Returns the Zone origin's NS records */
public RRset
getNS() {
return NS;
}
/** Returns the Zone's SOA record */
public SOARecord
getSOA() {
return SOA;
}
/** Returns the Zone's class */
public int
getDClass() {
return dclass;
}
private synchronized Object
exactName(Name name) {
return data.get(name);
}
private synchronized RRset []
allRRsets(Object types) {
if (types instanceof List) {
List typelist = (List) types;
return (RRset []) typelist.toArray(new RRset[typelist.size()]);
} else {
RRset set = (RRset) types;
return new RRset [] {set};
}
}
private synchronized RRset
oneRRset(Object types, int type) {
if (type == Type.ANY)
throw new IllegalArgumentException("oneRRset(ANY)");
if (types instanceof List) {
List list = (List) types;
for (int i = 0; i < list.size(); i++) {
RRset set = (RRset) list.get(i);
if (set.getType() == type)
return set;
}
} else {
RRset set = (RRset) types;
if (set.getType() == type)
return set;
}
return null;
}
private synchronized RRset
findRRset(Name name, int type) {
Object types = exactName(name);
if (types == null)
return null;
return oneRRset(types, type);
}
private synchronized void
addRRset(Name name, RRset rrset) {
if (!hasWild && name.isWild())
hasWild = true;
Object types = data.get(name);
if (types == null) {
data.put(name, rrset);
return;
}
int rtype = rrset.getType();
if (types instanceof List) {
List list = (List) types;
for (int i = 0; i < list.size(); i++) {
RRset set = (RRset) list.get(i);
if (set.getType() == rtype) {
list.set(i, rrset);
return;
}
}
list.add(rrset);
} else {
RRset set = (RRset) types;
if (set.getType() == rtype)
data.put(name, rrset);
else {
LinkedList list = new LinkedList();
list.add(set);
list.add(rrset);
data.put(name, list);
}
}
}
private synchronized void
removeRRset(Name name, int type) {
Object types = data.get(name);
if (types == null) {
return;
}
if (types instanceof List) {
List list = (List) types;
for (int i = 0; i < list.size(); i++) {
RRset set = (RRset) list.get(i);
if (set.getType() == type) {
list.remove(i);
if (list.size() == 0)
data.remove(name);
return;
}
}
} else {
RRset set = (RRset) types;
if (set.getType() != type)
return;
data.remove(name);
}
}
private synchronized SetResponse
lookup(Name name, int type) {
int labels;
int olabels;
int tlabels;
RRset rrset;
Name tname;
Object types;
SetResponse sr;
if (!name.subdomain(origin))
return SetResponse.ofType(SetResponse.NXDOMAIN);
labels = name.labels();
olabels = origin.labels();
for (tlabels = olabels; tlabels <= labels; tlabels++) {
boolean isOrigin = (tlabels == olabels);
boolean isExact = (tlabels == labels);
if (isOrigin)
tname = origin;
else if (isExact)
tname = name;
else
tname = new Name(name, labels - tlabels);
types = exactName(tname);
if (types == null)
continue;
/* If this is a delegation, return that. */
if (!isOrigin) {
RRset ns = oneRRset(types, Type.NS);
if (ns != null)
return new SetResponse(SetResponse.DELEGATION,
ns);
}
/* If this is an ANY lookup, return everything. */
if (isExact && type == Type.ANY) {
sr = new SetResponse(SetResponse.SUCCESSFUL);
RRset [] sets = allRRsets(types);
for (int i = 0; i < sets.length; i++)
sr.addRRset(sets[i]);
return sr;
}
/*
* If this is the name, look for the actual type or a CNAME.
* Otherwise, look for a DNAME.
*/
if (isExact) {
rrset = oneRRset(types, type);
if (rrset != null) {
sr = new SetResponse(SetResponse.SUCCESSFUL);
sr.addRRset(rrset);
return sr;
}
rrset = oneRRset(types, Type.CNAME);
if (rrset != null)
return new SetResponse(SetResponse.CNAME,
rrset);
} else {
rrset = oneRRset(types, Type.DNAME);
if (rrset != null)
return new SetResponse(SetResponse.DNAME,
rrset);
}
/* We found the name, but not the type. */
if (isExact)
return SetResponse.ofType(SetResponse.NXRRSET);
}
if (hasWild) {
for (int i = 0; i < labels - olabels; i++) {
tname = name.wild(i + 1);
types = exactName(tname);
if (types == null)
continue;
rrset = oneRRset(types, type);
if (rrset != null) {
sr = new SetResponse(SetResponse.SUCCESSFUL);
sr.addRRset(rrset);
return sr;
}
}
}
return SetResponse.ofType(SetResponse.NXDOMAIN);
}
/**
* Looks up Records in the Zone. This follows CNAMEs and wildcards.
* @param name The name to look up
* @param type The type to look up
* @return A SetResponse object
* @see SetResponse
*/
public SetResponse
findRecords(Name name, int type) {
return lookup(name, type);
}
/**
* Looks up Records in the zone, finding exact matches only.
* @param name The name to look up
* @param type The type to look up
* @return The matching RRset
* @see RRset
*/
public RRset
findExactMatch(Name name, int type) {
Object types = exactName(name);
if (types == null)
return null;
return oneRRset(types, type);
}
/**
* Adds an RRset to the Zone
* @param rrset The RRset to be added
* @see RRset
*/
public void
addRRset(RRset rrset) {
Name name = rrset.getName();
addRRset(name, rrset);
}
/**
* Adds a Record to the Zone
* @param r The record to be added
* @see Record
*/
public void
addRecord(Record r) {
Name name = r.getName();
int rtype = r.getRRsetType();
synchronized (this) {
RRset rrset = findRRset(name, rtype);
if (rrset == null) {
rrset = new RRset(r);
addRRset(name, rrset);
} else {
rrset.addRR(r);
}
}
}
/**
* Removes a record from the Zone
* @param r The record to be removed
* @see Record
*/
public void
removeRecord(Record r) {
Name name = r.getName();
int rtype = r.getRRsetType();
synchronized (this) {
RRset rrset = findRRset(name, rtype);
if (rrset == null)
return;
if (rrset.size() == 1 && rrset.first().equals(r))
removeRRset(name, rtype);
else
rrset.deleteRR(r);
}
}
/**
* Returns an Iterator over the RRsets in the zone.
*/
public Iterator
iterator() {
return new ZoneIterator(false);
}
/**
* Returns an Iterator over the RRsets in the zone that can be used to
* construct an AXFR response. This is identical to {@link #iterator} except
* that the SOA is returned at the end as well as the beginning.
*/
public Iterator
AXFR() {
return new ZoneIterator(true);
}
private void
nodeToString(StringBuffer sb, Object node) {
RRset [] sets = allRRsets(node);
for (int i = 0; i < sets.length; i++) {
RRset rrset = sets[i];
Iterator it = rrset.rrs();
while (it.hasNext())
sb.append(it.next() + "\n");
it = rrset.sigs();
while (it.hasNext())
sb.append(it.next() + "\n");
}
}
/**
* Returns the contents of the Zone in master file format.
*/
public synchronized String
toMasterFile() {
Iterator zentries = data.entrySet().iterator();
StringBuffer sb = new StringBuffer();
nodeToString(sb, originNode);
while (zentries.hasNext()) {
Map.Entry entry = (Map.Entry) zentries.next();
if (!origin.equals(entry.getKey()))
nodeToString(sb, entry.getValue());
}
return sb.toString();
}
/**
* Returns the contents of the Zone as a string (in master file format).
*/
public String
toString() {
return toMasterFile();
}
}