479 lines
14 KiB
Java
Executable File
479 lines
14 KiB
Java
Executable File
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
|
|
|
|
package org.xbill.DNS2.clients;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
import dorkbox.network.dns.Name;
|
|
import dorkbox.network.dns.constants.DnsClass;
|
|
import dorkbox.network.dns.constants.DnsRecordType;
|
|
import dorkbox.network.dns.exceptions.RelativeNameException;
|
|
import dorkbox.network.dns.exceptions.TextParseException;
|
|
import dorkbox.network.dns.records.DnsRecord;
|
|
import dorkbox.network.dns.records.SOARecord;
|
|
import dorkbox.network.dns.records.TTL;
|
|
import dorkbox.network.dns.utils.Tokenizer;
|
|
|
|
/**
|
|
* 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 DnsRecord 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;
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
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.
|
|
*
|
|
* @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.
|
|
*/
|
|
public
|
|
Master(InputStream in, Name origin) {
|
|
this(in, origin, -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.
|
|
*/
|
|
public
|
|
Master(InputStream in) {
|
|
this(in, null, -1);
|
|
}
|
|
|
|
private
|
|
Name parseName(String s, Name origin) throws TextParseException {
|
|
try {
|
|
return Name.Companion.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 = DnsClass.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 = DnsClass.value(s)) >= 0) {
|
|
s = st.getString();
|
|
}
|
|
else {
|
|
currentDClass = DnsClass.IN;
|
|
}
|
|
}
|
|
|
|
if ((currentType = DnsRecordType.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 != DnsRecordType.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 " + DnsRecordType.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
|
|
DnsRecord 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
|
|
DnsRecord _nextRecord() throws IOException {
|
|
Tokenizer.Token token;
|
|
String s;
|
|
|
|
if (included != null) {
|
|
DnsRecord rec = included.nextRecord();
|
|
if (rec != null) {
|
|
return rec;
|
|
}
|
|
included = null;
|
|
}
|
|
if (generator != null) {
|
|
DnsRecord 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 = DnsRecord.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
|
|
DnsRecord nextRecord() throws IOException {
|
|
DnsRecord 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();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected
|
|
void finalize() {
|
|
if (st != null) {
|
|
st.close();
|
|
}
|
|
}
|
|
|
|
}
|