Home | History | Annotate | Download | only in DNS
      1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) 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 
     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 
     95 private void
     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
    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
    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
    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
    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
    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
    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
    207 getOrigin() {
    208 	return origin;
    209 }
    210 
    211 /** Returns the Zone origin's NS records */
    212 public RRset
    213 getNS() {
    214 	return NS;
    215 }
    216 
    217 /** Returns the Zone's SOA record */
    218 public SOARecord
    219 getSOA() {
    220 	return SOA;
    221 }
    222 
    223 /** Returns the Zone's class */
    224 public int
    225 getDClass() {
    226 	return dclass;
    227 }
    228 
    229 private synchronized Object
    230 exactName(Name name) {
    231 	return data.get(name);
    232 }
    233 
    234 private synchronized RRset []
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    517 AXFR() {
    518 	return new ZoneIterator(true);
    519 }
    520 
    521 private void
    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
    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
    555 toString() {
    556 	return toMasterFile();
    557 }
    558 
    559 }
    560