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