aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/Master.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/Master.java')
-rw-r--r--src/org/xbill/DNS/Master.java427
1 files changed, 427 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Master.java b/src/org/xbill/DNS/Master.java
new file mode 100644
index 0000000..c795a9c
--- /dev/null
+++ b/src/org/xbill/DNS/Master.java
@@ -0,0 +1,427 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A DNS master file parser. This incrementally parses the file, returning
+ * one record at a time. When directives are seen, they are added to the
+ * state and used when parsing future records.
+ *
+ * @author Brian Wellington
+ */
+
+public class Master {
+
+private Name origin;
+private File file;
+private Record last = null;
+private long defaultTTL;
+private Master included = null;
+private Tokenizer st;
+private int currentType;
+private int currentDClass;
+private long currentTTL;
+private boolean needSOATTL;
+
+private Generator generator;
+private List generators;
+private boolean noExpandGenerate;
+
+Master(File file, Name origin, long initialTTL) throws IOException {
+ if (origin != null && !origin.isAbsolute()) {
+ throw new RelativeNameException(origin);
+ }
+ this.file = file;
+ st = new Tokenizer(file);
+ this.origin = origin;
+ defaultTTL = initialTTL;
+}
+
+/**
+ * Initializes the master file reader and opens the specified master file.
+ * @param filename The master file.
+ * @param origin The initial origin to append to relative names.
+ * @param ttl The initial default TTL.
+ * @throws IOException The master file could not be opened.
+ */
+public
+Master(String filename, Name origin, long ttl) throws IOException {
+ this(new File(filename), origin, ttl);
+}
+
+/**
+ * Initializes the master file reader and opens the specified master file.
+ * @param filename The master file.
+ * @param origin The initial origin to append to relative names.
+ * @throws IOException The master file could not be opened.
+ */
+public
+Master(String filename, Name origin) throws IOException {
+ this(new File(filename), origin, -1);
+}
+
+/**
+ * Initializes the master file reader and opens the specified master file.
+ * @param filename The master file.
+ * @throws IOException The master file could not be opened.
+ */
+public
+Master(String filename) throws IOException {
+ this(new File(filename), null, -1);
+}
+
+/**
+ * Initializes the master file reader.
+ * @param in The input stream containing a master file.
+ * @param origin The initial origin to append to relative names.
+ * @param ttl The initial default TTL.
+ */
+public
+Master(InputStream in, Name origin, long ttl) {
+ if (origin != null && !origin.isAbsolute()) {
+ throw new RelativeNameException(origin);
+ }
+ st = new Tokenizer(in);
+ this.origin = origin;
+ defaultTTL = ttl;
+}
+
+/**
+ * Initializes the master file reader.
+ * @param in The input stream containing a master file.
+ * @param origin The initial origin to append to relative names.
+ */
+public
+Master(InputStream in, Name origin) {
+ this(in, origin, -1);
+}
+
+/**
+ * Initializes the master file reader.
+ * @param in The input stream containing a master file.
+ */
+public
+Master(InputStream in) {
+ this(in, null, -1);
+}
+
+private Name
+parseName(String s, Name origin) throws TextParseException {
+ try {
+ return Name.fromString(s, origin);
+ }
+ catch (TextParseException e) {
+ throw st.exception(e.getMessage());
+ }
+}
+
+private void
+parseTTLClassAndType() throws IOException {
+ String s;
+ boolean seen_class = false;
+
+
+ // This is a bit messy, since any of the following are legal:
+ // class ttl type
+ // ttl class type
+ // class type
+ // ttl type
+ // type
+ seen_class = false;
+ s = st.getString();
+ if ((currentDClass = DClass.value(s)) >= 0) {
+ s = st.getString();
+ seen_class = true;
+ }
+
+ currentTTL = -1;
+ try {
+ currentTTL = TTL.parseTTL(s);
+ s = st.getString();
+ }
+ catch (NumberFormatException e) {
+ if (defaultTTL >= 0)
+ currentTTL = defaultTTL;
+ else if (last != null)
+ currentTTL = last.getTTL();
+ }
+
+ if (!seen_class) {
+ if ((currentDClass = DClass.value(s)) >= 0) {
+ s = st.getString();
+ } else {
+ currentDClass = DClass.IN;
+ }
+ }
+
+ if ((currentType = Type.value(s)) < 0)
+ throw st.exception("Invalid type '" + s + "'");
+
+ // BIND allows a missing TTL for the initial SOA record, and uses
+ // the SOA minimum value. If the SOA is not the first record,
+ // this is an error.
+ if (currentTTL < 0) {
+ if (currentType != Type.SOA)
+ throw st.exception("missing TTL");
+ needSOATTL = true;
+ currentTTL = 0;
+ }
+}
+
+private long
+parseUInt32(String s) {
+ if (!Character.isDigit(s.charAt(0)))
+ return -1;
+ try {
+ long l = Long.parseLong(s);
+ if (l < 0 || l > 0xFFFFFFFFL)
+ return -1;
+ return l;
+ }
+ catch (NumberFormatException e) {
+ return -1;
+ }
+}
+
+private void
+startGenerate() throws IOException {
+ String s;
+ int n;
+
+ // The first field is of the form start-end[/step]
+ // Regexes would be useful here.
+ s = st.getIdentifier();
+ n = s.indexOf("-");
+ if (n < 0)
+ throw st.exception("Invalid $GENERATE range specifier: " + s);
+ String startstr = s.substring(0, n);
+ String endstr = s.substring(n + 1);
+ String stepstr = null;
+ n = endstr.indexOf("/");
+ if (n >= 0) {
+ stepstr = endstr.substring(n + 1);
+ endstr = endstr.substring(0, n);
+ }
+ long start = parseUInt32(startstr);
+ long end = parseUInt32(endstr);
+ long step;
+ if (stepstr != null)
+ step = parseUInt32(stepstr);
+ else
+ step = 1;
+ if (start < 0 || end < 0 || start > end || step <= 0)
+ throw st.exception("Invalid $GENERATE range specifier: " + s);
+
+ // The next field is the name specification.
+ String nameSpec = st.getIdentifier();
+
+ // Then the ttl/class/type, in the same form as a normal record.
+ // Only some types are supported.
+ parseTTLClassAndType();
+ if (!Generator.supportedType(currentType))
+ throw st.exception("$GENERATE does not support " +
+ Type.string(currentType) + " records");
+
+ // Next comes the rdata specification.
+ String rdataSpec = st.getIdentifier();
+
+ // That should be the end. However, we don't want to move past the
+ // line yet, so put back the EOL after reading it.
+ st.getEOL();
+ st.unget();
+
+ generator = new Generator(start, end, step, nameSpec,
+ currentType, currentDClass, currentTTL,
+ rdataSpec, origin);
+ if (generators == null)
+ generators = new ArrayList(1);
+ generators.add(generator);
+}
+
+private void
+endGenerate() throws IOException {
+ // Read the EOL that we put back before.
+ st.getEOL();
+
+ generator = null;
+}
+
+private Record
+nextGenerated() throws IOException {
+ try {
+ return generator.nextRecord();
+ }
+ catch (Tokenizer.TokenizerException e) {
+ throw st.exception("Parsing $GENERATE: " + e.getBaseMessage());
+ }
+ catch (TextParseException e) {
+ throw st.exception("Parsing $GENERATE: " + e.getMessage());
+ }
+}
+
+/**
+ * Returns the next record in the master file. This will process any
+ * directives before the next record.
+ * @return The next record.
+ * @throws IOException The master file could not be read, or was syntactically
+ * invalid.
+ */
+public Record
+_nextRecord() throws IOException {
+ Tokenizer.Token token;
+ String s;
+
+ if (included != null) {
+ Record rec = included.nextRecord();
+ if (rec != null)
+ return rec;
+ included = null;
+ }
+ if (generator != null) {
+ Record rec = nextGenerated();
+ if (rec != null)
+ return rec;
+ endGenerate();
+ }
+ while (true) {
+ Name name;
+
+ token = st.get(true, false);
+ if (token.type == Tokenizer.WHITESPACE) {
+ Tokenizer.Token next = st.get();
+ if (next.type == Tokenizer.EOL)
+ continue;
+ else if (next.type == Tokenizer.EOF)
+ return null;
+ else
+ st.unget();
+ if (last == null)
+ throw st.exception("no owner");
+ name = last.getName();
+ }
+ else if (token.type == Tokenizer.EOL)
+ continue;
+ else if (token.type == Tokenizer.EOF)
+ return null;
+ else if (((String) token.value).charAt(0) == '$') {
+ s = token.value;
+
+ if (s.equalsIgnoreCase("$ORIGIN")) {
+ origin = st.getName(Name.root);
+ st.getEOL();
+ continue;
+ } else if (s.equalsIgnoreCase("$TTL")) {
+ defaultTTL = st.getTTL();
+ st.getEOL();
+ continue;
+ } else if (s.equalsIgnoreCase("$INCLUDE")) {
+ String filename = st.getString();
+ File newfile;
+ if (file != null) {
+ String parent = file.getParent();
+ newfile = new File(parent, filename);
+ } else {
+ newfile = new File(filename);
+ }
+ Name incorigin = origin;
+ token = st.get();
+ if (token.isString()) {
+ incorigin = parseName(token.value,
+ Name.root);
+ st.getEOL();
+ }
+ included = new Master(newfile, incorigin,
+ defaultTTL);
+ /*
+ * If we continued, we wouldn't be looking in
+ * the new file. Recursing works better.
+ */
+ return nextRecord();
+ } else if (s.equalsIgnoreCase("$GENERATE")) {
+ if (generator != null)
+ throw new IllegalStateException
+ ("cannot nest $GENERATE");
+ startGenerate();
+ if (noExpandGenerate) {
+ endGenerate();
+ continue;
+ }
+ return nextGenerated();
+ } else {
+ throw st.exception("Invalid directive: " + s);
+ }
+ } else {
+ s = token.value;
+ name = parseName(s, origin);
+ if (last != null && name.equals(last.getName())) {
+ name = last.getName();
+ }
+ }
+
+ parseTTLClassAndType();
+ last = Record.fromString(name, currentType, currentDClass,
+ currentTTL, st, origin);
+ if (needSOATTL) {
+ long ttl = ((SOARecord)last).getMinimum();
+ last.setTTL(ttl);
+ defaultTTL = ttl;
+ needSOATTL = false;
+ }
+ return last;
+ }
+}
+
+/**
+ * Returns the next record in the master file. This will process any
+ * directives before the next record.
+ * @return The next record.
+ * @throws IOException The master file could not be read, or was syntactically
+ * invalid.
+ */
+public Record
+nextRecord() throws IOException {
+ Record rec = null;
+ try {
+ rec = _nextRecord();
+ }
+ finally {
+ if (rec == null) {
+ st.close();
+ }
+ }
+ return rec;
+}
+
+/**
+ * Specifies whether $GENERATE statements should be expanded. Whether
+ * expanded or not, the specifications for generated records are available
+ * by calling {@link #generators}. This must be called before a $GENERATE
+ * statement is seen during iteration to have an effect.
+ */
+public void
+expandGenerate(boolean wantExpand) {
+ noExpandGenerate = !wantExpand;
+}
+
+/**
+ * Returns an iterator over the generators specified in the master file; that
+ * is, the parsed contents of $GENERATE statements.
+ * @see Generator
+ */
+public Iterator
+generators() {
+ if (generators != null)
+ return Collections.unmodifiableList(generators).iterator();
+ else
+ return Collections.EMPTY_LIST.iterator();
+}
+
+protected void
+finalize() {
+ st.close();
+}
+
+}