| // 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(); |
| } |
| |
| } |