/* * Copied from the DnsJava project * * Copyright (c) 1998-2011, Brian Wellington. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package io.milton.dns.record; import io.milton.dns.Name; 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 final int dclass = DClass.IN; private RRset NS; private SOARecord SOA; private boolean hasWild; class ZoneIterator implements Iterator { private final 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.isEmpty()) 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()).append("\n"); it = rrset.sigs(); while (it.hasNext()) sb.append(it.next()).append("\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(); } }